import { useQuery } from "@tanstack/react-query"; import { AssetOperation, DownloadTask, Task } from "@umt/contracts"; import { useEffect, useRef } from "react"; import { useNavigate } from "react-router"; import { z } from "zod"; import { apiParsed, isMissingEndpoint } from "~/api/parse.ts"; import { DetailSection, DetailSheet } from "~/components/console/detail-sheet.tsx"; import { ErrorState, LoadingState } from "~/components/console/empty-state.tsx"; import { RecordLink, recordPath } from "~/components/console/record-sheet.tsx"; import type { StatusTone } from "~/components/console/status-badge.tsx"; import { Timeline } from "~/components/console/timeline.tsx"; import { TASK_KIND_LABEL, TaskActions, TaskStatusLine, TaskSummary } from "~/components/downloads/business-tasks.tsx"; import { TTL_SECOND } from "~/lib/api.ts"; // 日志页任务抽屉打开的 id 不是引擎运行时(旧链接 /console/tasks/:id):先当旧业务任务查, // 有引擎运行就换成那条;没有就在抽屉里显示旧任务的概况与文件操作;再查不到当下载任务 id,换到下载面板的抽屉。 const OP_TONE: Record = { planned: "neutral", started: "neutral", succeeded: "success", failed: "destructive", skipped: "neutral", compensated: "warning", }; const OP_STATUS: Record = { planned: "待执行", started: "进行中", succeeded: "成功", failed: "失败", skipped: "已跳过", compensated: "已回滚", }; const OP_PHASE: Record = { decision: "判定", prepare: "准备", transfer: "传输", verify: "校验", commit: "提交", cleanup: "清理", compensate: "回滚", }; const OP_KIND: Record = { copy: "复制", move: "移动", rename: "改名", mkdir: "建目录", hardlink: "硬链接", delete_source: "删除源文件", delete_target: "删除目标文件", verify: "校验", register: "登记", qb_pause: "暂停下载", qb_resume: "恢复下载", qb_remove: "移除下载", qb_set_tags: "改下载标签", other: "其他", }; function useTask(id: string) { return useQuery({ queryKey: ["tasks", "one", id], retry: false, queryFn: ({ signal }) => apiParsed(Task, "/api/v1/tasks/:id", { pathParams: { id }, signal }), refetchInterval: (q) => q.state.data && ["pending", "running", "waiting"].includes(q.state.data.status) ? 5 * TTL_SECOND : false, }); } function useLegacyDownload(id: string, enabled: boolean) { return useQuery({ queryKey: ["downloads", "legacy-probe", id], enabled, retry: false, queryFn: ({ signal }) => apiParsed(DownloadTask, "/api/v1/download-tasks/:id", { pathParams: { id }, signal }), }); } function Operations({ taskId }: { taskId: string }) { const ops = useQuery({ queryKey: ["tasks", "operations", taskId], queryFn: ({ signal }) => apiParsed(z.object({ items: z.array(AssetOperation) }), "/api/v1/tasks/:id/operations", { pathParams: { id: taskId }, signal, }), }); if (ops.isPending) return ; if (ops.error) return void ops.refetch()} />; if (!ops.data.items.length) return

这个任务没有文件操作。

; return ( ({ id: o.id, at: o.startedAt ?? o.createdAt, tone: OP_TONE[o.status], title: `${OP_PHASE[o.phase]} · ${OP_KIND[o.kind]}:${OP_STATUS[o.status]}`, meta: o.attempts > 1 ? `${o.attempts} 次` : undefined, description: o.organizationRecordId ? ( 整理记录 {o.organizationRecordId.slice(0, 8)} ) : o.downloadTaskId ? ( 下载任务 {o.downloadTaskId.slice(0, 8)} ) : undefined, }))} /> ); } /** 引擎运行查不到时的任务抽屉 */ export function LegacyTaskSheet({ id, runError, onOpenRun, onClose, }: { id: string; /** 引擎运行查询的错误(不是「不存在」时直接显示) */ runError: unknown; onOpenRun: (runId: string) => void; onClose: () => void; }) { const navigate = useNavigate(); const runMissing = isMissingEndpoint(runError); const task = useTask(id); const notFound = !!task.error && isMissingEndpoint(task.error); const legacy = useLegacyDownload(id, runMissing && notFound); const t = task.data; const engineRunId = t?.engineRunId; const openRun = useRef(onOpenRun); openRun.current = onOpenRun; useEffect(() => { if (engineRunId) openRun.current(engineRunId); }, [engineRunId]); const downloadId = legacy.data?.id; useEffect(() => { if (downloadId) navigate(recordPath("downloads", downloadId), { replace: true }); }, [downloadId, navigate]); return ( !o && onClose()} width="lg" title={t ? (TASK_KIND_LABEL[t.kind] ?? t.kind) : "任务"} description={t ? : undefined} footer={t ? : null} > {!runMissing ? ( ) : task.isPending || (notFound && legacy.isPending) || engineRunId || downloadId ? ( ) : !t ? ( void task.refetch()} /> ) : ( <> )} ); }