cleanup(#1014): remove is_manager from ContainerView, add manager_name to StateSnapshot

This commit is contained in:
damocles 2026-06-03 16:24:13 +02:00 committed by mara
commit 6feab54882
3 changed files with 18 additions and 14 deletions

View file

@ -169,6 +169,13 @@ window.marked = marked;
if (containersState.delete(ev.name)) renderContainersFromState(); if (containersState.delete(ev.name)) renderContainersFromState();
} }
// Logical name of the manager agent. Populated from `s.manager_name`
// on snapshot load so chip lists don't hardcode the manager name.
let managerNameState = 'ruth';
function syncManagerNameFromSnapshot(s) {
if (s.manager_name) managerNameState = s.manager_name;
}
// Derived tombstones + meta_inputs. Both are emitted as full // Derived tombstones + meta_inputs. Both are emitted as full
// snapshots (not diffs) — the lists are tiny and recomputing // snapshots (not diffs) — the lists are tiny and recomputing
// avoids ordering races between a same-tick destroy + purge. // avoids ordering races between a same-tick destroy + purge.
@ -2586,14 +2593,12 @@ window.marked = marked;
// one place. Returns the wrapping `<label class="schedule-field">` // one place. Returns the wrapping `<label class="schedule-field">`
// ready to append to the form. // ready to append to the form.
function buildTargetChips({ idPrefix, fieldName, checked, extraNames = [] }) { function buildTargetChips({ idPrefix, fieldName, checked, extraNames = [] }) {
// Derive the manager's actual name from live state rather than // Surface manager first so it always appears before non-manager agents.
// hardcoding it — surface it first so it always appears before agents. const managerName = containersState.has(managerNameState) ? managerNameState : null;
const managerContainer = Array.from(containersState.values()).find((c) => c.is_manager);
const managerName = managerContainer?.name;
const candidates = ['operator']; const candidates = ['operator'];
if (managerName) candidates.push(managerName); if (managerName) candidates.push(managerName);
const containerNames = Array.from(containersState.values()) const containerNames = Array.from(containersState.values())
.filter((c) => !c.is_manager) .filter((c) => c.name !== managerNameState)
.map((c) => c.name) .map((c) => c.name)
.filter((n) => n !== 'operator') .filter((n) => n !== 'operator')
.sort(); .sort();
@ -2898,10 +2903,9 @@ window.marked = marked;
const out = []; const out = [];
const push = (n) => { if (!seen.has(n)) { seen.add(n); out.push(n); } }; const push = (n) => { if (!seen.has(n)) { seen.add(n); out.push(n); } };
push('operator'); push('operator');
const managerContainer = Array.from(containersState.values()).find((c) => c.is_manager); if (containersState.has(managerNameState)) push(managerNameState);
if (managerContainer) push(managerContainer.name);
const containerNames = Array.from(containersState.values()) const containerNames = Array.from(containersState.values())
.filter((c) => !c.is_manager) .filter((c) => c.name !== managerNameState)
.map((c) => c.name) .map((c) => c.name)
.filter((n) => n !== 'operator') .filter((n) => n !== 'operator')
.sort(); .sort();
@ -3470,6 +3474,7 @@ window.marked = marked;
// `transientsState` + `containersState`, not from `s.*`). // `transientsState` + `containersState`, not from `s.*`).
syncTransientsFromSnapshot(s); syncTransientsFromSnapshot(s);
syncContainersFromSnapshot(s); syncContainersFromSnapshot(s);
syncManagerNameFromSnapshot(s);
syncTombstonesFromSnapshot(s); syncTombstonesFromSnapshot(s);
syncMetaInputsFromSnapshot(s); syncMetaInputsFromSnapshot(s);
syncRebuildQueueFromSnapshot(s); syncRebuildQueueFromSnapshot(s);

View file

@ -12,7 +12,7 @@ use rusqlite::Connection;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::coordinator::Coordinator; use crate::coordinator::Coordinator;
use crate::lifecycle::{self, AGENT_PREFIX, MANAGER_NAME}; use crate::lifecycle::{self, AGENT_PREFIX};
/// An agent-declared extra navigation link surfaced on the dashboard card. /// An agent-declared extra navigation link surfaced on the dashboard card.
/// Written by the `hive-dashboard-links` NixOS oneshot into /// Written by the `hive-dashboard-links` NixOS oneshot into
@ -34,10 +34,6 @@ pub struct ContainerView {
/// not serialized to API responses since the dashboard no longer displays it. /// not serialized to API responses since the dashboard no longer displays it.
#[serde(skip)] #[serde(skip)]
pub container: String, pub container: String,
/// True when this is the manager agent. Informational — not used
/// to gate any server-side actions. Computed from `name ==
/// MANAGER_NAME` so it doesn't add new state.
pub is_manager: bool,
pub port: u16, pub port: u16,
pub running: bool, pub running: bool,
pub needs_update: bool, pub needs_update: bool,
@ -168,7 +164,6 @@ pub async fn build_all(coord: &Coordinator) -> Vec<ContainerView> {
(false, None, None, false, None, None) (false, None, None, false, None, None)
}; };
out.push(ContainerView { out.push(ContainerView {
is_manager: logical == MANAGER_NAME,
port: lifecycle::agent_web_port(&logical), port: lifecycle::agent_web_port(&logical),
running, running,
container: c.clone(), container: c.clone(),

View file

@ -173,6 +173,9 @@ struct StateSnapshot {
/// "apply everything you've buffered". /// "apply everything you've buffered".
seq: u64, seq: u64,
hostname: String, hostname: String,
/// The logical name of the manager agent. Lets the dashboard identify
/// the manager container without a separate `is_manager` bool on each row.
manager_name: String,
manager_port: u16, manager_port: u16,
any_stale: bool, any_stale: bool,
containers: Vec<ContainerView>, containers: Vec<ContainerView>,
@ -446,6 +449,7 @@ async fn api_state(headers: HeaderMap, State(state): State<AppState>) -> axum::J
axum::Json(StateSnapshot { axum::Json(StateSnapshot {
seq, seq,
hostname, hostname,
manager_name: MANAGER_NAME.to_owned(),
manager_port: lifecycle::agent_web_port(MANAGER_NAME), manager_port: lifecycle::agent_web_port(MANAGER_NAME),
any_stale, any_stale,
containers, containers,