dashboard(matrix-accounts): dim live dot when daemon heartbeat stalls
The daemon now force-rewrites its matrix-accounts.json snapshot every ~30s, so as_of_unix advances while the daemon is alive and a stalled value is an honest 'stopped publishing' signal rather than just an old snapshot. Use it: when an account's snapshot still says live but as_of hasn't advanced in more than ~90s (3 missed heartbeats) and the container is NOT explicitly down, dim + desaturate the green dot and label it 'online · no heartbeat', with a tooltip explaining the daemon is likely dead or wedged. The container cross-ref still takes precedence — a stopped container stays the solid amber 'stale'. Keeps the existing 3 states intact; adds a distinct degraded-green sub-state.
This commit is contained in:
parent
fd2eac99ce
commit
fe974c580d
2 changed files with 46 additions and 19 deletions
|
|
@ -65,6 +65,15 @@
|
|||
.ma-dot.offline,
|
||||
.ma-dot.stale { background: var(--amber); }
|
||||
.ma-dot.absent { background: var(--muted); }
|
||||
/* `live stale-age`: snapshot still says live but the daemon heartbeat stalled
|
||||
(> ~90s). Keep the green hue but dim + desaturate so it reads "was online,
|
||||
now uncertain" — visually distinct from the solid amber container-down
|
||||
`stale`. More-specific (3 classes) so it overrides `.ma-dot.live`. */
|
||||
.ma-dot.live.stale-age {
|
||||
background: var(--green);
|
||||
opacity: 0.4;
|
||||
filter: saturate(0.45);
|
||||
}
|
||||
.ma-name { font-weight: 600; color: var(--fg); }
|
||||
.ma-uid { color: var(--muted); font-size: 0.8rem; margin-left: 0.4em; }
|
||||
.ma-hs { color: var(--muted); font-size: 0.85rem; }
|
||||
|
|
@ -74,6 +83,7 @@
|
|||
.ma-status.offline,
|
||||
.ma-status.stale { color: var(--amber); }
|
||||
.ma-status.absent { color: var(--muted); }
|
||||
.ma-status.live.stale-age { color: var(--green); opacity: 0.6; }
|
||||
|
||||
.ma-result {
|
||||
margin-top: 0.7rem;
|
||||
|
|
|
|||
|
|
@ -21,20 +21,22 @@
|
|||
// The token is NEVER echoed back in any response, and this page never
|
||||
// re-renders a submitted secret.
|
||||
//
|
||||
// Live status dot (coordinated with the BE-4 snapshot): the daemon publishes a
|
||||
// host-visible snapshot rewritten on each (re)start, so `live` means "restored
|
||||
// as of `as_of_unix`", not a real-time heartbeat. We render 3 states —
|
||||
// green (live + container running) = online
|
||||
// amber (live + container DOWN) = stale (container down ⟹ daemon down)
|
||||
// amber (token_present + !live) = provisioned but offline
|
||||
// grey (no token) = not provisioned
|
||||
// `as_of_unix` is tooltipped ("live as of N ago") so a green isn't read as a
|
||||
// real-time guarantee. We deliberately do NOT dim a green purely on snapshot
|
||||
// age: an old `as_of` is ambiguous (stable long uptime vs dead daemon), and the
|
||||
// container cross-ref already catches the definitive "down" case; true
|
||||
// daemon-up-but-client-dead detection is a daemon-heartbeat follow-up. When
|
||||
// `live` is absent (v1 backend not yet deployed) the dot falls back to the
|
||||
// token-present rendering.
|
||||
// Live status dot (coordinated with the BE snapshot + heartbeat): the daemon
|
||||
// force-rewrites its host-visible snapshot every ~30s (heartbeat, BE), so
|
||||
// `as_of_unix` advances while the daemon is alive — a stalled `as_of` is now an
|
||||
// honest "daemon stopped publishing" signal, not just "snapshot is old". We
|
||||
// render —
|
||||
// green (live + running + fresh) = online
|
||||
// dim green (live but as_of stale > ~90s) = heartbeat stopped, likely dead
|
||||
// amber (live + container DOWN) = stale (container down ⟹ down)
|
||||
// amber (token_present + !live) = provisioned but offline
|
||||
// grey (no token) = not provisioned
|
||||
// The dim-green age case is meaningful only because of the heartbeat: 3 missed
|
||||
// ~30s beats (>90s) without the container being explicitly down means the
|
||||
// daemon is up-but-dead or wedged. The container cross-ref still takes
|
||||
// precedence (a stopped container is definitively stale regardless of age).
|
||||
// `as_of_unix` is tooltipped throughout so freshness is always legible. When
|
||||
// `live` is absent (v1 backend) the dot falls back to token-present rendering.
|
||||
|
||||
import { $, el, esc, fmtAgeSecs, renderServerWarnings } from './common.js';
|
||||
|
||||
|
|
@ -102,13 +104,18 @@ async function loadAccounts(agent) {
|
|||
// e.g. a failed /api/state read) is treated as not-down so we never flag a
|
||||
// false stale.
|
||||
const running = containerRunning.get(agent);
|
||||
// The daemon rewrites its snapshot only on (re)start, so this is "live as of"
|
||||
// the last publish, not a heartbeat — surface it so a green isn't read as a
|
||||
// real-time guarantee.
|
||||
// The daemon force-rewrites its snapshot every ~30s, so `as_of_unix` advances
|
||||
// while it's alive — this is a heartbeat, and a stalled value is meaningful.
|
||||
const ageSecs = asOf != null
|
||||
? Math.max(0, Math.floor(Date.now() / 1000) - asOf)
|
||||
: null;
|
||||
const asOfText = asOf != null
|
||||
? 'matrix snapshot · live as of '
|
||||
+ fmtAgeSecs(Math.max(0, Math.floor(Date.now() / 1000) - asOf)) + ' ago'
|
||||
? 'matrix snapshot · live as of ' + fmtAgeSecs(ageSecs) + ' ago'
|
||||
: 'no daemon snapshot yet';
|
||||
// 3 missed ~30s heartbeats. Past this a `live` snapshot whose container is
|
||||
// NOT down means the daemon stopped publishing (dead/wedged) — dim its dot.
|
||||
const STALE_AGE_SECS = 90;
|
||||
const staleByAge = ageSecs != null && ageSecs > STALE_AGE_SECS;
|
||||
list.replaceChildren();
|
||||
if (!accounts.length) {
|
||||
list.append(el('p', { class: 'meta' }, 'no matrix accounts configured for this agent.'));
|
||||
|
|
@ -130,6 +137,16 @@ async function loadAccounts(agent) {
|
|||
cls = 'stale';
|
||||
statusText = 'container stopped';
|
||||
dotTitle = 'container is stopped — live status is stale. ' + asOfText;
|
||||
} else if (acc.live && staleByAge) {
|
||||
// Snapshot says live, but the heartbeat (snapshot mtime = as_of) hasn't
|
||||
// advanced in > ~90s while the container is NOT down — the daemon stopped
|
||||
// publishing, so the "live" is no longer trustworthy. Keep the green
|
||||
// family but dim it (distinct from the amber container-down 'stale').
|
||||
cls = 'live stale-age';
|
||||
statusText = 'online · no heartbeat';
|
||||
dotTitle = 'snapshot says live but the daemon heartbeat stalled '
|
||||
+ fmtAgeSecs(ageSecs) + ' ago (publishes every ~30s) — likely dead or wedged. '
|
||||
+ asOfText;
|
||||
} else if (acc.live) {
|
||||
cls = 'live';
|
||||
statusText = 'online ✓';
|
||||
|
|
|
|||
Loading…
Reference in a new issue