import type { Metadata } from "next"; import { apiSafe } from "@/lib/api"; import { Badge, Card, Empty, SectionHeader } from "@/components/ui"; import { dateTimeFr, relTime } from "@/lib/format"; export const metadata: Metadata = { title: "Statut des données", description: "État en temps réel des connecteurs, sources et travaux d'ingestion de QC26." }; export const revalidate = 30; interface Status { components: Record; sources: { id: string; name: string; url: string; tier: number; kind: string; status: string; lastCheckedAt: string | null; lastOkAt: string | null; detail: string | null }[]; jobs: { id: number; kind: string; status: string; startedAt: string; finishedAt: string | null; items: number; message: string | null }[]; latestRun: { id: number; completedAt: string | null } | null; dbLatencyMs: number; sseClients: number; serverTime: string } const LABELS: Record = { pollWatcher: "Poll watcher", forecastEngine: "Forecast engine", electionFeed: "Election feed", documentWatcher: "Document watcher", officialData: "Données officielles", signals: "Signaux (marchés, presse, attention)" }; export default async function Page() { const st = await apiSafe("/api/status", { revalidate: 30 }); if (!st) return ; const tone = (s: string) => (s === "operational" ? "ok" : s === "ready" ? "accent" : "warn"); return (
QC26 Data Status

Statut des données

Heure serveur {dateTimeFr(st.serverTime)} · latence base {st.dbLatencyMs} ms · {st.sseClients} clients SSE

{Object.entries(st.components).map(([k, v]) =>
{LABELS[k] ?? k}
● {v}
)}
{st.sources.sort((a, b) => a.tier - b.tier).map((s) => )}
SourceTierStatutVérifiéDernier succèsDétail
{s.name}{s.tier}{s.status}{s.lastCheckedAt ? relTime(s.lastCheckedAt) : "—"}{s.lastOkAt ? relTime(s.lastOkAt) : "—"}{s.detail}
{st.jobs.map((j) => )}
TravailStatutDébutFinÉlémentsMessage
{j.kind}{j.status}{dateTimeFr(j.startedAt)}{dateTimeFr(j.finishedAt)}{j.items}{j.message}
); }