From 4f0610296df67ac93aaca7863e2faa9e0e24084a Mon Sep 17 00:00:00 2001 From: dborup Date: Sat, 18 Jul 2026 13:08:16 +0200 Subject: [PATCH] fix: always show heading for the region-breakdown sections when empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Repeaters by Region, Nodes Running This Region, and Bridge Repeaters all cleared their entire section (heading included) when the data was empty, which reads as "this feature doesn't exist" rather than "no data yet" — especially confusing right after a restart, when the neighbor graph and path resolution need a few minutes to catch back up before repeater-level scope data repopulates. Now always renders the heading + description with an explanatory empty-state message instead of hiding. --- public/analytics.js | 51 +++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/public/analytics.js b/public/analytics.js index c7bf320c..d16e75d6 100644 --- a/public/analytics.js +++ b/public/analytics.js @@ -4692,20 +4692,26 @@ function destroy() { _stopRolesRefresh(); _stopScopesRefresh(); _analyticsData = function renderRegionNodeGroups(elId, title, description, groups, unitLabel) { var el = document.getElementById(elId); if (!el) return; - if (!groups || !groups.length) { el.innerHTML = ''; return; } - var rows = groups.map(function(g) { - var links = g.repeaters.map(function(rp) { - return '' + esc(rp.name) + ''; - }).join(', '); - return '
' + - '' + esc(g.region) + ' — ' + g.count.toLocaleString() + ' ' + unitLabel + (g.count === 1 ? '' : 's') + '' + - '
' + links + '
' + - '
'; - }).join(''); + // Always show the heading — hiding the whole section when `groups` + // is empty (vs. showing an empty-state message) reads as "this + // feature doesn't exist" rather than "no data yet", which is + // especially confusing right after a restart while the neighbor + // graph + path resolution are still catching back up. + var body = (groups && groups.length) + ? groups.map(function(g) { + var links = g.repeaters.map(function(rp) { + return '' + esc(rp.name) + ''; + }).join(', '); + return '
' + + '' + esc(g.region) + ' — ' + g.count.toLocaleString() + ' ' + unitLabel + (g.count === 1 ? '' : 's') + '' + + '
' + links + '
' + + '
'; + }).join('') + : '

No data yet — path resolution catches up gradually as traffic flows; check back in a few minutes.

'; el.innerHTML = '

' + esc(title) + '

' + '

' + esc(description) + '

' + - rows; + body; } renderRegionNodeGroups('scopes-repeaters', 'Repeaters by Region', @@ -4717,6 +4723,7 @@ function destroy() { _stopRolesRefresh(); _stopScopesRefresh(); _analyticsData = var bridgeEl = document.getElementById('scopes-bridges'); if (bridgeEl) { var bridges = d.bridgeRepeaters || []; + var bridgeBody; if (bridges.length > 0) { var bridgeRows = bridges.map(function(b) { var regionList = b.regions.map(function(r) { return '' + esc(r) + ''; }).join(', '); @@ -4726,18 +4733,22 @@ function destroy() { _stopRolesRefresh(); _stopScopesRefresh(); _analyticsData = '' + regionList + '' + ''; }).join(''); - bridgeEl.innerHTML = - '

Bridge Repeaters

' + - '

' + - 'All-time — repeaters that have relayed traffic for more than one region. These connect otherwise-separate regional communities; losing one can split the mesh\'s regional coverage.' + - '

' + - '' + - '' + - '' + bridgeRows + '' + + bridgeBody = '
Repeater# RegionsRegions
' + + '' + + '' + bridgeRows + '' + '
Repeater# RegionsRegions
'; } else { - bridgeEl.innerHTML = ''; + // Needs a repeater confirmed in 2+ regions — a stricter bar than + // "Repeaters by Region" above, so it's normal for this to stay + // empty longer, especially right after a restart. + bridgeBody = '

No bridge repeaters found yet — this needs a repeater confirmed in 2+ different regions, which takes longer to accumulate than the single-region data above.

'; } + bridgeEl.innerHTML = + '

Bridge Repeaters

' + + '

' + + 'All-time — repeaters that have relayed traffic for more than one region. These connect otherwise-separate regional communities; losing one can split the mesh\'s regional coverage.' + + '

' + + bridgeBody; } renderRegionNodeGroups('scopes-origin-nodes', 'Nodes Running This Region',