kojin_dashboard/lib.rs
1//! JSON API dashboard for monitoring the kojin task queue.
2//!
3//! Provides both an embeddable [`axum::Router`] via [`dashboard_router()`] and a
4//! standalone binary.
5//!
6//! # Endpoints
7//!
8//! | Endpoint | Description |
9//! |----------|-------------|
10//! | `GET /` | Web UI dashboard |
11//! | `GET /healthz` | K8s liveness/readiness probe |
12//! | `GET /api/queues` | List queues with lengths + DLQ lengths |
13//! | `GET /api/queues/:name` | Single queue detail |
14//! | `GET /api/queues/:name/dlq` | Paginated DLQ messages |
15//! | `GET /api/metrics` | Throughput counters (requires `MetricsMiddleware`) |
16//! | `GET /api/tasks/:id` | Task result lookup (requires `ResultBackend`) |
17//!
18//! # Example
19//!
20//! ```rust,no_run
21//! use std::sync::Arc;
22//! use kojin_core::MemoryBroker;
23//! use kojin_dashboard::{DashboardState, dashboard_router};
24//!
25//! # #[tokio::main]
26//! # async fn main() {
27//! let broker = Arc::new(MemoryBroker::new());
28//! let state = DashboardState::new(broker);
29//! let app = dashboard_router(state);
30//!
31//! let listener = tokio::net::TcpListener::bind("0.0.0.0:9090").await.unwrap();
32//! axum::serve(listener, app).await.unwrap();
33//! # }
34//! ```
35
36pub mod routes;
37pub mod state;
38
39pub use state::DashboardState;
40
41use axum::Router;
42use tower_http::cors::CorsLayer;
43
44/// Configuration for the standalone dashboard server.
45#[derive(Debug, Clone)]
46pub struct DashboardConfig {
47 /// Port to listen on. Default: 9090.
48 pub port: u16,
49}
50
51impl Default for DashboardConfig {
52 fn default() -> Self {
53 Self { port: 9090 }
54 }
55}
56
57/// Build the dashboard [`Router`] with all API routes and web UI.
58pub fn dashboard_router(state: DashboardState) -> Router {
59 Router::new()
60 .route("/", axum::routing::get(routes::dashboard_ui))
61 .route("/healthz", axum::routing::get(routes::healthz))
62 .route("/api/queues", axum::routing::get(routes::list_queues))
63 .route("/api/queues/{name}", axum::routing::get(routes::get_queue))
64 .route(
65 "/api/queues/{name}/dlq",
66 axum::routing::get(routes::get_dlq),
67 )
68 .route("/api/metrics", axum::routing::get(routes::get_metrics))
69 .route(
70 "/api/tasks/{id}",
71 axum::routing::get(routes::get_task_result),
72 )
73 .layer(CorsLayer::permissive())
74 .with_state(state)
75}
76
77/// Start the dashboard as a background Tokio task. Returns the join handle.
78pub fn spawn_dashboard(
79 state: DashboardState,
80 port: u16,
81) -> tokio::task::JoinHandle<std::io::Result<()>> {
82 let app = dashboard_router(state);
83 tokio::spawn(async move {
84 let listener = tokio::net::TcpListener::bind(("0.0.0.0", port)).await?;
85 tracing::info!(port, "dashboard listening");
86 axum::serve(listener, app).await
87 })
88}