import { useQuery } from "@tanstack/react-query"; import { type Dashboard, DownloadBoard, type HealthLevel } from "@umt/contracts"; import { ArrowRight, Download, Gauge, type LucideIcon } from "lucide-react"; import type { ReactNode } from "react"; import { Link } from "react-router"; import { useSitesWithAccounts } from "~/api/dashboard.ts"; import { apiParsed } from "~/api/parse.ts"; import { useSystemHealth, useSystemResources } from "~/api/system.ts"; import { EmptyState, ErrorState, LoadingState } from "~/components/console/empty-state.tsx"; import { consolePath } from "~/components/console/nav.ts"; import { StatusBadge, type StatusTone } from "~/components/console/status-badge.tsx"; import { Sparkline, usageTone } from "~/components/status/resource-section.tsx"; import { formatRelative } from "~/lib/labels.ts"; import { cn, formatBytes } from "~/lib/utils.ts"; const LEVEL: Record = { ok: { label: "正常", tone: "success" }, warn: { label: "需要关注", tone: "warning" }, critical: { label: "需要处理", tone: "destructive" }, unknown: { label: "用量未知", tone: "neutral" }, }; const SCOPE = { data_dir: "系统数据", library_root: "媒体库", download_dir: "下载目录" }; function OverviewCard({ title, icon: Icon, to, children, }: { title: string; icon: LucideIcon; to: string; children: ReactNode; }) { return (

{title}

详情
{children}
); } const BAD_ACCOUNT = ["invalid", "expired", "challenge"]; /** 一行「名称 正常数 / 总数」;有异常时数值标黄 */ function SummaryItem({ label, ok, total, to }: { label: string; ok: number; total: number; to: string }) { if (!total) return null; return ( {label} {ok}/{total} ); } /** * 运行状态简版(界面第二轮,参考 MP 仪表盘):整体健康、CPU、内存、磁盘,以及下载器 / 站点 / 媒体库目录在线摘要。 * 详细版在系统 → 运行状态。 */ function RuntimeOverview({ downloaders }: { downloaders?: Dashboard["sources"]["downloaders"] }) { const q = useSystemHealth(60_000); const r = useSystemResources(30, 10_000); const sites = useSitesWithAccounts(); const h = q.data; const res = r.data; const mediaDisks = h?.disks.filter((d) => d.scope !== "data_dir") ?? []; const disks = (mediaDisks.length ? mediaDisks : (h?.disks ?? [])) .filter((d) => d.usedRatio !== null) .sort((a, b) => (b.usedRatio ?? 0) - (a.usedRatio ?? 0)) .slice(0, 3); const enabledDl = (downloaders ?? []).filter((d) => d.enabled); const okDl = enabledDl.filter((d) => !["offline", "failing"].includes(d.health)).length; const siteRows = (sites.data?.items ?? []).filter((s) => s.enabled); const okSites = siteRows.filter((s) => !(s.accountStatus && BAD_ACCOUNT.includes(s.accountStatus))).length; const roots = h?.mounts.filter((m) => m.kind === "library_root") ?? []; const okRoots = roots.filter((m) => m.status === "online").length; const level = h ? LEVEL[h.level === "unknown" ? "unknown" : h.level] : null; return ( {q.isPending ? ( ) : q.isError ? ( q.refetch()} className="py-6" /> ) : h ? ( <>
{level ? {h.level === "ok" ? "全部正常" : level.label} : null} 检查于 {formatRelative(h.checkedAt)}
{[ { label: "CPU", v: res?.cpu.percent, key: "cpuPercent" as const }, { label: "内存", v: res?.memory.percent, key: "memoryPercent" as const }, ].map((x) => (

{x.label} {x.v == null ? "—" : `${Math.round(x.v)}%`}

{res ? ( `${v.toFixed(1)}%`} max={100} height={32} label={`${x.label}占用趋势(最近 30 分钟)`} /> ) : (
)}
))}
{disks.length ? (
    {disks.map((d) => { const pct = (d.usedRatio ?? 0) * 100; return (
  • {SCOPE[d.scope]} {d.name} {Math.round(pct)}% · 剩余{" "} {d.availableBytes === null ? "未知" : formatBytes(d.availableBytes, 1)}
  • ); })}
) : null}

) : ( )} ); } function DownloaderOverview({ downloaders }: { downloaders?: Dashboard["sources"]["downloaders"] }) { // 首页仅取一条面板记录,速度和计数仍是完整汇总;无需拉取下载任务列表。 const q = useQuery({ queryKey: ["downloads", "home-overview"], queryFn: ({ signal }) => apiParsed(DownloadBoard, "/api/v1/download-tasks/board", { params: { state: "active", limit: 1 }, signal }), refetchInterval: 15_000, refetchIntervalInBackground: false, }); const board = q.data; const instances = board?.transfer.instances ?? []; const sources = downloaders ?? instances.map((d) => ({ id: d.instanceId, name: d.name, enabled: true, health: "unknown", lastSeenAt: null })); return ( {q.isPending ? ( ) : q.isError ? ( q.refetch()} className="py-6" /> ) : !sources.length ? ( ) : ( <>

{board?.counts.active} 个下载进行中

    {sources.map((d) => { const live = instances.find((i) => i.instanceId === d.id); const state = !d.enabled ? { label: "已停用", tone: "neutral" as const } : !live ? { label: "状态未知", tone: "neutral" as const } : live.reachable ? { label: "在线", tone: "success" as const } : { label: "连接失败", tone: "warning" as const }; const readable = d.enabled && live?.reachable; return (
  • {d.name} {state.label}
    {d.enabled ? (

    下载{" "} {readable ? `${formatBytes(live.dlSpeed, 1)}/s` : "未知"} 上传{" "} {readable ? `${formatBytes(live.upSpeed, 1)}/s` : "未知"}

    ) : null}
  • ); })}

进行中包括还没提交、已暂停和出错的下载 · 速度是下载器里全部任务的合计

{board?.liveAt ? (

速度更新于 {formatRelative(board.liveAt)}

) : null} )}
); } export function HomeRuntime({ downloaders }: { downloaders?: Dashboard["sources"]["downloaders"] }) { return (
); }