Skip to main content

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}