From 4b75e9f7e6e44b8a33782ad81fa572aad8b61b0e Mon Sep 17 00:00:00 2001 From: Anirudh Oppiliappan Date: Fri, 28 Aug 2026 13:57:13 +0300 Subject: [PATCH] status: fix 'no data' bug Signed-off-by: Anirudh Oppiliappan --- status/src/index.ts | 61 ++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/status/src/index.ts b/status/src/index.ts index 46f183d9..80b3db4e 100644 --- a/status/src/index.ts +++ b/status/src/index.ts @@ -300,9 +300,9 @@ function headerRow(pillClass: string, pillLabel: string): string { -
+

Service Status

- ${escapeHtml(pillLabel)} + ${escapeHtml(pillLabel)}
`; } @@ -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 {
-
+
-
+
@@ -709,14 +722,16 @@ async function adminPage(env: Env): Promise {

Services

    `); for (const m of services) { - body.push(`
  • +body.push(`
  • ${escapeHtml(m.name)} ${m.enabled === 1 ? 'enabled' : 'disabled'}
    ${escapeHtml(m.url)}
    - - +
    + + +
  • `); } body.push('
'); @@ -751,7 +766,7 @@ async function adminPage(env: Env): Promise { ${inc.description ? `

${escapeHtml(inc.description)}

` : ''} -
+