import type { Dashboard } from "@umt/contracts"; import { Brush, HardDrive } from "lucide-react"; import { toast } from "sonner"; import { useCacheCleanup, useCacheVolumes } from "~/api/downloads.ts"; import { ConfirmDialog } from "~/components/console/confirm-dialog.tsx"; import { ErrorState, LoadingState } from "~/components/console/empty-state.tsx"; 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]; function VolumeBar({ v }: { v: Volume }) { const pct = v.usedPercent; const tripped = pct !== null && pct >= v.hardThresholdPercent; const nearing = pct !== null && !tripped && pct >= v.resumePercent; return (
{v.volumeKey} {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(); return (

缓存与保留

立即清理 } title="按规则清理下载缓存?" description="只清理已核验入库、且满足保留期与 H&R 的纳管任务;未纳管任务与未满足保留的不会动。" confirmLabel="清理" onConfirm={() => cleanup.mutateAsync(undefined).then( (r) => { const vols = (r.report as { volumes?: { removed?: unknown[]; skipped?: unknown[] }[] }).volumes ?? []; const removed = vols.reduce((n, v) => n + (v.removed?.length ?? 0), 0); const skipped = vols.reduce((n, v) => n + (v.skipped?.length ?? 0), 0); toast.success(`清理完成:移除 ${removed} 个任务的缓存${skipped ? `,${skipped} 个因保留/H&R 跳过` : ""}`); }, (e) => toast.error(errorMessage(e)), ) } />
{vols.isPending ? ( ) : vols.error ? ( void vols.refetch()} /> ) : !vols.data.length ? (

还没有下载器缓存卷的统计(未配置下载器或尚未巡检)。

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