1#![allow(dead_code)]
9
10mod routes;
11mod sse;
12mod state;
13
14use anyhow::Result;
15use clap::Parser;
16use std::sync::Arc;
17use tokio::sync::broadcast;
18use tracing::info;
19
20#[derive(Parser)]
21#[command(name = "zernel-dashboard")]
22#[command(about = "Zernel Web Dashboard — browser-based ML monitoring")]
23#[command(version)]
24struct Args {
25 #[arg(long, default_value = "3000", env = "ZERNEL_DASHBOARD_PORT")]
27 port: u16,
28
29 #[arg(long, default_value = "ws://127.0.0.1:9092", env = "ZERNEL_WS_URL")]
31 zerneld_url: String,
32}
33
34#[tokio::main]
35async fn main() -> Result<()> {
36 tracing_subscriber::fmt()
37 .with_env_filter(
38 std::env::var("ZERNEL_LOG").unwrap_or_else(|_| "zernel_dashboard=info".into()),
39 )
40 .init();
41
42 let args = Args::parse();
43
44 info!("Zernel Dashboard v{}", env!("CARGO_PKG_VERSION"));
45
46 let (sse_tx, _) = broadcast::channel::<String>(64);
48
49 let app_state = Arc::new(state::AppState::new(sse_tx.clone()));
51
52 let relay_url = args.zerneld_url.clone();
54 let relay_tx = sse_tx.clone();
55 tokio::spawn(async move {
56 sse::relay_zerneld_to_sse(&relay_url, relay_tx).await;
57 });
58
59 let app = routes::build_router(app_state);
61
62 let addr = format!("0.0.0.0:{}", args.port);
63 let listener = tokio::net::TcpListener::bind(&addr).await?;
64
65 info!(
66 port = args.port,
67 zerneld = args.zerneld_url,
68 "dashboard ready at http://localhost:{}",
69 args.port
70 );
71
72 axum::serve(listener, app).await?;
73
74 Ok(())
75}