diff --git a/public/analytics.js b/public/analytics.js
index c2b00aeb..23df9649 100644
--- a/public/analytics.js
+++ b/public/analytics.js
@@ -1140,9 +1140,10 @@
async function renderNodesTab(el) {
el.innerHTML = '
Loading node analytics…
';
try {
- const [nodesResp, bulkHealth] = await Promise.all([
+ const [nodesResp, bulkHealth, netStatus] = await Promise.all([
api('/nodes?limit=200&sortBy=lastSeen'),
- api('/nodes/bulk-health?limit=50')
+ api('/nodes/bulk-health?limit=50'),
+ api('/nodes/network-status')
]);
const nodes = nodesResp.nodes || nodesResp;
const myNodes = JSON.parse(localStorage.getItem('meshcore-my-nodes') || '[]');
@@ -1159,24 +1160,8 @@
const byObservers = [...enriched].sort((a, b) => (b.health.observers?.length || 0) - (a.health.observers?.length || 0));
const byRecent = [...enriched].filter(n => n.health.stats.lastHeard).sort((a, b) => new Date(b.health.stats.lastHeard) - new Date(a.health.stats.lastHeard));
- // Status counts
- const now = Date.now();
- let active = 0, degraded = 0, silent = 0;
- enriched.forEach(n => {
- const lh = n.health.stats.lastHeard;
- const age = lh ? now - new Date(lh).getTime() : Infinity;
- const role = (n.role || '').toLowerCase();
- const isInfra = role === 'repeater' || role === 'room';
- const degradedMs = isInfra ? 86400000 : 3600000;
- const silentMs = isInfra ? 259200000 : 86400000;
- if (age < degradedMs) active++;
- else if (age < silentMs) degraded++;
- else silent++;
- });
-
- // Role breakdown
- const roleCounts = {};
- nodes.forEach(n => { const r = n.role || 'unknown'; roleCounts[r] = (roleCounts[r] || 0) + 1; });
+ // Use server-computed status across ALL nodes
+ const { active, degraded, silent, total: totalNodes, roleCounts } = netStatus;
function nodeLink(n) {
return `${esc(n.name || n.public_key.slice(0, 12))}`;
@@ -1204,7 +1189,7 @@
🔴 Silent
-
${nodes.length}
+
${totalNodes}
Total Nodes
diff --git a/public/index.html b/public/index.html
index bc7293cc..7e377610 100644
--- a/public/index.html
+++ b/public/index.html
@@ -83,9 +83,9 @@
-
+
-
+