From 78fda07ab27b732151d6ee3abef72fccd4c9ba20 Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Sun, 21 Dec 2025 12:04:53 -0700 Subject: [PATCH] Configurable Struct --- unshell-gui/src/config/mod.rs | 6 ++- unshell-gui/src/interface/mod.rs | 14 ++++++- unshell-lib/src/config/config_struct.rs | 49 ++++++++++++++++++++++- unshell-lib/src/config/tree.rs | 18 +++++---- unshell-lib/src/error.rs | 24 +++++++++++ unshell-lib/src/lib.rs | 25 +----------- unshell-server/src/api.rs | 21 +++++----- unshell-server/src/config/blob.rs | 4 +- unshell-server/src/config/mod.rs | 27 ------------- unshell-server/src/server/mod.rs | 53 ++++++++++++++----------- unshell-server/src/server/tree2.rs | 17 +++++++- 11 files changed, 160 insertions(+), 98 deletions(-) diff --git a/unshell-gui/src/config/mod.rs b/unshell-gui/src/config/mod.rs index f2c33c3..68fce14 100644 --- a/unshell-gui/src/config/mod.rs +++ b/unshell-gui/src/config/mod.rs @@ -67,7 +67,8 @@ impl Config { state_lock.trees = Some(response); state_lock.is_requesting = false; drop(state_lock); - }); + }) + .unwrap(); } else if tree_list_none && is_requesting { ui.spinner(); } @@ -92,7 +93,8 @@ impl Config { state_lock.tree_keys = Some(response.unwrap()); state_lock.is_requesting = false; }, - ); + ) + .unwrap(); } else if key_list_none && is_requesting { ui.spinner(); } diff --git a/unshell-gui/src/interface/mod.rs b/unshell-gui/src/interface/mod.rs index 36d856a..e85aa8a 100644 --- a/unshell-gui/src/interface/mod.rs +++ b/unshell-gui/src/interface/mod.rs @@ -89,11 +89,23 @@ impl InterfaceWindow { let mut state_lock = state_clone.lock().unwrap(); - let mut branch = (state_lock.branch).as_mut().unwrap(); + let branch = (state_lock.branch).as_mut().unwrap(); let clear = match branch { TreeMessage::InterfaceAndValue(interface_struct, interface_data) => { render_interface::render(ui, interface_struct, interface_data); + + if ui.button("Save").clicked() { + auth.post( + &format!("/api/interface{}", self.path.display()), + &TreeMessage::State(interface_data.clone()), + move |response: Result| { + crate::log(&format!("{response:?}")); + }, + ) + .unwrap(); + } + false } TreeMessage::Folder(items) => { diff --git a/unshell-lib/src/config/config_struct.rs b/unshell-lib/src/config/config_struct.rs index 8ae63c5..713411a 100644 --- a/unshell-lib/src/config/config_struct.rs +++ b/unshell-lib/src/config/config_struct.rs @@ -1,5 +1,11 @@ use serde::{Deserialize, Serialize}; -use serde_json::Value; +use serde_json::{Value, json}; + +use crate::{ + ModuleError, Result, + config::{InterfaceData, InterfaceStruct, TreeMessage}, + warn, +}; pub type ConfigStructKeys = Vec; pub type ConfigStructValues = Vec; @@ -9,6 +15,47 @@ pub struct Config { values: ConfigStructValues, } +impl Config { + pub fn new(keys: ConfigStructKeys) -> Self { + let values = keys + .iter() + .map(|key| match key { + ConfigStructField::Header(_) => Value::Null, + ConfigStructField::Text(_) => Value::Null, + ConfigStructField::String { default, .. } => json!(default), + ConfigStructField::Integer { default, .. } => json!(default), + }) + .collect(); + + Self { keys, values } + } + + pub fn get(&mut self, message: TreeMessage) -> Result { + match message { + TreeMessage::State(InterfaceData::ConfigStruct(values)) => { + self.values = values; + Ok(TreeMessage::Success) + } + + TreeMessage::RequestStruct => Ok(TreeMessage::Interface( + InterfaceStruct::ConfigStruct(self.keys.clone()), + )), + TreeMessage::RequestState => Ok(TreeMessage::State(InterfaceData::ConfigStruct( + self.values.clone(), + ))), + TreeMessage::RequestStructAndValue => Ok(TreeMessage::InterfaceAndValue( + InterfaceStruct::ConfigStruct(self.keys.clone()), + InterfaceData::ConfigStruct(self.values.clone()), + )), + + _ => { + warn!("Tree got invalid message"); + Err(ModuleError::Error("Invalid Request".into())) + } + } + } +} + #[derive(Debug, Clone, Deserialize, Serialize)] pub enum ConfigStructField { Header(String), diff --git a/unshell-lib/src/config/tree.rs b/unshell-lib/src/config/tree.rs index e528a6b..cafaabb 100644 --- a/unshell-lib/src/config/tree.rs +++ b/unshell-lib/src/config/tree.rs @@ -1,5 +1,4 @@ use serde::{Deserialize, Serialize}; -use serde_json::Value; use crate::{ModuleError, Result, config::config_struct}; @@ -12,13 +11,13 @@ pub trait Tree { unimplemented!(); } - fn select_child(&self, child: &str, _message: TreeMessage) -> Result; + fn select_child(&mut self, child: &str, _message: TreeMessage) -> Result; fn get_value(&self, _message: TreeMessage) -> TreeMessage { unimplemented!() } - fn get_path(&self, elements: &mut Vec<&str>, message: TreeMessage) -> Result { + fn get_path(&mut self, elements: &mut Vec<&str>, message: TreeMessage) -> Result { if elements.is_empty() { return if Self::is_folder() { Ok(TreeMessage::Folder(self.get_children_string())) @@ -38,7 +37,7 @@ pub trait Tree { } } - fn get(&self, path: &str, message: TreeMessage) -> Result { + fn get(&mut self, path: &str, message: TreeMessage) -> Result { let mut path = if path.is_empty() { Vec::new() } else { @@ -49,25 +48,28 @@ pub trait Tree { } } -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum TreeMessage { RequestState, RequestStruct, RequestStructAndValue, - State(Value), + State(InterfaceData), Interface(InterfaceStruct), InterfaceAndValue(InterfaceStruct, InterfaceData), + Success, + Failure, + Folder(Vec), } -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum InterfaceStruct { ConfigStruct(config_struct::ConfigStructKeys), } -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum InterfaceData { ConfigStruct(config_struct::ConfigStructValues), } diff --git a/unshell-lib/src/error.rs b/unshell-lib/src/error.rs index d66f542..6743dfe 100644 --- a/unshell-lib/src/error.rs +++ b/unshell-lib/src/error.rs @@ -1,3 +1,7 @@ +use std::fmt; + +pub type Result = std::result::Result; + ///Generic error type for module-related operations. #[derive(Debug, serde::Serialize, serde::Deserialize)] pub enum ModuleError { @@ -30,3 +34,23 @@ impl From> for ModuleError { ModuleError::Error(value.to_string()) } } + +impl std::error::Error for ModuleError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } + + fn description(&self) -> &str { + "description() is deprecated; use Display" + } + + fn cause(&self) -> Option<&dyn std::error::Error> { + Some(self) + } +} + +impl fmt::Display for ModuleError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(format!("{:?}", self).as_str()) + } +} diff --git a/unshell-lib/src/lib.rs b/unshell-lib/src/lib.rs index af6a713..92280fb 100644 --- a/unshell-lib/src/lib.rs +++ b/unshell-lib/src/lib.rs @@ -5,34 +5,11 @@ mod error; pub mod logger; mod announcement; -use std::fmt::{self, Debug}; -pub use error::ModuleError; +pub use error::{ModuleError, Result}; pub use announcement::Announcement; -pub type Result = std::result::Result; - -impl std::error::Error for ModuleError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - None - } - - fn description(&self) -> &str { - "description() is deprecated; use Display" - } - - fn cause(&self) -> Option<&dyn std::error::Error> { - Some(self) - } -} - -impl fmt::Display for ModuleError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(format!("{:?}", self).as_str()) - } -} - // pub trait Component { // fn name(&self) -> &'static str; // // fn start_runtime(&self, manager: Arc>) -> Option>; diff --git a/unshell-server/src/api.rs b/unshell-server/src/api.rs index 4bd4534..e9492fb 100644 --- a/unshell-server/src/api.rs +++ b/unshell-server/src/api.rs @@ -22,16 +22,16 @@ macro_rules! route_get { }}; } -// macro_rules! route_post { -// ($router:expr, $path:expr, $func:expr) => {{ -// { -// $router.route( -// $path, -// post($func).layer(middleware::from_fn(auth::authorize)), -// ) -// } -// }}; -// } +macro_rules! route_post { + ($router:expr, $path:expr, $func:expr) => {{ + { + $router.route( + $path, + post($func).layer(middleware::from_fn(auth::authorize)), + ) + } + }}; +} pub async fn start_api(address: &str, server: Server) { let listener = TcpListener::bind(address) @@ -51,6 +51,7 @@ pub async fn start_api(address: &str, server: Server) { router = route_get!(router, "/api/interface/", Server::get_tree2_root); router = route_get!(router, "/api/interface/{*path}", Server::get_tree2); + router = route_post!(router, "/api/interface/{*path}", Server::post_tree2); // router = route_get_log(router); diff --git a/unshell-server/src/config/blob.rs b/unshell-server/src/config/blob.rs index a9d63c7..b5f62a7 100644 --- a/unshell-server/src/config/blob.rs +++ b/unshell-server/src/config/blob.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; -use crate::config::ConfigStructField; +use unshell_lib::config::config_struct::ConfigStructField; + +// use crate::config::ConfigStructField; #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] pub struct Blob { diff --git a/unshell-server/src/config/mod.rs b/unshell-server/src/config/mod.rs index 9ee54ff..264df87 100644 --- a/unshell-server/src/config/mod.rs +++ b/unshell-server/src/config/mod.rs @@ -33,33 +33,6 @@ struct BuildConfig { features: HashMap, } -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] -pub enum ConfigStructField { - Header(String), - Text(String), - String { - // Default value of string edit in struct - #[serde(default)] - default: String, - max_length: Option, - // Display string edit as password - #[serde(default)] - protected: Option, - }, - Integer { - // Default value of integer in struct - #[serde(default)] - default: i32, - min: Option, - max: Option, - }, - // Checkbox - // Dropdown - // Collapsing header - // Slider - // ... -} - #[derive(Clone, Debug)] pub struct ComponentState { metadata: ComponentMetadata, diff --git a/unshell-server/src/server/mod.rs b/unshell-server/src/server/mod.rs index 7a39e47..bd306bf 100644 --- a/unshell-server/src/server/mod.rs +++ b/unshell-server/src/server/mod.rs @@ -1,15 +1,15 @@ use std::{ - collections::HashMap, path::PathBuf, sync::{Arc, Mutex}, }; -use serde_json::Value; use unshell_lib::{ - ModuleError, - config::{InterfaceData, Tree, TreeMessage, config_struct::ConfigStructField}, + ModuleError, Result, + config::{ + Tree, TreeMessage, + config_struct::{Config, ConfigStructField}, + }, }; -use unshell_lib::{Result, config::InterfaceStruct}; use unshell_manager::Manager; mod blobs; @@ -18,25 +18,41 @@ mod tree2; #[derive(Clone)] pub struct Server { - pub component_configs: Vec, + // pub component_configs: Vec, // pub interface: InterfaceWrapper, pub manager: Arc>, pub db: sled::Db, // pub tree: Tree2, + test_thing: Arc>, } impl Server { - pub fn new(config_paths: Vec, database: String) -> Result { - let mut component_configs: Vec = Vec::new(); + pub fn new(_config_paths: Vec, database: String) -> Result { + // let mut component_configs: Vec = Vec::new(); - for config in &config_paths { - component_configs.extend(crate::config::load_config(config)?); - } + // for config in &config_paths { + // component_configs.extend(crate::config::load_config(config)?); + // } Ok(Self { - component_configs, + // component_configs, manager: Manager::start(&crate::SERVER_CONFIG, Vec::new()), db: sled::open(database).map_err(|e| ModuleError::DatabaseError(e.to_string()))?, + + test_thing: Arc::new(Mutex::new(Config::new(vec![ + ConfigStructField::Header("Test Heading".into()), + ConfigStructField::Text("Test Texttttttttttttttt".into()), + ConfigStructField::String { + default: "Test Texttttttttttttttt".into(), + max_length: None, + protected: None, + }, + ConfigStructField::String { + default: "Test ".into(), + max_length: None, + protected: None, + }, + ]))), // tree: Tree2::default(), // interface: get_test_interface(), }) @@ -52,18 +68,9 @@ impl Tree for Server { vec!["connection_count".into()] } - fn select_child(&self, child: &str, _message: TreeMessage) -> Result { + fn select_child(&mut self, child: &str, message: TreeMessage) -> Result { match child { - "connection_count" => { - let interface = vec![ConfigStructField::Header(format!("Test Heading!"))]; - - let value = vec![Value::Null]; - - Ok(TreeMessage::InterfaceAndValue( - InterfaceStruct::ConfigStruct(interface), - InterfaceData::ConfigStruct(value), - )) - } + "connection_count" => self.test_thing.lock().unwrap().get(message), _ => Err("No such child".into()), } } diff --git a/unshell-server/src/server/tree2.rs b/unshell-server/src/server/tree2.rs index 87662fa..12744be 100644 --- a/unshell-server/src/server/tree2.rs +++ b/unshell-server/src/server/tree2.rs @@ -21,7 +21,7 @@ impl Server { } pub async fn get_tree2( - State(server): State, + State(mut server): State, Path(path): Path, Extension(_): Extension, ) -> Json { @@ -33,4 +33,19 @@ impl Server { Json(serde_json::to_value(result).unwrap()) } + + pub async fn post_tree2( + State(mut server): State, + Path(path): Path, + Extension(_): Extension, + Json(tree_message): Json, + ) -> Json { + debug!("POST /api/interface/{}", path); + + let result = server + .get(&path, tree_message) + .map_err(|e| ModuleError::CryptError(e.to_string())); + + Json(serde_json::to_value(result).unwrap()) + } }