/* * A Configuration and Control UI for ODR-DabMux * Copyright (C) 2024 Matthias P. Braendli * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see . */ use std::net::SocketAddr; use askama::Template; use axum::{ Json, Router, extract::State, http::StatusCode, routing::{get, post}, }; use log::info; use serde::Deserialize; use tower_http::services::ServeDir; use crate::config; use crate::SharedState; pub async fn serve(port: u16, shared_state: SharedState) { let app = Router::new() .route("/", get(dashboard)) .route("/settings", get(show_settings)) .route("/api/settings", post(post_settings)) .route("/api/set_rc", post(post_rc)) .nest_service("/static", ServeDir::new("static")) /* For an example for timeouts and tracing, have a look at the git history */ .with_state(shared_state); let listener = tokio::net::TcpListener::bind(("0.0.0.0", port)).await.unwrap(); axum::serve(listener, app.into_make_service_with_connect_info::()) .await.unwrap() } #[derive(PartialEq)] enum ActivePage { Dashboard, Settings, } impl ActivePage { // Used by templates/head.html to include the correct js files in fn styles(&self) -> Vec<&'static str> { match self { ActivePage::Dashboard => vec!["dashboard.js", "main.js"], ActivePage::Settings => vec!["settings.js", "main.js"], } } } #[derive(Template)] #[template(path = "dashboard.html")] struct DashboardTemplate<'a> { title: &'a str, page: ActivePage, conf: config::Config, params: Vec, params_errors: Option, stats: Option, stats_errors: Option, } async fn dashboard(State(state): State) -> DashboardTemplate<'static> { let (conf, params_result, stats_result) = { let mut st = state.lock().unwrap(); let params_result = st.dabmux.get_rc_parameters(); let stats_result = st.dabmux.get_stats(); info!("STATS: {:?}", stats_result); (st.conf.clone(), params_result, stats_result) }; let (params, params_errors) = match params_result { Ok(v) => (v, None), Err(e) => (Vec::new(), Some(format!("{}", e))), }; let (stats, stats_errors) = match stats_result { Ok(v) => (Some(v), None), Err(e) => (None, Some(format!("{}", e))), }; DashboardTemplate { title: "Dashboard", conf, page: ActivePage::Dashboard, params, params_errors, stats, stats_errors, } } #[derive(Deserialize)] struct SetRc { pub module : String, pub param : String, pub value : String, } async fn post_rc( State(state): State, Json(set_rc): Json) -> (StatusCode, String) { let set_rc_result = { let mut st = state.lock().unwrap(); st.dabmux.set_rc_parameter(&set_rc.module, &set_rc.param, &set_rc.value) }; match set_rc_result { Ok(()) => (StatusCode::OK, "".to_owned()), Err(e) => (StatusCode::BAD_REQUEST, e.to_string()), } } #[derive(Template)] #[template(path = "settings.html")] struct SettingsTemplate<'a> { title: &'a str, page: ActivePage, conf: config::Config, } async fn show_settings(State(state): State) -> SettingsTemplate<'static> { SettingsTemplate { title: "Settings", page: ActivePage::Settings, conf: state.lock().unwrap().conf.clone(), } } async fn post_settings( State(state): State, Json(conf): Json) -> (StatusCode, String) { match conf.store() { Ok(()) => { state.lock().unwrap().conf.clone_from(&conf); match conf.write_dabmux_json() { Ok(()) => (StatusCode::OK, "".to_owned()), Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to write odr-dabmux config: {}", e.to_string())) } } Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to write UI config: {}", e.to_string())) } }