"use client"; import Link from "next/link"; import { useMemo, useState } from "react"; import { Badge, Button, EmptyState, LineChart, RelativeTime, SkeletonBlock, SkeletonRows, Tab, TabList, TabPanel, Tabs, Table, TBody, Td, Th, THead, Tr } from "@/components/ui"; import { cn } from "@/components/ui/cn"; import { DASH, fmtDateTimeUtc, fmtInt } from "@/lib/format"; import { SOURCE_KIND_LABEL } from "@/lib/labels"; import type { AdminRun, ConnectorAdminDetail } from "@/lib/admin-api"; import { PauseResumeButton, RunForm, ScheduleEditor } from "./connector-actions"; import { Code, Disclosure, durationBetween, ErrorNote, ExtLink, fmtMs, fmtPct01, HealthDot, JsonView, KV, Mono, Panel, RefreshButton, RunStatusBadge, Stat } from "./primitives"; import { AdminPageHeader } from "./shell"; import { useAdminQuery } from "./use-admin-query"; import { toYaml } from "./yaml"; function n(v: unknown): number | null { return typeof v === "number" && Number.isFinite(v) ? v : null; } function RunLog({ id }: { id: string }) { const q = useAdminQuery(`runs/${encodeURIComponent(id)}`); if (q.loading) return ; if (q.error || !q.data) return void q.refresh()} />; const log = q.data.log ?? []; return (
{log.length === 0 ? (

No log lines recorded.

) : (
{log.map((l, i) => (
{l.t ? String(l.t).slice(11, 19) : ""} {l.level ?? ""} {String(l.msg ?? JSON.stringify(l))}
))}
)}
); } export function ConnectorDetailView({ id }: { id: string }) { const q = useAdminQuery(`connectors/${encodeURIComponent(id)}`, { refreshMs: 30_000, timeoutMs: 30_000 }); const [runOpen, setRunOpen] = useState(false); const d = q.data; const h = d?.health; const yaml = useMemo(() => (d ? toYaml(d.config) : ""), [d]); const series = useMemo(() => { if (!d) return []; const runs = [...d.runs].reverse().filter((r) => r.startedAt); const label = (iso: string | null) => (iso ? iso.slice(5, 16).replace("T", " ") : ""); const pick = (k: string) => runs.map((r) => ({ x: label(r.startedAt), y: n((r.stats ?? {})[k]) })); return [ { name: "fetched", data: pick("fetched"), color: "var(--ink-2)" }, { name: "changed", data: pick("changed"), color: "var(--accent)" }, { name: "failed", data: pick("failed"), color: "var(--danger)" }, ]; }, [d]); if (q.error && !d) { return (
void q.refresh()} />
); } return (
Connectors } title={ {h && } {id} } description={ h ? ( {h.sourceName} · {h.domain} {SOURCE_KIND_LABEL[h.kind] ?? h.kind} {h.mode} parser {h.parserVersion} {d?.paused && ( paused )} {!h.enabled && !d?.paused && disabled} ) : undefined } aside={ <> void q.refresh()} busy={q.refreshing} updatedAt={q.updatedAt} /> {h && ( <> void q.refresh()} /> )} } /> {runOpen && h && ( { setRunOpen(false); void q.refresh(); }} /> )} {d?.lastError && } {!h ? ( ) : (
0 ? "ok" : undefined} /> 0 ? "danger" : undefined} /> : DASH} note={h.lastRunAt ? last : "never run"} />
)}
{!d ? : series[0]!.data.length < 2 ? : } {!h ? ( ) : ( 0 && "text-accent")}>{fmtInt(h.cost.credits)} }, { k: "Fetch level", v: {String((d?.config.fetch as { level?: number } | undefined)?.level ?? 1)} → max {String((d?.config.fetch as { maxLevel?: number } | undefined)?.maxLevel ?? 2)} }, { k: "Max credits / run", v: fmtInt((d?.config.fetch as { maxCreditsPerRun?: number } | undefined)?.maxCreditsPerRun ?? null) }, { k: "Created", v: d?.createdAt ? fmtDateTimeUtc(d.createdAt) : DASH }, { k: "Updated", v: d?.updatedAt ? fmtDateTimeUtc(d.updatedAt) : DASH }, ]} /> )}
Runs Config Schedule a + x.count, 0) : null}> Documents Errors {!d ? ( ) : d.runs.length === 0 ? ( ) : (
{d.runs.map((r) => { const s = r.stats ?? {}; return ( {r.task} {fmtMs(durationBetween(r.startedAt, r.finishedAt))} {fmtInt(n(s.fetched))} fetched · {fmtInt(n(s.changed))} changed · {fmtInt(n(s.entities))} entities · 0 && "text-danger")}>{fmtInt(n(s.failed))} failed {n(s.credits) ? · {fmtInt(n(s.credits))} cr : null} {r.id} → {r.error && {r.error}} } > ); })}
)}
{!d ? ( ) : (
Config stored in the connectors table (synced from config/connectors/{id}.yaml). Read-only here — edit in the dev tool.
)}
{h ? void q.refresh()} /> : } {!d ? ( ) : d.documentsByPageType.length === 0 ? ( ) : ( {d.documentsByPageType.map((x) => ( ))}
Page type Documents Changed Failed Quarantined
{x.pageType} {fmtInt(x.count)} {fmtInt(x.changed)} 0 && "text-danger")}> {fmtInt(x.failed)} 0 && "text-warn")}> {fmtInt(x.quarantined)}
)}
{!d ? ( ) : d.errorSamples.length === 0 ? ( ) : ( {d.errorSamples.map((e) => ( ))}
URL Error HTTP Count Last checked
{e.url.replace(/^https?:\/\//, "")} {e.error ?? DASH} {e.statusCode ?? DASH} {fmtInt(e.errorCount)}
)}
); }