From 10081b33e2d0f52aed81f8a8bd29f155e93bc5ba Mon Sep 17 00:00:00 2001 From: Malar Invention Date: Mon, 13 Jul 2026 12:18:55 +0530 Subject: [PATCH] fix(zt-gateway): prevent health dots flashing red on each poll cycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cache last-known health state per region so renderGatewayRow uses correct values instead of defaulting to unreachable. Parallelize refreshHealth with Promise.all so all gateway pings resolve together instead of sequentially (3 gateways × -W 3 timeout was ~18s worst case). --- .../resources/view/zt-gateway/overview.js | 59 +++++++++++-------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/htdocs/luci-static/resources/view/zt-gateway/overview.js b/htdocs/luci-static/resources/view/zt-gateway/overview.js index a38ab1a..0511892 100644 --- a/htdocs/luci-static/resources/view/zt-gateway/overview.js +++ b/htdocs/luci-static/resources/view/zt-gateway/overview.js @@ -59,7 +59,8 @@ function ubusSetup(command) { return view.extend({ state: { selectedRegion: null, - switchMode: 'force' + switchMode: 'force', + healthCache: {} }, load() { @@ -87,11 +88,14 @@ return view.extend({ change: ev => this.onSelectRegion(ev.target.value), checked: isSelected || null, })), - E('td', { class: 'zt-region-cell' }, [ - this.renderHealthDot(false, null), - ' ', - isActive ? E('strong', {}, gw.region) : document.createTextNode(gw.region) - ]), + (() => { + const cached = this.state.healthCache[gw.region]; + const dot = this.renderHealthDot(cached ? cached.reachable : false, cached ? cached.latency_ms : null); + return E('td', { class: 'zt-region-cell' }, [ + dot, ' ', + isActive ? E('strong', {}, gw.region) : document.createTextNode(gw.region) + ]); + })(), E('td', {}, gw.label), E('td', {}, gw.ip), E('td', {}, isActive ? E('em', {}, _('active')) : '') @@ -250,24 +254,33 @@ return view.extend({ async refreshHealth(data) { const gws = data.gateways || []; - for (let i = 0; i < gws.length; i++) { - const region = gws[i].region; - try { - const h = await ubusHealth(region); - const row = document.querySelector('.zt-gateway-row[data-region="%s"]'.format(region)); - if (row && h) { - const node = row.querySelector('.zt-health'); - if (node) { - node.classList.toggle('zt-health-up', !!h.reachable); - node.classList.toggle('zt-health-down', !h.reachable); - node.title = h.reachable && h.latency_ms != null - ? _('UP (%sms)').format(h.latency_ms) - : (h.reachable ? _('UP') : _('DOWN')); + const checks = gws.map(gw => { + return ubusHealth(gw.region).then(h => { + if (h) { + this.state.healthCache[gw.region] = { + reachable: !!h.reachable, + latency_ms: h.latency_ms ?? null + }; } - } - } catch (_) { - /* health check is best-effort */ - } + return { region: gw.region, h }; + }).catch(() => { + this.state.healthCache[gw.region] = { reachable: false, latency_ms: null }; + return { region: gw.region, h: null }; + }); + }); + + const results = await Promise.all(checks); + for (const { region, h } of results) { + if (!h) continue; + const row = document.querySelector('.zt-gateway-row[data-region="%s"]'.format(region)); + if (!row) continue; + const node = row.querySelector('.zt-health'); + if (!node) continue; + node.classList.toggle('zt-health-up', !!h.reachable); + node.classList.toggle('zt-health-down', !h.reachable); + node.title = h.reachable && h.latency_ms != null + ? _('UP (%sms)').format(h.latency_ms) + : (h.reachable ? _('UP') : _('DOWN')); } },