dashboard: msgflow uses shared terminal + backfill via /messages/history
This commit is contained in:
parent
f27108aecf
commit
8c186d4fb7
5 changed files with 116 additions and 72 deletions
|
|
@ -129,6 +129,36 @@ impl Broker {
|
|||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Latest `limit` messages across every recipient, newest-first.
|
||||
/// Backs the dashboard's message-flow backfill so a reload doesn't
|
||||
/// blank the operator's view of recent traffic. Returns each row as
|
||||
/// a [`MessageEvent::Sent`] so the dashboard's live renderer (which
|
||||
/// already speaks `MessageEvent`) can replay history through the
|
||||
/// same code path. We don't synthesise `Delivered` events here —
|
||||
/// the recv-side acks live in a different table column and would
|
||||
/// double-render on backfill; the live stream picks them up
|
||||
/// immediately on the first new `recv`.
|
||||
pub fn recent_all(&self, limit: u64) -> Result<Vec<MessageEvent>> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
let limit_i = i64::try_from(limit.min(i64::MAX as u64)).unwrap_or(i64::MAX);
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT sender, recipient, body, sent_at
|
||||
FROM messages
|
||||
ORDER BY id DESC
|
||||
LIMIT ?1",
|
||||
)?;
|
||||
let rows = stmt.query_map(params![limit_i], |row| {
|
||||
Ok(MessageEvent::Sent {
|
||||
from: row.get(0)?,
|
||||
to: row.get(1)?,
|
||||
body: row.get(2)?,
|
||||
at: row.get(3)?,
|
||||
})
|
||||
})?;
|
||||
rows.collect::<rusqlite::Result<Vec<_>>>()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Number of undelivered messages addressed to `recipient`. Non-mutating
|
||||
/// — used by the harness to surface "N unread" in tool-result status
|
||||
/// lines without popping the queue.
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ pub async fn serve(port: u16, coord: Arc<Coordinator>) -> Result<()> {
|
|||
.route("/op-send", post(post_op_send))
|
||||
.route("/meta-update", post(post_meta_update))
|
||||
.route("/messages/stream", get(messages_stream))
|
||||
.route("/messages/history", get(messages_history))
|
||||
.route("/static/hive-fr0nt.js", get(serve_shared_js))
|
||||
.with_state(AppState { coord });
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
||||
let listener = bind_with_retry(addr).await?;
|
||||
|
|
@ -133,6 +135,13 @@ async fn serve_app_js() -> impl IntoResponse {
|
|||
)
|
||||
}
|
||||
|
||||
async fn serve_shared_js() -> impl IntoResponse {
|
||||
(
|
||||
[("content-type", "application/javascript")],
|
||||
hive_fr0nt::TERMINAL_JS,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct StateSnapshot {
|
||||
hostname: String,
|
||||
|
|
@ -699,6 +708,23 @@ fn dir_size_bytes(root: &Path) -> u64 {
|
|||
total
|
||||
}
|
||||
|
||||
async fn messages_history(State(state): State<AppState>) -> Response {
|
||||
// Backfill source for the dashboard message-flow terminal. Returns
|
||||
// up to ~200 historical broker messages as `MessageEvent::Sent` JSON
|
||||
// — same shape as the live `/messages/stream`, so the renderer
|
||||
// doesn't branch on history vs. live.
|
||||
const HISTORY_LIMIT: u64 = 200;
|
||||
match state.coord.broker.recent_all(HISTORY_LIMIT) {
|
||||
Ok(mut events) => {
|
||||
// recent_all returns newest-first; reverse so the replay
|
||||
// builds chronologically (matches the agent /events/history).
|
||||
events.reverse();
|
||||
axum::Json(events).into_response()
|
||||
}
|
||||
Err(e) => error_response(&format!("messages/history failed: {e:#}")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn messages_stream(
|
||||
State(state): State<AppState>,
|
||||
) -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue