my own status page
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: unify status calcs

+61 -59
+48
src/overall.ts
··· 1 + import type { Env } from "./types"; 2 + import { getManifest } from "./manifest"; 3 + import { getLatestPing } from "./db"; 4 + import { getDeviceStatus } from "./tailscale"; 5 + 6 + export type OverallGrade = "up" | "degraded" | "down"; 7 + 8 + export interface OverallStatus { 9 + grade: OverallGrade; 10 + label: string; 11 + } 12 + 13 + export async function getOverallStatus(env: Env): Promise<OverallStatus> { 14 + const manifest = await getManifest(env); 15 + const servers = Object.entries(manifest).filter( 16 + ([, m]) => m.type === "server" && m.services.length > 0, 17 + ); 18 + 19 + const statuses: string[] = []; 20 + let anyServerOffline = false; 21 + 22 + for (const [, machine] of servers) { 23 + const online = await getDeviceStatus(env, machine.tailscale_host); 24 + if (!online) anyServerOffline = true; 25 + 26 + for (const svc of machine.services.filter((s) => s.health_url)) { 27 + const ping = await getLatestPing(env.DB, svc.name); 28 + statuses.push((ping?.status as string) ?? "unknown"); 29 + } 30 + } 31 + 32 + const downCount = statuses.filter( 33 + (s) => s === "down" || s === "timeout", 34 + ).length; 35 + const downRatio = statuses.length > 0 ? downCount / statuses.length : 0; 36 + const onFire = anyServerOffline || downRatio >= 0.4; 37 + const hasDegraded = statuses.some( 38 + (s) => 39 + s === "down" || 40 + s === "timeout" || 41 + s === "degraded" || 42 + s === "misconfigured", 43 + ); 44 + 45 + if (onFire) return { grade: "down", label: "On fire" }; 46 + if (hasDegraded) return { grade: "degraded", label: "Some systems degraded" }; 47 + return { grade: "up", label: "All systems operational" }; 48 + }
+4 -28
src/routes/favicon.ts
··· 1 1 import type { Env } from "../types"; 2 - import { getManifest } from "../manifest"; 3 - import { getLatestPing } from "../db"; 2 + import { getOverallStatus, type OverallGrade } from "../overall"; 4 3 5 - const COLORS: Record<string, string> = { 4 + const COLORS: Record<OverallGrade, string> = { 6 5 up: "#2ecc71", 7 6 degraded: "#f39c12", 8 7 down: "#e74c3c", 9 8 }; 10 9 11 10 export async function handleFavicon(env: Env): Promise<Response> { 12 - const manifest = await getManifest(env); 13 - const activeServers = Object.values(manifest).filter( 14 - (m) => m.type === "server" && m.services.length > 0, 15 - ); 16 - const statuses: string[] = []; 17 - 18 - for (const machine of activeServers) { 19 - for (const svc of machine.services.filter((s) => s.health_url)) { 20 - const ping = await getLatestPing(env.DB, svc.name); 21 - statuses.push((ping?.status as string) ?? "unknown"); 22 - } 23 - } 24 - 25 - const downCount = statuses.filter((s) => s === "down" || s === "timeout").length; 26 - const downRatio = statuses.length > 0 ? downCount / statuses.length : 0; 27 - const hasIssues = statuses.some( 28 - (s) => s === "down" || s === "timeout" || s === "degraded" || s === "misconfigured", 29 - ); 30 - 31 - let color: string; 32 - if (downRatio >= 0.4) color = COLORS.down; 33 - else if (hasIssues) color = COLORS.degraded; 34 - else color = COLORS.up; 35 - 36 - const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><circle cx="8" cy="8" r="7" fill="${color}"/></svg>`; 11 + const { grade } = await getOverallStatus(env); 12 + const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><circle cx="8" cy="8" r="7" fill="${COLORS[grade]}"/></svg>`; 37 13 38 14 return new Response(svg, { 39 15 headers: {
+2 -18
src/routes/index.ts
··· 2 2 import { getManifest } from "../manifest"; 3 3 import { getLatestPing, getUptime7d, getLastCheckTime } from "../db"; 4 4 import { getDeviceStatus } from "../tailscale"; 5 + import { getOverallStatus } from "../overall"; 5 6 import { COMMIT_SHA } from "../version"; 6 7 7 8 export async function handleIndex(env: Env): Promise<Response> { ··· 37 38 const servers = machines.filter((m) => m.type === "server"); 38 39 const clients = machines.filter((m) => m.type === "client"); 39 40 40 - const activeServers = servers.filter((m) => m.services.length > 0); 41 - const anyServerOffline = activeServers.some((m) => !m.online); 42 - const svcStatuses = activeServers.flatMap((m) => m.services.map((s) => s.status)); 43 - const downCount = svcStatuses.filter((s) => s === "down" || s === "timeout").length; 44 - const downRatio = svcStatuses.length > 0 ? downCount / svcStatuses.length : 0; 45 - const onFire = anyServerOffline || downRatio >= 0.4; 46 - const hasDegraded = 47 - svcStatuses.includes("down") || 48 - svcStatuses.includes("timeout") || 49 - svcStatuses.includes("degraded") || 50 - svcStatuses.includes("misconfigured") || 51 - svcStatuses.includes("partial"); 52 - const overallClass = onFire ? "down" : hasDegraded ? "degraded" : "up"; 53 - const overallText = onFire 54 - ? "On fire" 55 - : hasDegraded 56 - ? "Some systems degraded" 57 - : "All systems operational"; 41 + const { grade: overallClass, label: overallText } = await getOverallStatus(env); 58 42 59 43 const html = `<!DOCTYPE html> 60 44 <html lang="en">
+7 -13
src/routes/status.ts
··· 2 2 import { getManifest } from "../manifest"; 3 3 import { getLatestPing, getUptime7d } from "../db"; 4 4 import { getDeviceStatus } from "../tailscale"; 5 + import { getOverallStatus } from "../overall"; 5 6 6 7 function worstStatus(statuses: string[]): string { 7 8 if (statuses.length === 0) return "unknown"; ··· 19 20 const allServices = Object.values(manifest).flatMap((m) => m.services); 20 21 const monitored = allServices.filter((s) => s.health_url !== null); 21 22 22 - const statuses: string[] = []; 23 23 let totalUptime = 0; 24 - 25 24 for (const svc of monitored) { 26 - const ping = await getLatestPing(env.DB, svc.name); 27 25 const uptime = await getUptime7d(env.DB, svc.name); 28 - statuses.push((ping?.status as string) ?? "unknown"); 29 26 totalUptime += uptime; 30 27 } 31 28 32 - const status = worstStatus(statuses); 29 + const { grade } = await getOverallStatus(env); 33 30 const avgUptime = 34 31 monitored.length > 0 35 32 ? Math.round((totalUptime / monitored.length) * 100) / 100 ··· 37 34 38 35 return Response.json( 39 36 { 40 - ok: status === "up", 41 - status, 37 + ok: grade === "up", 38 + status: grade, 42 39 uptime_7d: avgUptime, 43 40 services_total: allServices.length, 44 41 services_monitored: monitored.length, ··· 79 76 }), 80 77 ); 81 78 82 - const allStatuses = machines 83 - .filter((m) => m.type === "server") 84 - .flatMap((m) => [m.online ? "up" : "down", ...m.services.map((s) => s.status)]); 85 - const status = worstStatus(allStatuses); 79 + const { grade } = await getOverallStatus(env); 86 80 87 81 return Response.json( 88 82 { 89 - ok: status === "up", 90 - status, 83 + ok: grade === "up", 84 + status: grade, 91 85 machines, 92 86 } ); 93 87 }