import { runtimeApi, segmentControl } from "./api.js"; const STATE_LABEL = { idle: "空闲", checking: "校验", executing: "执行", confirming: "等待确认", resetting: "复位", completed: "完成", blocked: "阻塞", faulted: "故障", manual_ack_required: "待人工确认", }; const STATE_CLASS = { idle: "state-idle", checking: "state-active", executing: "state-active", confirming: "state-active", resetting: "state-active", completed: "state-active", blocked: "state-warn", faulted: "state-error", manual_ack_required: "state-warn", }; const segments = new Map(); function escapeHtml(text) { if (text === null || text === undefined) return ""; return String(text) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">"); } function renderState(runtime) { const state = runtime?.state || "idle"; const label = STATE_LABEL[state] || state; const cls = STATE_CLASS[state] || "state-idle"; return `${escapeHtml(label)}`; } function renderActions(seg) { const runtime = seg.runtime || {}; const autoOn = runtime.auto_enabled === true; const state = runtime.state || "idle"; const canAck = state === "faulted" || state === "manual_ack_required"; const canReset = canAck || state === "blocked"; return `
`; } function renderCard(seg) { const segment = seg.segment; const runtime = seg.runtime || {}; const note = runtime.fault_message || runtime.blocked_reason || ""; const lineTag = segment.line_code ? `${escapeHtml(segment.line_code)}` : ""; const modeTag = `${escapeHtml(segment.mode)}`; const autoTag = runtime.auto_enabled ? `AUTO` : ""; const stepText = runtime.current_step_no === null || runtime.current_step_no === undefined ? "—" : `Step ${runtime.current_step_no}`; return `
${escapeHtml(segment.code)} ${escapeHtml(segment.name)}
${lineTag}${modeTag}${autoTag}${renderState(runtime)}
当前步骤${escapeHtml(stepText)}
${note ? `
${escapeHtml(note)}
` : ""}
${renderActions(seg)}
`; } function renderAll() { const root = document.getElementById("segmentList"); if (!root) return; const items = Array.from(segments.values()); items.sort((a, b) => a.segment.code.localeCompare(b.segment.code)); if (items.length === 0) { root.innerHTML = `
尚无段配置;执行种子或通过配置页新增段。
`; return; } root.innerHTML = items.map(renderCard).join(""); } function setBanner(message, level = "info") { const root = document.getElementById("segmentList"); if (!root) return; const existing = root.querySelector(".ops-banner"); if (existing) existing.remove(); const div = document.createElement("div"); div.className = `ops-banner banner-${level}`; div.textContent = message; root.prepend(div); window.setTimeout(() => div.remove(), 4000); } async function callAndRefresh(label, fn) { try { await fn(); setBanner(`${label} 成功`, "info"); } catch (err) { setBanner(err.message || String(err), "error"); } } function handleAction(event) { const button = event.target.closest("button[data-action]"); if (!button) return; const action = button.dataset.action; const id = button.dataset.id; switch (action) { case "start-auto": return callAndRefresh("启动自动控制", () => segmentControl.startAuto(id)); case "stop-auto": return callAndRefresh("停止自动控制", () => segmentControl.stopAuto(id)); case "ack-fault": return callAndRefresh("故障确认", () => segmentControl.ackFault(id)); case "reset": return callAndRefresh("复位", () => segmentControl.reset(id)); default: return undefined; } } export async function loadSegments() { const data = await runtimeApi.fetchOverview(); segments.clear(); (data?.segments || []).forEach((entry) => { segments.set(entry.segment.id, entry); }); renderAll(); } /// Apply a SegmentRuntime payload pushed via WebSocket app_event. export function applyRuntimeUpdate(runtime) { if (!runtime?.segment_id) return; const entry = segments.get(runtime.segment_id); if (!entry) { // Unknown segment — refresh from overview so we pick it up. void loadSegments(); return; } entry.runtime = runtime; renderAll(); } export function bindSegmentEvents() { const root = document.getElementById("segmentList"); if (root) root.addEventListener("click", handleAction); const refreshBtn = document.getElementById("refreshSegmentsBtn"); if (refreshBtn) { refreshBtn.addEventListener("click", () => callAndRefresh("刷新", loadSegments)); } const batchStart = document.getElementById("batchStartAutoBtn"); if (batchStart) { batchStart.addEventListener("click", async () => { await callAndRefresh("批量启动", () => segmentControl.batchStart()); await loadSegments(); }); } const batchStop = document.getElementById("batchStopAutoBtn"); if (batchStop) { batchStop.addEventListener("click", async () => { await callAndRefresh("批量停止", () => segmentControl.batchStop()); await loadSegments(); }); } }