diff --git a/db.js b/db.js index 81dda78d..36358d2f 100644 --- a/db.js +++ b/db.js @@ -346,6 +346,8 @@ const stmts = { getObservers: db.prepare(`SELECT * FROM observers ORDER BY last_seen DESC`), countPackets: db.prepare(`SELECT COUNT(*) as count FROM observations`), countNodes: db.prepare(`SELECT COUNT(*) as count FROM nodes`), + countActiveNodes: db.prepare(`SELECT COUNT(*) as count FROM nodes WHERE last_seen > ?`), + countActiveNodesByRole: db.prepare(`SELECT COUNT(*) as count FROM nodes WHERE role = ? AND last_seen > ?`), countObservers: db.prepare(`SELECT COUNT(*) as count FROM observers`), countRecentPackets: schemaVersion >= 3 ? db.prepare(`SELECT COUNT(*) as count FROM observations WHERE timestamp > CAST(strftime('%s', ?) AS INTEGER)`) @@ -579,6 +581,7 @@ function getObservers() { function getStats() { const oneHourAgo = new Date(Date.now() - 3600000).toISOString(); + const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 3600000).toISOString(); // Try to get transmission count from normalized schema let totalTransmissions = null; try { @@ -588,7 +591,8 @@ function getStats() { totalPackets: totalTransmissions || stmts.countPackets.get().count, totalTransmissions, totalObservations: stmts.countPackets.get().count, - totalNodes: stmts.countNodes.get().count, + totalNodes: stmts.countActiveNodes.get(sevenDaysAgo).count, + totalNodesAllTime: stmts.countNodes.get().count, totalObservers: stmts.countObservers.get().count, packetsLastHour: stmts.countRecentPackets.get(oneHourAgo).count, }; diff --git a/server.js b/server.js index d12746b6..54335514 100644 --- a/server.js +++ b/server.js @@ -876,10 +876,11 @@ app.use(express.json()); app.get('/api/stats', (req, res) => { const stats = db.getStats(); - // Get role counts + // Get role counts (active nodes only — same 7-day window as totalNodes) + const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 3600000).toISOString(); const counts = {}; for (const role of ['repeater', 'room', 'companion', 'sensor']) { - const r = db.db.prepare(`SELECT COUNT(*) as count FROM nodes WHERE role = ?`).get(role); + const r = db.db.prepare(`SELECT COUNT(*) as count FROM nodes WHERE role = ? AND last_seen > ?`).get(role, sevenDaysAgo); counts[role + 's'] = r.count; } res.json({ ...stats, counts }); diff --git a/test-db.js b/test-db.js index fca0a4fa..863102e1 100644 --- a/test-db.js +++ b/test-db.js @@ -213,6 +213,33 @@ console.log('\ngetStats:'); assert(stats.totalObservers >= 1, 'totalObservers'); assert(typeof stats.totalPackets === 'number', 'totalPackets is number'); assert(typeof stats.packetsLastHour === 'number', 'packetsLastHour is number'); + assert(typeof stats.totalNodesAllTime === 'number', 'totalNodesAllTime is number'); + assert(stats.totalNodesAllTime >= stats.totalNodes, 'totalNodesAllTime >= totalNodes'); +} + +// --- getStats active node filtering --- +console.log('\ngetStats active node filtering:'); +{ + // Insert a node with last_seen 30 days ago (should be excluded from totalNodes) + const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 3600000).toISOString(); + db.upsertNode({ public_key: 'deadnode0000000000000000deadnode00', name: 'DeadNode', role: 'repeater', last_seen: thirtyDaysAgo, first_seen: thirtyDaysAgo }); + + // Insert a node with last_seen now (should be included) + db.upsertNode({ public_key: 'livenode0000000000000000livenode00', name: 'LiveNode', role: 'companion', last_seen: new Date().toISOString() }); + + const stats = db.getStats(); + assert(stats.totalNodesAllTime > stats.totalNodes, 'dead node excluded from totalNodes but included in totalNodesAllTime'); + + // Verify the dead node is in totalNodesAllTime + const allTime = stats.totalNodesAllTime; + assert(allTime >= 3, 'totalNodesAllTime includes dead + live nodes'); + + // Verify active count doesn't include the 30-day-old node + // The dead node's last_seen is 30 days ago, window is 7 days + const nodeInDb = db.getNode('deadnode0000000000000000deadnode00'); + assert(nodeInDb !== null, 'dead node exists in DB'); + const liveNode = db.getNode('livenode0000000000000000livenode00'); + assert(liveNode !== null, 'live node exists in DB'); } // --- getNodeHealth ---