From 49ed4b80e2b5f2252931cdc51a96149c3d90bebe Mon Sep 17 00:00:00 2001 From: you Date: Thu, 19 Mar 2026 07:35:05 +0000 Subject: [PATCH] Fix scrub replay: add 'until' filter to API, fetch correct time window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ROOT CAUSE: API query used ORDER BY timestamp DESC with no upper bound — scrubbing to 3h ago fetched the newest 200 packets (from now), not packets near the scrub target. Fix: - Added 'until' query param to /api/packets (both grouped and flat) - vcrReplayFromTs fetches a 5-minute window around the scrub target - Server restart required (old process was stale on port 3000) --- public/live.js | 3 ++- server.js | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/public/live.js b/public/live.js index 9724dc74..0474ce17 100644 --- a/public/live.js +++ b/public/live.js @@ -112,9 +112,10 @@ function vcrReplayFromTs(targetTs) { const fetchFrom = new Date(targetTs - 5000).toISOString(); + const fetchTo = new Date(targetTs + 300000).toISOString(); // 5 min window stopReplay(); vcrSetMode('REPLAY'); - fetch(`/api/packets?limit=200&grouped=false&since=${encodeURIComponent(fetchFrom)}`) + fetch(`/api/packets?limit=200&grouped=false&since=${encodeURIComponent(fetchFrom)}&until=${encodeURIComponent(fetchTo)}`) .then(r => r.json()) .then(data => { const pkts = (data.packets || []).reverse(); diff --git a/server.js b/server.js index e8c88558..a4f83f61 100644 --- a/server.js +++ b/server.js @@ -277,7 +277,7 @@ app.get('/api/stats', (req, res) => { }); app.get('/api/packets', (req, res) => { - const { limit = 50, offset = 0, type, route, region, observer, hash, since, groupByHash, node } = req.query; + const { limit = 50, offset = 0, type, route, region, observer, hash, since, until, groupByHash, node } = req.query; if (groupByHash === 'true') { let where = []; @@ -288,6 +288,7 @@ app.get('/api/packets', (req, res) => { if (observer) { where.push('observer_id = @observer'); params.observer = observer; } if (hash) { where.push('hash = @hash'); params.hash = hash; } if (since) { where.push('timestamp > @since'); params.since = since; } + if (until) { where.push('timestamp < @until'); params.until = until; } if (node) { where.push("(decoded_json LIKE @nodePattern OR decoded_json LIKE @nodeNamePattern)"); params.nodePattern = `%${node}%`; const n = db.db.prepare('SELECT name FROM nodes WHERE public_key = ?').get(node); params.nodeNamePattern = n ? `%${n.name}%` : `%${node}%`; } const clause = where.length ? 'WHERE ' + where.join(' AND ') : ''; const packets = db.db.prepare(`SELECT hash, COUNT(DISTINCT observer_id) as observer_count, COUNT(*) as count, MAX(timestamp) as latest, (SELECT observer_id FROM packets pObs WHERE pObs.hash = packets.hash ORDER BY pObs.timestamp ASC LIMIT 1) as observer_id, (SELECT observer_name FROM packets pOn WHERE pOn.hash = packets.hash ORDER BY pOn.timestamp ASC LIMIT 1) as observer_name, (SELECT path_json FROM packets p2 WHERE p2.hash = packets.hash ORDER BY p2.timestamp DESC LIMIT 1) as path_json, (SELECT payload_type FROM packets p3 WHERE p3.hash = packets.hash ORDER BY p3.timestamp DESC LIMIT 1) as payload_type, (SELECT raw_hex FROM packets p4 WHERE p4.hash = packets.hash ORDER BY p4.timestamp DESC LIMIT 1) as raw_hex, (SELECT decoded_json FROM packets p5 WHERE p5.hash = packets.hash ORDER BY p5.timestamp DESC LIMIT 1) as decoded_json FROM packets ${clause} GROUP BY hash ORDER BY latest DESC LIMIT @limit OFFSET @offset`).all({ ...params, limit: Number(limit), offset: Number(offset) }); @@ -303,6 +304,7 @@ app.get('/api/packets', (req, res) => { if (observer) { where.push('observer_id = @observer'); params.observer = observer; } if (hash) { where.push('hash = @hash'); params.hash = hash; } if (since) { where.push('timestamp > @since'); params.since = since; } + if (until) { where.push('timestamp < @until'); params.until = until; } if (node) { where.push("(decoded_json LIKE @nodePattern OR decoded_json LIKE @nodeNamePattern)"); params.nodePattern = `%${node}%`; const nn = db.db.prepare('SELECT name FROM nodes WHERE public_key = ?').get(node); params.nodeNamePattern = nn ? `%${nn.name}%` : `%${node}%`; } const clause = where.length ? 'WHERE ' + where.join(' AND ') : ''; const packets = db.db.prepare(`SELECT * FROM packets ${clause} ORDER BY timestamp DESC LIMIT @limit OFFSET @offset`).all({ ...params, limit: Number(limit), offset: Number(offset) });