diff --git a/src/overall.ts b/src/overall.ts new file mode 100644 index 0000000..a55b058 --- /dev/null +++ b/src/overall.ts @@ -0,0 +1,48 @@ +import type { Env } from "./types"; +import { getManifest } from "./manifest"; +import { getLatestPing } from "./db"; +import { getDeviceStatus } from "./tailscale"; + +export type OverallGrade = "up" | "degraded" | "down"; + +export interface OverallStatus { + grade: OverallGrade; + label: string; +} + +export async function getOverallStatus(env: Env): Promise { + const manifest = await getManifest(env); + const servers = Object.entries(manifest).filter( + ([, m]) => m.type === "server" && m.services.length > 0, + ); + + const statuses: string[] = []; + let anyServerOffline = false; + + for (const [, machine] of servers) { + const online = await getDeviceStatus(env, machine.tailscale_host); + if (!online) anyServerOffline = true; + + for (const svc of machine.services.filter((s) => s.health_url)) { + const ping = await getLatestPing(env.DB, svc.name); + statuses.push((ping?.status as string) ?? "unknown"); + } + } + + const downCount = statuses.filter( + (s) => s === "down" || s === "timeout", + ).length; + const downRatio = statuses.length > 0 ? downCount / statuses.length : 0; + const onFire = anyServerOffline || downRatio >= 0.4; + const hasDegraded = statuses.some( + (s) => + s === "down" || + s === "timeout" || + s === "degraded" || + s === "misconfigured", + ); + + if (onFire) return { grade: "down", label: "On fire" }; + if (hasDegraded) return { grade: "degraded", label: "Some systems degraded" }; + return { grade: "up", label: "All systems operational" }; +} diff --git a/src/routes/favicon.ts b/src/routes/favicon.ts index 389f19f..e701f77 100644 --- a/src/routes/favicon.ts +++ b/src/routes/favicon.ts @@ -1,39 +1,15 @@ import type { Env } from "../types"; -import { getManifest } from "../manifest"; -import { getLatestPing } from "../db"; +import { getOverallStatus, type OverallGrade } from "../overall"; -const COLORS: Record = { +const COLORS: Record = { up: "#2ecc71", degraded: "#f39c12", down: "#e74c3c", }; export async function handleFavicon(env: Env): Promise { - const manifest = await getManifest(env); - const activeServers = Object.values(manifest).filter( - (m) => m.type === "server" && m.services.length > 0, - ); - const statuses: string[] = []; - - for (const machine of activeServers) { - for (const svc of machine.services.filter((s) => s.health_url)) { - const ping = await getLatestPing(env.DB, svc.name); - statuses.push((ping?.status as string) ?? "unknown"); - } - } - - const downCount = statuses.filter((s) => s === "down" || s === "timeout").length; - const downRatio = statuses.length > 0 ? downCount / statuses.length : 0; - const hasIssues = statuses.some( - (s) => s === "down" || s === "timeout" || s === "degraded" || s === "misconfigured", - ); - - let color: string; - if (downRatio >= 0.4) color = COLORS.down; - else if (hasIssues) color = COLORS.degraded; - else color = COLORS.up; - - const svg = ``; + const { grade } = await getOverallStatus(env); + const svg = ``; return new Response(svg, { headers: { diff --git a/src/routes/index.ts b/src/routes/index.ts index 2a8d8df..7d0b906 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -2,6 +2,7 @@ import type { Env } from "../types"; import { getManifest } from "../manifest"; import { getLatestPing, getUptime7d, getLastCheckTime } from "../db"; import { getDeviceStatus } from "../tailscale"; +import { getOverallStatus } from "../overall"; import { COMMIT_SHA } from "../version"; export async function handleIndex(env: Env): Promise { @@ -37,24 +38,7 @@ export async function handleIndex(env: Env): Promise { const servers = machines.filter((m) => m.type === "server"); const clients = machines.filter((m) => m.type === "client"); - const activeServers = servers.filter((m) => m.services.length > 0); - const anyServerOffline = activeServers.some((m) => !m.online); - const svcStatuses = activeServers.flatMap((m) => m.services.map((s) => s.status)); - const downCount = svcStatuses.filter((s) => s === "down" || s === "timeout").length; - const downRatio = svcStatuses.length > 0 ? downCount / svcStatuses.length : 0; - const onFire = anyServerOffline || downRatio >= 0.4; - const hasDegraded = - svcStatuses.includes("down") || - svcStatuses.includes("timeout") || - svcStatuses.includes("degraded") || - svcStatuses.includes("misconfigured") || - svcStatuses.includes("partial"); - const overallClass = onFire ? "down" : hasDegraded ? "degraded" : "up"; - const overallText = onFire - ? "On fire" - : hasDegraded - ? "Some systems degraded" - : "All systems operational"; + const { grade: overallClass, label: overallText } = await getOverallStatus(env); const html = ` diff --git a/src/routes/status.ts b/src/routes/status.ts index 6228087..62555d2 100644 --- a/src/routes/status.ts +++ b/src/routes/status.ts @@ -2,6 +2,7 @@ import type { Env } from "../types"; import { getManifest } from "../manifest"; import { getLatestPing, getUptime7d } from "../db"; import { getDeviceStatus } from "../tailscale"; +import { getOverallStatus } from "../overall"; function worstStatus(statuses: string[]): string { if (statuses.length === 0) return "unknown"; @@ -19,17 +20,13 @@ async function overallStatus(env: Env): Promise { const allServices = Object.values(manifest).flatMap((m) => m.services); const monitored = allServices.filter((s) => s.health_url !== null); - const statuses: string[] = []; let totalUptime = 0; - for (const svc of monitored) { - const ping = await getLatestPing(env.DB, svc.name); const uptime = await getUptime7d(env.DB, svc.name); - statuses.push((ping?.status as string) ?? "unknown"); totalUptime += uptime; } - const status = worstStatus(statuses); + const { grade } = await getOverallStatus(env); const avgUptime = monitored.length > 0 ? Math.round((totalUptime / monitored.length) * 100) / 100 @@ -37,8 +34,8 @@ async function overallStatus(env: Env): Promise { return Response.json( { - ok: status === "up", - status, + ok: grade === "up", + status: grade, uptime_7d: avgUptime, services_total: allServices.length, services_monitored: monitored.length, @@ -79,15 +76,12 @@ async function fullStatus(env: Env): Promise { }), ); - const allStatuses = machines - .filter((m) => m.type === "server") - .flatMap((m) => [m.online ? "up" : "down", ...m.services.map((s) => s.status)]); - const status = worstStatus(allStatuses); + const { grade } = await getOverallStatus(env); return Response.json( { - ok: status === "up", - status, + ok: grade === "up", + status: grade, machines, } ); }