import type { AgentCheckTrigger, ScheduleGroup, ScheduleRunStatus, ScheduleSpec, ScheduleTrigger, } from "@umt/contracts"; import type { StatusTone } from "~/components/console/status-badge.tsx"; // 定时任务看板的标签映射(只在本页用,不放 lib/labels.ts) export const GROUP_ORDER: ScheduleGroup[] = ["collect", "download", "organize", "maintain", "agent"]; export const GROUP_LABEL: Record = { collect: "采集", download: "下载", organize: "整理", maintain: "维护", agent: "Agent 巡检", }; export const RUN_STATUS: Record = { ok: { label: "成功", tone: "success" }, failed: { label: "失败", tone: "destructive" }, skipped: { label: "已跳过", tone: "warning" }, running: { label: "运行中", tone: "neutral" }, }; export const TRIGGER_LABEL: Record = { schedule: "定时", manual: "手动", skip: "跳过", }; export const CHECK_TRIGGER_LABEL: Record = { always: "每次都跑", open_decisions: "有待处理时", stalled_downloads: "有下载无进展时", subscriptions_active: "有追踪订阅时", }; export type IntervalUnit = "s" | "m" | "h"; export const UNIT_MS: Record = { s: 1000, m: 60_000, h: 3600_000 }; export const UNIT_LABEL: Record = { s: "秒", m: "分钟", h: "小时" }; /** 毫秒 → 最大整除单位 */ export function splitInterval(ms: number): { value: number; unit: IntervalUnit } { if (ms % UNIT_MS.h === 0) return { value: ms / UNIT_MS.h, unit: "h" }; if (ms % UNIT_MS.m === 0) return { value: ms / UNIT_MS.m, unit: "m" }; return { value: Math.max(1, Math.round(ms / 1000)), unit: "s" }; } /** 调度的简短描述:"每 30 秒" / "cron 0 3 * * *" */ export function describeSchedule(s: ScheduleSpec): string { if (s.type === "cron") return `cron ${s.cron}`; if (s.everyMs < 1000) return `每 ${s.everyMs} 毫秒`; const { value, unit } = splitInterval(s.everyMs); return `每 ${value} ${UNIT_LABEL[unit]}`; } /** 毫秒耗时:"320 毫秒" / "2.4 秒" / "3 分 5 秒" */ export function formatMs(ms: number | null | undefined): string { if (ms === null || ms === undefined) return "—"; if (ms < 1000) return `${ms} 毫秒`; if (ms < 60_000) return `${(ms / 1000).toFixed(ms < 10_000 ? 1 : 0)} 秒`; const sec = Math.round(ms / 1000); if (sec < 3600) return `${Math.floor(sec / 60)} 分 ${sec % 60} 秒`; return `${Math.floor(sec / 3600)} 小时 ${Math.floor((sec % 3600) / 60)} 分`; } /** 倒计时:"12 秒后" / "3 分 5 秒后" / "已到期" */ export function countdown(iso: string, now: number): string { const diff = Math.round((new Date(iso).getTime() - now) / 1000); if (Number.isNaN(diff)) return "—"; if (diff <= 0) return "即将运行"; if (diff < 60) return `${diff} 秒后`; if (diff < 3600) return `${Math.floor(diff / 60)} 分 ${diff % 60} 秒后`; if (diff < 86400) return `${Math.floor(diff / 3600)} 小时 ${Math.floor((diff % 3600) / 60)} 分后`; return `${Math.floor(diff / 86400)} 天 ${Math.floor((diff % 86400) / 3600)} 小时后`; } export function formatClock(iso: string | null | undefined): string { if (!iso) return "—"; const d = new Date(iso); if (Number.isNaN(d.getTime())) return iso; return d.toLocaleString("zh-CN", { hour12: false, month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", }); }