"use client"; import Link from "next/link"; import { Badge, Button, EmptyState, RelativeTime, SkeletonRows, Table, TBody, Td, Th, THead, Tr } from "@/components/ui"; import { cn } from "@/components/ui/cn"; import { DASH, fmtInt } from "@/lib/format"; import type { OpsOverview } from "@/lib/admin-api"; import { durationBetween, ErrorNote, fmtBytes, fmtMs, HealthDot, JsonView, KV, Mono, Panel, RefreshButton, RunStatusBadge, Stat } from "./primitives"; import { AdminPageHeader } from "./shell"; import { useToast } from "./toast"; import { useAdminMutation, useAdminQuery } from "./use-admin-query"; const REFRESH_MS = 30_000; function num(v: unknown): number | null { return typeof v === "number" && Number.isFinite(v) ? v : typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v)) ? Number(v) : null; } function str(v: unknown): string | null { return typeof v === "string" ? v : null; } /** Best-effort reading of the worker heartbeat (shape is free-form JSON published by the worker). */ function readWorker(w: unknown): { heartbeat: string | null; host: string | null; pid: number | null; running: Array> | number | null; version: string | null; rest: Record } | null { if (!w || typeof w !== "object") return null; const o = w as Record; const heartbeat = str(o.heartbeat) ?? str(o.at) ?? str(o.ts) ?? str(o.updatedAt) ?? str(o.time) ?? str(o.lastHeartbeat) ?? null; const host = str(o.host) ?? str(o.hostname) ?? null; const pid = num(o.pid); const runningRaw = o.running ?? o.activeRuns ?? o.jobs ?? o.active ?? null; const running = Array.isArray(runningRaw) ? (runningRaw as Array>) : num(runningRaw); const version = str(o.version) ?? null; const rest: Record = {}; for (const [k, v] of Object.entries(o)) if (!["heartbeat", "at", "ts", "updatedAt", "time", "lastHeartbeat", "host", "hostname", "pid", "running", "activeRuns", "jobs", "active", "version"].includes(k)) rest[k] = v; return { heartbeat, host, pid, running, version, rest }; } function BudgetBar({ label, used, limit }: { label: string; used: number; limit: number }) { const share = limit > 0 ? Math.min(1, used / limit) : 0; const tone = share >= 0.9 ? "bg-danger" : share >= 0.6 ? "bg-warn" : "bg-ink-2"; return (
{label} {fmtInt(used)} / {fmtInt(limit)} credits
); } export function OpsOverviewView() { const q = useAdminQuery("ops", { refreshMs: REFRESH_MS, timeoutMs: 20_000 }); const { busy, run } = useAdminMutation(); const toast = useToast(); const d = q.data; const worker = readWorker(d?.worker); // age measured against the time the ops payload arrived (state) — keeps render pure const hbAge = worker?.heartbeat && q.updatedAt ? q.updatedAt - new Date(worker.heartbeat).getTime() : null; const workerOk = worker ? (hbAge == null ? true : hbAge < 90_000) : false; const budgets = d?.budgets; const used = budgets?.used ?? {}; const scrapflyUsed = used.scrapfly?.credits ?? (typeof budgets?.["dci:budget:scrapfly"] === "string" ? Number(budgets["dci:budget:scrapfly"]) : 0) ?? 0; const firecrawlUsed = used.firecrawl?.credits ?? (typeof budgets?.["dci:budget:firecrawl"] === "string" ? Number(budgets["dci:budget:firecrawl"]) : 0) ?? 0; const maintenance = async (task: "rankings" | "metrics" | "refresh-stats") => { const r = await run<{ enqueued: boolean; job?: { id?: string } }>(`m:${task}`, `maintenance/${task}`, { body: {} }); if (r.ok) toast({ tone: "ok", title: `Enqueued ${task}`, description: r.data.job?.id ? `job ${r.data.job.id}` : undefined }); else toast({ tone: "danger", title: `Could not enqueue ${task}`, description: r.error }); }; const invalidate = async () => { const r = await run<{ invalidated: number }>("cache", "cache/invalidate", { body: { prefix: "" } }); if (r.ok) toast({ tone: "ok", title: "API cache invalidated", description: `${fmtInt(r.data.invalidated)} entries` }); else toast({ tone: "danger", title: "Cache invalidation failed", description: r.error }); }; const resolve = async (id: string) => { const r = await run<{ resolved: boolean }>(`alert:${id}`, `alerts/${id}/resolve`, { body: {} }); if (r.ok) { q.mutate((prev) => (prev ? { ...prev, alerts: prev.alerts.filter((a) => a.id !== id) } : prev)); toast({ tone: "ok", title: "Alert resolved" }); } else toast({ tone: "danger", title: "Could not resolve alert", description: r.error }); }; return (
void q.refresh()} busy={q.refreshing} updatedAt={q.updatedAt} />} /> void q.refresh()} className="mb-3" /> {/* health strip */}
: d ? "no heartbeat key" : undefined} /> a + t.rows, 0))} rows (est.)` : undefined} /> {d.clickhouse.url.replace(/^https?:\/\//, "")} : undefined} /> {d.minio.bucket} : undefined} /> a.level === "error" || a.level === "critical").length} error-level` : undefined} />
{!d ? ( ) : !worker ? ( ) : (
: DASH }, { k: "Host", v: worker.host ? {worker.host} : DASH }, { k: "PID", v: worker.pid != null ? {worker.pid} : DASH }, { k: "Version", v: worker.version ? {worker.version} : DASH }, { k: "Running jobs", v: Array.isArray(worker.running) ? fmtInt(worker.running.length) : worker.running != null ? fmtInt(worker.running) : DASH }, ]} /> {Array.isArray(worker.running) && worker.running.length > 0 && (
    {worker.running.slice(0, 8).map((j, i) => (
  • {str(j.connectorId) ?? str(j.id) ?? JSON.stringify(j).slice(0, 60)} {str(j.task) && {str(j.task)}}
  • ))}
)} {Object.keys(worker.rest).length > 0 && }
)}
{!d ? (
) : ( {Object.entries(d.queues).map(([name, c]) => ( ))} {Object.keys(d.queues).length === 0 && ( )}
Queue Wait Prio Active Delay Done Failed
{name.replace(/^dci:/, "")} {fmtInt(c.waiting ?? 0)} {fmtInt(c.prioritized ?? 0)} 0 && "text-accent")}> {fmtInt(c.active ?? 0)} {fmtInt(c.delayed ?? 0)} {fmtInt(c.completed ?? 0)} 0 && "text-danger")}> {fmtInt(c.failed ?? 0)}
No queue data (Redis unreachable?)
)}
{!d ? ( ) : (
)}
0 ? ( {fmtInt(d.alerts.length)} open ) : undefined } > {!d ? (
) : d.alerts.length === 0 ? ( ) : (
    {d.alerts.map((a) => (
  • {a.level} {a.component}
    {a.message}
  • ))}
)}
Maintenance jobs
{(["rankings", "metrics", "refresh-stats"] as const).map((t) => ( ))}
{d && ( <>
Largest tables
({ k: {t.table}, v: {fmtInt(t.rows)} rows · {fmtBytes(t.bytes)} }))} /> )}
All connectors}> {!d ? (
) : d.lastRuns.length === 0 ? ( ) : ( {d.lastRuns.map((r) => { const s = r.stats ?? {}; return ( ); })}
Connector Task Status Started Duration Fetched Changed Entities Failed
{r.connectorId}
{r.id} →
{r.error &&
{r.error}
}
{r.task} {fmtMs(durationBetween(r.startedAt, r.finishedAt))} {num(s.fetched) != null ? fmtInt(num(s.fetched)) : DASH} {num(s.changed) != null ? fmtInt(num(s.changed)) : DASH} {num(s.entities) != null ? fmtInt(num(s.entities)) : DASH} 0 && "text-danger")}> {num(s.failed) != null ? fmtInt(num(s.failed)) : DASH}
)}
); }