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',