diff --git a/frontend/packages/dashboard/src/core.js b/frontend/packages/dashboard/src/core.js
index 7723a754..75059976 100644
--- a/frontend/packages/dashboard/src/core.js
+++ b/frontend/packages/dashboard/src/core.js
@@ -16,12 +16,8 @@ import { createTabStrip } from '@hive/shared/tabs.js';
// ─── derived state (own copies; this bundle has its own runtime) ──────────
let tombstonesState = [];
-// ContainerView list from the last /api/state snapshot — used by renderContainerLoad
-// to splice in the configured cpu_quota/memory_max limits alongside live cgroup data.
-let containersState = [];
function syncFromSnapshot(s) {
tombstonesState = (s.tombstones || []).slice();
- containersState = (s.containers || []).slice();
}
// ─── kept state (tombstones) ──────────────────────────────────────────────
@@ -173,12 +169,7 @@ function cloadMeter(pct) {
return m;
}
-// Stash the last load rows so re-renders triggered by SSE state updates can
-// call renderContainerLoad(lastLoadRows) without waiting for the next poll.
-let lastLoadRows = [];
-
function renderContainerLoad(rows) {
- lastLoadRows = rows;
const root = $('container-load-section');
if (!root) return;
if (!Array.isArray(rows) || rows.length === 0) {
@@ -187,25 +178,12 @@ function renderContainerLoad(rows) {
p.textContent = 'no running agent containers'; root.append(p);
return;
}
-
- // Build a name→ContainerView lookup so we can splice in the configured
- // cpu_quota / memory_max limits (set in meta/resource-limits.json and
- // resolved by the server) next to the live cgroup readings.
- const cvByName = new Map(containersState.map((c) => [c.name, c]));
-
const table = document.createElement('table');
table.className = 'hive-stats-table';
- // "cpu cap" / "mem cap" show the configured ceilings from ContainerView
- // (effective drop-in values, take effect on next start/restart).
- // "limit" remains the live cgroup memory ceiling from /api/container-resources.
table.innerHTML = '| agent | cpu | memory | '
- + 'peak | limit | disk | '
- + 'cpu cap | '
- + 'mem cap | '
- + ' |
';
+ + '
peak | limit | disk | ';
const tb = document.createElement('tbody');
for (const r of rows) {
- const cv = cvByName.get(r.name);
const tr = document.createElement('tr');
const name = document.createElement('td'); name.textContent = r.name; tr.append(name);
const cpu = document.createElement('td'); cpu.className = 'num';
@@ -232,113 +210,7 @@ function renderContainerLoad(rows) {
disk.title = 'state dir + container writable rootfs (shared nix store excluded); sampled every few minutes';
disk.textContent = (r.disk_bytes != null) ? cloadFmtBytes(Number(r.disk_bytes)) : '—';
tr.append(disk);
-
- // Configured CPU / memory caps from ContainerView (resolved effective
- // values: per-agent override when set, hive-wide default otherwise).
- const cpuCap = document.createElement('td'); cpuCap.className = 'num cload-cap';
- cpuCap.title = 'configured ceiling — takes effect on next start';
- cpuCap.textContent = cv?.cpu_quota || '—';
- tr.append(cpuCap);
- const memCap = document.createElement('td'); memCap.className = 'num cload-cap';
- memCap.title = 'configured ceiling — takes effect on next start';
- memCap.textContent = cv?.memory_max || '—';
- tr.append(memCap);
-
- // S3T button toggles the inline edit row for this agent.
- const actTd = document.createElement('td');
- const setBtn = document.createElement('button');
- setBtn.type = 'button';
- setBtn.className = 'btn btn-sm cload-set-btn';
- setBtn.textContent = 'S3T';
- setBtn.title = 'set CPU / memory cap for ' + r.name;
- tr.append(actTd);
- actTd.append(setBtn);
tb.append(tr);
-
- // Inline edit row (hidden by default, toggled by the S3T button).
- const editRow = document.createElement('tr');
- editRow.className = 'cload-edit-row';
- editRow.hidden = true;
- const editTd = document.createElement('td');
- editTd.colSpan = 9;
- editTd.className = 'cload-edit-cell';
-
- const editForm = document.createElement('form');
- editForm.className = 'cload-edit-form';
- editForm.addEventListener('submit', async (e) => {
- e.preventDefault();
- const cpuInput = editForm.querySelector('.cload-cpu-input');
- const memInput = editForm.querySelector('.cload-mem-input');
- const errSpan = editForm.querySelector('.cload-edit-err');
- const submitBtn = editForm.querySelector('[type="submit"]');
- errSpan.hidden = true;
- submitBtn.disabled = true;
- try {
- const body = new URLSearchParams({
- cpu_quota: cpuInput.value.trim(),
- memory_max: memInput.value.trim(),
- });
- const resp = await fetch(
- '/api/resource-limits/' + encodeURIComponent(r.name),
- { method: 'POST', body },
- );
- if (!resp.ok) {
- errSpan.textContent = await resp.text().catch(() => 'error ' + resp.status);
- errSpan.hidden = false;
- return;
- }
- // Success: collapse the edit row. The SSE rescan will push updated
- // ContainerView data (cpu_quota/memory_max) to containersState,
- // triggering a re-render of the cap columns via refreshContainerLoad.
- editRow.hidden = true;
- setBtn.textContent = 'S3T';
- } catch (err) {
- errSpan.textContent = String(err);
- errSpan.hidden = false;
- } finally {
- submitBtn.disabled = false;
- }
- });
-
- const cpuLabel = document.createElement('label');
- cpuLabel.className = 'cload-edit-label';
- cpuLabel.textContent = 'cpu quota';
- const cpuInput = document.createElement('input');
- cpuInput.type = 'text'; cpuInput.className = 'cload-cpu-input';
- cpuInput.placeholder = cv ? cv.cpu_quota : 'e.g. 200%';
- cpuInput.title = 'systemd CPUQuota= value (e.g. "400%"). empty = use hive default';
- cpuLabel.append(cpuInput);
-
- const memLabel = document.createElement('label');
- memLabel.className = 'cload-edit-label';
- memLabel.textContent = 'mem max';
- const memInput = document.createElement('input');
- memInput.type = 'text'; memInput.className = 'cload-mem-input';
- memInput.placeholder = cv ? cv.memory_max : 'e.g. 8G';
- memInput.title = 'systemd MemoryMax= value (e.g. "8G", "50%", "infinity"). empty = use hive default';
- memLabel.append(memInput);
-
- const submitBtn = document.createElement('button');
- submitBtn.type = 'submit'; submitBtn.className = 'btn btn-restart cload-save-btn';
- submitBtn.textContent = 'S4V3';
-
- const hintSpan = document.createElement('span');
- hintSpan.className = 'meta cload-edit-hint';
- hintSpan.textContent = '↺ restart to apply to a running container';
-
- const errSpan = document.createElement('span');
- errSpan.className = 'cload-edit-err'; errSpan.hidden = true;
-
- editForm.append(cpuLabel, memLabel, submitBtn, hintSpan, errSpan);
- editTd.append(editForm);
- editRow.append(editTd);
- tb.append(editRow);
-
- setBtn.addEventListener('click', () => {
- const open = !editRow.hidden;
- editRow.hidden = open;
- setBtn.textContent = open ? 'S3T' : '✕';
- });
}
table.append(tb);
root.replaceChildren(table);
@@ -440,19 +312,6 @@ const SSE_HANDLERS = {
tombstonesState = (ev.tombstones || []).slice();
renderTombstones({ tombstones: tombstonesState });
},
- // When a container's state changes (e.g. after resource-limits update
- // triggers rescan_containers_and_emit), update containersState in place
- // so the cap columns in the LOAD table reflect the new configured values.
- container_state_changed(ev) {
- if (!ev.container) return;
- const idx = containersState.findIndex((c) => c.name === ev.container.name);
- if (idx >= 0) {
- containersState[idx] = ev.container;
- } else {
- containersState.push(ev.container);
- }
- if (lastLoadRows.length) renderContainerLoad(lastLoadRows);
- },
// Refresh the stale-perms sub-section when perm data changes (a ghost
// was cleared, or perms were saved for an agent whose name collides).
capabilities_changed(_ev) {
diff --git a/frontend/packages/dashboard/src/system-sections.css b/frontend/packages/dashboard/src/system-sections.css
index 1f848ac5..07aa6338 100644
--- a/frontend/packages/dashboard/src/system-sections.css
+++ b/frontend/packages/dashboard/src/system-sections.css
@@ -213,6 +213,12 @@
.rqe-node-arrow { color: var(--muted); }
.rqe-node-log { margin-left: 0.1em; text-decoration: none; }
+.rqe-step {
+ flex-basis: 100%;
+ margin: 0.1em 0 0 1.8em;
+ color: var(--cyan);
+ font-size: 0.85em;
+}
.rqe-error {
flex-basis: 100%;
margin: 0.3em 0 0;
@@ -349,61 +355,6 @@
background: var(--red);
}
-/* Configured-cap columns: muted header so they read apart from live metrics.
- The S3T button sits in its own column; keep it compact. */
-.cload-cap-th {
- color: var(--muted);
-}
-.cload-cap {
- color: var(--muted);
-}
-.cload-set-btn {
- padding: 0.1em 0.5em;
- font-size: 0.8em;
-}
-
-/* Inline edit row — spans all columns, contains a mini-form. */
-.cload-edit-row td {
- border-top: none;
- padding-top: 0;
-}
-.cload-edit-cell {
- padding: 0.2em 0.4em 0.6em 1.2em;
-}
-.cload-edit-form {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 0.5em;
-}
-.cload-edit-label {
- display: flex;
- align-items: center;
- gap: 0.3em;
- font-size: 0.85em;
- color: var(--muted);
-}
-.cload-cpu-input,
-.cload-mem-input {
- width: 7em;
- font-size: 0.9em;
-}
-.cload-save-btn {
- padding: 0.15em 0.6em;
- font-size: 0.85em;
-}
-.cload-edit-hint {
- font-size: 0.8em;
-}
-.cload-edit-err {
- color: var(--red);
- font-size: 0.85em;
-}
-
-/* Remove the now-dead `.rqe-step` rule (the `step` sub-step label was
- retired from the wire in `feat(job_queue): retire the now-off-wire step
- sub-step label` — the rule has had no JS referents since then). */
-
/* ─── K3PT ST4T3 › stale permission entries sub-section ────────────────────
Agents with explicit capability / tool-group entries in the JSON but no
live container (renamed or manually-deleted agents). Lazy-loaded on tab
diff --git a/hive-c0re/src/dashboard/lifecycle_ops.rs b/hive-c0re/src/dashboard/lifecycle_ops.rs
index e05dd608..70d4acbf 100644
--- a/hive-c0re/src/dashboard/lifecycle_ops.rs
+++ b/hive-c0re/src/dashboard/lifecycle_ops.rs
@@ -187,76 +187,6 @@ 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,
- AxumPath(name): AxumPath,
- Form(form): Form,
-) -> 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) -> Response {
let containers = lifecycle::list().await.unwrap_or_default();
for container in containers {
diff --git a/hive-c0re/src/dashboard/mod.rs b/hive-c0re/src/dashboard/mod.rs
index 1470d7fd..cdb35f55 100644
--- a/hive-c0re/src/dashboard/mod.rs
+++ b/hive-c0re/src/dashboard/mod.rs
@@ -195,10 +195,6 @@ 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}",