From 6e2ead19e5814f89ecb22d4eae9aefda8cf7dbdb Mon Sep 17 00:00:00 2001 From: Nezumi-2711 Date: Fri, 28 Aug 2026 16:30:50 +0700 Subject: [PATCH] feat: add status page --- src/client/App.tsx | 2 + src/client/api/status.ts | 26 +++ src/client/components/SiteIcon.tsx | 7 +- src/client/components/StatusHistoryBar.tsx | 52 +++++ .../components/charts/LatencySparkline.tsx | 2 +- src/client/components/charts/UptimeBar.tsx | 2 +- src/client/pages/DashboardPage.tsx | 3 +- src/client/pages/LoginPage.tsx | 4 +- src/client/pages/MonitorDetailPage.tsx | 6 +- src/client/pages/SettingsPage.tsx | 4 +- src/client/pages/StatusPage.tsx | 159 +++++++++++++++ src/client/queries/status.ts | 15 ++ src/client/styles.css | 94 +++++++++ src/worker/index.ts | 2 + src/worker/routes/status.ts | 185 ++++++++++++++++++ test/status.spec.ts | 159 +++++++++++++++ 16 files changed, 710 insertions(+), 12 deletions(-) create mode 100644 src/client/api/status.ts create mode 100644 src/client/components/StatusHistoryBar.tsx create mode 100644 src/client/pages/StatusPage.tsx create mode 100644 src/client/queries/status.ts create mode 100644 src/worker/routes/status.ts create mode 100644 test/status.spec.ts diff --git a/src/client/App.tsx b/src/client/App.tsx index 4386257..8407f78 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -4,9 +4,11 @@ import { DashboardPage } from "./pages/DashboardPage"; import { LoginPage } from "./pages/LoginPage"; import { MonitorDetailPage } from "./pages/MonitorDetailPage"; import { SettingsPage } from "./pages/SettingsPage"; +import { StatusPage } from "./pages/StatusPage"; function App() { const pathname = usePathname(); + if (pathname === "/") return ; if (pathname === "/login") return ; const monitorMatch = pathname.match(/^\/monitors\/(\d+)\/?$/); const page = monitorMatch diff --git a/src/client/api/status.ts b/src/client/api/status.ts new file mode 100644 index 0000000..f7bea93 --- /dev/null +++ b/src/client/api/status.ts @@ -0,0 +1,26 @@ +import { getJson } from "./http"; + +export type PublicServiceStatus = "up" | "down" | "unknown"; +export type PublicOverallStatus = "operational" | "degraded" | "down"; + +export type PublicService = { + id: number; + name: string; + status: PublicServiceStatus; + lastCheckedAt: string | null; + uptime90d: number | null; + history: Array<{ + day: number; + uptimePct: number | null; + }>; +}; + +export type PublicStatus = { + overall: PublicOverallStatus; + updatedAt: number; + services: PublicService[]; +}; + +export function getStatus(signal?: AbortSignal) { + return getJson("/api/status", { signal }); +} diff --git a/src/client/components/SiteIcon.tsx b/src/client/components/SiteIcon.tsx index 6e448f2..8b3ec3b 100644 --- a/src/client/components/SiteIcon.tsx +++ b/src/client/components/SiteIcon.tsx @@ -3,16 +3,19 @@ import { useState } from "react"; type SiteIconProps = { monitorId: number; + favicon?: "admin" | "public"; }; -export function SiteIcon({ monitorId }: SiteIconProps) { +export function SiteIcon({ monitorId, favicon = "admin" }: SiteIconProps) { const [failed, setFailed] = useState(false); if (failed) return