dashboard: transient_set / transient_cleared mutation events + client derived state

This commit is contained in:
müde 2026-05-17 14:20:51 +02:00
parent 1879b2f485
commit 7956e1c627
3 changed files with 103 additions and 7 deletions

View file

@ -105,6 +105,21 @@ pub enum TransientKind {
Destroying,
}
impl TransientKind {
/// Wire/UI label. Matches the strings the dashboard already
/// renders in the transient spinner.
pub fn as_str(self) -> &'static str {
match self {
TransientKind::Spawning => "spawning",
TransientKind::Starting => "starting",
TransientKind::Stopping => "stopping",
TransientKind::Restarting => "restarting",
TransientKind::Rebuilding => "rebuilding",
TransientKind::Destroying => "destroying",
}
}
}
impl Coordinator {
pub fn open(
db_path: &Path,
@ -318,10 +333,30 @@ impl Coordinator {
since: std::time::Instant::now(),
},
);
// Live-update dashboards. `since_unix` is wall-clock so the
// browser can tick "Ns spawning…" without polling. The
// intra-process map keeps using `Instant` for monotonicity.
let since_unix = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.ok()
.and_then(|d| i64::try_from(d.as_secs()).ok())
.unwrap_or(0);
self.emit_dashboard_event(DashboardEvent::TransientSet {
seq: self.next_seq(),
name: name.to_owned(),
transient_kind: kind.as_str(),
since_unix,
});
}
pub fn clear_transient(&self, name: &str) {
self.transient.lock().unwrap().remove(name);
let removed = self.transient.lock().unwrap().remove(name).is_some();
if removed {
self.emit_dashboard_event(DashboardEvent::TransientCleared {
seq: self.next_seq(),
name: name.to_owned(),
});
}
}
/// Set a transient state and return a guard that clears it on drop.

View file

@ -105,4 +105,20 @@ pub enum DashboardEvent {
answered_at: i64,
cancelled: bool,
},
/// A lifecycle action started for an agent (spawn / start / stop
/// / restart / rebuild / destroy). Clients render a spinner next
/// to the row; the client computes "seconds in this state"
/// locally from `since_unix` so a slow rebuild's elapsed time
/// ticks without polling.
TransientSet {
seq: u64,
name: String,
/// Lifecycle kind: `"spawning"` / `"starting"` / `"stopping"` /
/// `"restarting"` / `"rebuilding"` / `"destroying"`.
transient_kind: &'static str,
since_unix: i64,
},
/// The matching lifecycle action resolved (success or failure).
/// Clients drop the spinner row.
TransientCleared { seq: u64, name: String },
}