forge_runtime/dashboard/
mod.rs1mod api;
2mod assets;
3mod pages;
4
5pub use api::DashboardApi;
6pub use assets::DashboardAssets;
7pub use pages::DashboardPages;
8
9use std::sync::Arc;
10
11use axum::{
12 routing::{get, post},
13 Router,
14};
15use sqlx::PgPool;
16use tower_http::cors::{Any, CorsLayer};
17
18use crate::cron::CronRegistry;
19use crate::jobs::{JobDispatcher, JobRegistry};
20use crate::workflow::{WorkflowExecutor, WorkflowRegistry};
21
22#[derive(Debug, Clone)]
24pub struct DashboardConfig {
25 pub enabled: bool,
27
28 pub path_prefix: String,
30
31 pub api_prefix: String,
33
34 pub require_auth: bool,
36
37 pub admin_users: Vec<String>,
39}
40
41impl Default for DashboardConfig {
42 fn default() -> Self {
43 Self {
44 enabled: true,
45 path_prefix: "/_dashboard".to_string(),
46 api_prefix: "/_api".to_string(),
47 require_auth: false,
48 admin_users: Vec::new(),
49 }
50 }
51}
52
53#[derive(Clone)]
55pub struct DashboardState {
56 pub pool: PgPool,
57 pub config: DashboardConfig,
58 pub job_registry: JobRegistry,
59 pub cron_registry: Arc<CronRegistry>,
60 pub workflow_registry: WorkflowRegistry,
61 pub job_dispatcher: Option<Arc<JobDispatcher>>,
63 pub workflow_executor: Option<Arc<WorkflowExecutor>>,
65}
66
67pub fn create_dashboard_router(state: DashboardState) -> Router {
69 Router::new()
70 .route("/", get(pages::index))
72 .route("/metrics", get(pages::metrics))
73 .route("/logs", get(pages::logs))
74 .route("/traces", get(pages::traces))
75 .route("/traces/{trace_id}", get(pages::trace_detail))
76 .route("/alerts", get(pages::alerts))
77 .route("/jobs", get(pages::jobs))
78 .route("/workflows", get(pages::workflows))
79 .route("/crons", get(pages::crons))
80 .route("/cluster", get(pages::cluster))
81 .route("/assets/styles.css", get(assets::styles_css))
83 .route("/assets/main.js", get(assets::main_js))
84 .route("/assets/chart.js", get(assets::chart_js))
85 .with_state(state)
86}
87
88pub fn create_api_router(state: DashboardState) -> Router {
90 let cors = CorsLayer::new()
91 .allow_origin(Any)
92 .allow_methods(Any)
93 .allow_headers(Any);
94
95 Router::new()
96 .route("/metrics", get(api::list_metrics))
98 .route("/metrics/{name}", get(api::get_metric))
99 .route("/metrics/series", get(api::get_metric_series))
100 .route("/logs", get(api::list_logs))
102 .route("/logs/search", get(api::search_logs))
103 .route("/traces", get(api::list_traces))
105 .route("/traces/{trace_id}", get(api::get_trace))
106 .route("/alerts", get(api::list_alerts))
108 .route("/alerts/active", get(api::get_active_alerts))
109 .route("/alerts/{id}/acknowledge", post(api::acknowledge_alert))
110 .route("/alerts/{id}/resolve", post(api::resolve_alert))
111 .route(
113 "/alerts/rules",
114 get(api::list_alert_rules).post(api::create_alert_rule),
115 )
116 .route(
117 "/alerts/rules/{id}",
118 get(api::get_alert_rule)
119 .put(api::update_alert_rule)
120 .delete(api::delete_alert_rule),
121 )
122 .route("/jobs", get(api::list_jobs))
124 .route("/jobs/stats", get(api::get_job_stats))
125 .route("/jobs/registered", get(api::list_registered_jobs))
126 .route("/jobs/{id}", get(api::get_job))
127 .route("/jobs/{job_type}/dispatch", post(api::dispatch_job))
128 .route("/workflows", get(api::list_workflows))
130 .route("/workflows/stats", get(api::get_workflow_stats))
131 .route("/workflows/registered", get(api::list_registered_workflows))
132 .route("/workflows/{id}", get(api::get_workflow))
133 .route(
134 "/workflows/{workflow_name}/start",
135 post(api::start_workflow),
136 )
137 .route("/crons", get(api::list_crons))
139 .route("/crons/stats", get(api::get_cron_stats))
140 .route("/crons/history", get(api::get_cron_history))
141 .route("/crons/registered", get(api::list_registered_crons))
142 .route("/crons/{name}/trigger", post(api::trigger_cron))
143 .route("/crons/{name}/pause", post(api::pause_cron))
144 .route("/crons/{name}/resume", post(api::resume_cron))
145 .route("/cluster/nodes", get(api::list_nodes))
147 .route("/cluster/health", get(api::get_cluster_health))
148 .route("/system/info", get(api::get_system_info))
150 .route("/system/stats", get(api::get_system_stats))
151 .layer(cors)
152 .with_state(state)
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_dashboard_config_default() {
161 let config = DashboardConfig::default();
162 assert!(config.enabled);
163 assert_eq!(config.path_prefix, "/_dashboard");
164 assert_eq!(config.api_prefix, "/_api");
165 assert!(!config.require_auth);
166 }
167
168 #[test]
169 fn test_dashboard_state() {
170 let _ = DashboardConfig::default();
172 }
173}