diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index 800b30ae..0236bb57 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -77,6 +77,7 @@ pub async fn serve(port: u16, coord: Arc) -> Result<()> { .route("/api/schedules", get(api_schedules).post(post_schedule_new)) .route("/api/schedules/{id}/cancel", post(post_schedule_cancel)) .route("/api/schedules/{id}/fire-now", post(post_schedule_fire_now)) + .route("/api/rebuild-queue/{id}/cancel", post(post_rebuild_queue_cancel)) .route("/dashboard/stream", get(dashboard_stream)) .route("/dashboard/history", get(dashboard_history)) // Anything not matched by the dynamic routes above falls @@ -1459,6 +1460,28 @@ async fn post_schedule_fire_now( } } +/// `POST /api/rebuild-queue/{id}/cancel` — drop a `Queued` entry +/// from the rebuild queue (#447). Refuses `Running` / terminal +/// entries: an in-flight rebuild owns the agent's nix store + +/// nixos-container update lock and can't be safely interrupted +/// from the queue side. Always returns 200; the body is +/// `{"cancelled": true}` on a successful flip from Queued → +/// Cancelled, `{"cancelled": false}` when the row was Running / +/// terminal / gone. On success a fresh `RebuildQueueChanged` +/// snapshot fires so the row's state flip surfaces live. +async fn post_rebuild_queue_cancel( + State(state): State, + AxumPath(id): AxumPath, +) -> Response { + let cancelled = state.coord.rebuild_queue.cancel(id); + if cancelled { + state.coord.emit_rebuild_queue_snapshot(); + axum::Json(serde_json::json!({"cancelled": true})).into_response() + } else { + axum::Json(serde_json::json!({"cancelled": false})).into_response() + } +} + #[derive(serde::Deserialize, Default)] struct CancelScheduleForm { /// `None` / absent / empty array → cancel whole schedule.