Configurable Struct

This commit is contained in:
Michael Mikovsky
2025-12-21 12:04:53 -07:00
parent c7d66c5560
commit 78fda07ab2
11 changed files with 160 additions and 98 deletions
+4 -2
View File
@@ -67,7 +67,8 @@ impl Config {
state_lock.trees = Some(response); state_lock.trees = Some(response);
state_lock.is_requesting = false; state_lock.is_requesting = false;
drop(state_lock); drop(state_lock);
}); })
.unwrap();
} else if tree_list_none && is_requesting { } else if tree_list_none && is_requesting {
ui.spinner(); ui.spinner();
} }
@@ -92,7 +93,8 @@ impl Config {
state_lock.tree_keys = Some(response.unwrap()); state_lock.tree_keys = Some(response.unwrap());
state_lock.is_requesting = false; state_lock.is_requesting = false;
}, },
); )
.unwrap();
} else if key_list_none && is_requesting { } else if key_list_none && is_requesting {
ui.spinner(); ui.spinner();
} }
+13 -1
View File
@@ -89,11 +89,23 @@ impl InterfaceWindow {
let mut state_lock = state_clone.lock().unwrap(); 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 { let clear = match branch {
TreeMessage::InterfaceAndValue(interface_struct, interface_data) => { TreeMessage::InterfaceAndValue(interface_struct, interface_data) => {
render_interface::render(ui, 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<TreeMessage>| {
crate::log(&format!("{response:?}"));
},
)
.unwrap();
}
false false
} }
TreeMessage::Folder(items) => { TreeMessage::Folder(items) => {
+48 -1
View File
@@ -1,5 +1,11 @@
use serde::{Deserialize, Serialize}; 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<ConfigStructField>; pub type ConfigStructKeys = Vec<ConfigStructField>;
pub type ConfigStructValues = Vec<Value>; pub type ConfigStructValues = Vec<Value>;
@@ -9,6 +15,47 @@ pub struct Config {
values: ConfigStructValues, 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<TreeMessage> {
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)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub enum ConfigStructField { pub enum ConfigStructField {
Header(String), Header(String),
+10 -8
View File
@@ -1,5 +1,4 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{ModuleError, Result, config::config_struct}; use crate::{ModuleError, Result, config::config_struct};
@@ -12,13 +11,13 @@ pub trait Tree {
unimplemented!(); unimplemented!();
} }
fn select_child(&self, child: &str, _message: TreeMessage) -> Result<TreeMessage>; fn select_child(&mut self, child: &str, _message: TreeMessage) -> Result<TreeMessage>;
fn get_value(&self, _message: TreeMessage) -> TreeMessage { fn get_value(&self, _message: TreeMessage) -> TreeMessage {
unimplemented!() unimplemented!()
} }
fn get_path(&self, elements: &mut Vec<&str>, message: TreeMessage) -> Result<TreeMessage> { fn get_path(&mut self, elements: &mut Vec<&str>, message: TreeMessage) -> Result<TreeMessage> {
if elements.is_empty() { if elements.is_empty() {
return if Self::is_folder() { return if Self::is_folder() {
Ok(TreeMessage::Folder(self.get_children_string())) Ok(TreeMessage::Folder(self.get_children_string()))
@@ -38,7 +37,7 @@ pub trait Tree {
} }
} }
fn get(&self, path: &str, message: TreeMessage) -> Result<TreeMessage> { fn get(&mut self, path: &str, message: TreeMessage) -> Result<TreeMessage> {
let mut path = if path.is_empty() { let mut path = if path.is_empty() {
Vec::new() Vec::new()
} else { } else {
@@ -49,25 +48,28 @@ pub trait Tree {
} }
} }
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum TreeMessage { pub enum TreeMessage {
RequestState, RequestState,
RequestStruct, RequestStruct,
RequestStructAndValue, RequestStructAndValue,
State(Value), State(InterfaceData),
Interface(InterfaceStruct), Interface(InterfaceStruct),
InterfaceAndValue(InterfaceStruct, InterfaceData), InterfaceAndValue(InterfaceStruct, InterfaceData),
Success,
Failure,
Folder(Vec<String>), Folder(Vec<String>),
} }
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum InterfaceStruct { pub enum InterfaceStruct {
ConfigStruct(config_struct::ConfigStructKeys), ConfigStruct(config_struct::ConfigStructKeys),
} }
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum InterfaceData { pub enum InterfaceData {
ConfigStruct(config_struct::ConfigStructValues), ConfigStruct(config_struct::ConfigStructValues),
} }
+24
View File
@@ -1,3 +1,7 @@
use std::fmt;
pub type Result<T> = std::result::Result<T, ModuleError>;
///Generic error type for module-related operations. ///Generic error type for module-related operations.
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum ModuleError { pub enum ModuleError {
@@ -30,3 +34,23 @@ impl From<Box<dyn std::error::Error>> for ModuleError {
ModuleError::Error(value.to_string()) 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())
}
}
+1 -24
View File
@@ -5,34 +5,11 @@ mod error;
pub mod logger; pub mod logger;
mod announcement; mod announcement;
use std::fmt::{self, Debug};
pub use error::ModuleError; pub use error::{ModuleError, Result};
pub use announcement::Announcement; pub use announcement::Announcement;
pub type Result<T> = std::result::Result<T, ModuleError>;
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 { // pub trait Component {
// fn name(&self) -> &'static str; // fn name(&self) -> &'static str;
// // fn start_runtime(&self, manager: Arc<Mutex<Manager>>) -> Option<Box<dyn ModuleRuntime>>; // // fn start_runtime(&self, manager: Arc<Mutex<Manager>>) -> Option<Box<dyn ModuleRuntime>>;
+11 -10
View File
@@ -22,16 +22,16 @@ macro_rules! route_get {
}}; }};
} }
// macro_rules! route_post { macro_rules! route_post {
// ($router:expr, $path:expr, $func:expr) => {{ ($router:expr, $path:expr, $func:expr) => {{
// { {
// $router.route( $router.route(
// $path, $path,
// post($func).layer(middleware::from_fn(auth::authorize)), post($func).layer(middleware::from_fn(auth::authorize)),
// ) )
// } }
// }}; }};
// } }
pub async fn start_api(address: &str, server: Server) { pub async fn start_api(address: &str, server: Server) {
let listener = TcpListener::bind(address) 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/", Server::get_tree2_root);
router = route_get!(router, "/api/interface/{*path}", Server::get_tree2); 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); // router = route_get_log(router);
+3 -1
View File
@@ -1,6 +1,8 @@
use std::collections::HashMap; 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)] #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct Blob { pub struct Blob {
-27
View File
@@ -33,33 +33,6 @@ struct BuildConfig {
features: HashMap<String, String>, features: HashMap<String, String>,
} }
#[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<usize>,
// Display string edit as password
#[serde(default)]
protected: Option<bool>,
},
Integer {
// Default value of integer in struct
#[serde(default)]
default: i32,
min: Option<i32>,
max: Option<i32>,
},
// Checkbox
// Dropdown
// Collapsing header
// Slider
// ...
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ComponentState { pub struct ComponentState {
metadata: ComponentMetadata, metadata: ComponentMetadata,
+30 -23
View File
@@ -1,15 +1,15 @@
use std::{ use std::{
collections::HashMap,
path::PathBuf, path::PathBuf,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use serde_json::Value;
use unshell_lib::{ use unshell_lib::{
ModuleError, ModuleError, Result,
config::{InterfaceData, Tree, TreeMessage, config_struct::ConfigStructField}, config::{
Tree, TreeMessage,
config_struct::{Config, ConfigStructField},
},
}; };
use unshell_lib::{Result, config::InterfaceStruct};
use unshell_manager::Manager; use unshell_manager::Manager;
mod blobs; mod blobs;
@@ -18,25 +18,41 @@ mod tree2;
#[derive(Clone)] #[derive(Clone)]
pub struct Server { pub struct Server {
pub component_configs: Vec<crate::config::ComponentState>, // pub component_configs: Vec<crate::config::ComponentState>,
// pub interface: InterfaceWrapper, // pub interface: InterfaceWrapper,
pub manager: Arc<Mutex<Manager>>, pub manager: Arc<Mutex<Manager>>,
pub db: sled::Db, pub db: sled::Db,
// pub tree: Tree2, // pub tree: Tree2,
test_thing: Arc<Mutex<Config>>,
} }
impl Server { impl Server {
pub fn new(config_paths: Vec<PathBuf>, database: String) -> Result<Self> { pub fn new(_config_paths: Vec<PathBuf>, database: String) -> Result<Self> {
let mut component_configs: Vec<crate::config::ComponentState> = Vec::new(); // let mut component_configs: Vec<crate::config::ComponentState> = Vec::new();
for config in &config_paths { // for config in &config_paths {
component_configs.extend(crate::config::load_config(config)?); // component_configs.extend(crate::config::load_config(config)?);
} // }
Ok(Self { Ok(Self {
component_configs, // component_configs,
manager: Manager::start(&crate::SERVER_CONFIG, Vec::new()), manager: Manager::start(&crate::SERVER_CONFIG, Vec::new()),
db: sled::open(database).map_err(|e| ModuleError::DatabaseError(e.to_string()))?, 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(), // tree: Tree2::default(),
// interface: get_test_interface(), // interface: get_test_interface(),
}) })
@@ -52,18 +68,9 @@ impl Tree for Server {
vec!["connection_count".into()] vec!["connection_count".into()]
} }
fn select_child(&self, child: &str, _message: TreeMessage) -> Result<TreeMessage> { fn select_child(&mut self, child: &str, message: TreeMessage) -> Result<TreeMessage> {
match child { match child {
"connection_count" => { "connection_count" => self.test_thing.lock().unwrap().get(message),
let interface = vec![ConfigStructField::Header(format!("Test Heading!"))];
let value = vec![Value::Null];
Ok(TreeMessage::InterfaceAndValue(
InterfaceStruct::ConfigStruct(interface),
InterfaceData::ConfigStruct(value),
))
}
_ => Err("No such child".into()), _ => Err("No such child".into()),
} }
} }
+16 -1
View File
@@ -21,7 +21,7 @@ impl Server {
} }
pub async fn get_tree2( pub async fn get_tree2(
State(server): State<Server>, State(mut server): State<Server>,
Path(path): Path<String>, Path(path): Path<String>,
Extension(_): Extension<CurrentUser>, Extension(_): Extension<CurrentUser>,
) -> Json<Value> { ) -> Json<Value> {
@@ -33,4 +33,19 @@ impl Server {
Json(serde_json::to_value(result).unwrap()) Json(serde_json::to_value(result).unwrap())
} }
pub async fn post_tree2(
State(mut server): State<Server>,
Path(path): Path<String>,
Extension(_): Extension<CurrentUser>,
Json(tree_message): Json<TreeMessage>,
) -> Json<Value> {
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())
}
} }