"use client"; import { Play } from "lucide-react"; import { useState } from "react"; import { Button } from "@/components/ui"; import type { ConnectorHealthDTO } from "@/lib/admin-api"; import { Checkbox, Field, Input, Select } from "./primitives"; import { useToast } from "./toast"; import { useAdminMutation } from "./use-admin-query"; export type RunTask = "crawl" | "discover" | "full" | "reprocess"; /** Inline "run connector" form: task, group, limit, force → POST connectors/:id/run. */ export function RunForm({ connector, onDone, compact }: { connector: Pick; onDone?: () => void; compact?: boolean }) { const [task, setTask] = useState("crawl"); const [group, setGroup] = useState(""); const [limit, setLimit] = useState(""); const [force, setForce] = useState(false); const { busy, run } = useAdminMutation(); const toast = useToast(); const groups = Object.keys(connector.schedule ?? {}); const submit = async () => { const body: Record = { task }; if (group) body.group = group; if (limit && Number(limit) > 0) body.limit = Math.floor(Number(limit)); if (force) body.force = true; const r = await run<{ enqueued: boolean; job?: { id?: string; name?: string } }>("run", `connectors/${encodeURIComponent(connector.id)}/run`, { body }); if (r.ok) { toast({ tone: "ok", title: `Run enqueued · ${connector.id}`, description: `task ${task}${group ? ` · group ${group}` : ""}${r.data.job?.id ? ` · job ${r.data.job.id}` : ""}` }); onDone?.(); } else toast({ tone: "danger", title: "Run failed to enqueue", description: r.error }); }; return (
setGroup(e.target.value)} placeholder="all groups" mono /> {groups.length > 0 && ( {groups.map((g) => ( )} setLimit(e.target.value)} placeholder="∞" mono /> setForce(e.target.checked)} />
); } /** Inline schedule editor: one interval per group → PATCH connectors/:id/schedule. */ export function ScheduleEditor({ connector, onSaved }: { connector: Pick; onSaved?: (schedule: Record) => void }) { const [rows, setRows] = useState>(() => Object.entries(connector.schedule ?? {}).map(([group, interval]) => ({ group, interval }))); const [error, setError] = useState(null); const { busy, run } = useAdminMutation(); const toast = useToast(); const save = async () => { const body: Record = {}; for (const r of rows) { if (!r.group.trim()) continue; if (!/^[a-z0-9_*-]+$/i.test(r.group.trim())) return setError(`bad group name "${r.group}"`); if (!/^(hourly|daily|weekly|biweekly|monthly|quarterly|never|\d+\s*(m|min|h|d|w))$/i.test(r.interval.trim())) return setError(`bad interval for ${r.group}: "${r.interval}" (daily, weekly, 12h, 30m…)`); body[r.group.trim()] = r.interval.trim().toLowerCase(); } if (!Object.keys(body).length) return setError("nothing to save"); setError(null); const r = await run<{ id: string; schedule: Record; file: { updated: boolean; error?: string; backup: string | null } }>("schedule", `connectors/${encodeURIComponent(connector.id)}/schedule`, { method: "PATCH", body }); if (r.ok) { toast({ tone: r.data.file.updated ? "ok" : "warn", title: "Schedule saved", description: r.data.file.updated ? `YAML updated${r.data.file.backup ? " (backup kept)" : ""}` : `DB updated; YAML not updated: ${r.data.file.error ?? "unknown"}` }); onSaved?.(r.data.schedule); } else { setError(r.error); toast({ tone: "danger", title: "Schedule not saved", description: r.error }); } }; return (
{rows.map((r, i) => (
setRows((p) => p.map((x, j) => (j === i ? { ...x, group: e.target.value } : x)))} mono /> setRows((p) => p.map((x, j) => (j === i ? { ...x, interval: e.target.value } : x)))} mono />
))}
{["hourly", "3h", "6h", "12h", "daily", "weekly", "biweekly", "monthly", "quarterly", "never"].map((v) => ( {error &&

{error}

}
Intervals: hourly, daily, weekly, biweekly, monthly, quarterly, never, or 30m / 12h / 3d / 2w. Writes the DB row and the YAML file (backup kept).
); } /** Pause / resume toggle → POST connectors/:id/pause|resume. */ export function PauseResumeButton({ connector, onChanged, size = "xs" }: { connector: Pick; onChanged?: (paused: boolean) => void; size?: "xs" | "sm" }) { const { busy, run } = useAdminMutation(); const toast = useToast(); const paused = connector.health === "paused"; const toggle = async () => { const action = paused ? "resume" : "pause"; const r = await run<{ id: string; paused: boolean; health: string }>(action, `connectors/${encodeURIComponent(connector.id)}/${action}`, { body: {} }); if (r.ok) { toast({ tone: "ok", title: `${connector.id} ${r.data.paused ? "paused" : "resumed"}` }); onChanged?.(r.data.paused); } else toast({ tone: "danger", title: `Could not ${action}`, description: r.error }); }; return ( ); }