import { type Schedule, type ScheduleArtifact, type ScheduleNode, type ScheduleNodeRun, type ScheduleRunSummary, } from "../../services/api"; import { EMPTY_SCHEDULE_FORM, EMPTY_NODE_FORM } from "./constants"; import type { NodeForm } from "./state/types"; export function formatTime(value: string | null): string { if (!value) return "尚未执行"; return new Intl.DateTimeFormat("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false, }).format(new Date(value)); } export function shortHash(value: string): string { return value ? `${value.slice(0, 7)}…${value.slice(-5)}` : "—"; } export function formatDuration(value: number | null): string { if (value === null) return "—"; if (value < 1000) return `${value} ms`; if (value < 60_000) return `${(value / 1000).toFixed(1)} s`; return `${Math.floor(value / 60_000)}m ${Math.round((value % 60_000) / 1000)}s`; } export function formatSize(value: number): string { if (value < 1024) return `${value} B`; if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`; return `${(value / 1024 / 1024).toFixed(1)} MB`; } export const RUN_STATUS_LABELS: Record = { queued: "排队中", running: "运行中", succeeded: "成功", failed: "失败", cancelled: "已取消", timed_out: "已超时", }; export const NODE_STATUS_LABELS: Record = { ...RUN_STATUS_LABELS, skipped: "已跳过", }; export const TRIGGER_TYPE_LABELS: Record = { manual: "手动触发", cron: "Cron 定时", api: "API 触发", retry: "失败重试", }; export function parseObject(text: string, label: string): Record { let value: unknown; try { value = JSON.parse(text); } catch { throw new Error(`${label}必须是合法 JSON`); } if (!value || Array.isArray(value) || typeof value !== "object") { throw new Error(`${label}必须是 JSON 对象`); } return value as Record; } export function scheduleToForm(schedule: Schedule) { return { scheduleName: schedule.schedule_name, description: schedule.description ?? "", triggerType: schedule.trigger_type, cronExpression: schedule.cron_expression ?? "0 9 * * *", timezone: schedule.timezone, enabled: schedule.enabled, maxConcurrency: String(schedule.max_concurrency), failurePolicy: schedule.failure_policy, }; } export function nodeToForm(node: ScheduleNode): NodeForm { return { nodeName: node.node_name, timeoutSeconds: String(node.timeout_seconds), retryCount: String(node.retry_count), retryIntervalSec: String(node.retry_interval_sec), argumentsJson: JSON.stringify(node.arguments_json ?? {}, null, 2), envRefsJson: JSON.stringify(node.env_refs_json ?? {}, null, 2), pythonVersion: node.python_version ?? "3.12", }; } export function artifactNodeKey( artifact: ScheduleArtifact, schedule: Schedule, ): string { const ascii = artifact.script_name .replace(/\.[^.]+$/, "") .replace(/[^A-Za-z0-9_-]+/g, "_") .replace(/^([^A-Za-z])/, "n_$1") .replace(/^_+|_+$/g, "") .slice(0, 48); const base = ascii || `node_${artifact.versions_id.slice(-6).toLowerCase()}`; const existing = new Set(schedule.nodes.map((item) => item.node_key)); if (!existing.has(base)) return base; let index = 2; while (existing.has(`${base}_${index}`)) index += 1; return `${base}_${index}`.slice(0, 64); } export function edgePath( source: ScheduleNode, target: ScheduleNode, ): string { const x1 = source.position_x + 218; const y1 = source.position_y + 52; const x2 = target.position_x; const y2 = target.position_y + 52; const curve = Math.max(70, Math.abs(x2 - x1) * 0.45); return `M ${x1} ${y1} C ${x1 + curve} ${y1}, ${x2 - curve} ${y2}, ${x2} ${y2}`; } export function withSuppressedError(action: () => Promise): void { void action().catch(() => undefined); }