dashboard events: unified coord channel + /dashboard/{stream,history}; broker forwards
This commit is contained in:
parent
d48cee7c2d
commit
a478792914
6 changed files with 205 additions and 66 deletions
|
|
@ -4,15 +4,23 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use crate::agent_server::{self, AgentSocket};
|
||||
use crate::approvals::Approvals;
|
||||
use crate::broker::Broker;
|
||||
use crate::dashboard_events::DashboardEvent;
|
||||
use crate::operator_questions::OperatorQuestions;
|
||||
|
||||
/// Capacity of the dashboard event channel. Slow browser subscribers
|
||||
/// (idle tab, throttled connection) drop frames past this — that's
|
||||
/// fine, the seq dedupe makes a reconnect resync safe.
|
||||
const DASHBOARD_CHANNEL: usize = 256;
|
||||
|
||||
const AGENT_RUNTIME_ROOT: &str = "/run/hyperhive/agents";
|
||||
const MANAGER_RUNTIME_ROOT: &str = "/run/hyperhive/manager";
|
||||
/// Manager-editable per-agent config repos. Bind-mounted RW into the manager
|
||||
|
|
@ -47,6 +55,15 @@ pub struct Coordinator {
|
|||
/// Read by the dashboard to render a spinner; cleared when the action
|
||||
/// resolves (success or failure).
|
||||
transient: Mutex<HashMap<String, TransientState>>,
|
||||
/// Unified wire-facing event channel feeding the dashboard SSE
|
||||
/// stream. Carries broker messages (mirrored from `broker.subscribe`
|
||||
/// by the forwarder task in `main.rs`) and dashboard-only mutation
|
||||
/// events (approval added/resolved, question added/answered, etc.).
|
||||
/// Snapshot endpoints capture `event_seq` before reading state so
|
||||
/// the client can dedupe its buffered live traffic against the
|
||||
/// snapshot.
|
||||
dashboard_events: broadcast::Sender<DashboardEvent>,
|
||||
event_seq: AtomicU64,
|
||||
}
|
||||
|
||||
/// Per-agent in-progress state that the dashboard surfaces between approve
|
||||
|
|
@ -98,6 +115,7 @@ impl Coordinator {
|
|||
let broker = Broker::open(db_path).context("open broker")?;
|
||||
let approvals = Approvals::open(db_path).context("open approvals")?;
|
||||
let questions = OperatorQuestions::open(db_path).context("open operator_questions")?;
|
||||
let (dashboard_events, _) = broadcast::channel(DASHBOARD_CHANNEL);
|
||||
Ok(Self {
|
||||
broker: Arc::new(broker),
|
||||
approvals: Arc::new(approvals),
|
||||
|
|
@ -107,9 +125,42 @@ impl Coordinator {
|
|||
operator_pronouns,
|
||||
agents: Mutex::new(HashMap::new()),
|
||||
transient: Mutex::new(HashMap::new()),
|
||||
dashboard_events,
|
||||
event_seq: AtomicU64::new(0),
|
||||
})
|
||||
}
|
||||
|
||||
/// Subscribe to the unified dashboard event channel. Used by the
|
||||
/// `/dashboard/stream` SSE handler and by the broker-to-dashboard
|
||||
/// forwarder task.
|
||||
pub fn dashboard_subscribe(&self) -> broadcast::Receiver<DashboardEvent> {
|
||||
self.dashboard_events.subscribe()
|
||||
}
|
||||
|
||||
/// Stamp the next sequence number. Each emission of a
|
||||
/// `DashboardEvent` should fill its `seq` with `next_seq()` so the
|
||||
/// frame the wire carries is the one the client uses to dedupe.
|
||||
pub fn next_seq(&self) -> u64 {
|
||||
self.event_seq.fetch_add(1, Ordering::SeqCst) + 1
|
||||
}
|
||||
|
||||
/// Current high-water seq. Snapshot endpoints read this *before*
|
||||
/// gathering state so the (snapshot.seq, snapshot) pair satisfies:
|
||||
/// any frame with `seq > snapshot.seq` is post-snapshot. The seq
|
||||
/// captured here may grow during snapshot construction — clients
|
||||
/// may double-apply such events, which renderers must tolerate.
|
||||
pub fn current_seq(&self) -> u64 {
|
||||
self.event_seq.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Broadcast a freshly-built `DashboardEvent` (caller fills `seq`
|
||||
/// via `next_seq()`). Returns silently when there are no
|
||||
/// subscribers — the dashboard channel is best-effort presentation
|
||||
/// plumbing, not a delivery guarantee.
|
||||
pub fn emit_dashboard_event(&self, event: DashboardEvent) {
|
||||
let _ = self.dashboard_events.send(event);
|
||||
}
|
||||
|
||||
pub fn register_agent(self: &Arc<Self>, name: &str) -> Result<PathBuf> {
|
||||
// Idempotent: drop any existing listener so re-registration (e.g. on rebuild,
|
||||
// or after a hive-c0re restart cleared /run/hyperhive) gets a fresh socket.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue