import type { Metadata } from 'next'; import Link from 'next/link'; import { DataStrip } from '@/components/layout/terminal'; import { BreadcrumbLd } from '@/components/meta/breadcrumb-ld'; import { TierBadge } from '@/components/ui/badges'; import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table'; import { Hint } from '@/components/ui/hint'; import { Container, Note, PageHeader } from '@/components/ui/section'; import { Unavailable } from '@/components/ui/unavailable'; import { adminApi } from '@/lib/admin/admin-api'; import { getAdminToken } from '@/lib/admin/session'; import type { AdminConnector } from '@/lib/admin/types'; import { apiD3, safe } from '@/lib/api'; import { cn } from '@/lib/cn'; import { fmtAgo, fmtDateTime, fmtDuration, fmtInt, fmtPct, num, titleCase } from '@/lib/format'; import { routes, SITE_NAME } from '@/lib/site'; import type { SourceRow11 } from '@/lib/types'; export const metadata: Metadata = { title: 'Sources — health center: every site AI Atlas reads, tier, freshness and connector status', description: 'Transparency page: the sources AI Atlas crawls, their tier, last successful observation, documents and connector health. Operators see request-level health when signed in.', alternates: { canonical: '/sources' } }; /** Reads the admin cookie to decide whether to show operator columns → dynamic. API fetches stay ISR-cached. */ export const dynamic = 'force-dynamic'; const HEALTH: Record = { ok: 'text-positive', healthy: 'text-positive', degraded: 'text-warning', failing: 'text-danger', broken: 'text-danger', disabled: 'text-ink-3', unknown: 'text-ink-3' }; const DOT: Record = { ok: 'bg-positive', healthy: 'bg-positive', degraded: 'bg-warning', disabled: 'bg-ink-3', unknown: 'bg-ink-3' }; function statusOf(s: SourceRow11): string { if (!s.enabled) return 'disabled'; const hs = (s.connectors ?? []).map((c) => c.health); if (!hs.length) return 'no connector'; if (hs.some((h) => h === 'failing' || h === 'broken')) return 'failing'; if (hs.some((h) => h === 'degraded')) return 'degraded'; if (hs.every((h) => h === 'ok' || h === 'healthy')) return 'ok'; return hs[0] ?? 'unknown'; } function lastSuccess(s: SourceRow11): string | null { const ts = (s.connectors ?? []).map((c) => c.last_success_at).filter((x): x is string => !!x); return ts.length ? ts.sort().at(-1)! : s.last_crawled_at; } export default async function SourcesPage() { const token = await getAdminToken(); const [res, admin] = await Promise.all([ safe(apiD3.sources()), token ? adminApi.connectors().catch(() => null) : Promise.resolve(null), ]); const items = ((res?.items ?? []) as SourceRow11[]).slice().sort((a, b) => a.tier - b.tier || (num(b.documents) ?? 0) - (num(a.documents) ?? 0)); const byConnector = new Map((admin?.items ?? []).map((c) => [c.name, c])); const isAdmin = !!admin; const connectors = items.flatMap((s) => s.connectors ?? []); const count = (pred: (h: string) => boolean) => connectors.filter((c) => pred(c.health)).length; const docs = items.reduce((n, s) => n + (num(s.documents) ?? 0), 0); const snaps = items.reduce((n, s) => n + (num(s.snapshots) ?? 0), 0); const claims = items.reduce((n, s) => n + (num(s.claims) ?? 0), 0); const stale = items.filter((s) => { const t = lastSuccess(s); return s.enabled && t && Date.now() - new Date(t).getTime() > 3 * 86400000; }).length; return ( {fmtInt(res.total ?? items.length)} sources · {fmtInt(connectors.length)} connectors

: undefined} />
{!res ? ( ) : ( <> h === 'ok' || h === 'healthy')), definition: 'Connectors whose last run succeeded within their interval.' }, { label: 'Degraded', value: fmtInt(count((h) => h === 'degraded')), definition: 'Connectors with recent failures or a breakage suspicion.', delta: count((h) => h === 'degraded') ? { value: 'attention', tone: 'neutral' } : undefined }, { label: 'Failing', value: fmtInt(count((h) => h === 'failing' || h === 'broken')), definition: 'Connectors whose circuit breaker is open or whose last runs failed.' }, { label: 'Stale > 3 d', value: fmtInt(stale), definition: 'Enabled sources with no successful observation in the last 3 days.' }, ]} />
Tiers:{' '} {Object.entries(res.tiers ?? {}).map(([t, l], i) => ( {i > 0 && ' · '} {l} ))} {isAdmin && operator columns on}
Source Category Tier Last successful observation Documents Status Connectors {isAdmin && ( <> Last request HTTP Unchanged share Docs changed Claims Errors 7 d Parser LLM Latency )} {items.length === 0 && No sources registered.} {items.map((s) => { const st = statusOf(s); const ls = lastSuccess(s); const conns = (s.connectors ?? []).map((c) => byConnector.get(c.name)).filter((c): c is AdminConnector => !!c); const run = conns.map((c) => c.last_run).find((r) => r) ?? null; const fetched = num(run?.docs_fetched); const unchanged = num(run?.docs_unchanged); return ( {s.name} {s.domain} {s.organization ? ` · ${s.organization.name}` : ''} {titleCase(s.category)}{titleCase(s.kind)} {ls ? fmtAgo(ls) : never} {fmtInt(s.documents)} {st} {s.connectors?.length ? (
    {s.connectors.map((c) => (
  • {c.name} {c.health} every {fmtDuration(c.interval_seconds)}
  • ))}
) : ( — )} {isAdmin && ( <> {conns[0]?.last_attempt_at ? fmtAgo(conns[0].last_attempt_at) : '—'} — {fetched && unchanged !== null ? fmtPct((unchanged / fetched) * 100, 0) : '—'} {run ? fmtInt(run.docs_changed) : '—'} {run ? fmtInt(run.claims_written) : '—'} (num((c as unknown as { errors_7d?: unknown }).errors_7d) ?? 0) > 0) && 'text-danger')}>{conns.length ? fmtInt(conns.reduce((n, c) => n + (num((c as unknown as { errors_7d?: unknown }).errors_7d) ?? 0), 0)) : '—'} {conns[0]?.parser_version ?? '—'} {conns.length ? (conns.some((c) => (c.meta as { needs_llm?: boolean } | undefined)?.needs_llm) ? 'fallback on' : 'deterministic') : '—'} {num(run?.duration_ms) === null ? '—' : fmtDuration(Math.round((num(run?.duration_ms) ?? 0) / 1000))} )} ); })}
Crawling policy: robots.txt honoured, per-domain rate limits, conditional requests, identified user agent (AIAtlasBot). Tiers and confidence are explained in the methodology. {isAdmin ? <> Operator columns come from /admin/connectors (last run); HTTP status, conditional-hit rate and LLM fallback rate are not exposed per connector by the API and show “—”. Full console: Connectors → : token ? ' Operator columns unavailable (admin API did not answer).' : null} )}
); }