hive-c0re: annotate remaining dashboard routes with utoipa

This commit is contained in:
damocles 2026-07-31 23:00:12 +02:00 committed by mara
commit 582ebe5eee
21 changed files with 738 additions and 45 deletions

View file

@ -14,12 +14,13 @@ use axum::{
response::{IntoResponse, Response},
};
use serde::Deserialize;
use utoipa::{IntoParams, ToSchema};
/// Query params for `post_kill` / `post_restart`. `?graceful=1` routes to
/// the graceful-stop/-restart orchestration (quiesce the harness, flush
/// `/state`, then container stop/restart) instead of an immediate hard
/// action. Defaults false → today's hard kill/restart.
#[derive(Deserialize)]
#[derive(Deserialize, IntoParams)]
pub(super) struct GracefulParams {
#[serde(default)]
graceful: bool,
@ -29,6 +30,18 @@ use super::{AppState, Ident, error_response, guard_agent_name, strip_container_p
use crate::job_queue::{Source, submit};
use crate::{actions, lifecycle};
/// `POST /api/rebuild/{name}` — queue a rebuild DAG for `name`.
#[utoipa::path(
post,
path = "/api/rebuild/{name}",
params(("name" = String, Path, description = "agent name")),
responses(
(status = 200, description = "rebuild queued", body = String),
(status = 400, description = "bad agent name"),
(status = 404, description = "no such agent"),
),
tag = "lifecycle_ops"
)]
pub(super) async fn post_rebuild(
State(state): State<AppState>,
AxumPath(name): AxumPath<String>,
@ -46,6 +59,22 @@ pub(super) async fn post_rebuild(
(StatusCode::OK, "ok").into_response()
}
/// `POST /api/kill/{name}?graceful=1` — stop `name`, hard by default or
/// gracefully (quiesce → drain → stop) when `graceful=1`.
#[utoipa::path(
post,
path = "/api/kill/{name}",
params(
("name" = String, Path, description = "agent name"),
GracefulParams,
),
responses(
(status = 200, description = "stop queued/performed", body = String),
(status = 400, description = "bad agent name"),
(status = 404, description = "no such agent"),
),
tag = "lifecycle_ops"
)]
pub(super) async fn post_kill(
State(state): State<AppState>,
AxumPath(name): AxumPath<String>,
@ -90,6 +119,22 @@ pub(super) async fn post_kill(
(StatusCode::OK, "ok").into_response()
}
/// `POST /api/restart/{name}?graceful=1` — restart `name`, hard by default
/// or gracefully (quiesce → drain → restart) when `graceful=1`.
#[utoipa::path(
post,
path = "/api/restart/{name}",
params(
("name" = String, Path, description = "agent name"),
GracefulParams,
),
responses(
(status = 200, description = "restart queued/performed", body = String),
(status = 400, description = "bad agent name"),
(status = 404, description = "no such agent"),
),
tag = "lifecycle_ops"
)]
pub(super) async fn post_restart(
State(state): State<AppState>,
AxumPath(name): AxumPath<String>,
@ -119,6 +164,18 @@ pub(super) async fn post_restart(
(StatusCode::OK, "ok").into_response()
}
/// `POST /api/start/{name}` — start `name`.
#[utoipa::path(
post,
path = "/api/start/{name}",
params(("name" = String, Path, description = "agent name")),
responses(
(status = 200, description = "start queued", body = String),
(status = 400, description = "bad agent name"),
(status = 404, description = "no such agent"),
),
tag = "lifecycle_ops"
)]
pub(super) async fn post_start(
State(state): State<AppState>,
AxumPath(name): AxumPath<String>,
@ -145,6 +202,18 @@ pub(super) async fn post_start(
/// when the container next boots). Triggers an immediate rescan so the
/// `paused` badge flips on the dashboard without waiting for the next
/// periodic sweep.
#[utoipa::path(
post,
path = "/api/pause/{name}",
params(("name" = String, Path, description = "agent name")),
responses(
(status = 200, description = "pause marker written", body = String),
(status = 400, description = "bad agent name"),
(status = 404, description = "no such agent"),
(status = 500, description = "marker write failed"),
),
tag = "lifecycle_ops"
)]
pub(super) async fn post_pause(
State(state): State<AppState>,
AxumPath(name): AxumPath<String>,
@ -168,6 +237,18 @@ pub(super) async fn post_pause(
///
/// The inverse of `post_pause`. Removing a non-existent marker is a no-op
/// (idempotent). Triggers an immediate rescan so the paused badge clears.
#[utoipa::path(
post,
path = "/api/resume/{name}",
params(("name" = String, Path, description = "agent name")),
responses(
(status = 200, description = "pause marker removed", body = String),
(status = 400, description = "bad agent name"),
(status = 404, description = "no such agent"),
(status = 500, description = "marker removal failed"),
),
tag = "lifecycle_ops"
)]
pub(super) async fn post_resume(
State(state): State<AppState>,
AxumPath(name): AxumPath<String>,
@ -190,7 +271,7 @@ pub(super) async fn post_resume(
/// Form fields for `post_resource_limits`. Both fields are optional strings;
/// an empty value clears the per-agent override for that field, falling back
/// to the hive-wide default.
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, ToSchema)]
pub(super) struct ResourceLimitsForm {
#[serde(default)]
cpu_quota: String,
@ -207,6 +288,20 @@ pub(super) struct ResourceLimitsForm {
/// limits take effect on the next container start or restart. Triggers an
/// immediate rescan so `ContainerView.cpu_quota`/`memory_max` update on
/// the dashboard via SSE without waiting for the next periodic sweep.
#[utoipa::path(
post,
path = "/api/resource-limits/{name}",
params(("name" = String, Path, description = "agent name")),
request_body(content = ResourceLimitsForm, content_type = "application/x-www-form-urlencoded"),
responses(
(status = 200, description = "limits written", body = String),
(status = 400, description = "bad agent name"),
(status = 404, description = "no such agent"),
(status = 422, description = "invalid cpu_quota/memory_max value"),
(status = 500, description = "commit or drop-in write failed"),
),
tag = "lifecycle_ops"
)]
pub(super) async fn post_resource_limits(
State(state): State<AppState>,
AxumPath(name): AxumPath<String>,
@ -257,6 +352,14 @@ pub(super) async fn post_resource_limits(
(StatusCode::OK, "ok").into_response()
}
/// `POST /api/update-all` — queue a rebuild DAG for every live agent
/// container.
#[utoipa::path(
post,
path = "/api/update-all",
responses((status = 200, description = "rebuilds queued", body = String)),
tag = "lifecycle_ops"
)]
pub(super) async fn post_update_all(State(state): State<AppState>) -> Response {
let containers = lifecycle::list().await.unwrap_or_default();
for container in containers {
@ -276,12 +379,28 @@ pub(super) async fn post_update_all(State(state): State<AppState>) -> Response {
(StatusCode::OK, "ok").into_response()
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, ToSchema)]
pub(super) struct DestroyForm {
#[serde(default)]
purge: Option<String>,
}
/// `POST /api/destroy/{name}` — destroy `name`'s container. Form field
/// `purge` (any non-empty value, e.g. `"on"`) also wipes the retained
/// state dir instead of leaving a tombstone.
#[utoipa::path(
post,
path = "/api/destroy/{name}",
params(("name" = String, Path, description = "agent name")),
request_body(content = DestroyForm, content_type = "application/x-www-form-urlencoded"),
responses(
(status = 200, description = "destroyed", body = String),
(status = 400, description = "bad agent name"),
(status = 404, description = "no such agent"),
(status = 500, description = "destroy failed"),
),
tag = "lifecycle_ops"
)]
pub(super) async fn post_destroy(
State(state): State<AppState>,
AxumPath(name): AxumPath<String>,