spb/internetpressure
Public
TypeScript 36.3%
Python 31.8%
Go 18%
JavaScript 9.8%
Shell 1.9%
SQL 1.4%
CSS 0.5%
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