hivectl/dashboard: add --paused / ?paused=1 to agent start

This commit is contained in:
damocles 2026-08-02 19:52:11 +02:00
commit af3976a76a
4 changed files with 118 additions and 16 deletions

View file

@ -164,26 +164,64 @@ pub(super) async fn post_restart(
(StatusCode::OK, "ok").into_response()
}
/// `POST /api/start/{name}` — start `name`.
/// Query params for `post_start`. `?paused=1` writes the pause marker
/// before (or instead of) starting — see `post_start`'s doc.
#[derive(Deserialize, IntoParams)]
pub(super) struct StartParams {
#[serde(default)]
paused: bool,
}
/// `POST /api/start/{name}?paused=1` — start `name`, optionally paused.
///
/// Plain `?paused=1` mirrors `hivectl agent <name> start --paused`: if
/// `name` is already running, this just writes the pause marker in place
/// and returns without submitting a start DAG (nothing to start). If it's
/// down, the marker is written *before* the start DAG is submitted, so
/// the container comes up paused rather than racing the harness's own
/// pause-gate poll against an already-in-flight start.
#[utoipa::path(
post,
path = "/api/start/{name}",
params(("name" = String, Path, description = "agent name")),
params(
("name" = String, Path, description = "agent name"),
StartParams,
),
responses(
(status = 200, description = "start queued", body = String),
(status = 200, description = "start queued (or paused in place)", body = String),
(status = 400, description = "bad agent name"),
(status = 404, description = "no such agent"),
(status = 500, description = "pause marker write failed"),
),
tag = "lifecycle_ops"
)]
pub(super) async fn post_start(
State(state): State<AppState>,
AxumPath(name): AxumPath<String>,
Query(params): Query<StartParams>,
) -> Response {
let logical = strip_container_prefix(&name);
if let Some(reject) = guard_agent_name(&state, &logical).await {
return reject;
}
if params.paused {
let ident = match Ident::parse(&logical) {
Ok(i) => i,
Err(e) => {
return (StatusCode::BAD_REQUEST, format!("bad agent name: {e}")).into_response();
}
};
let already_running = lifecycle::is_running(&logical).await;
if let Err(e) = crate::coordinator::Coordinator::set_paused(&ident, true).await {
return error_response(&format!("pause {logical}: {e}"));
}
state.coord.rescan_containers_and_emit().await;
if already_running {
// Already up — pausing in place is the whole request, no DAG
// to submit.
return (StatusCode::OK, "ok").into_response();
}
}
submit::start(
&state.coord,
&logical,