diff --git a/Plugin/unraid/api/auth.php b/Plugin/unraid/api/auth.php index 1cdb208..b885141 100644 --- a/Plugin/unraid/api/auth.php +++ b/Plugin/unraid/api/auth.php @@ -203,6 +203,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { 'last_change' => $r['last_change'] ?? null, 'last_detail' => $r['last_detail'] ?? null, 'last_ms' => $r['last_ms'] ?? null, + // When this domain was last actually probed. The probe follows NPM's host list, so + // a domain removed there simply stops being visited while its record stays here — + // and a stored state of "down" then describes the moment it left, not today. The + // card needs this to tell "broken" from "no longer served". + 'last_at' => $r['last_at'] ?? null, // The history card below the table. One aligned series per period rather than the // raw buckets: the client would otherwise have to re-derive calendar keys to know // which of thirty slots a given day belongs in, and there would then be two diff --git a/Plugin/unraid/pages/auth.php b/Plugin/unraid/pages/auth.php index 0247bfb..c076276 100644 --- a/Plugin/unraid/pages/auth.php +++ b/Plugin/unraid/pages/auth.php @@ -345,6 +345,9 @@ require_once dirname(__DIR__) . '/include/ai_chat.php'; absence — flat, unsaturated, no height to compare against the bars beside it. */ .vv-au-hb-bars i.vv-au-hb-gap { background:#191919; height:2px !important; } .vv-au-hb-cover { font-size:9px; color:#3a3a3a; font-family:monospace; line-height:1; } +/* Dimmed rather than hidden: the history is real and worth keeping, the domain just is not being + served any more. Sorted below the live ones, so it never leads the card at 0%. */ +.vv-au-hb-dom.vv-au-hb-stale { color:#4a4a4a; } /* ── Why ─────────────────────────────────────────────────────────────────── */ /* On the figure's own line, not under the strip: the strip is 179px of fixed-width bars and is what @@ -968,14 +971,31 @@ function _renderUptimeHistory() { const vals = _UP_PERIODS.map(([k]) => h[k] && h[k].pct).filter(v => v !== null && v !== undefined); return vals.length ? Math.min(...vals) : 101; }; - doms.sort((a, b) => score(a) - score(b) || a.localeCompare(b)); + + // The probe follows NPM's host list, so a domain removed there stops being visited while its + // record stays in the store — frozen at whatever it was doing when it left. Its stored state is + // then a fact about that moment and not about now, and the worst thing this card could do is + // report a decommissioned host as down: it reads as an outage nobody is fixing. + // + // Ten minutes at a one-minute cadence, so a single missed pass or a slow one never counts. The + // history stays visible — it was true — but the row says so and sorts below live domains rather + // than leading the list at 0%. + const _now = Math.round(Date.now() / 1000); + const stale = d => !_uptime[d].last_at || (_now - _uptime[d].last_at) > 600; + + doms.sort((a, b) => (stale(a) - stale(b)) || score(a) - score(b) || a.localeCompare(b)); box.innerHTML = doms.map(d => { const rec = _uptime[d], h = rec.hist || {}; - // Three states, not two: a domain the probe has never reached a verdict on is grey. Painting - // it green because it is "not down" would be the card asserting something it does not know. - const dot = rec.state === 'down' ? '#ef5350' : (rec.state === 'up' ? '#2d5a2d' : '#333'); - const dotT = rec.state === 'down' ? 'down now' : (rec.state === 'up' ? 'up now' : 'not yet probed'); + // Four states, not two: up, down, never probed, and no longer probed. Painting a domain green + // because it is "not down", or red when nobody has asked it anything for eleven hours, would + // both be the card asserting something it does not know. + const st = stale(d); + const dot = st ? '#333' + : (rec.state === 'down' ? '#ef5350' : (rec.state === 'up' ? '#2d5a2d' : '#333')); + const dotT = st ? ('last probed ' + (rec.last_at ? _ago(rec.last_at) + ' ago' : 'never') + + ' — no longer in NPM, so nothing is checking it') + : (rec.state === 'down' ? 'down now' : (rec.state === 'up' ? 'up now' : 'not yet probed')); const cells = _UP_PERIODS.map(([k]) => { const p = h[k]; if (!p) return '