dashboard: show and edit per-agent resource limits in core LOAD tab
Adds CPU/memory cap columns and an inline edit form to the container-load
table in /core.html, backed by a new POST /api/resource-limits/{name}
dashboard endpoint.
## Backend (hive-c0re)
lifecycle_ops.rs — new post_resource_limits handler:
- Parses ResourceLimitsForm { cpu_quota, memory_max } (both optional; empty
string = clear override, fall back to hive-wide default).
- Validates each non-empty value via resource_limits::validate_cpu_quota /
validate_memory_max — returns 422 UNPROCESSABLE_ENTITY with a human-
readable message on invalid input so the dashboard can surface it inline.
- Calls meta::commit_resource_limits (staged git write under META_LOCK, same
as hivectl set-limits).
- Re-applies the drop-in immediately via lifecycle::write_dropins so the new
ceilings take effect on the next container start without waiting for a
rebuild.
- Triggers rescan_containers_and_emit so ContainerView.cpu_quota/memory_max
update via SSE without waiting for the next periodic sweep.
dashboard/mod.rs — registers the route:
POST /api/resource-limits/{name}
## Frontend (core.js + system-sections.css)
core.js:
- containersState derived from /api/state snapshot alongside tombstonesState
— supplies configured cpu_quota/memory_max to the LOAD table.
- lastLoadRows stash lets SSE-triggered re-renders call renderContainerLoad
without waiting for the next 5s poll.
- renderContainerLoad: adds cpu cap / mem cap columns (muted; tooltip
'configured ceiling — takes effect on next start') sourced from
ContainerView, plus a per-row S3T toggle button that expands an inline
edit form with cpu_quota / memory_max text inputs and a S4V3 button.
The edit form shows a restart hint, surfaces validation errors inline, and
collapses on success.
- container_state_changed SSE handler: updates containersState in place and
re-renders the LOAD table so the cap columns flip immediately after a save.
system-sections.css:
- CSS for the new cap columns (.cload-cap-th, .cload-cap) and inline edit
form (.cload-edit-row, .cload-edit-form, .cload-edit-label, etc.).
- Remove dead .rqe-step rule (step sub-step label retired from the wire in
'job_queue: retire the now-off-wire step sub-step label').
This commit is contained in:
parent
6973610d39
commit
6c9bf2012f
4 changed files with 263 additions and 7 deletions
|
|
@ -187,6 +187,68 @@ pub(super) async fn post_resume(
|
|||
(StatusCode::OK, "ok").into_response()
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
pub(super) struct ResourceLimitsForm {
|
||||
#[serde(default)]
|
||||
cpu_quota: String,
|
||||
#[serde(default)]
|
||||
memory_max: String,
|
||||
}
|
||||
|
||||
/// `POST /api/resource-limits/{name}` — write per-agent CPU/memory limit
|
||||
/// overrides for `name`.
|
||||
///
|
||||
/// An empty `cpu_quota` or `memory_max` field clears that field's override,
|
||||
/// falling back to the hive-wide default. Both empty together removes the
|
||||
/// agent's entry entirely. The new drop-in is written immediately — the
|
||||
/// 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.
|
||||
pub(super) async fn post_resource_limits(
|
||||
State(state): State<AppState>,
|
||||
AxumPath(name): AxumPath<String>,
|
||||
Form(form): Form<ResourceLimitsForm>,
|
||||
) -> Response {
|
||||
let logical = strip_container_prefix(&name);
|
||||
if let Some(reject) = guard_agent_name(&state, &logical).await {
|
||||
return reject;
|
||||
}
|
||||
let ident = match Ident::parse(&logical) {
|
||||
Ok(i) => i,
|
||||
Err(e) => return (StatusCode::BAD_REQUEST, format!("bad agent name: {e}")).into_response(),
|
||||
};
|
||||
let cpu_quota = if form.cpu_quota.is_empty() { None } else { Some(form.cpu_quota.as_str()) };
|
||||
let memory_max = if form.memory_max.is_empty() { None } else { Some(form.memory_max.as_str()) };
|
||||
if let Some(v) = cpu_quota
|
||||
&& let Err(e) = crate::resource_limits::validate_cpu_quota(v)
|
||||
{
|
||||
return (StatusCode::UNPROCESSABLE_ENTITY, e).into_response();
|
||||
}
|
||||
if let Some(v) = memory_max
|
||||
&& let Err(e) = crate::resource_limits::validate_memory_max(v)
|
||||
{
|
||||
return (StatusCode::UNPROCESSABLE_ENTITY, e).into_response();
|
||||
}
|
||||
let limits = crate::resource_limits::AgentLimits {
|
||||
cpu_quota: cpu_quota.map(str::to_owned),
|
||||
memory_max: memory_max.map(str::to_owned),
|
||||
};
|
||||
if let Err(e) = crate::meta::commit_resource_limits(ident.as_str(), &limits).await {
|
||||
return error_response(&format!("set limits {logical}: {e:#}"));
|
||||
}
|
||||
let agent_dir = crate::paths::agent_runtime_dir(ident.as_str());
|
||||
let hive = state.coord.hive_env();
|
||||
let paths = crate::coordinator::Coordinator::agent_paths(ident.as_str(), agent_dir);
|
||||
if let Err(e) = crate::lifecycle::write_dropins(ident.as_str(), &hive, &paths).await {
|
||||
return error_response(&format!("write_dropins {logical}: {e:#}"));
|
||||
}
|
||||
state.coord.rescan_containers_and_emit().await;
|
||||
(StatusCode::OK, "ok").into_response()
|
||||
}
|
||||
|
||||
pub(super) async fn post_update_all(State(state): State<AppState>) -> Response {
|
||||
let containers = lifecycle::list().await.unwrap_or_default();
|
||||
for container in containers {
|
||||
|
|
|
|||
|
|
@ -195,6 +195,10 @@ pub async fn serve(
|
|||
.route("/api/rebuild/{name}", post(lifecycle_ops::post_rebuild))
|
||||
.route("/api/pause/{name}", post(lifecycle_ops::post_pause))
|
||||
.route("/api/resume/{name}", post(lifecycle_ops::post_resume))
|
||||
.route(
|
||||
"/api/resource-limits/{name}",
|
||||
post(lifecycle_ops::post_resource_limits),
|
||||
)
|
||||
.route("/api/update-all", post(lifecycle_ops::post_update_all))
|
||||
.route(
|
||||
"/api/infra-container/{name}/{action}",
|
||||
|
|
|
|||
Loading…
Reference in a new issue