`;
}
@@ -376,18 +376,23 @@ interface ServiceBatch {
}
// Current state follows the latest batch of checks — the checks recorded by
-// the most recent cron tick (last 60 seconds) — so a single earlier failure
-// does not keep a service Degraded; the pill tracks the latest probes. The
-// 24h bars keep the history for context.
+// the most recent scheduled run (service's own interval, min 60s). When no
+// batch is in the window, fall back to the last known check result so a
+// service shows its most recent state instead of "Unknown"; only services
+// with no checks at all report no_data. Bars keep the 24h history for context.
function serviceStatus(
s: Service,
batch: ServiceBatch | null,
+ latest: LatestCheck | null,
): ServiceStatusKey {
if (s.enabled === 0) return 'disabled';
- if (batch === null || batch.total === 0) return 'no_data';
- if (batch.up === 0) return 'outage';
- if (batch.up < batch.total) return 'degraded';
- return 'operational';
+ if (batch !== null && batch.total > 0) {
+ if (batch.up === 0) return 'outage';
+ if (batch.up < batch.total) return 'degraded';
+ return 'operational';
+ }
+ if (latest !== null) return latest.ok === 1 ? 'operational' : 'outage';
+ return 'no_data';
}
type OverallStatusKey = 'operational' | 'degraded' | 'outage' | 'unknown';
@@ -448,14 +453,22 @@ async function loadSnapshot(env: Env): Promise {
WHERE service_id = ? ORDER BY checked_at DESC LIMIT 1`,
).bind(s.id).first() ?? null,
);
+ // Current-state batch: checks recorded within the service's own check
+ // interval (min 60s — the cron cadence), so state follows the most recent
+ // scheduled batch instead of a hardcoded window.
+ const windowSeconds = Math.max(s.interval_seconds, 60);
const batch = await env.DB.prepare(
`SELECT COUNT(*) AS total, SUM(CASE WHEN ok = 1 THEN 1 ELSE 0 END) AS up
FROM checks
- WHERE service_id = ?1 AND checked_at >= strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-60 seconds')`,
- ).bind(s.id).first<{ total: number; up: number }>();
+ WHERE service_id = ?1 AND checked_at >= strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ?2)`,
+ ).bind(s.id, `-${windowSeconds} seconds`).first<{ total: number; up: number }>();
statuses.set(
s.id,
- serviceStatus(s, batch && batch.total > 0 ? { total: Number(batch.total), up: Number(batch.up) } : null),
+ serviceStatus(
+ s,
+ batch && batch.total > 0 ? { total: Number(batch.total), up: Number(batch.up) } : null,
+ latest.get(s.id) ?? null,
+ ),
);
histories.set(s.id, await serviceHistory(env, s.id));
}
@@ -632,12 +645,12 @@ async function dashboard(env: Env): Promise {
})
.join('');
body.push(`
-
-
-
${escapeHtml(s.name)}
- ${status.label}
+
+
+
${escapeHtml(s.name)}
+ ${status.label}
-
+
${bars}
@@ -692,11 +705,11 @@ async function adminPage(env: Env): Promise {