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.' },
]}
/>
);
})}
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}
>
)}