mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
Add tree thing
This commit is contained in:
@@ -5,7 +5,7 @@ use std::collections::HashSet;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::windows::WindowWrapper, auth::Auth, config::Config, flowchart::FlowChart,
|
app::windows::WindowWrapper, auth::Auth, config::Config, flowchart::FlowChart,
|
||||||
log_viewer::LogViewer, payload_config::PayloadConfig,
|
interface::InterfaceWindow, log_viewer::LogViewer, payload_config::PayloadConfig,
|
||||||
};
|
};
|
||||||
pub use app::TemplateApp;
|
pub use app::TemplateApp;
|
||||||
use egui_tiles::{TileId, Tree};
|
use egui_tiles::{TileId, Tree};
|
||||||
@@ -20,6 +20,7 @@ pub struct AppState {
|
|||||||
pub config: Config,
|
pub config: Config,
|
||||||
pub payload_config: PayloadConfig,
|
pub payload_config: PayloadConfig,
|
||||||
pub log_viewer: LogViewer,
|
pub log_viewer: LogViewer,
|
||||||
|
pub interface: InterfaceWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
@@ -29,6 +30,7 @@ impl AppState {
|
|||||||
(AppWindow::PayloadConfig, "Payload Config"),
|
(AppWindow::PayloadConfig, "Payload Config"),
|
||||||
(AppWindow::Config, "Config"),
|
(AppWindow::Config, "Config"),
|
||||||
(AppWindow::LogViewer, "Log Viewer"),
|
(AppWindow::LogViewer, "Log Viewer"),
|
||||||
|
(AppWindow::InterfaceWindow, "Tree Test"),
|
||||||
])
|
])
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -103,6 +105,7 @@ pub enum AppWindow {
|
|||||||
Config,
|
Config,
|
||||||
PayloadConfig,
|
PayloadConfig,
|
||||||
LogViewer,
|
LogViewer,
|
||||||
|
InterfaceWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppWindow {
|
impl AppWindow {
|
||||||
@@ -112,6 +115,7 @@ impl AppWindow {
|
|||||||
AppWindow::Config => state.config.update(&mut state.auth, ui),
|
AppWindow::Config => state.config.update(&mut state.auth, ui),
|
||||||
AppWindow::PayloadConfig => state.payload_config.update(ui),
|
AppWindow::PayloadConfig => state.payload_config.update(ui),
|
||||||
AppWindow::LogViewer => state.log_viewer.update(&mut state.auth, ui),
|
AppWindow::LogViewer => state.log_viewer.update(&mut state.auth, ui),
|
||||||
|
AppWindow::InterfaceWindow => state.interface.update(&mut state.auth, ui),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub enum Tree2Repr {
|
||||||
|
File(String),
|
||||||
|
Folder(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[derive(Clone, serde::Deserialize, serde::Serialize)]
|
||||||
|
// pub struct InterfaceWrapper {
|
||||||
|
// pub name: String,
|
||||||
|
// pub interface: Interface,
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[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
|
||||||
|
// ...
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
mod config;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
path::PathBuf,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{auth::Auth, interface::config::Tree2Repr};
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct InterfaceWindow {
|
||||||
|
path: PathBuf,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
state: Arc<Mutex<InterfaceWindowState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InterfaceWindowState {
|
||||||
|
is_request: bool,
|
||||||
|
is_error: bool,
|
||||||
|
branch: Option<Tree2Repr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterfaceWindow {
|
||||||
|
pub fn update(&mut self, auth: &mut Auth, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Interface");
|
||||||
|
ui.label(self.path.to_string_lossy());
|
||||||
|
|
||||||
|
{
|
||||||
|
if !self.path.eq("/") && ui.button("Go up").clicked() {
|
||||||
|
self.go_up();
|
||||||
|
self.state.lock().unwrap().branch = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let state_lock = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
let (is_request, is_error, is_some) = (
|
||||||
|
state_lock.is_request,
|
||||||
|
state_lock.is_error,
|
||||||
|
state_lock.branch.is_some(),
|
||||||
|
);
|
||||||
|
|
||||||
|
drop(state_lock);
|
||||||
|
|
||||||
|
if is_request {
|
||||||
|
ui.spinner();
|
||||||
|
} else if is_error {
|
||||||
|
self.reset_path();
|
||||||
|
let mut state_lock = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
state_lock.is_error = false;
|
||||||
|
state_lock.is_request = false;
|
||||||
|
state_lock.branch = None;
|
||||||
|
|
||||||
|
drop(state_lock)
|
||||||
|
} else if !is_some {
|
||||||
|
{
|
||||||
|
let mut state_lock = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
state_lock.is_request = true;
|
||||||
|
|
||||||
|
drop(state_lock)
|
||||||
|
}
|
||||||
|
|
||||||
|
let state_clone = self.state.clone();
|
||||||
|
auth.get(
|
||||||
|
&format!("/api/interface{}", self.path.display()),
|
||||||
|
move |response: Result<Tree2Repr, String>| {
|
||||||
|
let mut state_lock = state_clone.lock().unwrap();
|
||||||
|
|
||||||
|
match response {
|
||||||
|
Ok(item) => {
|
||||||
|
state_lock.branch = Some(item);
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
state_lock.is_error = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state_lock.is_request = false;
|
||||||
|
|
||||||
|
drop(state_lock);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let state_clone = self.state.clone();
|
||||||
|
|
||||||
|
let mut state_lock = state_clone.lock().unwrap();
|
||||||
|
|
||||||
|
let branch = (state_lock.branch).as_ref().unwrap();
|
||||||
|
|
||||||
|
let clear = match branch {
|
||||||
|
Tree2Repr::File(file) => {
|
||||||
|
ui.label(&format!("File {}", file));
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Tree2Repr::Folder(items) => {
|
||||||
|
let mut clear = false;
|
||||||
|
for item in items {
|
||||||
|
if ui.button(&format!("Item {}", item)).clicked() {
|
||||||
|
self.go_down(item.clone());
|
||||||
|
clear = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clear
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if clear {
|
||||||
|
state_lock.branch = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(state_lock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_path(&mut self) {
|
||||||
|
self.path = PathBuf::from("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn go_up(&mut self) {
|
||||||
|
self.path = PathBuf::from(self.path.parent().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn go_down(&mut self, path: String) {
|
||||||
|
self.path = self.path.join(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for InterfaceWindow {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
path: "/".into(),
|
||||||
|
state: Arc::new(Mutex::new(InterfaceWindowState::default())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for InterfaceWindowState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
is_request: false,
|
||||||
|
branch: None,
|
||||||
|
is_error: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,10 @@ extern crate log;
|
|||||||
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
mod auth;
|
mod auth;
|
||||||
|
mod blobs;
|
||||||
mod config;
|
mod config;
|
||||||
mod flowchart;
|
mod flowchart;
|
||||||
|
mod interface;
|
||||||
mod log_viewer;
|
mod log_viewer;
|
||||||
mod payload_config;
|
mod payload_config;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
GET /api/interface/*/some_config -> Returns the interface and struct at that location
|
||||||
|
|
||||||
|
GET /api/interface/*/some_folder -> Returns the list of sub-folders and interfaces at that location
|
||||||
@@ -53,6 +53,9 @@ pub async fn start_api(address: &str, server: Server) {
|
|||||||
router = route_get!(router, "/api/keys/{*path}", Server::all_tree_keys_api);
|
router = route_get!(router, "/api/keys/{*path}", Server::all_tree_keys_api);
|
||||||
router = route_get!(router, "/api/values/{*path}", Server::all_tree_values_api);
|
router = route_get!(router, "/api/values/{*path}", Server::all_tree_values_api);
|
||||||
|
|
||||||
|
router = route_get!(router, "/api/interface/", Server::get_tree2_root);
|
||||||
|
router = route_get!(router, "/api/interface/{*path}", Server::get_tree2);
|
||||||
|
|
||||||
// router = route_get_log(router);
|
// router = route_get_log(router);
|
||||||
|
|
||||||
axum::serve(listener, router.with_state(server))
|
axum::serve(listener, router.with_state(server))
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::config::ConfigStructField;
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub enum Interface {
|
||||||
|
Sub(HashMap<String, InterfaceWrapper>),
|
||||||
|
Struct {
|
||||||
|
fields: HashMap<String, ConfigStructField>,
|
||||||
|
value: HashMap<String, Value>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct InterfaceWrapper {
|
||||||
|
pub name: String,
|
||||||
|
pub interface: Interface,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterfaceWrapper {
|
||||||
|
pub fn get_path(&self, elements: &Vec<&str>, depth: usize) -> Result<InterfaceWrapper, String> {
|
||||||
|
if depth == elements.len() {
|
||||||
|
return Ok(self.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let element = elements[depth];
|
||||||
|
|
||||||
|
match &self.interface {
|
||||||
|
Interface::Sub(interface_wrappers) => {
|
||||||
|
if let Some(interface) = interface_wrappers.get(element) {
|
||||||
|
interface.get_path(elements, depth)
|
||||||
|
} else {
|
||||||
|
Err("Invalid Path".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => Err("Invalid Path".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_test_interface() -> InterfaceWrapper {
|
||||||
|
InterfaceWrapper {
|
||||||
|
name: "Root Interface".into(),
|
||||||
|
interface: Interface::Sub(HashMap::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
mod blob;
|
mod blob;
|
||||||
|
// pub mod interface;
|
||||||
|
|
||||||
pub use blob::Blob;
|
pub use blob::Blob;
|
||||||
|
// pub use interface::InterfaceWrapper;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
@@ -37,7 +39,7 @@ struct BuildConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
enum ConfigStructField {
|
pub enum ConfigStructField {
|
||||||
Header(String),
|
Header(String),
|
||||||
Text(String),
|
Text(String),
|
||||||
String {
|
String {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ mod config;
|
|||||||
pub mod logger;
|
pub mod logger;
|
||||||
mod modules;
|
mod modules;
|
||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
pub use api::app::start_api;
|
pub use api::app::start_api;
|
||||||
|
|
||||||
pub use server::Server;
|
pub use server::Server;
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
use std::{error::Error, path::PathBuf};
|
use std::{error::Error, path::PathBuf};
|
||||||
|
|
||||||
|
use crate::server::tree2::Tree2;
|
||||||
|
|
||||||
mod blobs;
|
mod blobs;
|
||||||
mod database;
|
mod database;
|
||||||
mod manager;
|
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 manager: Arc<Mutex<Manager>>,
|
// pub manager: Arc<Mutex<Manager>>,
|
||||||
pub db: sled::Db,
|
pub db: sled::Db,
|
||||||
|
pub tree: Tree2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
@@ -23,6 +27,8 @@ impl Server {
|
|||||||
component_configs,
|
component_configs,
|
||||||
// manager: Manager::start(&SERVER_CONFIG, Vec::new()),
|
// manager: Manager::start(&SERVER_CONFIG, Vec::new()),
|
||||||
db: sled::open(database)?,
|
db: sled::open(database)?,
|
||||||
|
tree: Tree2::default(),
|
||||||
|
// interface: get_test_interface(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
Extension, Json,
|
||||||
|
extract::{Path, State},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value;
|
||||||
|
use unshell_lib::debug;
|
||||||
|
|
||||||
|
use crate::{Server, api::CurrentUser};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum Tree2Branch {
|
||||||
|
File(String),
|
||||||
|
Folder(String, HashMap<String, Tree2Branch>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
enum Tree2Repr {
|
||||||
|
File(String),
|
||||||
|
Folder(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Tree2 {
|
||||||
|
root: Tree2Branch,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tree2Branch {
|
||||||
|
pub fn get_path(&self, elements: &Vec<&str>, depth: usize) -> Result<&Tree2Branch, String> {
|
||||||
|
if depth == elements.len() {
|
||||||
|
return Ok(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
let element = elements[depth];
|
||||||
|
|
||||||
|
if let Tree2Branch::Folder(_, hash_map) = self {
|
||||||
|
if let Some(branch) = hash_map.get(element) {
|
||||||
|
branch.get_path(elements, depth + 1)
|
||||||
|
} else {
|
||||||
|
Err("Invalid Path".into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err("This is a folder, not a file".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_repr(&self) -> Tree2Repr {
|
||||||
|
match self {
|
||||||
|
Tree2Branch::File(name) => Tree2Repr::File(name.clone()),
|
||||||
|
Tree2Branch::Folder(_, hash_map) => {
|
||||||
|
Tree2Repr::Folder(hash_map.keys().cloned().collect::<Vec<String>>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tree2 {
|
||||||
|
fn get(&self, path: &str) -> Result<Tree2Repr, String> {
|
||||||
|
let elements = if path.is_empty() {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
path.split("/").collect::<Vec<&str>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
// let elements = path.split("/").collect::<Vec<&str>>();
|
||||||
|
|
||||||
|
let branch = self.root.get_path(&elements, 0)?;
|
||||||
|
Ok(branch.to_repr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Tree2 {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
root: Tree2Branch::Folder(
|
||||||
|
"ROOT".into(),
|
||||||
|
HashMap::from([
|
||||||
|
("File A".into(), Tree2Branch::File("A".into())),
|
||||||
|
("File B".into(), Tree2Branch::File("B".into())),
|
||||||
|
(
|
||||||
|
"Folder C".into(),
|
||||||
|
Tree2Branch::Folder(
|
||||||
|
"A".into(),
|
||||||
|
HashMap::from([
|
||||||
|
("File A".into(), Tree2Branch::File("A".into())),
|
||||||
|
("File B".into(), Tree2Branch::File("B".into())),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
pub async fn get_tree2(
|
||||||
|
State(server): State<Server>,
|
||||||
|
Path(path): Path<String>,
|
||||||
|
Extension(_): Extension<CurrentUser>,
|
||||||
|
) -> Json<Value> {
|
||||||
|
debug!("GET /api/interface/{}", path);
|
||||||
|
|
||||||
|
let result = (|| -> Result<_, String> {
|
||||||
|
let interface = server.tree.get(&path)?;
|
||||||
|
|
||||||
|
Ok(interface)
|
||||||
|
})();
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user