diff options
-rw-r--r-- | src/config.rs | 63 | ||||
-rw-r--r-- | src/ui.rs | 79 | ||||
-rw-r--r-- | templates/head.html | 2 | ||||
-rw-r--r-- | templates/settings.html | 76 |
4 files changed, 156 insertions, 64 deletions
diff --git a/src/config.rs b/src/config.rs index 3e0571d..54f94dd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,16 +2,75 @@ use std::fs; use anyhow::Context; use serde::{Deserialize, Serialize}; +type Protection = u8; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Service { + pub sid: u32, + pub ecc: u8, + pub label: String, + pub shortlabel: String, + pub input_port: u16, + pub bitrate: u32, + pub protection: Protection, +} + +impl Service { + pub fn sid_hex(&self) -> String { + format!("{:04X}", self.sid) + } + + pub fn ecc_hex(&self) -> String { + format!("{:02X}", self.ecc) + } +} #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Config { - pub name: String, + pub instance_name: String, + pub tist: bool, + pub tist_offset: i32, + // TODO tai_clock_bulletins + pub ensemble_id: u16, + pub ensemble_ecc: u8, + pub ensemble_label: String, + pub ensemble_shortlabel: String, + pub output_edi_port: u16, + pub services: Vec<Service>, +} + +impl Config { + pub fn ensemble_id_hex(&self) -> String { + format!("{:04X}", self.ensemble_id) + } + + pub fn ensemble_ecc_hex(&self) -> String { + format!("{:02X}", self.ensemble_ecc) + } } impl Default for Config { fn default() -> Self { Config { - name: "CHANGEME".to_owned(), + instance_name: "CHANGEME".to_owned(), + tist: true, + tist_offset: 0, + ensemble_id: 0x4FFF, + ensemble_ecc: 0xE1, + ensemble_label: "OpenDigitalRadio".to_owned(), + ensemble_shortlabel: "ODR".to_owned(), + output_edi_port: 8951, + services: vec![ + Service { + sid: 0x4DAA, + ecc: 0xE1, + label: "nothing".to_owned(), + shortlabel: "no".to_owned(), + input_port: 9001, + bitrate: 128, + protection: 2 + } + ], } } } @@ -22,7 +22,8 @@ use crate::SharedState; pub async fn serve(port: u16, shared_state: SharedState) { let app = Router::new() .route("/", get(dashboard)) - .route("/settings", get(show_settings).post(post_settings)) + .route("/settings", get(show_settings)) + .route("/api/settings", post(post_settings)) .nest_service("/static", ServeDir::new("static")) /* For an example for timeouts and tracing, have a look at the git history */ .with_state(shared_state); @@ -45,7 +46,7 @@ impl ActivePage { fn styles(&self) -> Vec<&'static str> { match self { ActivePage::Dashboard => vec![], - ActivePage::Settings => vec![], + ActivePage::Settings => vec!["settings.js", "main.js"], ActivePage::None => vec![], } } @@ -99,60 +100,32 @@ struct SettingsAppliedTemplate<'a> { error_reason: String, } -#[derive(Deserialize, Debug)] -struct FormConfig { - name: String, -} - -impl TryFrom<FormConfig> for config::Config { - type Error = anyhow::Error; - - fn try_from(value: FormConfig) -> Result<Self, Self::Error> { - Ok(config::Config { - name: value.name, - }) - } -} - async fn post_settings( State(state): State<SharedState>, - Form(input): Form<FormConfig>) -> (StatusCode, SettingsAppliedTemplate<'static>) { - match config::Config::try_from(input) { - Ok(c) => { - match c.store() { - Ok(()) => { - state.lock().unwrap().conf.clone_from(&c); - - (StatusCode::OK, SettingsAppliedTemplate { - title: "Settings", - conf: c, - page: ActivePage::None, - ok: true, - error_message: "", - error_reason: "".to_owned(), - }) - } - Err(e) => { - (StatusCode::INTERNAL_SERVER_ERROR, SettingsAppliedTemplate { - title: "Settings", - conf : c, - page: ActivePage::None, - ok: false, - error_message: "Failed to store config", - error_reason: e.to_string(), - }) - }, - } - }, + Json(conf): Json<config::Config>) -> (StatusCode, SettingsAppliedTemplate<'static>) { + + match conf.store() { + Ok(()) => { + state.lock().unwrap().conf.clone_from(&conf); + + (StatusCode::OK, SettingsAppliedTemplate { + title: "Settings", + conf, + page: ActivePage::None, + ok: true, + error_message: "", + error_reason: "".to_owned(), + }) + } Err(e) => { - (StatusCode::BAD_REQUEST, SettingsAppliedTemplate { - title: "Settings", - conf: state.lock().unwrap().conf.clone(), - page: ActivePage::None, - ok: false, - error_message: "Error interpreting POST data", - error_reason: e.to_string(), - }) + (StatusCode::INTERNAL_SERVER_ERROR, SettingsAppliedTemplate { + title: "Settings", + conf, + page: ActivePage::None, + ok: false, + error_message: "Failed to store config", + error_reason: e.to_string(), + }) }, } } diff --git a/templates/head.html b/templates/head.html index 47b314f..7a2e5bb 100644 --- a/templates/head.html +++ b/templates/head.html @@ -16,7 +16,7 @@ <div class="head-nav-topdiv"> <div class="nav-title"> <p class="text-lg">ODR-DabMux</p> - <p class="text-lg"><b>{{ conf.name }}</b></p> + <p class="text-lg"><b>{{ conf.instance_name }}</b></p> </div> <div class="div-menu"> diff --git a/templates/settings.html b/templates/settings.html index 8a7d56e..dfe71cf 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -1,15 +1,75 @@ {% include "head.html" %} <div class="content"> <h1>ODR-DabMux Settings</h1> - <div class="section"> - <form action="/settings" method="post"> - <fieldset> - <legend>General</legend> - <div><label for="name">Name of this instance:</label><input class="textinput" type="text" name="name" value="{{ conf.name }}"></div> - </fieldset> - <div><input class="btn" type="submit" value="Update"></div> - </form> + <h2>General</h2> + <div><label for="instance_name">Name of this instance:</label><input class="textinput" type="text" id="instance_name" value="{{ conf.instance_name }}"></div> + <div> + <label for="tist">Enable TIST:</label> + <input type="checkbox" id="tist" value="Enable TIST" + {% if conf.tist %} checked {% endif %} > + </div> + <div> + <label for="tist_offset">TIST offset:</label> + <input class="textinput" type="text" id="tist_offset" placeholder="TIST offset in seconds" value="{{ conf.tist_offset }}"> + </div> + <div> + <label for="ensemble_id">EId:</label> + <input class="textinput" type="text" id="ensemble_id" placeholder="Ensemble ID in hex" value="{{ conf.ensemble_id_hex() }}"> + </div> + <div> + <label for="ensemble_ecc">ECC:</label> + <input class="textinput" type="text" id="ensemble_ecc" placeholder="Ensemble ECC in hex" value="{{ conf.ensemble_ecc_hex() }}"> + </div> + <div> + <label for="ensemble_label">Label and shortlabel:</label> + <input class="textinput" type="text" id="ensemble_label" placeholder="Ensemble Label" value="{{ conf.ensemble_label }}"> + <input class="textinput" type="text" id="ensemble_shortlabel" placeholder="Ensemble Short Label" value="{{ conf.ensemble_shortlabel }}"> + </div> + <div> + <label for="output_edi_port">EDI TCP Listen Port</label> + <input class="textinput" type="text" id="output_edi_port" placeholder="TCP Listen Port for EDI Output" value="{{ conf.output_edi_port }}"> + </div> + </div> + <div class="section"> + <template id="service_template"> + <p class="service"> + <input class="textinput srv_sid" type="text" placeholder="Service ID in hex"> + <input class="textinput srv_ecc" type="text" placeholder="Service ECC in hex"> + <input class="textinput srv_label" type="text" placeholder="Service Label"> + <input class="textinput srv_shortlabel" type="text" placeholder="Service Short Label"> + <input class="textinput srv_input_port" type="text" placeholder="EDI TCP Input Port"> + <input class="textinput srv_bitrate" type="text" placeholder="Bitrate in kbps"> + <input class="textinput srv_protection" type="text" placeholder="Protection 1 to 4"> + <button class="btn" type="button" onclick="btn_settings_remove_service(this)">Remove</button> + </p> + </template> + <div id="services"> + {% for srv in conf.services %} + <p class="service"> + <input class="textinput srv_sid" type="text" placeholder="Service ID in hex" + value="{{ srv.sid_hex() }}"> + <input class="textinput srv_ecc" type="text" placeholder="Service ECC in hex" + value="{{ srv.ecc_hex() }}"> + <input class="textinput srv_label" type="text" placeholder="Service Label" + value="{{ srv.label }}"> + <input class="textinput srv_shortlabel" type="text" placeholder="Service Short Label" + value="{{ srv.shortlabel }}"> + <input class="textinput srv_input_port" type="text" placeholder="EDI TCP Input Port" + value="{{ srv.input_port }}"> + <input class="textinput srv_bitrate" type="text" placeholder="Bitrate in kbps" + value="{{ srv.bitrate }}"> + <input class="textinput srv_protection" type="text" placeholder="Protection 1 to 4" + value="{{ srv.protection }}"> + <button class="btn" type="button" onclick="btn_settings_remove_service(this)">Remove</button> + </p> + {% endfor %} + </div> + <button class="btn" type="button" onclick="btn_settings_add_service()">Add service</button> + </div> + <div class="section"> + <button class="btn" type="button" onclick="btn_settings_send()">Send</button> + </div> </div> {% include "foot.html" %} {# vi:set et sw=2 ts=2: #} |