use axum::{
extract::State,
http::StatusCode,
response::{Html, Json},
routing::{get, post},
Router,
};
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use std::sync::Arc;
use crate::shield::universal_display::UniversalDisplay;
use crate::shield::{Shield, UniversalShieldStatus};
#[derive(Debug, Clone)]
pub struct DashboardConfig {
pub listen_addr: SocketAddr,
pub update_interval_ms: u64,
pub auth_enabled: bool,
}
impl Default for DashboardConfig {
fn default() -> Self {
Self {
listen_addr: ([127, 0, 0, 1], 3000).into(),
update_interval_ms: 1000,
auth_enabled: false,
}
}
}
#[derive(Clone)]
struct AppState {
shield: Arc<Shield>,
config: DashboardConfig,
}
pub struct DashboardServer {
state: AppState,
}
impl DashboardServer {
pub const fn new(shield: Arc<Shield>, config: DashboardConfig) -> Self {
Self {
state: AppState { shield, config },
}
}
pub async fn run(self) -> Result<(), Box<dyn std::error::Error>> {
let app = Router::new()
.route("/", get(serve_dashboard))
.route("/api/status", get(get_status))
.route("/api/shield/toggle", post(toggle_shield))
.route("/api/mode/toggle", post(toggle_mode))
.with_state(self.state.clone());
let listener = tokio::net::TcpListener::bind(&self.state.config.listen_addr).await?;
tracing::info!(
"Dashboard running at http://{}",
self.state.config.listen_addr
);
axum::serve(listener, app).await?;
Ok(())
}
}
async fn serve_dashboard() -> Html<&'static str> {
Html(include_str!("../../templates/dashboard.html"))
}
async fn get_status(State(state): State<AppState>) -> Json<UniversalShieldStatus> {
let display = UniversalDisplay::new(
state.shield,
crate::shield::universal_display::UniversalDisplayConfig {
color: false,
detailed: true,
format: crate::shield::universal_display::DisplayFormat::Json,
status_file: None,
},
);
Json(display.get_status())
}
async fn toggle_shield(State(state): State<AppState>) -> StatusCode {
let current = state.shield.is_active();
state.shield.set_active(!current);
StatusCode::OK
}
async fn toggle_mode(State(state): State<AppState>) -> StatusCode {
let current = state.shield.is_event_processor_enabled();
state.shield.set_event_processor_enabled(!current);
StatusCode::OK
}
#[derive(Debug, Serialize, Deserialize)]
struct ToggleResponse {
success: bool,
new_state: bool,
}
#[derive(Debug, Deserialize)]
struct CommandQuery {
#[allow(dead_code)]
cmd: String,
#[allow(dead_code)]
args: Option<String>,
}