From 25b31117ffc1a098b9429bcad5900a99de86cc28 Mon Sep 17 00:00:00 2001 From: you Date: Thu, 19 Mar 2026 22:17:00 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20richer=20node=20detail=20=E2=80=94=20st?= =?UTF-8?q?atus=20badge,=20avg=20SNR/hops,=20observer=20breakdown=20table,?= =?UTF-8?q?=20totalPackets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db.js | 6 ++++- public/index.html | 2 +- public/nodes.js | 59 +++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/db.js b/db.js index 24f7c580..590a250b 100644 --- a/db.js +++ b/db.js @@ -328,6 +328,10 @@ function getNodeHealth(pubkey) { } const avgHops = hopCount > 0 ? Math.round(totalHops / hopCount) : 0; + const totalPackets = db.prepare(` + SELECT COUNT(*) as count FROM packets WHERE ${whereClause} + `).get(params).count; + // Recent 10 packets const recentPackets = db.prepare(` SELECT * FROM packets WHERE ${whereClause} ORDER BY timestamp DESC LIMIT 10 @@ -336,7 +340,7 @@ function getNodeHealth(pubkey) { return { node, observers, - stats: { packetsToday, avgSnr: avgStats.avgSnr, avgHops, lastHeard }, + stats: { totalPackets, packetsToday, avgSnr: avgStats.avgSnr, avgHops, lastHeard }, recentPackets, }; } diff --git a/public/index.html b/public/index.html index 9128b578..752b4d75 100644 --- a/public/index.html +++ b/public/index.html @@ -80,7 +80,7 @@ - + diff --git a/public/nodes.js b/public/nodes.js index 37be8fc7..d5b195a3 100644 --- a/public/nodes.js +++ b/public/nodes.js @@ -114,15 +114,31 @@

Stats

-
First Seen
${n.first_seen ? new Date(n.first_seen).toLocaleString() : '—'}
Last Heard
${lastHeard ? timeAgo(lastHeard) : (n.last_seen ? timeAgo(n.last_seen) : '—')}
+
First Seen
${n.first_seen ? new Date(n.first_seen).toLocaleString() : '—'}
Total Packets
${stats.totalPackets || n.advert_count || 0}
Packets Today
${stats.packetsToday || 0}
-
Observers
${observers.length || 0}${observers.length ? ' (' + observers.map(o => escapeHtml(o.observer_name || o.observer_id)).join(', ') + ')' : ''}
+ ${stats.avgSnr != null ? `
Avg SNR
${stats.avgSnr.toFixed(1)} dB
` : ''} + ${stats.avgHops ? `
Avg Hops
${stats.avgHops}
` : ''} ${hasLoc ? `
Location
${n.lat.toFixed(5)}, ${n.lon.toFixed(5)}
` : ''}
+ ${observers.length ? `
+

Heard By (${observers.length} observer${observers.length > 1 ? 's' : ''})

+ + + + ${observers.map(o => ` + + + + + `).join('')} + +
ObserverPacketsAvg SNRAvg RSSI
${escapeHtml(o.observer_name || o.observer_id)}${o.packetCount}${o.avgSnr != null ? o.avgSnr.toFixed(1) + ' dB' : '—'}${o.avgRssi != null ? o.avgRssi.toFixed(0) + ' dBm' : '—'}
+
` : ''} +

Recent Activity (${recent.length})

@@ -373,16 +389,29 @@ function renderDetail(panel, data) { const n = data.node; const adverts = data.recentAdverts || []; - const recent = data.healthData?.recentPackets || []; + const h = data.healthData || {}; + const stats = h.stats || {}; + const observers = h.observers || []; + const recent = h.recentPackets || []; const roleColor = ROLE_COLORS[n.role] || '#6b7280'; const hasLoc = n.lat != null && n.lon != null; const nodeUrl = location.origin + '#/nodes/' + encodeURIComponent(n.public_key); + // Status calculation + const lastHeard = stats.lastHeard; + const statusAge = lastHeard ? (Date.now() - new Date(lastHeard).getTime()) : Infinity; + const role = (n.role || '').toLowerCase(); + const isInfra = role === 'repeater' || role === 'room'; + const degradedMs = isInfra ? 86400000 : 3600000; + const silentMs = isInfra ? 259200000 : 86400000; + const statusLabel = statusAge < degradedMs ? '🟢 Active' : statusAge < silentMs ? '🟡 Degraded' : '🔴 Silent'; + const totalPackets = stats.totalPackets || n.advert_count || 0; + panel.innerHTML = `
${hasLoc ? `
` : ''} -
${n.name || '(unnamed)'}
-
${n.role}
+
${escapeHtml(n.name || '(unnamed)')}
+
${n.role} ${statusLabel}

Public Key

@@ -391,15 +420,28 @@
-

Info

+

Overview

+
Last Heard
${lastHeard ? timeAgo(lastHeard) : (n.last_seen ? timeAgo(n.last_seen) : '—')}
First Seen
${n.first_seen ? new Date(n.first_seen).toLocaleString() : '—'}
-
Last Seen
${n.last_seen ? timeAgo(n.last_seen) : '—'}
-
Adverts
${n.advert_count || 0}
+
Total Packets
${totalPackets}
+
Packets Today
${stats.packetsToday || 0}
+ ${stats.avgSnr != null ? `
Avg SNR
${stats.avgSnr.toFixed(1)} dB
` : ''} + ${stats.avgHops ? `
Avg Hops
${stats.avgHops}
` : ''} ${hasLoc ? `
Location
${n.lat.toFixed(5)}, ${n.lon.toFixed(5)}
` : ''}
+ ${observers.length ? `
+

Heard By (${observers.length} observer${observers.length > 1 ? 's' : ''})

+
+ ${observers.map(o => `
+ ${escapeHtml(o.observer_name || o.observer_id)} + ${o.packetCount} pkts · ${o.avgSnr != null ? 'SNR ' + o.avgSnr.toFixed(1) + 'dB' : ''}${o.avgRssi != null ? ' · RSSI ' + o.avgRssi.toFixed(0) : ''} +
`).join('')} +
+
` : ''} +
@@ -419,7 +461,6 @@
`; }).join('') : '
No recent adverts
'} - }).join('') : '
No recent adverts
'}