diff --git a/web/js/units.js b/web/js/units.js
index 105da74..ca71781 100644
--- a/web/js/units.js
+++ b/web/js/units.js
@@ -74,6 +74,27 @@ async function selectUnit(unitId) {
await loadEvents();
}
+function runtimeBadge(runtime) {
+ if (!runtime) return 'OFFLINE';
+ const stateLabels = {
+ stopped: 'STOPPED',
+ running: 'RUNNING',
+ distributor_running: 'DIST RUN',
+ fault_locked: 'FAULT',
+ comm_locked: 'COMM ERR',
+ };
+ const stateCls = {
+ stopped: '',
+ running: 'online',
+ distributor_running: 'online',
+ fault_locked: 'danger',
+ comm_locked: 'offline',
+ };
+ const label = stateLabels[runtime.state] ?? runtime.state;
+ const cls = stateCls[runtime.state] ?? '';
+ return `${label}`;
+}
+
export function renderUnits() {
dom.unitList.innerHTML = "";
@@ -86,13 +107,15 @@ export function renderUnits() {
const card = document.createElement("div");
const selected = state.selectedUnitId === unit.id;
card.className = `list-item unit-card ${selected ? "selected" : ""}`;
+ const runtime = state.runtimes.get(unit.id);
card.innerHTML = `
${unit.code}
- ${unit.enabled ? "ENABLED" : "DISABLED"}
+ ${runtimeBadge(runtime)}
+ ${unit.enabled ? "EN" : "DIS"}
${unit.name}
- 设备 ${equipmentCount(unit.id)} 台
+ 设备 ${equipmentCount(unit.id)} 台 | Acc ${runtime ? Math.floor(runtime.accumulated_run_sec / 1000) : 0}s
Run ${unit.run_time_sec}s / Stop ${unit.stop_time_sec}s / Acc ${unit.acc_time_sec}s / BL ${unit.bl_time_sec}s
`;
@@ -124,6 +147,32 @@ export function renderUnits() {
});
actions.append(editBtn, deleteBtn);
+
+ const isAutoOn = runtime?.auto_enabled;
+ const autoBtn = document.createElement("button");
+ autoBtn.className = isAutoOn ? "danger" : "secondary";
+ autoBtn.textContent = isAutoOn ? "Stop Auto" : "Start Auto";
+ autoBtn.title = isAutoOn ? "停止自动控制" : "启动自动控制";
+ autoBtn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ const url = `/api/control/unit/${unit.id}/${isAutoOn ? "stop-auto" : "start-auto"}`;
+ apiFetch(url, { method: "POST" }).then(() => loadUnits()).catch(() => {});
+ });
+ actions.append(autoBtn);
+
+ if (runtime?.manual_ack_required) {
+ const ackBtn = document.createElement("button");
+ ackBtn.className = "danger";
+ ackBtn.textContent = "Ack Fault";
+ ackBtn.title = "人工确认解除故障锁定";
+ ackBtn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ apiFetch(`/api/control/unit/${unit.id}/ack-fault`, { method: "POST" })
+ .then(() => loadUnits()).catch(() => {});
+ });
+ actions.append(ackBtn);
+ }
+
dom.unitList.appendChild(card);
});
}