From 42cdbbc0ccddeb89b5748b2367b9feeff279f02d Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 26 Mar 2026 09:11:47 +0800 Subject: [PATCH] fix(web): fetch all unit runtimes on page load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: state.runtimes was empty after refresh because the engine only pushes UnitRuntimeChanged on state transitions — if the engine is mid-wait-phase, no push occurs and badges show OFFLINE. Fix: add GET /api/unit/runtimes batch endpoint (returns all known runtimes as { unit_id: UnitRuntime }) and call it in parallel with the unit list fetch inside loadUnits(), so runtime badges are correct immediately after page load. Co-Authored-By: Claude Sonnet 4.6 --- src/control/runtime.rs | 4 ++++ src/handler/control.rs | 8 ++++++++ src/main.rs | 4 ++++ web/js/units.js | 9 ++++++++- 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/control/runtime.rs b/src/control/runtime.rs index 5653749..80e710e 100644 --- a/src/control/runtime.rs +++ b/src/control/runtime.rs @@ -81,6 +81,10 @@ impl ControlRuntimeStore { .clone() } + pub async fn get_all(&self) -> HashMap { + self.inner.read().await.clone() + } + /// Wake the engine task for a unit (e.g., when auto_enabled or fault_locked changes). pub async fn notify_unit(&self, unit_id: Uuid) { if let Some(n) = self.notifiers.read().await.get(&unit_id) { diff --git a/src/handler/control.rs b/src/handler/control.rs index 0bac201..1e84b85 100644 --- a/src/handler/control.rs +++ b/src/handler/control.rs @@ -509,3 +509,11 @@ pub async fn get_unit_runtime( let runtime = state.control_runtime.get_or_init(unit_id).await; Ok(Json(runtime)) } + +/// Returns all known runtimes as { unit_id: UnitRuntime }. +/// Used by the frontend on page load to populate initial state. +pub async fn get_all_unit_runtimes( + State(state): State, +) -> impl IntoResponse { + Json(state.control_runtime.get_all().await) +} diff --git a/src/main.rs b/src/main.rs index c9e96c7..5226d79 100644 --- a/src/main.rs +++ b/src/main.rs @@ -209,6 +209,10 @@ fn build_router(state: AppState) -> Router { "/api/unit", get(handler::control::get_unit_list).post(handler::control::create_unit), ) + .route( + "/api/unit/runtimes", + get(handler::control::get_all_unit_runtimes), + ) .route( "/api/unit/{unit_id}", get(handler::control::get_unit) diff --git a/web/js/units.js b/web/js/units.js index c654ddc..1d2e98e 100644 --- a/web/js/units.js +++ b/web/js/units.js @@ -180,7 +180,10 @@ export function renderUnits() { } export async function loadUnits() { - const response = await apiFetch("/api/unit?page=1&page_size=-1"); + const [response, runtimes] = await Promise.all([ + apiFetch("/api/unit?page=1&page_size=-1"), + apiFetch("/api/unit/runtimes").catch(() => ({})), + ]); state.units = response.data || []; state.unitMap = new Map(state.units.map((unit) => [unit.id, unit])); @@ -188,6 +191,10 @@ export async function loadUnits() { state.selectedUnitId = null; } + for (const [unitId, runtime] of Object.entries(runtimes)) { + state.runtimes.set(unitId, runtime); + } + renderUnits(); renderUnitOptions(dom.equipmentUnitId?.value || "", dom.equipmentUnitId); renderUnitOptions(dom.equipmentBatchUnitId?.value || "", dom.equipmentBatchUnitId);