import { useQueryClient } from "@tanstack/react-query"; import type { EngineRun } from "@umt/contracts"; import { Ban, CheckCircle2, Clock, Cpu, LoaderCircle, RotateCw, XCircle } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { useNavigate, useSearchParams } from "react-router"; import { useEngineRuns, useEngineStats } from "~/api/engine.ts"; import { DataTable } from "~/components/console/data-table.tsx"; import { FilterBar, FilterSelect } from "~/components/console/filter-bar.tsx"; import { consolePath } from "~/components/console/nav.ts"; import { PageHeader } from "~/components/console/page-header.tsx"; import { StatTile } from "~/components/console/stat-tile.tsx"; import { StatusBadge } from "~/components/console/status-badge.tsx"; import { Button } from "~/components/ui/button.tsx"; import { engineRunLabel, formatDuration, formatRelative, spanSec as runDurationSec } from "~/lib/labels.ts"; import { qk } from "~/lib/query-keys.ts"; import { useSse } from "~/lib/sse.ts"; export const meta = () => [{ title: "工作流引擎 · UHD 媒体自动化控制台" }]; const STAT_ORDER = [ { status: "pending", label: "待执行", icon: Clock, tone: "neutral" }, { status: "running", label: "运行中", icon: LoaderCircle, tone: "neutral" }, { status: "completed", label: "完成", icon: CheckCircle2, tone: "success" }, { status: "failed", label: "失败", icon: XCircle, tone: "destructive" }, { status: "canceled", label: "已取消", icon: Ban, tone: "warning" }, ] as const; const STATUS_OPTIONS = ["pending", "running", "completed", "failed", "canceled", "sleeping", "succeeded"].map((s) => ({ value: s, label: engineRunLabel(s).label, })); /** 持续任务事件到达时节流刷新引擎视图(引擎本身不发事件,业务任务事件足以代表它在动) */ const ENGINE_EVENTS = [ "task.created", "task.status_changed", "download.status_changed", "organization.changed", "agent.run_status", ]; function useDebounced(value: T, ms = 350): T { const [v, setV] = useState(value); useEffect(() => { const t = setTimeout(() => setV(value), ms); return () => clearTimeout(t); }, [value, ms]); return v; } export default function EnginePage() { const navigate = useNavigate(); const qc = useQueryClient(); const [params, setParams] = useSearchParams(); const status = params.get("status") ?? ""; const [name, setName] = useState(params.get("workflow") ?? ""); const workflowName = useDebounced(name.trim()); useEffect(() => { setParams( (p) => { if (workflowName) p.set("workflow", workflowName); else p.delete("workflow"); return p; }, { replace: true }, ); }, [workflowName, setParams]); const setStatus = (s: string) => setParams( (p) => { if (s) p.set("status", s); else p.delete("status"); return p; }, { replace: true }, ); const runs = useEngineRuns({ status: status || undefined, workflowName: workflowName || undefined }); const stats = useEngineStats(); const timer = useRef | null>(null); useSse(ENGINE_EVENTS, () => { if (timer.current) return; timer.current = setTimeout(() => { timer.current = null; qc.invalidateQueries({ queryKey: qk.all("engine") }); }, 2000); }); const rows = runs.data?.pages.flatMap((p) => p.items); const s = stats.data; return ( <> qc.invalidateQueries({ queryKey: qk.all("engine") })} disabled={runs.isFetching} > 刷新 } />
{STAT_ORDER.map((t) => ( 0 ? t.tone : "neutral"} value={stats.isPending ? "…" : stats.isError ? "—" : (s?.byStatus[t.status] ?? 0)} active={status === t.status} onClick={() => setStatus(status === t.status ? "" : t.status)} /> ))}
最老待执行: {s?.oldestPending ? ( ) : ( "无" )} 持有租约的 worker: {s && s.activeWorkers.length > 0 ? s.activeWorkers.map((w) => `${w.workerId}(${w.running})`).join("、") : "无"} (快照,非权威注册表)
{ setName(""); setStatus(""); } : undefined } > rows={rows} rowKey={(r) => r.id} loading={runs.isPending} error={runs.error} onRetry={() => runs.refetch()} empty={{ title: status || workflowName ? "没有符合条件的 run" : "引擎还没有运行记录", description: status || workflowName ? "换个筛选条件试试。" : "有任务入队后会出现在这里。", }} onRowClick={(r) => navigate(consolePath("engine", r.id))} hasMore={!!runs.hasNextPage} onLoadMore={() => runs.fetchNextPage()} loadingMore={runs.isFetchingNextPage} columns={[ { key: "status", header: "状态", cell: (r) => { const l = engineRunLabel(r.status); return {l.label}; }, }, { key: "name", header: "工作流", cell: (r) => (

{r.workflowName} {r.version ? {r.version} : null}

{r.id}

), }, { key: "attempts", header: "尝试", hideBelow: "md", align: "right", cell: (r) => r.attempts }, { key: "dur", header: "耗时", hideBelow: "sm", cell: (r) => {formatDuration(runDurationSec(r))}, }, { key: "created", header: "创建", hideBelow: "md", cell: (r) => {formatRelative(r.createdAt)}, }, ]} /> ); }