dashboard: live cache-age indicator
The web server's dashboardStats now reports how stale its shared cache
snapshot is — data.cache = { enabled, ttlMs, ageMs }, using the age of the
oldest dataset feeding the view (OLT-STATE, plus ONU-STATE under detailed
scan). cache.js gains ageOf(key).
The dashboard renders a live-ticking badge from it (renderCacheBadge):
- within TTL -> "◷ cached Ns ago" (info)
- past TTL -> same, warn-coloured (next load will refetch)
- no cache -> "● live" (Electron app, or PFW_CACHE_TTL_SECONDS=0)
The Dashboard Refresh button now sends fresh:true to bypass the cache and
reset the age; the detailed-stats toggle and auto-loads use the cache.
Shared renderer change, so the Electron app shows "live" (it sends no cache
field). Verified offscreen over HTTP: cached / stale / live states render
with the right text + colour.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6ee1d37a79
commit
c2206ad5a7
6 changed files with 84 additions and 5 deletions
|
|
@ -492,6 +492,20 @@ button.danger:hover:not(:disabled) {
|
|||
}
|
||||
.dash-toggle { text-transform: none; letter-spacing: 0; color: var(--fg-dim); font-size: 12px; }
|
||||
|
||||
/* Cache-age badge in the dashboard header. Colour comes from a status-*
|
||||
class set in JS (info = within TTL, warn = stale, ok = live/no cache). */
|
||||
.dash-cache {
|
||||
font-size: 11px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.3px;
|
||||
padding: 3px 9px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid currentColor;
|
||||
white-space: nowrap;
|
||||
cursor: default;
|
||||
}
|
||||
.dash-cache:empty { display: none; }
|
||||
|
||||
.dash-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
|
||||
.tile {
|
||||
background: linear-gradient(180deg, rgba(33, 200, 255, 0.07), transparent 70%), var(--bg-elev);
|
||||
|
|
|
|||
|
|
@ -1551,8 +1551,10 @@ $('#btn-olt-execute').addEventListener('click', async () => {
|
|||
|
||||
// -------- Dashboard: network telemetry --------
|
||||
|
||||
$('#dash-refresh').addEventListener('click', loadDashboard);
|
||||
$('#dash-extra').addEventListener('change', loadDashboard);
|
||||
// Refresh forces a fresh server fetch (bypasses the cache); the toggle and
|
||||
// auto-loads use the cache. Wrap so the DOM event isn't passed as `force`.
|
||||
$('#dash-refresh').addEventListener('click', () => loadDashboard(true));
|
||||
$('#dash-extra').addEventListener('change', () => loadDashboard());
|
||||
|
||||
// Bit-rate formatter, ported from the iOS app's Format.bps.
|
||||
function fmtBps(v) {
|
||||
|
|
@ -1578,6 +1580,44 @@ function fmtDbm(n) {
|
|||
return typeof n === 'number' ? n.toFixed(1) : '—';
|
||||
}
|
||||
|
||||
function fmtAge(ms) {
|
||||
const s = Math.max(0, Math.round(ms / 1000));
|
||||
if (s < 90) return `${s}s`;
|
||||
const m = Math.floor(s / 60);
|
||||
if (m < 60) return `${m}m ${s % 60}s`;
|
||||
return `${Math.floor(m / 60)}h ${m % 60}m`;
|
||||
}
|
||||
|
||||
// Cache-age badge. The web server reports how old its shared cache snapshot
|
||||
// is (`d.cache`); the Electron app sends no cache info → "live". The badge
|
||||
// ticks every second so the operator can see the data going stale.
|
||||
let dashCacheTimer = null;
|
||||
function renderCacheBadge(cache) {
|
||||
const el = $('#dash-cache');
|
||||
if (!el) return;
|
||||
if (dashCacheTimer) { clearInterval(dashCacheTimer); dashCacheTimer = null; }
|
||||
if (!cache || !cache.enabled) {
|
||||
el.className = 'dash-cache status-ok';
|
||||
el.textContent = '● live';
|
||||
el.title = 'Fetched directly from MCMS (no server-side cache).';
|
||||
return;
|
||||
}
|
||||
const ttl = cache.ttlMs || 0;
|
||||
const baseAge = cache.ageMs || 0;
|
||||
const receivedAt = Date.now();
|
||||
const paint = () => {
|
||||
const age = baseAge + (Date.now() - receivedAt);
|
||||
const stale = ttl > 0 && age > ttl;
|
||||
el.className = 'dash-cache ' + (stale ? 'status-warn' : 'status-info');
|
||||
el.textContent = `◷ cached ${fmtAge(age)} ago`;
|
||||
el.title = stale
|
||||
? `Shared server cache is past its ${Math.round(ttl / 1000)}s TTL — the next load refetches from MCMS.`
|
||||
: `Shared server cache (TTL ${Math.round(ttl / 1000)}s) — collapses repeated MCMS requests across operators. Refresh to force-refetch.`;
|
||||
};
|
||||
paint();
|
||||
dashCacheTimer = setInterval(paint, 1000);
|
||||
}
|
||||
|
||||
function dashHealthRow(title, detail, count, drillKey) {
|
||||
const cls = count > 0 ? 'status-warn' : 'status-ok';
|
||||
const tappable = drillKey && count > 0;
|
||||
|
|
@ -1613,13 +1653,13 @@ function showListModal(title, items) {
|
|||
overlay.querySelector('.modal-ok').focus();
|
||||
}
|
||||
|
||||
async function loadDashboard() {
|
||||
async function loadDashboard(force) {
|
||||
const extra = $('#dash-extra').checked;
|
||||
$('#dash-status').textContent = extra
|
||||
? 'Loading network + per-ONU Rx/FEC scan… (this fetches every ONU-STATE)'
|
||||
: 'Loading network overview…';
|
||||
$('#dash-refresh').disabled = true;
|
||||
const res = await window.api.dashboardStats({ extraStats: extra });
|
||||
const res = await window.api.dashboardStats({ extraStats: extra, fresh: !!force });
|
||||
$('#dash-refresh').disabled = false;
|
||||
if (!res.ok) {
|
||||
$('#dash-status').textContent = `Error: ${res.error?.message || 'unknown'}`;
|
||||
|
|
@ -1741,4 +1781,6 @@ function renderDashboard(d) {
|
|||
const stamp = d.sampledAt ? new Date(d.sampledAt).toLocaleTimeString() : '';
|
||||
$('#dash-status').textContent =
|
||||
`${d.onuTotal} ONUs · ${d.oltCount} OLTs${d.extraStats ? ' · detailed scan' : ''}${stamp ? ' · ' + stamp : ''}`;
|
||||
|
||||
renderCacheBadge(d.cache);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@
|
|||
<div class="fleet-head">
|
||||
<h2>Dashboard</h2>
|
||||
<div class="row">
|
||||
<span id="dash-cache" class="dash-cache" title=""></span>
|
||||
<label class="inline dash-toggle">
|
||||
<input id="dash-extra" type="checkbox" />
|
||||
Detailed stats (Rx / FEC scan)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue