Show when a watchdog cycle last completed

Without it the page renders zeros, all-clear pills and green dots when the
orchestrator has stopped entirely — its healthiest appearance describing a
machine with no watchdogs, against a header promising absence is never drawn as
an all-clear. The heartbeat already existed; overdue is measured against the
cron schedule rather than an assumed fifteen minutes.
This commit is contained in:
Gmer4Lfe
2026-08-14 16:18:14 -04:00
parent a7387c27fb
commit 9f32644c32
2 changed files with 50 additions and 0 deletions
+23
View File
@@ -419,6 +419,27 @@ function _systemCard(node, cfg) {
? '<span style="color:#7a6a20;">off — CONF_SYNC_ENABLED</span>'
: '<span style="color:#4caf50;">active</span>';
// ── Is anything still running? ──────────────────────────────────────────────
// Every other number on this card is only meaningful if a cycle actually happened. If the
// orchestrator stopped, the strike maps stop changing and the whole page renders zeros, "all
// clear" pills and green dots — its healthiest possible appearance, describing a machine with
// no watchdogs at all. This is the one reading that distinguishes quiet from absent, and the
// page's own contract says absence must never be drawn as an all-clear.
//
// stability_watchdog.sh writes the heartbeat last in the chain, so a fresh value means a whole
// cycle finished rather than one that started and died halfway. Overdue is measured against the
// schedule in force (cfg.cycle_min, read from the cron), not a constant: one missed cycle is
// worth noticing, two is worth alarming about.
const cycleMin = cfg.cycle_min || 15;
const lastCyc = (node.states || {}).last_cycle || 0;
const cycAge = lastCyc ? Math.max(0, Math.floor(Date.now() / 1000) - lastCyc) : null;
const cycCol = cycAge == null ? '#555'
: cycAge > cycleMin * 60 * 3 ? '#ef5350'
: cycAge > cycleMin * 60 * 2 ? '#ffb74d' : '#4caf50';
const cycTxt = cycAge == null ? 'never' : _relTime(lastCyc);
const cycNote = cycAge == null ? ' — no cycle has completed'
: cycAge > cycleMin * 60 * 2 ? ' — overdue, expected every ' + cycleMin + 'm' : '';
const apiOnly = sys.api_only === true;
const daemonDot = sys.daemon_ok === null ? '#555' : sys.daemon_ok ? '#4caf50' : '#ef5350';
const daemonTxt = sys.daemon_ok === null ? '—' : sys.daemon_ok ? 'daemon ok' : 'daemon err';
@@ -439,6 +460,8 @@ function _systemCard(node, cfg) {
>${apiOnly ? 'API ONLY' : _levelLabel(level)}</span>
</div>
<div class="vv-wd-sec">System</div>
${!apiOnly ? _row('Last cycle', `<span style="color:${cycCol}">${vvEscHtml(cycTxt)}</span>`
+ `<span style="color:#3a3a3a;font-size:10px;">${vvEscHtml(cycNote)}</span>`) : ''}
${_row('RAM free', `<span style="color:${memCol}">${_fmtBytes(sys.mem_avail)}</span> / ${_fmtBytes(sys.mem_total)}`)}
${_bar(usedPct, memCol)}
<div style="display:flex;justify-content:space-between;margin-top:1px;font-size:10px;color:#333">