← back to Macstudio1 Dashboard
feat: viewer counter on dashboard odometer — see if browser is actually polling
19f97affe25652cca7d2c4bd3e26aaed97639ce0 · 2026-05-08 00:16:09 -0700 · SteveStudio2
Files touched
M public/index.htmlM server.js
Diff
commit 19f97affe25652cca7d2c4bd3e26aaed97639ce0
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date: Fri May 8 00:16:09 2026 -0700
feat: viewer counter on dashboard odometer — see if browser is actually polling
---
public/index.html | 7 +++++++
server.js | 24 ++++++++++++++++++++++--
2 files changed, 29 insertions(+), 2 deletions(-)
diff --git a/public/index.html b/public/index.html
index 5e9c9b1..0d0eab1 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 -->
@@ -868,6 +872,9 @@ async function tick() {
document.getElementById('odo-uptime').textContent = fmtUptime(r.uptime_s || 0);
document.getElementById('odo-power').textContent = (r.power_mode || 'auto').toUpperCase();
document.getElementById('odo-calls').textContent = String(CALL_COUNT).padStart(4, '0');
+ document.getElementById('odo-viewers').textContent = String(r.viewers || 0);
+ const now = new Date(r.ts || Date.now());
+ document.getElementById('odo-poll').textContent = String(now.getHours()).padStart(2,'0') + ':' + String(now.getMinutes()).padStart(2,'0') + ':' + String(now.getSeconds()).padStart(2,'0');
} catch (e) {
console.warn('status fetch failed', e);
diff --git a/server.js b/server.js
index 7cc2fe6..6b5986c 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) });
← 729b4e1 feat: self-healing version check — dashboard auto-reloads on
·
back to Macstudio1 Dashboard
·
feat: BOOST button on Mac1 dashboard restarts ollama with NU fda8505 →