import { useQueryClient } from "@tanstack/react-query"; import type { DownloadStatus, DownloadTask } from "@umt/contracts"; import { EventType } from "@umt/contracts"; import { Download, RefreshCw } from "lucide-react"; import { useState } from "react"; import { Link } from "react-router"; import { DOWNLOAD_STATUS_LABEL, useDownloaderNames, useDownloadTasks } from "~/api/downloads.ts"; import { type DataColumn, DataTable } from "~/components/console/data-table.tsx"; import { DetailSheet } from "~/components/console/detail-sheet.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 { useQueryState } from "~/components/console/use-query-state.ts"; import { CacheVolumes } from "~/components/downloads/cache-volumes.tsx"; import { DownloadDetail, DownloadStatusBadges, PauseResume, ProgressLine, } from "~/components/downloads/download-detail.tsx"; import { Button } from "~/components/ui/button.tsx"; import { relTime } from "~/components/workbench/release-fields.ts"; import { useSse } from "~/lib/sse.ts"; import { formatBytes } from "~/lib/utils.ts"; export const meta = () => [{ title: "下载队列 · UHD 媒体自动化控制台" }]; export default function DownloadsPage() { const f = useQueryState(["status", "managed"] as const); const [openId, setOpenId] = useState(null); const qc = useQueryClient(); const downloads = useDownloadTasks({ status: (f.values.status || undefined) as DownloadStatus | undefined, managed: (f.values.managed || undefined) as "true" | "false" | undefined, }); const names = useDownloaderNames(); // 下载进度/状态、阈值事件到达时刷新列表(SSE 为主,轮询兜底) useSse( [ EventType.downloadProgress, EventType.downloadStatusChanged, EventType.downloadSubmitted, EventType.downloadThreshold, ], () => void qc.invalidateQueries({ queryKey: ["downloads"] }), ); const rows = downloads.data?.pages.flatMap((p) => p.items); const open = rows?.find((t) => t.id === openId) ?? null; const columns: DataColumn[] = [ { key: "name", header: "下载任务", cell: (t) => (

{t.name}

{names.data?.get(t.instanceId) ?? "下载器"} · {t.tags.join(", ") || "无标签"}

), }, { key: "status", header: "状态", cell: (t) => }, { key: "progress", header: "进度", hideBelow: "sm", cell: (t) => }, { key: "size", header: "大小", hideBelow: "lg", align: "right", cell: (t) => ( {t.sizeBytes === null ? "未知" : formatBytes(t.sizeBytes, 1)} ), }, { key: "at", header: "更新", hideBelow: "md", cell: (t) => ( {relTime(t.lastSyncedAt ?? t.updatedAt)} ), }, { key: "ops", header: "", align: "right", cell: (t) => }, ]; return ( <> void qc.invalidateQueries({ queryKey: ["downloads"] })}> 刷新 } /> f.set("status", v)} options={Object.entries(DOWNLOAD_STATUS_LABEL).map(([value, label]) => ({ value, label }))} /> f.set("managed", v)} options={[ { value: "true", label: "纳管任务" }, { value: "false", label: "未纳管" }, ]} /> t.id} loading={downloads.isPending} error={downloads.error} onRetry={() => void downloads.refetch()} onRowClick={(t) => setOpenId(t.id)} selectedKey={openId} hasMore={downloads.hasNextPage} onLoadMore={() => void downloads.fetchNextPage()} loadingMore={downloads.isFetchingNextPage} empty={{ icon: Download, title: f.active ? "没有符合筛选的下载任务" : "还没有下载任务", description: f.active ? undefined : "自动收录或在站点页下载后出现在这里。", }} /> !o && setOpenId(null)} width="lg" title={open?.name ?? ""} footer={ open ? ( ) : null } > {open ? : null} ); }