diff --git a/src/handler/control.rs b/src/handler/control.rs index c35513a..1da822c 100644 --- a/src/handler/control.rs +++ b/src/handler/control.rs @@ -410,6 +410,58 @@ pub async fn stop_auto_unit( Ok(Json(json!({ "ok_msg": "Auto control stopped", "unit_id": unit_id }))) } +pub async fn batch_start_auto( + State(state): State, +) -> Result { + let units = crate::service::get_all_enabled_units(&state.pool).await?; + let mut started = Vec::new(); + let mut skipped = Vec::new(); + + for unit in units { + let mut runtime = state.control_runtime.get_or_init(unit.id).await; + if runtime.auto_enabled { + skipped.push(unit.id); + continue; + } + if runtime.fault_locked || runtime.comm_locked { + skipped.push(unit.id); + continue; + } + runtime.auto_enabled = true; + runtime.state = crate::control::runtime::UnitRuntimeState::Stopped; + runtime.current_stop_elapsed_sec = 0; + state.control_runtime.upsert(runtime).await; + let _ = state + .event_manager + .send(crate::event::AppEvent::AutoControlStarted { unit_id: unit.id }); + started.push(unit.id); + } + + Ok(Json(json!({ "started": started, "skipped": skipped }))) +} + +pub async fn batch_stop_auto( + State(state): State, +) -> Result { + let units = crate::service::get_all_enabled_units(&state.pool).await?; + let mut stopped = Vec::new(); + + for unit in units { + let mut runtime = state.control_runtime.get_or_init(unit.id).await; + if !runtime.auto_enabled { + continue; + } + runtime.auto_enabled = false; + state.control_runtime.upsert(runtime).await; + let _ = state + .event_manager + .send(crate::event::AppEvent::AutoControlStopped { unit_id: unit.id }); + stopped.push(unit.id); + } + + Ok(Json(json!({ "stopped": stopped }))) +} + pub async fn ack_fault_unit( State(state): State, Path(unit_id): Path, diff --git a/src/main.rs b/src/main.rs index 84f9729..c9e96c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -235,6 +235,14 @@ fn build_router(state: AppState) -> Router { "/api/control/unit/{unit_id}/stop-auto", post(handler::control::stop_auto_unit), ) + .route( + "/api/control/unit/batch-start-auto", + post(handler::control::batch_start_auto), + ) + .route( + "/api/control/unit/batch-stop-auto", + post(handler::control::batch_stop_auto), + ) .route( "/api/control/unit/{unit_id}/ack-fault", post(handler::control::ack_fault_unit), diff --git a/web/html/ops-panel.html b/web/html/ops-panel.html index 2106894..9385f09 100644 --- a/web/html/ops-panel.html +++ b/web/html/ops-panel.html @@ -3,6 +3,10 @@ diff --git a/web/index.html b/web/index.html index a9c6821..1eecd2c 100644 --- a/web/index.html +++ b/web/index.html @@ -4,7 +4,7 @@ PLC Control - +
@@ -22,6 +22,6 @@
- + diff --git a/web/js/dom.js b/web/js/dom.js index 60b41c5..5297451 100644 --- a/web/js/dom.js +++ b/web/js/dom.js @@ -4,6 +4,8 @@ export const dom = { statusText: byId("statusText"), wsDot: byId("wsDot"), wsLabel: byId("wsLabel"), + batchStartAutoBtn: byId("batchStartAutoBtn"), + batchStopAutoBtn: byId("batchStopAutoBtn"), tabOps: byId("tabOps"), tabConfig: byId("tabConfig"), opsUnitList: byId("opsUnitList"), diff --git a/web/js/ops.js b/web/js/ops.js index cf3b25d..6080ef3 100644 --- a/web/js/ops.js +++ b/web/js/ops.js @@ -190,6 +190,18 @@ function renderOpsEquipments(equipments) { export function startOps() { renderOpsUnits(); loadAllEquipmentCards(); + + dom.batchStartAutoBtn?.addEventListener("click", () => { + apiFetch("/api/control/unit/batch-start-auto", { method: "POST" }) + .then(() => loadUnits()) + .catch(() => {}); + }); + + dom.batchStopAutoBtn?.addEventListener("click", () => { + apiFetch("/api/control/unit/batch-stop-auto", { method: "POST" }) + .then(() => loadUnits()) + .catch(() => {}); + }); } /** Called by WS handler when a unit's runtime changes — syncs manual button disabled state. */ diff --git a/web/styles.css b/web/styles.css index c67d710..ebdc01f 100644 --- a/web/styles.css +++ b/web/styles.css @@ -323,6 +323,16 @@ body { padding: 7px 12px; border-bottom: 1px solid var(--border-light); flex-shrink: 0; + flex-wrap: wrap; + gap: 6px; +} +.ops-batch-actions { + display: flex; + gap: 4px; +} +.ops-batch-actions button { + font-size: 11px; + padding: 2px 8px; } h2, h3 {