feat(web): add runtime badge and auto/ack buttons to ops unit list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4076f6575e
commit
36cfe9ecfc
|
|
@ -2,10 +2,20 @@ import { apiFetch } from "./api.js";
|
||||||
import { dom } from "./dom.js";
|
import { dom } from "./dom.js";
|
||||||
import { formatValue } from "./points.js";
|
import { formatValue } from "./points.js";
|
||||||
import { state } from "./state.js";
|
import { state } from "./state.js";
|
||||||
|
import { loadUnits } from "./units.js";
|
||||||
|
|
||||||
const SIGNAL_ROLES = ["rem", "run", "flt"];
|
const SIGNAL_ROLES = ["rem", "run", "flt"];
|
||||||
const ROLE_LABELS = { rem: "REM", run: "RUN", flt: "FLT" };
|
const ROLE_LABELS = { rem: "REM", run: "RUN", flt: "FLT" };
|
||||||
|
|
||||||
|
function runtimeBadge(runtime) {
|
||||||
|
if (!runtime) return '<span class="badge offline">OFFLINE</span>';
|
||||||
|
if (runtime.comm_locked) return '<span class="badge offline">COMM ERR</span>';
|
||||||
|
if (runtime.fault_locked) return '<span class="badge danger">FAULT</span>';
|
||||||
|
const labels = { stopped: "STOPPED", running: "RUNNING", distributor_running: "DIST RUN", fault_locked: "FAULT", comm_locked: "COMM ERR" };
|
||||||
|
const cls = { stopped: "", running: "online", distributor_running: "online", fault_locked: "danger", comm_locked: "offline" };
|
||||||
|
return `<span class="badge ${cls[runtime.state] ?? ""}">${labels[runtime.state] ?? runtime.state}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
export function renderOpsUnits() {
|
export function renderOpsUnits() {
|
||||||
if (!dom.opsUnitList) return;
|
if (!dom.opsUnitList) return;
|
||||||
dom.opsUnitList.innerHTML = "";
|
dom.opsUnitList.innerHTML = "";
|
||||||
|
|
@ -22,11 +32,41 @@ export function renderOpsUnits() {
|
||||||
item.innerHTML = `
|
item.innerHTML = `
|
||||||
<div class="ops-unit-item-name">${unit.code} / ${unit.name}</div>
|
<div class="ops-unit-item-name">${unit.code} / ${unit.name}</div>
|
||||||
<div class="ops-unit-item-meta">
|
<div class="ops-unit-item-meta">
|
||||||
|
${runtimeBadge(runtime)}
|
||||||
<span class="badge ${unit.enabled ? "" : "offline"}">${unit.enabled ? "EN" : "DIS"}</span>
|
<span class="badge ${unit.enabled ? "" : "offline"}">${unit.enabled ? "EN" : "DIS"}</span>
|
||||||
${runtime ? `<span>Acc ${Math.floor(runtime.accumulated_run_sec / 1000)}s</span>` : ""}
|
${runtime ? `<span class="muted">Acc ${Math.floor(runtime.accumulated_run_sec / 1000)}s</span>` : ""}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ops-unit-item-actions"></div>
|
||||||
`;
|
`;
|
||||||
item.addEventListener("click", () => selectOpsUnit(unit.id));
|
item.addEventListener("click", () => selectOpsUnit(unit.id));
|
||||||
|
|
||||||
|
const actions = item.querySelector(".ops-unit-item-actions");
|
||||||
|
|
||||||
|
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();
|
||||||
|
apiFetch(`/api/control/unit/${unit.id}/${isAutoOn ? "stop-auto" : "start-auto"}`, { 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.opsUnitList.appendChild(item);
|
dom.opsUnitList.appendChild(item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -286,9 +286,21 @@ body {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--text-3);
|
color: var(--text-3);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ops-unit-item-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-unit-item-actions button {
|
||||||
|
padding: 2px 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Panel Header ───────────────────────────────── */
|
/* ── Panel Header ───────────────────────────────── */
|
||||||
|
|
||||||
.panel-head {
|
.panel-head {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue