[object Object]

← back to Macstudio2 Dashboard

feat: viewer counter on dashboard odometer — same as Mac1

a87669db5311c82297fc8ee002be5f3a9becc106 · 2026-05-08 00:16:09 -0700 · SteveStudio2

Files touched

Diff

commit a87669db5311c82297fc8ee002be5f3a9becc106
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Fri May 8 00:16:09 2026 -0700

    feat: viewer counter on dashboard odometer — same as Mac1
---
 public/index.html |  4 ++++
 server.js         | 24 ++++++++++++++++++++++--
 2 files changed, 26 insertions(+), 2 deletions(-)

diff --git a/public/index.html b/public/index.html
index da4a0a1..9249110 100644
--- a/public/index.html
+++ b/public/index.html
@@ -685,6 +685,10 @@ footer a { color: var(--gold); text-decoration: none; }
     <span class="digit" id="odo-power">AUTO</span>
     <span>CALLS</span>
     <span class="digit" id="odo-calls">0000</span>
+    <span>👀 VIEWERS</span>
+    <span class="digit" id="odo-viewers">0</span>
+    <span>LAST POLL</span>
+    <span class="digit" id="odo-poll">—</span>
   </div>
 
   <!-- Ollama live log tail — what's actually hitting the server -->
diff --git a/server.js b/server.js
index 0a15b0b..5307a82 100644
--- a/server.js
+++ b/server.js
@@ -23,6 +23,22 @@ app.use(express.static(path.join(__dirname, 'public'), {
   }
 }));
 
+// Viewer tracking — every /api/status hit from the dashboard front-end
+// counts toward "is anyone watching". 30s window decides "active".
+const viewerHits = new Map(); // ip -> last_at
+function recordViewer(ip) {
+  viewerHits.set(ip || 'unknown', Date.now());
+  // GC entries older than 5 min so map stays bounded
+  const cutoff = Date.now() - 5 * 60 * 1000;
+  for (const [k, v] of viewerHits) if (v < cutoff) viewerHits.delete(k);
+}
+function activeViewers() {
+  const cutoff = Date.now() - 30 * 1000;
+  let n = 0;
+  for (const v of viewerHits.values()) if (v >= cutoff) n++;
+  return n;
+}
+
 // Recent activity log — last 30 inference triggers seen via /api/ps polling.
 // Each tick of the front-end's fetch indirectly produces a record when ps changes.
 const recentCalls = []; // { at, model, queue }
@@ -140,8 +156,9 @@ async function readPowerMode() {
   return 'auto';
 }
 
-app.get('/api/status', async (_req, res) => {
+app.get('/api/status', async (req, res) => {
   try {
+    recordViewer(req.ip);
     const [cpu, mem, oll, power, ollProcs] = await Promise.all([
       readCpu(), readMemory(), readOllama(), readPowerMode(), readOllamaProc()
     ]);
@@ -154,7 +171,10 @@ app.get('/api/status', async (_req, res) => {
       hostname: require('os').hostname(),
       cpu, memory: mem, ollama: oll, ollama_procs: ollProcs, power_mode: power,
       recent_calls: recentCalls.slice(0, 10),
-      uptime_s: Math.round(require('os').uptime())
+      uptime_s: Math.round(require('os').uptime()),
+      viewers: activeViewers(),
+      viewer_hits_30s: Array.from(viewerHits.values()).filter(t => Date.now() - t < 30000).length,
+      total_known_viewers: viewerHits.size
     });
   } catch (e) {
     res.status(500).json({ error: String(e.message || e) });

← aee1fdd feat: same self-healing version check on Mac2 dashboard  ·  back to Macstudio2 Dashboard  ·  feat: TEST button on Mac2 dashboard — same single-prompt lat 2796cb3 →