import type { Dashboard } from "@umt/contracts"; import { Brush, HardDrive, SlidersHorizontal } from "lucide-react"; import { Link, useNavigate } from "react-router"; import { toast } from "sonner"; import { useCacheCleanup, useCacheVolumes } from "~/api/downloads.ts"; import { downloaders } from "~/api/instances.ts"; import { ConfirmDialog } from "~/components/console/confirm-dialog.tsx"; import { ErrorState, LoadingState } from "~/components/console/empty-state.tsx"; import { consolePath, settingsPath } from "~/components/console/nav.ts"; import { StatusBadge } from "~/components/console/status-badge.tsx"; import { Button } from "~/components/ui/button.tsx"; import { errorMessage } from "~/lib/api.ts"; import { cn, formatBytes } from "~/lib/utils.ts"; type Volume = Dashboard["cache"]["volumes"][number]; /** 缓存卷显示名:没填缓存卷标识时服务端用 `instance:`,换成下载器实例名,不露内部 ID */ export function useVolumeLabel() { const list = downloaders.useList(); return (v: Pick) => { if (!v.volumeKey.startsWith("instance:")) return v.volumeKey; const names = v.instanceIds.map((id) => list.data?.items.find((i) => i.id === id)?.name).filter(Boolean); return names.length ? names.join("、") : "下载器缓存"; }; } function VolumeBar({ v }: { v: Volume }) { const volumeLabel = useVolumeLabel(); const pct = v.usedPercent; const tripped = pct !== null && pct >= v.hardThresholdPercent; const nearing = pct !== null && !tripped && pct >= v.resumePercent; return (
{volumeLabel(v)} {pct === null ? "使用率未知" : `${pct.toFixed(1)}%`} {v.usedBytes !== null && v.totalBytes !== null ? ` · ${formatBytes(v.usedBytes, 1)} / ${formatBytes(v.totalBytes, 1)}` : ""}
{pct !== null ? (
) : null}
硬阈值 {v.hardThresholdPercent}% / 恢复 {v.resumePercent}% 在途 {formatBytes(v.pendingBytes, 1)} 预留 {formatBytes(v.reservedBytes, 1)} 可清理 {formatBytes(v.cleanableBytes, 1)} 保留锁定 {formatBytes(v.retentionLockedBytes, 1)}
{pct === null ? 读不到磁盘状态,已暂停新提交 : null} {tripped ? 已达硬阈值,停止新提交 : null} {v.pausedByThreshold ? 阈值暂停 {v.pausedByThreshold} 个任务 : null}
{v.notes.map((n) => (

{n}

))}
); } /** 缓存卷水位 / 阈值条 + 阈值设置与手动清理入口(下载队列页头) */ export function CacheVolumes() { const vols = useCacheVolumes(); const cleanup = useCacheCleanup(); const navigate = useNavigate(); return (

缓存水位

立即清理 } title="按规则清理下载缓存?" description="只清理已入库、且满足保留期与 H&R 的纳管任务,其余不动。" confirmLabel="清理" onConfirm={() => cleanup.mutateAsync(undefined).then( (r) => { toast.success("清理已排队"); navigate(consolePath("tasks", r.taskId)); }, (e) => toast.error(errorMessage(e)), ) } />
{vols.isPending ? ( ) : vols.error ? ( void vols.refetch()} /> ) : !vols.data.length ? (

还没有缓存卷数据。登记下载器并完成巡检后显示。

) : (
{vols.data.map((v) => ( ))}
)}
); }