feat(frontend): show runtime state and auto/ack buttons on unit cards

This commit is contained in:
caoqianming 2026-03-24 14:58:07 +08:00
parent 31ccf49b75
commit 89023e867b
1 changed files with 51 additions and 2 deletions

View File

@ -74,6 +74,27 @@ async function selectUnit(unitId) {
await loadEvents(); await loadEvents();
} }
function runtimeBadge(runtime) {
if (!runtime) return '<span class="badge offline">OFFLINE</span>';
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 `<span class="badge ${cls}">${label}</span>`;
}
export function renderUnits() { export function renderUnits() {
dom.unitList.innerHTML = ""; dom.unitList.innerHTML = "";
@ -86,13 +107,15 @@ export function renderUnits() {
const card = document.createElement("div"); const card = document.createElement("div");
const selected = state.selectedUnitId === unit.id; const selected = state.selectedUnitId === unit.id;
card.className = `list-item unit-card ${selected ? "selected" : ""}`; card.className = `list-item unit-card ${selected ? "selected" : ""}`;
const runtime = state.runtimes.get(unit.id);
card.innerHTML = ` card.innerHTML = `
<div class="row"> <div class="row">
<strong>${unit.code}</strong> <strong>${unit.code}</strong>
<span class="badge ${unit.enabled ? "" : "offline"}">${unit.enabled ? "ENABLED" : "DISABLED"}</span> ${runtimeBadge(runtime)}
<span class="badge ${unit.enabled ? "" : "offline"}">${unit.enabled ? "EN" : "DIS"}</span>
</div> </div>
<div>${unit.name}</div> <div>${unit.name}</div>
<div class="muted">设备 ${equipmentCount(unit.id)} </div> <div class="muted">设备 ${equipmentCount(unit.id)} | Acc ${runtime ? Math.floor(runtime.accumulated_run_sec / 1000) : 0}s</div>
<div class="muted">Run ${unit.run_time_sec}s / Stop ${unit.stop_time_sec}s / Acc ${unit.acc_time_sec}s / BL ${unit.bl_time_sec}s</div> <div class="muted">Run ${unit.run_time_sec}s / Stop ${unit.stop_time_sec}s / Acc ${unit.acc_time_sec}s / BL ${unit.bl_time_sec}s</div>
<div class="row unit-card-actions"></div> <div class="row unit-card-actions"></div>
`; `;
@ -124,6 +147,32 @@ export function renderUnits() {
}); });
actions.append(editBtn, deleteBtn); 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); dom.unitList.appendChild(card);
}); });
} }