hive-c0re: annotate the 3 SSE/stream dashboard routes with utoipa
This commit is contained in:
parent
dbff9f0987
commit
d10eebd455
3 changed files with 50 additions and 26 deletions
|
|
@ -207,6 +207,18 @@ struct BuildLogFrame {
|
|||
/// always carries the full accumulated log so far (cursors start at 0);
|
||||
/// subsequent frames carry only new bytes. `done: true` on the final
|
||||
/// frame signals the browser to close the `EventSource`.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/build-logs/id/{id}/stream",
|
||||
params(("id" = i64, Path, description = "build log row id")),
|
||||
responses(
|
||||
(status = 200, description = "server-sent event stream; each event's \
|
||||
`data` is a JSON-serialised `BuildLogFrame` \
|
||||
(stdout_append/stderr_append/status/done)",
|
||||
body = String, content_type = "text/event-stream"),
|
||||
),
|
||||
tag = "build_logs"
|
||||
)]
|
||||
pub(super) async fn get_build_log_stream(
|
||||
State(state): State<AppState>,
|
||||
AxumPath(id): AxumPath<i64>,
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@ use std::sync::Arc;
|
|||
|
||||
use anyhow::{Context, Result};
|
||||
use axum::{
|
||||
Router,
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
routing::get,
|
||||
};
|
||||
use utoipa::OpenApi;
|
||||
use utoipa_axum::{router::OpenApiRouter, routes};
|
||||
|
|
@ -120,27 +118,12 @@ pub async fn serve(
|
|||
) -> Result<()> {
|
||||
// API-only: the gateway static-serves the dashboard dist and proxies
|
||||
// non-static requests here (see hive-gateway.nix). Unmatched paths 404.
|
||||
// Every `#[utoipa::path]`-annotated route is registered further down
|
||||
// via `OpenApiRouter` instead of a plain `.route(...)` call here — see
|
||||
// the `router`/`api` split below. What's left in this plain chain is
|
||||
// exactly the SSE/streaming endpoints utoipa can't model (see the
|
||||
// `ApiDoc` doc comment) plus anything not yet annotated.
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/api/build-logs/id/{id}/stream",
|
||||
get(build_logs::get_build_log_stream),
|
||||
)
|
||||
.route(
|
||||
"/api/dashboard/stream",
|
||||
get(state_snapshot::dashboard_stream),
|
||||
)
|
||||
.route(
|
||||
"/api/dashboard/history",
|
||||
get(state_snapshot::dashboard_history),
|
||||
)
|
||||
// No static fallback — the gateway owns the dist; unmatched paths 404.
|
||||
;
|
||||
|
||||
// No static fallback needed here either. Every route (SSE streams
|
||||
// included — `utoipa::path` can document an SSE body as an opaque
|
||||
// `text/event-stream` string, it just can't model individual frame
|
||||
// shapes) is registered below via `OpenApiRouter`; see the `router`/
|
||||
// `api` split.
|
||||
//
|
||||
// `routes!()` folds every handler it's given into ONE shared
|
||||
// `MethodRouter` for the whole macro invocation — it does NOT group
|
||||
// by path internally, so passing it handlers for more than one
|
||||
|
|
@ -219,7 +202,9 @@ pub async fn serve(
|
|||
.routes(routes!(questions::post_cancel_question))
|
||||
.routes(routes!(tombstones::post_purge_tombstone))
|
||||
.routes(routes!(meta_inputs::post_meta_update))
|
||||
.merge(app.into())
|
||||
.routes(routes!(build_logs::get_build_log_stream))
|
||||
.routes(routes!(state_snapshot::dashboard_stream))
|
||||
.routes(routes!(state_snapshot::dashboard_history))
|
||||
.split_for_parts();
|
||||
let app = router
|
||||
.merge(SwaggerUi::new("/api/docs").url("/api/openapi.json", api))
|
||||
|
|
@ -453,6 +438,9 @@ mod router_build_probe {
|
|||
.routes(routes!(questions::post_answer_question))
|
||||
.routes(routes!(questions::post_cancel_question))
|
||||
.routes(routes!(tombstones::post_purge_tombstone))
|
||||
.routes(routes!(meta_inputs::post_meta_update));
|
||||
.routes(routes!(meta_inputs::post_meta_update))
|
||||
.routes(routes!(build_logs::get_build_log_stream))
|
||||
.routes(routes!(state_snapshot::dashboard_stream))
|
||||
.routes(routes!(state_snapshot::dashboard_history));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ use hive_sh4re::Approval;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use tokio_stream::wrappers::BroadcastStream;
|
||||
use tokio_stream::{Stream, StreamExt};
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::container_view::ContainerView;
|
||||
|
||||
|
|
@ -655,6 +656,17 @@ fn build_approval_views(approvals: Vec<Approval>) -> Vec<ApprovalView> {
|
|||
out
|
||||
}
|
||||
|
||||
#[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)
|
||||
|
|
@ -731,7 +743,7 @@ pub(super) async fn dashboard_history(State(state): State<AppState>) -> Response
|
|||
/// 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.
|
||||
#[derive(Deserialize, Default)]
|
||||
#[derive(Deserialize, Default, IntoParams)]
|
||||
pub(super) struct DashboardStreamQuery {
|
||||
/// Comma-separated event kinds to forward. Each token is
|
||||
/// trimmed; unknown kinds are silently ignored on lookup
|
||||
|
|
@ -739,6 +751,18 @@ pub(super) struct DashboardStreamQuery {
|
|||
kinds: Option<String>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/dashboard/stream",
|
||||
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)",
|
||||
body = String, content_type = "text/event-stream"),
|
||||
),
|
||||
tag = "state_snapshot"
|
||||
)]
|
||||
pub(super) async fn dashboard_stream(
|
||||
State(state): State<AppState>,
|
||||
axum::extract::Query(q): axum::extract::Query<DashboardStreamQuery>,
|
||||
|
|
|
|||
Loading…
Reference in a new issue