The Monitor fallback card described a state file without saying whether anything still writes it

This commit is contained in:
Gmer4Lfe
2026-08-22 01:11:00 -04:00
parent 13c221244e
commit 8ce8f0dfbe
2 changed files with 122 additions and 5 deletions
+65 -3
View File
@@ -1212,6 +1212,49 @@ function vvPollMonitor(live) {
}
fbHtml += `<div style="font-size:11px;color:${sColor};font-weight:500;margin-bottom:6px;">${sLabel}${stateExtra}</div>`;
// ── Daemon liveness — shown in EVERY state ─────────────────────────────
// The line above describes a file. This one describes whether anything is still writing
// it. Without it the card read "✓ Nominal · monitoring · 30s" on a host where nothing had
// run for five days: the state file said NORMAL, and nothing contradicted it.
const dae = fb.daemon ?? {};
let daeHtml;
if (dae.stale_lock) {
daeHtml = `<span style="color:#f44336;">✕ not running</span>
<span style="color:#444;"> · stale lock${dae.pid ? ' (PID ' + dae.pid + ')' : ''}</span>`;
} else if (dae.running === false) {
daeHtml = `<span style="color:#f44336;">✕ not running</span>
<span style="color:#444;"> · nothing is watching</span>`;
} else if (dae.running && dae.mode === 'dry-run') {
// A preview decides nothing and writes to its own state copy, so the live figures on
// this card stop moving while it runs. Said plainly rather than left to infer.
daeHtml = `<span style="color:#4a9eff;">◐ dry run</span>
<span style="color:#444;"> · preview only, PID ${dae.pid}</span>`;
} else if (dae.running) {
daeHtml = `<span style="color:#4caf50;">◉ watching</span>
<span style="color:#444;"> · every ${interval}s</span>`;
} else {
daeHtml = `<span style="color:#555;">— unknown</span>`;
}
fbHtml += `<div style="font-size:10px;margin-bottom:7px;">${daeHtml}</div>`;
// ── Mutual coverage ────────────────────────────────────────────────────
// Both directions, because they are configured in different files and are routinely
// asymmetric — this mesh currently has one side fully populated and the other empty,
// which is invisible if the card only reports its own.
const pid2 = fb.partner_id;
if (pid2) {
const we = fb.we_cover;
const they = fb.they_cover;
const num = (n, warn) => n === null || n === undefined
? `<span style="color:#333;">—</span>`
: `<span style="color:${n === 0 && warn ? '#ff9800' : '#888'};">${n}</span>`;
fbHtml += `<div style="display:grid;grid-template-columns:1fr auto;gap:1px 8px;font-size:10px;margin-bottom:6px;">
<span style="color:#444;">We cover ${vvEscHtml(pid2)}</span>${num(we, true)}
<span style="color:#444;">${vvEscHtml(pid2)} covers us</span>${num(they, false)}
</div>`;
if (we === 0) fbHtml += `<div style="font-size:9px;color:#5a4020;margin-bottom:6px;">No tiers set for ${vvEscHtml(pid2)} — we would start nothing</div>`;
}
// ── Handback strike progress ───────────────────────────────────────────
if (state === 'FALLBACK' && strikes > 0) {
const dots = Array.from({length: maxStrikes}, (_,i) =>
@@ -1257,9 +1300,22 @@ function vvPollMonitor(live) {
// ── NORMAL quiet state ─────────────────────────────────────────────────
if (state === 'NORMAL' && !fbActive.length) {
let meta = `monitoring · ${interval}s`;
if (ptRequired) meta += ' · partnership gated';
fbHtml += `<div style="font-size:10px;color:#2a2a2a;">${meta}</div>`;
// "monitoring · 30s" used to live here and was the card's only footer — a claim about a
// running process made from a file. The daemon row above owns that claim now, so this
// reports the things the state file genuinely knows.
const bits = [];
const lc = fb.last_change;
if (lc !== null && lc !== undefined) {
const a = lc < 60 ? lc + 's' : lc < 3600 ? Math.floor(lc/60) + 'm'
: lc < 86400 ? Math.floor(lc/3600) + 'h' : Math.floor(lc/86400) + 'd';
bits.push(`no change in ${a}`);
}
if (ptRequired) bits.push('partnership gated');
// Only mentioned when OFF, and only here: handback writeback is the step that carries
// work done during an outage back to the recovered host, and losing it silently is the
// expensive half of a failover nobody notices until afterwards.
if (fb.rsync_on_handback === false) bits.push('<span style="color:#5a4020;">no handback rsync</span>');
if (bits.length) fbHtml += `<div style="font-size:9px;color:#2a2a2a;">${bits.join(' · ')}</div>`;
}
// ── NO_INTERNET / DARK detail ──────────────────────────────────────────
@@ -1276,6 +1332,12 @@ function vvPollMonitor(live) {
if (fbCard) {
fbCard.classList.remove('vv-accent-ok','vv-accent-warn','vv-accent-err');
if (!enabled || ptSuspended) fbCard.classList.add('vv-accent-warn');
// A green accent is a claim that this is covered. It is not, if nothing is running or
// the only thing running is a preview that would take no action.
else if (fb.daemon && (fb.daemon.running === false || fb.daemon.stale_lock))
fbCard.classList.add('vv-accent-err');
else if (fb.daemon && fb.daemon.mode === 'dry-run')
fbCard.classList.add('vv-accent-warn');
else if (state === 'NORMAL') fbCard.classList.add('vv-accent-ok');
else if (state === 'FALLBACK') fbCard.classList.add('vv-accent-err');
else if (state !== 'UNKNOWN') fbCard.classList.add('vv-accent-warn');