fix(unit): block auto control start when fault is active or unacknowledged

Prevent starting unit auto control while fault_locked or manual_ack_required,
enforcing that faults must be manually acknowledged before resuming automation.
Also disable the Start Auto button in the frontend with descriptive tooltips.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-03-26 10:54:10 +08:00
parent f37924ae36
commit 00c16ae3d7
4 changed files with 25 additions and 5 deletions

View File

@ -96,7 +96,7 @@ async fn unit_task(state: AppState, store: Arc<ControlRuntimeStore>, unit_id: Uu
} }
// ── Wait when not active ────────────────────────────────────────────── // ── Wait when not active ──────────────────────────────────────────────
if !runtime.auto_enabled || runtime.fault_locked || runtime.comm_locked { if !runtime.auto_enabled || runtime.fault_locked || runtime.comm_locked || runtime.manual_ack_required {
tokio::select! { tokio::select! {
_ = fault_tick.tick() => {} _ = fault_tick.tick() => {}
_ = notify.notified() => { _ = notify.notified() => {
@ -261,7 +261,7 @@ async fn wait_phase(
store.upsert(runtime.clone()).await; store.upsert(runtime.clone()).await;
push_ws(state, &runtime).await; push_ws(state, &runtime).await;
} }
if !runtime.auto_enabled || runtime.fault_locked || runtime.comm_locked { if !runtime.auto_enabled || runtime.fault_locked || runtime.comm_locked || runtime.manual_ack_required {
return false; return false;
} }
} }

View File

@ -488,6 +488,18 @@ pub async fn start_auto_unit(
} }
let mut runtime = state.control_runtime.get_or_init(unit_id).await; let mut runtime = state.control_runtime.get_or_init(unit_id).await;
if runtime.fault_locked {
return Err(ApiErr::BadRequest(
"Unit is fault locked, cannot start auto control".to_string(),
None,
));
}
if runtime.manual_ack_required {
return Err(ApiErr::BadRequest(
"Fault acknowledgement required before starting auto control".to_string(),
None,
));
}
runtime.auto_enabled = true; runtime.auto_enabled = true;
runtime.state = crate::control::runtime::UnitRuntimeState::Stopped; runtime.state = crate::control::runtime::UnitRuntimeState::Stopped;
state.control_runtime.upsert(runtime).await; state.control_runtime.upsert(runtime).await;
@ -529,7 +541,7 @@ pub async fn batch_start_auto(
skipped.push(unit.id); skipped.push(unit.id);
continue; continue;
} }
if runtime.fault_locked || runtime.comm_locked { if runtime.fault_locked || runtime.comm_locked || runtime.manual_ack_required {
skipped.push(unit.id); skipped.push(unit.id);
continue; continue;
} }

View File

@ -50,10 +50,14 @@ export function renderOpsUnits() {
const actions = item.querySelector(".ops-unit-item-actions"); const actions = item.querySelector(".ops-unit-item-actions");
const isAutoOn = runtime?.auto_enabled; const isAutoOn = runtime?.auto_enabled;
const startBlocked = !isAutoOn && (runtime?.fault_locked || runtime?.manual_ack_required);
const autoBtn = document.createElement("button"); const autoBtn = document.createElement("button");
autoBtn.className = isAutoOn ? "danger" : "secondary"; autoBtn.className = isAutoOn ? "danger" : "secondary";
autoBtn.textContent = isAutoOn ? "Stop Auto" : "Start Auto"; autoBtn.textContent = isAutoOn ? "Stop Auto" : "Start Auto";
autoBtn.title = isAutoOn ? "停止自动控制" : "启动自动控制"; autoBtn.disabled = startBlocked;
autoBtn.title = startBlocked
? (runtime?.fault_locked ? "设备故障中,无法启动自动控制" : "需人工确认故障后才可启动自动控制")
: (isAutoOn ? "停止自动控制" : "启动自动控制");
autoBtn.addEventListener("click", (e) => { autoBtn.addEventListener("click", (e) => {
e.stopPropagation(); e.stopPropagation();
apiFetch(`/api/control/unit/${unit.id}/${isAutoOn ? "stop-auto" : "start-auto"}`, { method: "POST" }) apiFetch(`/api/control/unit/${unit.id}/${isAutoOn ? "stop-auto" : "start-auto"}`, { method: "POST" })

View File

@ -151,10 +151,14 @@ export function renderUnits() {
actions.append(editBtn, deleteBtn); actions.append(editBtn, deleteBtn);
const isAutoOn = runtime?.auto_enabled; const isAutoOn = runtime?.auto_enabled;
const startBlocked = !isAutoOn && (runtime?.fault_locked || runtime?.manual_ack_required);
const autoBtn = document.createElement("button"); const autoBtn = document.createElement("button");
autoBtn.className = isAutoOn ? "danger" : "secondary"; autoBtn.className = isAutoOn ? "danger" : "secondary";
autoBtn.textContent = isAutoOn ? "Stop Auto" : "Start Auto"; autoBtn.textContent = isAutoOn ? "Stop Auto" : "Start Auto";
autoBtn.title = isAutoOn ? "停止自动控制" : "启动自动控制"; autoBtn.disabled = startBlocked;
autoBtn.title = startBlocked
? (runtime?.fault_locked ? "设备故障中,无法启动自动控制" : "需人工确认故障后才可启动自动控制")
: (isAutoOn ? "停止自动控制" : "启动自动控制");
autoBtn.addEventListener("click", (e) => { autoBtn.addEventListener("click", (e) => {
e.stopPropagation(); e.stopPropagation();
const url = `/api/control/unit/${unit.id}/${isAutoOn ? "stop-auto" : "start-auto"}`; const url = `/api/control/unit/${unit.id}/${isAutoOn ? "stop-auto" : "start-auto"}`;