zernel_dashboard/
main.rs

1// Copyright (C) 2026 Dyber, Inc. — Proprietary
2//
3// Zernel Web Dashboard — browser-based ML monitoring
4//
5// Connects to zerneld for real-time GPU telemetry, reads experiment/job/model
6// data from SQLite, serves an htmx-powered dashboard with Server-Sent Events.
7
8#![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    /// HTTP port to serve the dashboard
26    #[arg(long, default_value = "3000", env = "ZERNEL_DASHBOARD_PORT")]
27    port: u16,
28
29    /// zerneld WebSocket URL for real-time telemetry
30    #[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    // Broadcast channel for SSE (telemetry updates)
47    let (sse_tx, _) = broadcast::channel::<String>(64);
48
49    // Shared application state
50    let app_state = Arc::new(state::AppState::new(sse_tx.clone()));
51
52    // Start zerneld relay in background
53    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    // Build router
60    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}