From e42edddd33382855202ff2f17e6b77d65f6ad152 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Fri, 20 Sep 2024 11:23:53 +0200 Subject: Show RC params in dashboard --- src/dabmux.rs | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 3 ++ src/ui.rs | 50 +++++++++++++++++++++++--- 3 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 src/dabmux.rs (limited to 'src') diff --git a/src/dabmux.rs b/src/dabmux.rs new file mode 100644 index 0000000..fcdd864 --- /dev/null +++ b/src/dabmux.rs @@ -0,0 +1,112 @@ +use anyhow::anyhow; +use serde_json::Value; + +const ZMQ_TIMEOUT : i64 = 2000; + +pub struct DabMux { + ctx : zmq::Context, + rc_endpoint : String, +} + +pub struct Param { + pub module : String, + pub param : String, + pub value : String, +} + + +impl DabMux { + pub fn new() -> Self { + let ctx = zmq::Context::new(); + Self { + ctx, + rc_endpoint : "tcp://127.0.0.1:12722".to_owned() + } + } + + fn value_to_params(v: Value) -> anyhow::Result> { + let root = v.as_object().ok_or(anyhow!("RC data is not a JSON object"))?; + + let mut all_params = Vec::new(); + + for (module_name, params_value) in root { + let params = params_value.as_object().ok_or(anyhow!("RC module {} is not a JSON object", module_name))?; + + for (param_name, value_json) in params { + + let value = match value_json { + Value::Null => "null".to_owned(), + Value::Bool(b) => b.to_string(), + Value::Number(n) => n.to_string(), + Value::String(s) => s.clone(), + Value::Array(_) => return Err(anyhow!(format!("Unexpected array in {}.{}", module_name, param_name))), + Value::Object(_) => return Err(anyhow!(format!("Unexpected object in {}.{}", module_name, param_name))), + }; + + all_params.push( + Param { + module: module_name.to_owned(), + param: param_name.to_owned(), + value + }); + } + } + + Ok(all_params) + } + + pub fn get_rc_parameters(&mut self) -> anyhow::Result> { + let sock = self.ctx.socket(zmq::REQ)?; + sock.connect(&self.rc_endpoint)?; + sock.send("showjson", 0)?; + + let mut msg = zmq::Message::new(); + let mut items = [ + sock.as_poll_item(zmq::POLLIN), + ]; + zmq::poll(&mut items, ZMQ_TIMEOUT).unwrap(); + if items[0].is_readable() { + sock.recv(&mut msg, 0)?; + let msg = msg.as_str().ok_or(anyhow!("RC response is not a str"))?; + + // JSON structure: + // { "module1": { "param1": "value", "param2": "value" }, "module2": { ... } } + let v: Value = serde_json::from_str(msg)?; + Self::value_to_params(v) + } + else { + Err(anyhow!("Timeout reading RC")) + } + } + + pub fn set_rc_parameter(&mut self, module: &str, param: &str, value: &str) -> anyhow::Result { + let sock = self.ctx.socket(zmq::REQ)?; + sock.connect(&self.rc_endpoint)?; + sock.send_multipart(["set", module, param, value], 0)?; + + let mut items = [ + sock.as_poll_item(zmq::POLLIN), + ]; + zmq::poll(&mut items, ZMQ_TIMEOUT).unwrap(); + if items[0].is_readable() { + let mut parts = sock.recv_multipart(0)?; + + let j : String = parts.drain(..) + .map(|p| match String::from_utf8(p) + { + Ok(s) => s, + Err(_) => "???".to_owned(), + }) + .collect::>() + .join(","); + + eprintln!("SET_RC: {}", j); + + let v: Value = serde_json::Value::String(j); + Ok(v) + } + else { + Err(anyhow!("Timeout reading RC")) + } + } +} diff --git a/src/main.rs b/src/main.rs index e64570a..66fbe23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,9 +4,11 @@ use log::{debug, info, warn, error}; mod ui; mod config; +mod dabmux; struct AppState { conf : config::Config, + dabmux : dabmux::DabMux, } type SharedState = Arc>; @@ -22,6 +24,7 @@ async fn main() -> std::io::Result<()> { let shared_state = Arc::new(Mutex::new(AppState { conf : conf.clone(), + dabmux : dabmux::DabMux::new(), })); let port = 3000; diff --git a/src/ui.rs b/src/ui.rs index 914d289..6bcb04d 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -24,6 +24,7 @@ pub async fn serve(port: u16, shared_state: SharedState) { .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); @@ -45,7 +46,7 @@ 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![], + ActivePage::Dashboard => vec!["dashboard.js", "main.js"], ActivePage::Settings => vec!["settings.js", "main.js"], ActivePage::None => vec![], } @@ -58,18 +59,59 @@ struct DashboardTemplate<'a> { title: &'a str, page: ActivePage, conf: config::Config, + errors: Option, + params: Vec, } async fn dashboard(State(state): State) -> DashboardTemplate<'static> { - let conf = { - let st = state.lock().unwrap(); - st.conf.clone() + let (conf, params_result) = { + let mut st = state.lock().unwrap(); + + let params_result = st.dabmux.get_rc_parameters(); + + (st.conf.clone(), params_result) + }; + + let (params, errors) = match params_result { + Ok(v) => { + (v, None) + }, + Err(e) => { + (Vec::new(), Some(format!("{}", e))) + }, }; DashboardTemplate { title: "Dashboard", conf, page: ActivePage::Dashboard, + params, + 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, Json) { + + 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(v) => (StatusCode::OK, Json(v)), + Err(e) => { + let e_str = serde_json::Value::String(e.to_string()); + (StatusCode::BAD_REQUEST, Json(e_str)) + }, } } -- cgit v1.2.3