From 038d8afd09e2a152ccea4fdfcb9c791cb59d94a4 Mon Sep 17 00:00:00 2001 From: Dan Farrelly Date: Thu, 9 Jan 2025 10:53:37 -0500 Subject: [PATCH] Update status widget to use incident.io (#1038) --- shared/StatusWidget.tsx | 95 ++++++++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 30 deletions(-) diff --git a/shared/StatusWidget.tsx b/shared/StatusWidget.tsx index b95a54d33..6faa030b7 100644 --- a/shared/StatusWidget.tsx +++ b/shared/StatusWidget.tsx @@ -1,32 +1,71 @@ "use client"; import { useState, useEffect } from "react"; -type Indicator = "none" | "minor" | "major" | "critical"; -type StatusPageStatusResponse = { - page: { +type Impact = "partial_outage" | "degraded_performance" | "full_outage"; +type Indicator = Impact | "none" | "maintenance"; +type StatusEvent = { + id: string; + name: string; + url: string; + last_update_at: string; // ISO-8601 + last_update_message: string; + affected_components: { id: string; name: string; - url: string; - updated_at: string; - }; - status: { - description: string; - indicator: Indicator; - }; + group_name?: string; + }[]; +}; +type Incident = StatusEvent & { + status: "identified" | "investigating" | "monitoring"; + current_worst_impact: Impact; +}; +type MaintenanceInProgressEvent = StatusEvent & { + status: "maintenance_in_progress"; + started_at: string; // ISO-8601 + scheduled_end_at: string; // ISO-8601 +}; +type MaintenanceScheduledEvent = StatusEvent & { + status: "maintenance_scheduled"; + starts_at: string; // ISO-8601 + ends_at: string; // ISO-8601 +}; + +type StatusPageSummaryResponse = { + page_title: string; + page_url: string; + ongoing_incidents: Incident[]; + in_progress_maintenances: MaintenanceInProgressEvent[]; + scheduled_maintenances: MaintenanceScheduledEvent[]; }; type Status = { url: string; description: string; - indicator: Indicator; + impact: Indicator; updated_at: string; }; -const STATUS_PAGE_URL = "http://status.inngest.com"; // Not https +const impactMessage: { [K in Indicator]: string } = { + none: "All systems operational", + degraded_performance: "Degraded performance", + partial_outage: "Partial system outage", + full_outage: "Major system outage", + maintenance: "Maintenance in progress", +}; +// We use hex colors b/c tailwind only includes what is initially rendered +const statusColor: { [K in Indicator]: string } = { + none: "rgba(var(--color-matcha-500))", + degraded_performance: "rgb(var(--color-honey-300))", + maintenance: "rgb(var(--color-honey-300))", + partial_outage: "rgb(var(--color-honey-500))", + full_outage: "rgb(var(--color-ruby-500))", +}; -const fetchStatus = async (): Promise => { - return await fetch("https://inngest.statuspage.io/api/v2/status.json").then( - (r) => r.json() +const STATUS_PAGE_URL = "https://status.inngest.com"; + +const fetchStatus = async (): Promise => { + return await fetch("https://status.inngest.com/api/v1/summary").then((r) => + r.json() ); }; @@ -34,22 +73,26 @@ const useStatus = (): Status => { const [status, setStatus] = useState({ url: STATUS_PAGE_URL, description: "Fetching status...", - indicator: "none", + impact: "none", updated_at: "", }); useEffect(() => { (async function () { try { const res = await fetchStatus(); + // Grab first incident + const incident = res.ongoing_incidents?.[0]; + const impact = incident?.current_worst_impact || "none"; setStatus({ - ...res.status, - updated_at: res.page.updated_at, - url: res.page.url, + impact, + description: impactMessage[impact], + updated_at: incident?.last_update_at || new Date().toString(), + url: incident?.url || STATUS_PAGE_URL, }); } catch (e) { setStatus({ description: "Status page", - indicator: "none", + impact: "none", updated_at: "", url: STATUS_PAGE_URL, }); @@ -59,21 +102,13 @@ const useStatus = (): Status => { return status; }; -// We use hex colors b/c tailwind only includes what is initially rendered -const statusColor: { [K in Indicator]: string } = { - none: "#22c55e", // green-500 - minor: "#fde047", // yellow-300 - major: "#f97316", // orange-500 - critical: "#dc2626", // red-600 -}; - export function StatusIcon({ className = "" }: { className?: string }) { const status = useStatus(); return ( @@ -96,7 +131,7 @@ export default function StatusWidget({ > {status.description}