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 @@ - + - + diff --git a/server.js b/server.js index 221ec18d..5be01a13 100644 --- a/server.js +++ b/server.js @@ -619,6 +619,26 @@ app.get('/api/nodes/bulk-health', (req, res) => { res.json(results); }); +app.get('/api/nodes/network-status', (req, res) => { + const now = Date.now(); + const allNodes = db.db.prepare('SELECT public_key, name, role, last_seen FROM nodes').all(); + let active = 0, degraded = 0, silent = 0; + const roleCounts = {}; + allNodes.forEach(n => { + const r = n.role || 'unknown'; + roleCounts[r] = (roleCounts[r] || 0) + 1; + const ls = n.last_seen ? new Date(n.last_seen).getTime() : 0; + const age = now - ls; + const isInfra = r === 'repeater' || r === 'room'; + const degradedMs = isInfra ? 86400000 : 3600000; + const silentMs = isInfra ? 259200000 : 86400000; + if (age < degradedMs) active++; + else if (age < silentMs) degraded++; + else silent++; + }); + res.json({ total: allNodes.length, active, degraded, silent, roleCounts }); +}); + app.get('/api/nodes/:pubkey', (req, res) => { const node = db.getNode(req.params.pubkey); if (!node) return res.status(404).json({ error: 'Not found' });