import Link from 'next/link'; import { requireAdmin } from '@/lib/admin/auth'; import { connectorsOverview } from '@/lib/admin/queries'; import { connectorAction } from '@/lib/admin/actions'; import { AdminShell, ActionButton, StatusPill, fmtTs, n } from '@/components/admin/shell'; import { HealthLabel, SourceLogo, Pct, Progress, Chip } from '@/components/admin/connector-bits'; import { Table, th, td, tdNum } from '@/components/ui/primitives'; type Row = Record; function str(v: unknown): string { return v === null || v === undefined ? '' : String(v); } /** Connector explorer (SPEC §29): every connector with source, geography, acquisition, health, usage, backfill and actions. */ export default async function ConnectorsPage({ searchParams }: { searchParams: Promise> }) { await requireAdmin(); const sp = await searchParams; const rowsAll = (await connectorsOverview()) as Row[]; const q = (sp.q ?? '').toLowerCase(); const status = sp.status ?? ''; const category = sp.category ?? ''; const country = sp.country ?? ''; const rows = rowsAll.filter((r) => { const meta = (r.meta ?? {}) as Row; if (status && String(r.health_status ?? 'unknown') !== status && String(r.status) !== status) return false; if (category && !((r.categories as string[]) ?? []).includes(category)) return false; if (country && String(meta.country ?? (r.regions as string[])?.[0] ?? '') !== country) return false; if (q && !`${str(r.id)} ${str(r.display_name)} ${str(meta.domain)} ${((r.categories as string[]) ?? []).join(' ')}`.toLowerCase().includes(q)) return false; return true; }); const counts = rowsAll.reduce>((acc, r) => { const k = String(r.health_status ?? 'unknown'); acc[k] = (acc[k] ?? 0) + 1; return acc; }, {}); const categories = [...new Set(rowsAll.flatMap((r) => (r.categories as string[]) ?? []))].sort(); const countries = [...new Set(rowsAll.map((r) => String(((r.meta ?? {}) as Row).country ?? (r.regions as string[])?.[0] ?? 'global')))].sort(); return ( {rowsAll.length} connectors · {Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(' · ')} · coverage dashboard · JSON } actions={
} >
{rows.length === 0 ? ( ) : null} {rows.map((r) => { const meta = (r.meta ?? {}) as Row; const health = (r.health ?? {}) as Row; const anomalies = Array.isArray(r.last_anomalies) ? (r.last_anomalies as string[]) : []; const drift = ((health.schema_drift as string[]) ?? []).length > 0 || anomalies.some((a) => /schema_drift|selector|pagination_failure|result_count_collapse/.test(a)); const httpErrors = Object.values((health.http_errors as Record) ?? {}).reduce((a, b) => a + b, 0); const cats = (r.categories as string[]) ?? []; const id = str(r.id); const domain = str(meta.domain); const country = str(meta.country ?? (r.regions as string[])?.[0] ?? 'global'); const refresh = Number(r.refresh_frequency_minutes); const refreshClass = str(meta.refreshClass) || (refresh <= 5 ? 'hot' : refresh <= 60 ? 'active' : refresh <= 1440 ? 'normal' : 'archive'); const missing = (health.missing_requirements as string[]) ?? []; return ( ); })}
Source Category · country Health Status Acquisition Last sync Items 24h Errors FC · SF credits Rate limit Priority Backfill Actions
No connector matches. Add meta.json files under connectors/, run pnpm registry && pnpm db:seed.
{str(r.display_name)}
{domain ? ( {domain} ) : ( id )} {' · '} {id}
{cats.slice(0, 3).map((c) => ( {c} ))} {cats.length > 3 ? +{cats.length - 3} : null} {country}
{health.success_rate_24h != null ? <>success : 'no runs 24h'} {r.health_at ? ` · ${fmtTs(r.health_at).slice(5, 16)}` : ''}
{drift ?
schema drift?
: null} {missing.length ?
needs {missing.join(', ')}
: null}
{r.last_run_status ?
run: {str(r.last_run_status)}
: null}
{str(meta.acquisitionMethod) || (r.engine_priority as string[]).join(' → ')}
{(r.engine_priority as string[]).join('→')} · {refreshClass} · every {n(refresh)} min
{fmtTs(r.last_started_at)}
ok: {fmtTs(r.last_success_at).slice(0, 16)}
fresh: {health.data_freshness ? fmtTs(health.data_freshness).slice(0, 10) : '—'}
{n(r.records_24h)}
{n(r.normalized_24h)} norm. · {n(r.duplicates_24h)} dup.
{n(r.pages_per_min, 1)} pages/min
{httpErrors || (r.last_error ? 1 : 0)} {anomalies.length ?
{anomalies.length} anomal.
: null} {Number(health.challenges_24h) > 0 ?
{n(health.challenges_24h)} challenges
: null}
{n(r.fc_credits_24h)} · {n(r.sf_credits_24h)}
FC · SF
{health.latency_ms_avg != null ? `${n(health.latency_ms_avg)} ms` : '—'}
{Number(health.rate_limited_24h) > 0 ? `${n(health.rate_limited_24h)}× 429` : 'no 429'} · {n(health.requests_24h)} req
{str(r.priority)}
trust {source_trust(meta)}
{r.backfill_id ? : {str(meta.historicalDepth) && str(meta.historicalDepth) !== 'none' ? `history: ${str(meta.historicalDepth)}` : '—'}}
{( [ ['probe', 'Test'], ['run', 'Run now'], ['backfill', 'Backfill'], ] as const ).map(([a, label]) => (
))}
Logs

Test = 25-record probe · Run now = incremental crawl · Backfill = resumable historical campaign (progress in the Backfill column) · Pause/Resume flips scheduling; Maintenance is available on the connector page. FC/SF = Firecrawl/Scrapfly credits spent in 24 h and success/fallback rates. Health labels: UP · DEGRADED · BROKEN · DISABLED · MAINTENANCE.

); } function source_trust(meta: Row): string { const t = Number(meta.trustScore); return Number.isFinite(t) ? `${Math.round(t * 100)}%` : '—'; }