cortexai_dashboard/
server.rs1use axum::{
4 routing::{get, post},
5 Router,
6};
7use cortexai_monitoring::CostTracker;
8use std::sync::Arc;
9use std::time::Duration;
10use tokio::net::TcpListener;
11use tower_http::cors::{Any, CorsLayer};
12use tower_http::services::ServeDir;
13use tracing::info;
14
15use crate::handlers::{
16 agent_handler, agents_handler, health_handler, index_handler, metrics_handler,
17 prometheus_metrics_handler, restart_agent_handler, session_handler, session_messages_handler,
18 session_traces_handler, sessions_handler, start_agent_handler, stats_handler,
19 stop_agent_handler, studio_handler, traces_handler, ws_handler,
20};
21use crate::state::DashboardState;
22
23pub struct DashboardServer {
25 state: Arc<DashboardState>,
26}
27
28impl DashboardServer {
29 pub fn new(cost_tracker: Arc<CostTracker>) -> Self {
31 let prometheus_handle = cortexai_monitoring::init_prometheus();
33
34 Self {
35 state: Arc::new(DashboardState::new(cost_tracker, prometheus_handle)),
36 }
37 }
38
39 pub fn state(&self) -> Arc<DashboardState> {
41 self.state.clone()
42 }
43
44 fn build_router(&self) -> Router {
46 let cors = CorsLayer::new()
47 .allow_origin(Any)
48 .allow_methods(Any)
49 .allow_headers(Any);
50
51 Router::new()
52 .route("/", get(index_handler))
54 .route("/studio", get(studio_handler))
55 .route("/ws", get(ws_handler))
57 .route("/api/metrics", get(metrics_handler))
59 .route("/api/stats", get(stats_handler))
60 .route("/api/traces", get(traces_handler))
62 .route("/api/sessions", get(sessions_handler))
64 .route("/api/sessions/:id", get(session_handler))
65 .route("/api/sessions/:id/traces", get(session_traces_handler))
66 .route("/api/sessions/:id/messages", get(session_messages_handler))
67 .route("/api/agents", get(agents_handler))
69 .route("/api/agents/:id", get(agent_handler))
70 .route("/api/agents/:id/start", post(start_agent_handler))
71 .route("/api/agents/:id/stop", post(stop_agent_handler))
72 .route("/api/agents/:id/restart", post(restart_agent_handler))
73 .route("/health", get(health_handler))
75 .route("/metrics", get(prometheus_metrics_handler))
77 .nest_service(
79 "/studio/pkg",
80 ServeDir::new("crates/dashboard/static/studio/pkg"),
81 )
82 .layer(cors)
83 .with_state(self.state.clone())
84 }
85
86 pub async fn run(self, addr: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
88 let router = self.build_router();
89 let listener = TcpListener::bind(addr).await?;
90
91 info!("Dashboard running at http://{}", addr);
92 info!(" Legacy dashboard: http://{}/", addr);
93 info!(" Agent Studio: http://{}/studio", addr);
94
95 let state = self.state.clone();
97 tokio::spawn(async move {
98 let mut interval = tokio::time::interval(Duration::from_secs(1));
99 loop {
100 interval.tick().await;
101 state.broadcast_metrics();
102 }
103 });
104
105 axum::serve(listener, router).await?;
106
107 Ok(())
108 }
109
110 pub fn spawn(self, addr: String) -> tokio::task::JoinHandle<()> {
112 let state = self.state.clone();
113
114 tokio::spawn(async move {
115 let broadcast_state = state.clone();
117 tokio::spawn(async move {
118 let mut interval = tokio::time::interval(Duration::from_secs(1));
119 loop {
120 interval.tick().await;
121 broadcast_state.broadcast_metrics();
122 }
123 });
124
125 let cors = CorsLayer::new()
126 .allow_origin(Any)
127 .allow_methods(Any)
128 .allow_headers(Any);
129
130 let router = Router::new()
131 .route("/", get(index_handler))
133 .route("/studio", get(studio_handler))
134 .route("/ws", get(ws_handler))
136 .route("/api/metrics", get(metrics_handler))
138 .route("/api/stats", get(stats_handler))
139 .route("/api/traces", get(traces_handler))
141 .route("/api/sessions", get(sessions_handler))
143 .route("/api/sessions/:id", get(session_handler))
144 .route("/api/sessions/:id/traces", get(session_traces_handler))
145 .route("/api/sessions/:id/messages", get(session_messages_handler))
146 .route("/api/agents", get(agents_handler))
148 .route("/api/agents/:id", get(agent_handler))
149 .route("/api/agents/:id/start", post(start_agent_handler))
150 .route("/api/agents/:id/stop", post(stop_agent_handler))
151 .route("/api/agents/:id/restart", post(restart_agent_handler))
152 .route("/health", get(health_handler))
154 .nest_service(
156 "/studio/pkg",
157 ServeDir::new("crates/dashboard/static/studio/pkg"),
158 )
159 .layer(cors)
160 .with_state(state);
161
162 let listener = TcpListener::bind(&addr).await.unwrap();
163 info!("Dashboard running at http://{}", addr);
164 info!(" Legacy dashboard: http://{}/", addr);
165 info!(" Agent Studio: http://{}/studio", addr);
166 axum::serve(listener, router).await.unwrap();
167 })
168 }
169}