import type { Metadata } from "next"; import Link from "next/link"; import { Badge } from "@/components/ui/badge"; import { formatDate, formatPercent, timeAgo } from "@/lib/format"; import { getStatusSnapshot, type ComponentState, type DayPoint, type Incident } from "@/lib/queries/status"; import { cn } from "@/lib/utils"; export const dynamic = "force-dynamic"; export const metadata: Metadata = { title: "Status", description: "Live operational status of the Fetcha API, dashboard, proxy networks and authentication, with 24-hour success rate and 30-day history.", alternates: { canonical: "/status" }, }; const STATE_META: Record = { operational: { label: "Operational", badge: "success", dot: "bg-success" }, degraded: { label: "Degraded", badge: "warning", dot: "bg-warning" }, outage: { label: "Major outage", badge: "danger", dot: "bg-danger" }, "not-launched": { label: "Not launched", badge: "outline", dot: "bg-border-strong" }, unknown: { label: "Unknown", badge: "default", dot: "bg-fg-subtle" }, }; const DAY_TONE: Record = { operational: "bg-success", degraded: "bg-warning", outage: "bg-danger", "no-data": "bg-bg-muted", "not-launched": "bg-bg-muted", unknown: "bg-bg-muted", }; function dayLabel(p: DayPoint): string { const date = new Date(`${p.day}T00:00:00Z`).toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" }); if (p.state === "no-data" || p.total === 0) return `${date} · No data`; return `${date} · ${formatPercent((p.successRate ?? 0) * 100, 2)} success · ${p.total.toLocaleString("en-US")} requests`; } function UptimeBar({ history, name }: { history: DayPoint[]; name: string }) { const measured = history.filter((p) => p.state !== "no-data" && p.successRate !== null); const avg = measured.length ? measured.reduce((a, p) => a + (p.successRate ?? 0), 0) / measured.length : null; return (
    {history.map((p) => { const label = dayLabel(p); return (
  • {label}
  • ); })}
30 days ago {avg === null ? "No data" : `${formatPercent(avg * 100, 2)} avg. success`} Today
); } function IncidentItem({ i }: { i: Incident }) { const sev = i.severity.toLowerCase(); return (
  • {i.resolvedAt ? "Resolved" : sev === "major" || sev === "critical" ? "Major" : "Minor"} {i.component}

    {i.title}

    {i.body ?

    {i.body}

    : null}

    Started {formatDate(i.startedAt)} {i.resolvedAt ? ` · Resolved ${formatDate(i.resolvedAt)}` : ` · Ongoing (${timeAgo(i.startedAt)})`}

  • ); } export default async function StatusPage() { const snap = await getStatusSnapshot(); const overall = STATE_META[snap.overall]; const headline = snap.overall === "operational" ? "All systems operational" : snap.overall === "degraded" ? "Some systems degraded" : snap.overall === "outage" ? "Major outage in progress" : "Status partially unavailable"; return (

    Status

    {headline}

    Computed live from health checks and request outcomes. Generated UTC.

    Success rate · 24 h
    {snap.successRate24h === null ? "—" : formatPercent(snap.successRate24h * 100, 2)}
    Requests · 24 h
    {snap.requests24h.toLocaleString("en-US")}
    {!snap.dbReachable ? (
    The status database could not be reached while rendering this page, so some components show as degraded or unknown. This reflects the status page itself, not necessarily the API.
    ) : null} {snap.openIncidents.length > 0 ? (

    Active incidents

      {snap.openIncidents.map((i) => ( ))}
    ) : null}

    Components

      {snap.components.map((c) => { const m = STATE_META[c.state]; return (
    • {c.name}

      {c.description}

      {c.detail ?

      {c.detail}

      : null}
      {m.label}
    • ); })}
      {[ ["bg-success", "≥ 99% success"], ["bg-warning", "95–99% or minor incident"], ["bg-danger", "< 95% or major incident"], ["bg-bg-muted border border-border", "No data"], ].map(([tone, label]) => (
    • {label}
    • ))}

    Incident history

    {snap.resolvedIncidents.length === 0 ? (

    No resolved incidents on record.

    ) : (
      {snap.resolvedIncidents.map((i) => ( ))}
    )}

    How this is computed

    Success rate counts completed requests whose outcome depended on the platform; requests rejected for invalid input, disallowed URLs or exhausted quotas are excluded. Network status comes from continuous upstream health checks.

    Machine-readable

    GET /api/health and GET /api/ready return JSON you can poll from your own monitoring.

    Report an issue

    Include the X-Fetcha-Request-ID from the failing response and email support@fetcha.co. See the changelog for releases.

    ); }