SPB Git forge
15commits 1branches 0releases
29.7 MBsize
maindefault branch
10 days agolast push
TypeScript 36.3% Python 31.8% Go 18% JavaScript 9.8% Shell 1.9% SQL 1.4% CSS 0.5%
2.7 KB · 52 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { IncidentsList } from '@/components/home/IncidentsList';4import { IncidentRow } from '@/components/incidents/IncidentRow';5import { Empty, Section } from '@/components/ui/primitives';6import { apiGet } from '@/lib/api';7import { fmtInt } from '@/lib/format';8import type { IncidentList } from '@/lib/types';910export const dynamic = 'force-dynamic';11export const metadata: Metadata = { title: 'Incidents', description: 'Detected, developing, active, recovering and resolved Internet incidents with evidence, hypotheses and confidence.' };1213export default async function IncidentsPage({ searchParams }: { searchParams: Promise<{ page?: string }> }) {14  const sp = await searchParams;15  const page = Math.max(1, Number(sp.page ?? 1) || 1);16  const limit = 25;17  const [active, resolved] = await Promise.all([apiGet<IncidentList>('/api/v1/incidents?status=active&limit=50'), apiGet<IncidentList>(`/api/v1/incidents?status=resolved&limit=${limit}&offset=${(page - 1) * limit}`)]);18  const pages = Math.max(1, Math.ceil(resolved.total / limit));19  return (20    <div className="pb-8">21      <header className="pt-6 pb-4">22        <p className="label">Event engine</p>23        <h1 className="mt-1 text-[26px] font-medium tracking-tight sm:text-[32px]">Incidents</h1>24        <p className="mt-1 max-w-[760px] text-[13px] text-ink-2">An incident opens when a component or regional score stays above the detection threshold for consecutive cycles, then moves detected → developing → active → recovering → resolved. Confidence reflects probe count, geographic diversity, signal agreement and BGP corroboration. Pages are kept forever.</p>25      </header>26      <Section label="Active" right={<span>{fmtInt(active.total)} open · live</span>}>27        <IncidentsList initial={active.incidents} />28      </Section>29      <Section label="Resolved" right={<span>{fmtInt(resolved.total)} total</span>}>30        {resolved.incidents.length ? (31          <ul className="divide-y divide-line">32            {resolved.incidents.map((i) => (33              <IncidentRow key={i.event_id} inc={i} />34            ))}35          </ul>36        ) : (37          <Empty>No resolved incident yet.</Empty>38        )}39        {pages > 1 && (40          <nav className="num mt-4 flex gap-2 text-[12px]" aria-label="Pagination">41            {Array.from({ length: pages }, (_, i) => i + 1).map((n) => (42              <Link key={n} href={`/incidents?page=${n}`} className={`rounded-[3px] border px-2 py-1 ${n === page ? 'border-line-2 text-ink' : 'border-line text-ink-2 hover:text-ink'}`} aria-current={n === page ? 'page' : undefined}>43                {n}44              </Link>45            ))}46          </nav>47        )}48      </Section>49    </div>50  );51}52