[object Object]

← back to Domain Sniper

dashboard: per-log tracker + idle counter + /api/perlog endpoint

7992d0c447fc4a9061e94ab7549e72de064311ec · 2026-05-12 18:03:59 -0700 · steve

Mirrors brand-typo-watcher's perLog map (fetched/x509/precert/emitted/
idle/lastSeenMs). Broadcast stats payload now includes perLog with a
computed 'stale' boolean (no event in 60s). Also adds GET /api/perlog
for headless inspection. Once public/index.html adds a renderer, browser
clients get the same alive-busy / alive-quiet / wedged diagnostic the
shell watcher has.

Also logged 4.44h slice 2-3 RECHECK#2 datapoint: 0/10 (no late snipes
since 3.57h). v1 cumulative now 0/130.

Files touched

Diff

commit 7992d0c447fc4a9061e94ab7549e72de064311ec
Author: steve <steve@designerwallcoverings.com>
Date:   Tue May 12 18:03:59 2026 -0700

    dashboard: per-log tracker + idle counter + /api/perlog endpoint
    
    Mirrors brand-typo-watcher's perLog map (fetched/x509/precert/emitted/
    idle/lastSeenMs). Broadcast stats payload now includes perLog with a
    computed 'stale' boolean (no event in 60s). Also adds GET /api/perlog
    for headless inspection. Once public/index.html adds a renderer, browser
    clients get the same alive-busy / alive-quiet / wedged diagnostic the
    shell watcher has.
    
    Also logged 4.44h slice 2-3 RECHECK#2 datapoint: 0/10 (no late snipes
    since 3.57h). v1 cumulative now 0/130.
---
 dashboard.js | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/dashboard.js b/dashboard.js
index 16d672b..e25d163 100644
--- a/dashboard.js
+++ b/dashboard.js
@@ -57,12 +57,19 @@ function classify(d) {
 const app = express();
 app.use(express.static(path.join(__dirname, 'public')));
 app.get('/api/keywords', (_req, res) => res.json({ keywords: BRAND_KEYWORDS }));
+app.get('/api/perlog', (_req, res) => {
+  const now = Date.now();
+  const out = {};
+  for (const [k, v] of Object.entries(perLog)) out[k] = { ...v, stale: v.lastSeenMs ? (now - v.lastSeenMs > 60_000) : false };
+  res.json({ stats, perLog: out });
+});
 
 const server = http.createServer(app);
 const wss = new WebSocket.Server({ server });
 const clients = new Set();
 
 let stats = { seen: 0, hot: 0, brand: 0, startedAt: Date.now() };
+const perLog = {}; // log_name -> { fetched, x509, precert, emitted, idle, lastSeenMs }
 
 wss.on('connection', (ws) => {
   clients.add(ws);
@@ -131,6 +138,14 @@ function connectCtLog() {
         console.log(`[${new Date().toISOString()}] ctlog seeded ${st.log} at tree_size=${st.position}`);
       } else if (st.type === 'resumed') {
         console.log(`[${new Date().toISOString()}] ctlog resumed ${st.log} at cursor=${st.position}`);
+      } else if (st.type === 'tick' && st.log) {
+        const p = (perLog[st.log] = perLog[st.log] || { fetched: 0, x509: 0, precert: 0, emitted: 0, idle: 0, lastSeenMs: 0 });
+        if (st.stats) { p.fetched = st.stats.fetched; p.x509 = st.stats.x509; p.precert = st.stats.precert; p.emitted = st.stats.emitted; }
+        p.lastSeenMs = Date.now();
+      } else if (st.type === 'idle' && st.log) {
+        const p = (perLog[st.log] = perLog[st.log] || { fetched: 0, x509: 0, precert: 0, emitted: 0, idle: 0, lastSeenMs: 0 });
+        p.idle++;
+        p.lastSeenMs = Date.now();
       }
     },
   });
@@ -141,7 +156,14 @@ function connectCT() {
   return connectCtLog();
 }
 
-setInterval(() => broadcast({ type: 'stats', ...stats }), 2000);
+setInterval(() => {
+  const now = Date.now();
+  const perLogPayload = {};
+  for (const [k, v] of Object.entries(perLog)) {
+    perLogPayload[k] = { ...v, stale: v.lastSeenMs ? (now - v.lastSeenMs > 60_000) : false };
+  }
+  broadcast({ type: 'stats', ...stats, perLog: perLogPayload });
+}, 2000);
 
 server.listen(PORT, '127.0.0.1', () => {
   console.log(`\n  domain-sniper :: live dashboard`);

← fa1502d brand-typo-watcher: idle counter + stale-log marker in per-l  ·  back to Domain Sniper  ·  dashboard ui: per-log source band with health dots 24e477f →