Start to make dynamic interfaces work

This commit is contained in:
Michael Mikovsky
2025-12-21 00:35:28 -07:00
parent 1ea26641d6
commit c7d66c5560
26 changed files with 720 additions and 296 deletions
+15 -16
View File
@@ -437,15 +437,6 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
@@ -851,9 +842,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.177"
version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "libloading"
@@ -1602,14 +1593,21 @@ dependencies = [
name = "unshell-lib"
version = "0.0.0"
dependencies = [
"bincode",
"chrono",
"crossbeam-channel",
"serde",
"serde_json",
"unshell-obfuscate",
]
[[package]]
name = "unshell-manager"
version = "0.0.0"
dependencies = [
"bincode",
"libc",
"libloading",
"rand",
"serde",
"serde_json",
"unshell-lib",
"unshell-obfuscate",
]
@@ -1627,7 +1625,7 @@ dependencies = [
[[package]]
name = "unshell-server"
version = "0.1.0"
version = "0.0.0"
dependencies = [
"axum",
"axum-extra",
@@ -1642,6 +1640,7 @@ dependencies = [
"tokio",
"toml",
"unshell-lib",
"unshell-manager",
"unshell-obfuscate",
]
+6 -5
View File
@@ -1,15 +1,16 @@
[package]
name = "unshell-server"
version = "0.1.0"
# version = "0.1.0"
edition = "2024"
[features]
log_debug = ["unshell-lib/log_debug"]
default=["log_debug"]
log_debug = ["unshell-lib/log_debug", "unshell-manager/log_debug"]
[dependencies]
unshell-lib = {path = "../unshell-lib"}
unshell-obfuscate = {path = "../unshell-obfuscate"}
unshell-lib = {path = "../unshell-lib", default-featues = false}
unshell-obfuscate = {path = "../unshell-obfuscate", default-featues = false}
unshell-manager = {path = "../unshell-manager", default-featues = false}
axum = "0.8.7"
axum-extra = {version="0.12.2", features = ["typed-header"]}
+8 -5
View File
@@ -7,7 +7,7 @@ use std::{
path::{Path, PathBuf},
};
use unshell_lib::{debug, info};
use unshell_lib::{ModuleError, Result, debug, info};
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
struct ComponentMetadata {
@@ -66,15 +66,18 @@ pub struct ComponentState {
path: PathBuf,
}
pub fn load_config(path: &PathBuf) -> Result<Vec<ComponentState>, Box<dyn Error>> {
let path_absolute = fs::canonicalize(path.clone())?;
pub fn load_config(path: &PathBuf) -> Result<Vec<ComponentState>> {
let path_absolute =
fs::canonicalize(path.clone()).map_err(|e| ModuleError::Error(e.to_string()))?;
debug!("Loading data from path: `{:?}`", path_absolute);
// Read string as path
let config_str = fs::read_to_string(path.clone())?;
let config_str =
fs::read_to_string(path.clone()).map_err(|e| ModuleError::Error(e.to_string()))?;
// Load config from String
let config = toml::from_str::<ComponentMetadata>(&config_str)?;
let config = toml::from_str::<ComponentMetadata>(&config_str)
.map_err(|e| ModuleError::Error(e.to_string()))?;
info!("Loaded component `{}`", config.name);
+1 -2
View File
@@ -2,7 +2,6 @@ mod api;
mod auth;
mod config;
pub mod logger;
mod modules;
mod server;
pub use server::Server;
@@ -18,7 +17,7 @@ pub static DEFAULT_HOST: String = "localhost".to_string();
pub static DATABASE_NAME: String = "database".to_string();
#[static_init::dynamic]
pub static SERVER_CONFIG: unshell_lib::config::PayloadConfig = unshell_lib::config::PayloadConfig {
pub static SERVER_CONFIG: unshell_manager::PayloadConfig = unshell_manager::PayloadConfig {
id: "Server",
components: Vec::new(),
runtime_config: Vec::new(),
View File
+21 -11
View File
@@ -1,12 +1,16 @@
use std::{
error::Error,
collections::HashMap,
path::PathBuf,
sync::{Arc, Mutex},
};
use unshell_lib::module::Manager;
use crate::server::tree2::{Tree, Tree2Repr};
use serde_json::Value;
use unshell_lib::{
ModuleError,
config::{InterfaceData, Tree, TreeMessage, config_struct::ConfigStructField},
};
use unshell_lib::{Result, config::InterfaceStruct};
use unshell_manager::Manager;
mod blobs;
mod database;
@@ -22,7 +26,7 @@ pub struct Server {
}
impl Server {
pub fn new(config_paths: Vec<PathBuf>, database: String) -> Result<Self, Box<dyn Error>> {
pub fn new(config_paths: Vec<PathBuf>, database: String) -> Result<Self> {
let mut component_configs: Vec<crate::config::ComponentState> = Vec::new();
for config in &config_paths {
@@ -32,7 +36,7 @@ impl Server {
Ok(Self {
component_configs,
manager: Manager::start(&crate::SERVER_CONFIG, Vec::new()),
db: sled::open(database)?,
db: sled::open(database).map_err(|e| ModuleError::DatabaseError(e.to_string()))?,
// tree: Tree2::default(),
// interface: get_test_interface(),
})
@@ -48,12 +52,18 @@ impl Tree for Server {
vec!["connection_count".into()]
}
fn select_child(&self, child: &str) -> Result<Tree2Repr, String> {
fn select_child(&self, child: &str, _message: TreeMessage) -> Result<TreeMessage> {
match child {
"connection_count" => Ok(Tree2Repr::File(format!(
"Connection count: {}",
self.manager.lock().unwrap().connections.len()
))),
"connection_count" => {
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()),
}
}
+16 -63
View File
@@ -2,63 +2,24 @@ use axum::{
Extension, Json,
extract::{Path, State},
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use unshell_lib::debug;
use unshell_lib::{
ModuleError,
config::{Tree, TreeMessage},
debug,
};
use crate::{Server, auth::structs::CurrentUser};
pub trait Tree {
fn is_folder() -> bool {
false
}
fn get_children_string(&self) -> Vec<String> {
Vec::new()
}
fn select_child(&self, child: &str) -> Result<Tree2Repr, String>;
fn get_value(&self) -> String {
"DEFAULT".into()
}
fn get_path(&self, elements: &mut Vec<&str>) -> Result<Tree2Repr, String> {
if elements.is_empty() {
return if Self::is_folder() {
Ok(Tree2Repr::Folder(self.get_children_string()))
} else {
Ok(Tree2Repr::File(self.get_value()))
};
}
let child = elements.remove(0);
if Self::is_folder() {
self.select_child(child)
} else {
Err("This is a folder, not a file".into())
}
}
fn get(&self, path: &str) -> Result<Tree2Repr, String> {
let mut path = if path.is_empty() {
Vec::new()
} else {
path.split("/").collect::<Vec<&str>>()
};
self.get_path(&mut path)
}
}
#[derive(Clone, Serialize, Deserialize)]
pub enum Tree2Repr {
File(String),
Folder(Vec<String>),
}
impl Server {
pub async fn get_tree2_root(
State(server): State<Server>,
Extension(extension): Extension<CurrentUser>,
) -> Json<Value> {
Self::get_tree2(State(server), Path("".into()), Extension(extension)).await
}
pub async fn get_tree2(
State(server): State<Server>,
Path(path): Path<String>,
@@ -66,18 +27,10 @@ impl Server {
) -> Json<Value> {
debug!("GET /api/interface/{}", path);
let result = (|| -> Result<_, String> {
let interface = server.get(&path)?;
Ok(interface)
})();
let result = server
.get(&path, TreeMessage::RequestStructAndValue)
.map_err(|e| ModuleError::CryptError(e.to_string()));
Json(serde_json::to_value(result).unwrap())
}
pub async fn get_tree2_root(
State(server): State<Server>,
Extension(extension): Extension<CurrentUser>,
) -> Json<Value> {
Self::get_tree2(State(server), Path("".into()), Extension(extension)).await
}
}