Watch
0
0
Fork
You've already forked hyperhive
0

dashboard: remove the flow page and the endpoints only it used

The flow page showed the raw broker message stream and had a compose box.
This removes the page and its bundle, its link in the pages menu, and the
styles only it used.

The history endpoint that backfilled it and the operator-send endpoint
behind the compose box had no other caller, so they go too, along with
the broker query behind the history endpoint. The stream endpoint stays:
the dashboard tabs still subscribe to it.

The docs lose the FL0W section and the references to the two endpoints.
This commit is contained in:
iris 2026-10-21 22:19:13 +02:00 • committed by mara
commit fa131b46a6
24 changed files with 62 additions and 1121 deletions

View file

@ -1,17 +1,14 @@
//! `/api/state` cold-load snapshot plus the dashboard's live read side:
//! the `StateSnapshot` shape and its view builders, the
//! `/api/dashboard/stream` SSE channel, and the `/api/dashboard/history`
//! backfill. SPA shape + SSE channels: docs/web-ui/shape.md.
//! `/api/dashboard/stream` SSE channel. SPA shape + SSE channels:
//! docs/web-ui/shape.md.
use std::convert::Infallible;
use axum::{
extract::State,
http::HeaderMap,
response::{
IntoResponse, Response,
sse::{Event, KeepAlive, Sse},
},
response::sse::{Event, KeepAlive, Sse},
};
use serde::{Deserialize, Serialize};
use tokio_stream::wrappers::BroadcastStream;
@ -20,9 +17,9 @@ use utoipa::IntoParams;
use crate::container_view::ContainerView;
use super::AppState;
use super::meta_inputs::{MetaInputView, read_meta_inputs};
use super::tombstones::{TombstoneView, build_tombstone_views};
use super::{AppState, error_response, scan_validated_paths};
#[allow(clippy::struct_excessive_bools)]
#[derive(Serialize)]
@ -351,105 +348,14 @@ pub(super) async fn jobq_rollup(
axum::Json(state.coord.job_queue.state_rollup())
}
/// Response body for `/api/dashboard/history`. No `ToSchema` — its
/// `events` field wraps [`crate::dashboard_events::DashboardEvent`],
/// which doesn't derive `ToSchema` either (a large enum with many
/// variants; see that type's doc comment for why annotating it is
/// out of scope here). The `responses(...)` doc below spells out the
/// shape in prose instead of a `body = ...` reference.
#[derive(Serialize)]
struct DashboardHistoryBody {
seq: u64,
events: Vec<crate::dashboard_events::DashboardEvent>,
}
#[utoipa::path(
get,
path = "/api/dashboard/history",
responses(
(status = 200, description = "`{ seq, events }` — up to the last 200 \
broker messages as `DashboardEvent::Sent`/`Delivered` JSON, plus \
`seq`: the dashboard channel's high-water mark at fetch time \
(used by clients to dedupe against buffered live SSE frames)"),
),
tag = "state_snapshot"
)]
pub(super) async fn dashboard_history(State(state): State<AppState>) -> Response {
// Backfill source for the dashboard terminal. Returns up to ~200
// historical broker messages (no other event kinds are persisted)
// converted to `DashboardEvent::Sent` JSON so the client can replay
// through the same dispatch path as live frames. Wrapped in
// `{ seq, events }`: the seq is the dashboard channel's high-water
// mark at fetch time. Clients use it to dedupe their buffered live
// SSE traffic (drop anything with `seq <= history_seq`) so a frame
// that lands between SSE-subscribe and history-fetch isn't shown
// twice and isn't lost. Historical rows carry `seq = 0`; the
// boundary seq is what closes the dedupe window.
const HISTORY_LIMIT: u64 = 200;
let seq = state.coord.current_seq();
match state.coord.broker.recent_all(HISTORY_LIMIT) {
Ok(mut messages) => {
messages.reverse();
let events: Vec<crate::dashboard_events::DashboardEvent> = messages
.into_iter()
.map(|m| match m {
crate::broker::MessageEvent::Sent {
id,
from,
to,
body,
at,
in_reply_to,
} => {
let file_refs = scan_validated_paths(&body);
crate::dashboard_events::DashboardEvent::Sent {
seq: 0,
id,
from,
to,
body,
at: hive_sh4re::wire_time::from_secs(at),
in_reply_to,
file_refs,
}
}
crate::broker::MessageEvent::Delivered {
id,
from,
to,
body,
at,
in_reply_to,
} => {
let file_refs = scan_validated_paths(&body);
crate::dashboard_events::DashboardEvent::Delivered {
seq: 0,
id,
from,
to,
body,
at: hive_sh4re::wire_time::from_secs(at),
in_reply_to,
file_refs,
}
}
})
.collect();
axum::Json(DashboardHistoryBody { seq, events }).into_response()
}
Err(e) => error_response(&format!("dashboard/history failed: {e:#}")),
}
}
/// `/dashboard/stream` query string. Today's only field is `kinds`:
/// a comma-separated allow-list of event-`kind` strings.
/// Empty / absent ⇒ no filter (current behaviour, all variants
/// forwarded). Set ⇒ only the named kinds reach the subscriber,
/// non-matches are skipped before the JSON serialise cost.
///
/// Useful for narrow pages (e.g. `flow.js` only cares about `sent`
/// / `delivered` / `container_state_changed` / `container_removed`)
/// that want to drop the dispatch overhead on every unrelated mutation.
/// Useful for narrow pages that only care about a few kinds and want to
/// drop the dispatch overhead on every unrelated mutation.
#[derive(Deserialize, Default, IntoParams)]
pub(super) struct DashboardStreamQuery {
/// Comma-separated event kinds to forward. Each token is
@ -464,8 +370,7 @@ pub(super) struct DashboardStreamQuery {
params(DashboardStreamQuery),
responses(
(status = 200, description = "server-sent event stream; each event's \
`data` is a JSON-serialised `DashboardEvent` (seq-tagged; pair \
with `/api/dashboard/history` to backfill + dedupe on connect)",
`data` is a JSON-serialised `DashboardEvent` (seq-tagged)",
body = String, content_type = "text/event-stream"),
),
tag = "state_snapshot"