From 7fb9aaf5343c9ee4e971cc98f5f58b8161de35c0 Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Tue, 2 Dec 2025 08:22:03 -0700 Subject: [PATCH] Make config use dropdown --- unshell-gui/Cargo.lock | 80 +++++++++++++++--- unshell-gui/Cargo.toml | 1 + unshell-gui/src/config/mod.rs | 145 +++++++++++++++++++++------------ unshell-server/src/api/app.rs | 23 +++++- unshell-server/src/database.rs | 36 +++++--- 5 files changed, 205 insertions(+), 80 deletions(-) diff --git a/unshell-gui/Cargo.lock b/unshell-gui/Cargo.lock index 75c87d7..1bc1dc6 100644 --- a/unshell-gui/Cargo.lock +++ b/unshell-gui/Cargo.lock @@ -485,6 +485,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "ecolor" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1" +dependencies = [ + "emath 0.31.1", +] + [[package]] name = "ecolor" version = "0.33.2" @@ -492,7 +501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "084980ebede2fb1ad6c4f54285b3e489052ef2b6aa4016e4c19349417adc75c5" dependencies = [ "bytemuck", - "emath", + "emath 0.33.2", "serde", ] @@ -505,7 +514,7 @@ dependencies = [ "ahash", "bytemuck", "document-features", - "egui", + "egui 0.33.2", "egui-wgpu", "egui-winit", "egui_glow", @@ -534,6 +543,20 @@ dependencies = [ "winit", ] +[[package]] +name = "egui" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" +dependencies = [ + "ahash", + "bitflags 2.10.0", + "emath 0.31.1", + "epaint 0.31.1", + "nohash-hasher", + "profiling", +] + [[package]] name = "egui" version = "0.33.2" @@ -543,8 +566,8 @@ dependencies = [ "accesskit", "ahash", "bitflags 2.10.0", - "emath", - "epaint", + "emath 0.33.2", + "epaint 0.33.2", "log", "nohash-hasher", "profiling", @@ -554,6 +577,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "egui-dropdown" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d277c6c1f49e4e227344e920132d2d22a51810296b5c78f90e6aef4eed63ea" +dependencies = [ + "egui 0.31.1", +] + [[package]] name = "egui-wgpu" version = "0.33.2" @@ -563,8 +595,8 @@ dependencies = [ "ahash", "bytemuck", "document-features", - "egui", - "epaint", + "egui 0.33.2", + "epaint 0.33.2", "log", "profiling", "thiserror 2.0.17", @@ -582,7 +614,7 @@ checksum = "4772ed5f16fa8ec2ba295e58f62b58ee83fcf49e67ec13d2b7ddf4e9a2dea34e" dependencies = [ "arboard", "bytemuck", - "egui", + "egui 0.33.2", "log", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -603,7 +635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "550e844e608e356f4ad6843c510aa9bb5838b427e4700ed0056e9746ceeed866" dependencies = [ "ahash", - "egui", + "egui 0.33.2", "enum-map", "log", "mime_guess2", @@ -617,7 +649,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b94ff67a1d18933fff2519f5f57c388f932c093036c381fb9ae2853b3e1e09" dependencies = [ "bytemuck", - "egui", + "egui 0.33.2", "glow", "log", "memoffset", @@ -634,7 +666,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebfac3ca35f5e4fe217d3b03312111b234fe55ce059faf62b4cb47f7cf6d54f1" dependencies = [ "ahash", - "egui", + "egui 0.33.2", "itertools", "log", "serde", @@ -646,6 +678,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "emath" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" + [[package]] name = "emath" version = "0.33.2" @@ -687,6 +725,21 @@ dependencies = [ "syn", ] +[[package]] +name = "epaint" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562" +dependencies = [ + "ab_glyph", + "ahash", + "ecolor 0.31.1", + "emath 0.31.1", + "nohash-hasher", + "parking_lot", + "profiling", +] + [[package]] name = "epaint" version = "0.33.2" @@ -696,8 +749,8 @@ dependencies = [ "ab_glyph", "ahash", "bytemuck", - "ecolor", - "emath", + "ecolor 0.33.2", + "emath 0.33.2", "epaint_default_fonts", "log", "nohash-hasher", @@ -2401,7 +2454,8 @@ version = "0.1.0" dependencies = [ "chrono", "eframe", - "egui", + "egui 0.33.2", + "egui-dropdown", "egui_extras", "egui_tiles", "log", diff --git a/unshell-gui/Cargo.toml b/unshell-gui/Cargo.toml index be2cebf..97d9cf2 100644 --- a/unshell-gui/Cargo.toml +++ b/unshell-gui/Cargo.toml @@ -41,6 +41,7 @@ wasm-bindgen = "0.2.106" web-sys = "0.3.70" # to access the DOM (to hide the loading text) serde_json = "1.0.145" chrono = "0.4.42" +egui-dropdown = "0.13.0" [profile.release] opt-level = 2 # fast and small wasm diff --git a/unshell-gui/src/config/mod.rs b/unshell-gui/src/config/mod.rs index 95e1897..a35faa4 100644 --- a/unshell-gui/src/config/mod.rs +++ b/unshell-gui/src/config/mod.rs @@ -1,9 +1,10 @@ -use std::sync::{ - Arc, Mutex, - atomic::{AtomicBool, Ordering}, +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, }; -use egui::Color32; +use egui::ComboBox; +use egui_extras::{Column, TableBuilder}; use crate::auth::Auth; @@ -20,7 +21,7 @@ pub struct Config { #[derive(Default)] struct ConfigState { trees: Option>, - tree_keys: Option>, + tree_keys: Option>, is_requesting: bool, } @@ -40,9 +41,9 @@ impl Default for Config { impl Config { pub fn update(&mut self, auth: &mut Auth, ui: &mut egui::Ui) { - let mut state_lock = self.state.lock().unwrap(); - let tree_list_none = state_lock.trees.is_none(); - let key_list_none = state_lock.tree_keys.is_none(); + let state_lock = self.state.lock().unwrap(); + let mut tree_list_none = state_lock.trees.is_none(); + let mut key_list_none = state_lock.tree_keys.is_none(); let is_requesting = state_lock.is_requesting; if !tree_list_none @@ -57,12 +58,18 @@ impl Config { drop(state_lock); - if ui.button("Refresh").clicked() { - self.tree_option.clear(); - (*self.state.lock().unwrap()).trees = None; - } - ui.horizontal(|ui| { + if ui.button("Refresh").clicked() { + // self.tree_option.clear(); + let mut state_lock = self.state.lock().unwrap(); + (*state_lock).trees = None; + (*state_lock).tree_keys = None; + drop(state_lock); + + tree_list_none = true; + key_list_none = true; + } + if tree_list_none && !is_requesting { self.state.lock().unwrap().is_requesting = true; let state_clone = self.state.clone(); @@ -76,57 +83,42 @@ impl Config { } else if tree_list_none && is_requesting { ui.spinner(); } - }); - ui.horizontal(|ui| { + let state_lock = self.state.lock().unwrap(); + // This might have changed since the above api call + tree_list_none = state_lock.trees.is_none(); + if !tree_list_none { - // let tree_len = self.state.lock().unwrap().trees.as_ref().unwrap().len(); - - // for tree in &state_lock.unwrap() { - // if ui.button(tree).clicked() { - // self.tree_option = tree.to_string(); - // (*state_lock).tree_keys.as_mut(); - // } - // } - - let state_lock = self.state.lock().unwrap(); let trees = state_lock.trees.as_ref().unwrap().clone(); drop(state_lock); - for tree in trees { - // let tree: &&String = self - // .state - // .lock() - // .unwrap() - // .tree_keys - // .unwrap() - // .iter() - // .nth(i) - // .unwrap(); + let before = &self.tree_option.clone(); + egui::ComboBox::from_id_salt("Select Tree") + .selected_text(&self.tree_option) + .show_ui(ui, |ui| { + for tree in trees { + ui.selectable_value(&mut self.tree_option, tree.clone(), tree); + } + }); - // let tree sel - - if ui.button(&tree).clicked() { - self.tree_option = tree.to_string(); - (*self.state.lock().unwrap()).tree_keys = None; - } + if before.ne(&self.tree_option) { + (*self.state.lock().unwrap()).tree_keys = None; + key_list_none = true; } } - }); - if !self.tree_option.is_empty() && !tree_list_none { - ui.horizontal(|ui| { - ui.label(&format!("Tree: {}", self.tree_option)); - }); + if !self.tree_option.is_empty() && !tree_list_none { + // ui.horizontal(|ui| { + // ui.label(&format!("Tree: {}", self.tree_option)); + // }); - ui.horizontal(|ui| { if key_list_none && !is_requesting { self.state.lock().unwrap().is_requesting = true; let state_clone = self.state.clone(); auth.get( - &format!("/api/keys/{}", self.tree_option), - move |response: Result, String>| { + &format!("/api/values/{}", self.tree_option), + move |response: Result, String>| { let mut state_lock = state_clone.lock().unwrap(); state_lock.tree_keys = Some(response.unwrap()); state_lock.is_requesting = false; @@ -134,13 +126,58 @@ impl Config { ); } else if key_list_none && is_requesting { ui.spinner(); - } else { - ui.label(&format!( - "Keys: {:?}", - self.state.lock().unwrap().tree_keys.clone() - )); } + } + }); + + if !key_list_none { + // let body_text_size = TextStyle::Body.resolve(ui.style()).size; + // use egui_extras::{Size, StripBuilder}; + // StripBuilder::new(ui) + // .size(Size::remainder().at_least(100.0)) // for the table + // .size(Size::exact(body_text_size)) + // .vertical(|mut strip| { + // strip.cell(|ui| { + + egui::ScrollArea::both().show(ui, |ui| { + let table = TableBuilder::new(ui) + .striped(true) + .resizable(true) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .column(Column::auto()) + .column(Column::auto()) + .min_scrolled_height(0.0) + .sense(egui::Sense::click()); + + table + .header(20., |mut header| { + header.col(|ui| { + ui.strong("key"); + }); + header.col(|ui| { + ui.strong("value"); + }); + }) + .body(|mut body| { + let state_lock = self.state.lock().unwrap(); + let map = state_lock.tree_keys.as_ref().unwrap(); + + for (key, value) in (map).iter() { + // // let runtime = self.current_runtimes + + body.row(18., |mut row| { + row.col(|ui| { + ui.label(key); + }); + row.col(|ui| { + ui.label(value); + }); + }); + } + }); }); + // }); + // }); } } } diff --git a/unshell-server/src/api/app.rs b/unshell-server/src/api/app.rs index 84de897..08a061a 100644 --- a/unshell-server/src/api/app.rs +++ b/unshell-server/src/api/app.rs @@ -21,7 +21,8 @@ pub async fn start_api(address: &str, database: Database) { let mut router = Router::new().route("/api/auth", post(auth::sign_in)); router = route_get_trees(router); - router = route_get_keys(router); + router = route_get_all_tree_values(router); + router = route_get_tree_keys(router); router = route_trees(router); axum::serve(listener, router.with_state(database)) @@ -46,7 +47,7 @@ fn route_get_trees(router: Router) -> Router { } // Route the "keys" api for each tree -fn route_get_keys(router: Router) -> Router { +fn route_get_tree_keys(router: Router) -> Router { router.route( "/api/keys/{*path}", get( @@ -63,6 +64,24 @@ fn route_get_keys(router: Router) -> Router { ) } +// Route the "values" api to get all the values for each tree +fn route_get_all_tree_values(router: Router) -> Router { + router.route( + "/api/values/{*path}", + get( + async |State(database): State, + Path(path): Path, + Extension(_): Extension| { + debug!("GET /api/values/{}", path); + let result = database.all_tree_values(&path); + + Json(serde_json::to_value(result).unwrap()) + }, + ) + .layer(middleware::from_fn(auth::authorize)), + ) +} + // Loop through all trees and add /api// POST aand GET listeners for them fn route_trees(mut router: Router) -> Router { for tree in crate::DATABASE_TREES.iter() { diff --git a/unshell-server/src/database.rs b/unshell-server/src/database.rs index 89ee0f7..64f04df 100644 --- a/unshell-server/src/database.rs +++ b/unshell-server/src/database.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use sled::Tree; use unshell_lib::error; @@ -28,17 +30,6 @@ impl Database { .collect::>() } - pub fn get_keys(&self, tree_name: &str) -> Result, String> { - Ok(self - .get_tree(tree_name)? - .iter() - .keys() - .map(|key| { - String::from_utf8_lossy(&key.expect("This key should exist").to_vec()).to_string() - }) - .collect::>()) - } - pub fn put_value(&self, tree_name: &str, key: &str, value: &str) -> Result<(), String> { match self.get_tree(tree_name)?.insert(key, value) { Ok(_) => Ok(()), @@ -62,6 +53,29 @@ impl Database { } } } + + pub fn get_keys(&self, tree_name: &str) -> Result, String> { + Ok(self + .get_tree(tree_name)? + .iter() + .keys() + .map(|key| { + String::from_utf8_lossy(&key.expect("This key should exist").to_vec()).to_string() + }) + .collect::>()) + } + + pub fn all_tree_values(&self, tree_name: &str) -> Result, String> { + Ok(self + .get_keys(tree_name)? + .iter() + .map(|key| -> Result<(String, String), String> { + Ok((key.clone(), self.get_value(tree_name, &key)?)) + }) + .collect::, String>>()? + .into_iter() + .collect::>()) + } } impl Drop for Database {