phase 6: container events + drop the 5s /api/state poll

new DashboardEvent::ContainerStateChanged + ContainerRemoved
close the last refetch loop on the dashboard. Coordinator's
rescan_containers_and_emit diffs a fresh container_view::build_all
against a cached last_containers map and fires per-row events.
called from actions::approve (post-spawn), actions::destroy,
the lifecycle_action wrapper, auto_update::rebuild_agent, and
the existing 10s crash_watch poll.

ContainerView extracted to its own module so coordinator and
dashboard can both build it. dashboard endpoints flip to 200;
container-lifecycle forms carry data-no-refresh. client drops
the periodic poll entirely — initial cold load + SSE for
everything afterwards. pending overlay reads from the existing
transientsState since the new event payload doesn't carry it.

PURG3 + meta-update keep the post-submit refetch since
tombstones + meta_inputs aren't event-derived yet; tracked in
TODO.md.
This commit is contained in:
müde 2026-05-17 22:01:15 +02:00
parent f153639cb4
commit e7ce35c503
11 changed files with 396 additions and 195 deletions

View file

@ -25,6 +25,8 @@
use serde::Serialize;
use crate::container_view::ContainerView;
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case", tag = "kind")]
pub enum DashboardEvent {
@ -121,4 +123,24 @@ pub enum DashboardEvent {
/// The matching lifecycle action resolved (success or failure).
/// Clients drop the spinner row.
TransientCleared { seq: u64, name: String },
/// One container row changed — new container appeared (post-spawn
/// finalise), an existing one flipped running/needs_update/sha,
/// etc. Clients upsert by `container.name`. Payload carries the
/// full row so cold-loaded clients and event-driven clients
/// converge on the same render.
///
/// Fired by `Coordinator::rescan_containers_and_emit`, which diffs
/// a fresh `nixos-container list`derived snapshot against the
/// last one cached on the coordinator. Mutation sites (lifecycle
/// endpoints, actions::destroy / approve, crash_watch's poll loop)
/// call the rescan after their work lands.
ContainerStateChanged {
seq: u64,
container: ContainerView,
},
/// A container that was in the previous snapshot is gone. Clients
/// drop the row by name. Fired alongside any
/// `nixos-container destroy` (operator-driven or otherwise) on the
/// next rescan.
ContainerRemoved { seq: u64, name: String },
}