Work on payload editor

This commit is contained in:
Michael Mikovsky
2025-11-26 12:48:23 -07:00
parent 0a3e3d9765
commit 3d9332059a
10 changed files with 742 additions and 921 deletions
+16
View File
@@ -153,6 +153,21 @@ dependencies = [
"libc",
]
[[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-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crypto-common"
version = "0.1.6"
@@ -528,6 +543,7 @@ version = "0.0.0"
dependencies = [
"bincode",
"chrono",
"crossbeam-channel",
"libc",
"libloading",
"rand",
+469 -837
View File
File diff suppressed because it is too large Load Diff
+6 -3
View File
@@ -3,7 +3,7 @@ name = "unshell-gui"
version = "0.1.0"
edition = "2024"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
authors = ["ASTATIN3"]
include = ["LICENSE-APACHE", "LICENSE-MIT", "**/*.rs", "Cargo.toml"]
rust-version = "1.88"
@@ -12,9 +12,11 @@ all-features = true
targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
[dependencies]
unshell-lib = {path="../unshell-lib"}
egui = "0.33.0"
eframe = { version = "0.33.0", default-features = false, features = [
"accesskit", # Make egui compatible with screen readers. NOTE: adds a lot of dependencies.
# "accesskit", # Make egui compatible with screen readers. NOTE: adds a lot of dependencies.
"default_fonts", # Embed the default egui fonts.
"glow", # Use the glow rendering backend. Alternative: "wgpu".
"persistence", # Enable restoring app state when restarting the app.
@@ -25,10 +27,11 @@ log = "0.4.27"
# You only need serde if you want app persistence:
serde = { version = "1.0.219", features = ["derive"] }
egui_extras = "0.33.2"
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
env_logger = "0.11.8"
pretty_env_logger = "0.5.0"
# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
+26 -71
View File
@@ -1,4 +1,4 @@
use crate::flowchart::FlowChart;
use crate::{config::Config, flowchart::FlowChart};
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
#[derive(serde::Deserialize, serde::Serialize)]
@@ -6,11 +6,11 @@ use crate::flowchart::FlowChart;
pub struct TemplateApp {
tab: Tab,
#[serde(skip)]
flowchart: FlowChart,
config: Config,
}
#[derive(serde::Deserialize, serde::Serialize)]
#[derive(serde::Deserialize, serde::Serialize, PartialEq, Eq)]
pub enum Tab {
Flowchart,
Test,
@@ -24,6 +24,7 @@ impl Default for TemplateApp {
// label: "Hello World!".to_owned(),
// value: 2.7,
flowchart: FlowChart::new(),
config: Config::new(),
}
}
}
@@ -58,29 +59,25 @@ impl eframe::App for TemplateApp {
egui::TopBottomPanel::top("tab_panel").show(ctx, |ui| {
// The top panel is often a good place for a menu bar:
egui::MenuBar::new()
// .style(StyleModifier::new(|s| s.visuals))
.ui(ui, |ui| {
if ui
.menu_button("Network", |ui| if ui.button("Quit").clicked() {})
.response
.clicked()
{
self.tab = Tab::Flowchart;
};
egui::MenuBar::new().ui(ui, |ui| {
if ui
.selectable_label(self.tab == Tab::Flowchart, "Network")
.clicked()
{
self.tab = Tab::Flowchart;
}
if ui
.menu_button("Test", |ui| if ui.button("Quit").clicked() {})
.response
.clicked()
{
self.tab = Tab::Test;
};
if ui
.selectable_label(self.tab == Tab::Test, self.config.title())
.clicked()
{
self.tab = Tab::Test;
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
egui::widgets::global_theme_preference_switch(ui);
});
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
egui::widgets::global_theme_preference_switch(ui);
});
});
});
egui::TopBottomPanel::bottom("tab_panel").show(ctx, |ui| {
@@ -90,54 +87,12 @@ impl eframe::App for TemplateApp {
});
});
// egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
// // The top panel is often a good place for a menu bar:
// egui::MenuBar::new().ui(ui, |ui| {
// if ui
// .menu_button("Network", |ui| if ui.button("Quit").clicked() {})
// .response
// .clicked()
// {
// self.tab = Tab::Flowchart;
// };
// if ui
// .menu_button("Test", |ui| if ui.button("Quit").clicked() {})
// .response
// .clicked()
// {
// self.tab = Tab::Test;
// };
// });
// });
egui::CentralPanel::default().show(ctx, |ui| {
match self.tab {
Tab::Flowchart => {
self.flowchart.paint(ui);
}
Tab::Test => {
// The central panel the region left after adding TopPanel's and SidePanel's
ui.heading("eframe template");
ui.horizontal(|ui| {
ui.label("Write something: ");
// ui.text_edit_singleline(&mut self.label);
});
// ui.add(egui::Slider::new(&mut self.value, 0.0..=10.0).text("value"));
// if ui.button("Increment").clicked() {
// self.value += 1.0;
// }
ui.separator();
ui.add(egui::github_link_file!(
"https://github.com/emilk/eframe_template/blob/main/",
"Source code."
));
}
egui::CentralPanel::default().show(ctx, |ui| match self.tab {
Tab::Flowchart => {
self.flowchart.paint(ui);
}
Tab::Test => {
self.config.update(ui);
}
});
}
+213
View File
@@ -0,0 +1,213 @@
use egui::{TextStyle, Ui};
use egui_extras::{Column, TableBuilder};
use unshell_lib::config::RuntimeConfig;
#[derive(serde::Deserialize, serde::Serialize)]
pub struct Config {
state: ConfigState,
current_payloads: Vec<PayloadConfig>,
}
#[derive(serde::Deserialize, serde::Serialize)]
enum ConfigState {
Base,
NewConfig(PayloadConfig),
EditConfig(usize, PayloadConfig),
}
impl Config {
pub fn new() -> Self {
Self {
state: ConfigState::Base,
current_payloads: vec![PayloadConfig {
name: "Test".to_string(),
components: vec!["server".to_string()],
runtimes: Vec::new(),
}],
}
}
pub fn title(&self) -> &str {
match self.state {
ConfigState::Base => "Config",
ConfigState::NewConfig(..) => "Config/New",
ConfigState::EditConfig(..) => "Config/Edit",
}
}
pub fn update(&mut self, ui: &mut Ui) {
match &mut self.state {
ConfigState::Base => self.table_ui(ui),
ConfigState::EditConfig(_, config) => {
ui.heading("Edit Payload");
match Self::edit_ui(ui, config) {
Some(true) => {
if let ConfigState::EditConfig(n, config) =
std::mem::replace(&mut self.state, ConfigState::Base)
{
self.current_payloads[n] = config;
}
}
Some(false) => {
self.state = ConfigState::Base;
}
_ => {}
}
}
ConfigState::NewConfig(config) => {
ui.heading("Edit Payload");
match Self::edit_ui(ui, config) {
Some(true) => {
if let ConfigState::NewConfig(config) =
std::mem::replace(&mut self.state, ConfigState::Base)
{
self.current_payloads.push(config);
}
}
Some(false) => {
self.state = ConfigState::Base;
}
_ => {}
}
}
}
}
fn table_ui(&mut self, ui: &mut Ui) {
ui.horizontal(|ui| {
ui.heading("Payloads");
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("New").clicked() {
self.state = ConfigState::NewConfig(PayloadConfig::new());
}
});
});
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)) // for the source code link
.vertical(|mut strip| {
strip.cell(|ui| {
egui::ScrollArea::horizontal().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().resizable(false))
.column(Column::remainder())
.column(Column::remainder())
.column(Column::remainder())
.min_scrolled_height(0.0)
.sense(egui::Sense::click());
table
.header(20., |mut header| {
header.col(|ui| {
ui.strong("#");
});
header.col(|ui| {
ui.strong("Name");
});
header.col(|ui| {
ui.strong("Components");
});
header.col(|ui| {
ui.strong("Runtimes");
});
})
.body(|mut body| {
for i in 0..self.current_payloads.len() {
// let runtime = self.current_runtimes
body.row(18., |mut row| {
row.col(|ui| {
ui.label(i.to_string());
});
row.col(|ui| {
ui.label(self.current_payloads[i].name.clone());
});
row.col(|ui| {
ui.label(format!(
"{:?}",
self.current_payloads[i].components.clone()
));
});
row.col(|ui| {
ui.label("A");
});
if row.response().clicked() {
self.state = ConfigState::EditConfig(
i,
self.current_payloads[i].clone(),
);
}
});
}
});
});
});
});
}
fn edit_ui(ui: &mut Ui, config: &mut PayloadConfig) -> Option<bool> {
ui.horizontal(|ui| {
ui.label("Name");
ui.text_edit_singleline(&mut config.name);
});
ui.horizontal(|ui| {
ui.label("Components: ");
for component in vec!["client", "server"] {
let enabled = config
.components
.iter()
.enumerate()
.find(|s| s.1.eq(component));
if ui.selectable_label(enabled.is_some(), component).clicked() {
if let Some((i, _)) = enabled {
let _ = config.components.remove(i);
} else {
config.components.push(component.to_string());
}
}
}
});
let mut ret = None;
ui.horizontal(|ui| {
ret = if ui.button("Back").clicked() {
Some(false)
} else if ui.button("Save").clicked() {
Some(true)
} else {
None
};
});
ret
// return None;
}
}
#[derive(Clone, serde::Deserialize, serde::Serialize)]
struct PayloadConfig {
name: String,
components: Vec<String>,
runtimes: Vec<RuntimeConfig>,
}
impl PayloadConfig {
pub fn new() -> Self {
Self {
name: "New Payload".to_string(),
components: Vec::new(),
runtimes: Vec::new(),
}
}
}
+5 -2
View File
@@ -8,6 +8,8 @@ const BG_STROKE: Stroke = Stroke {
color: Color32::GRAY,
};
#[derive(serde::Deserialize, serde::Serialize)]
pub struct FlowChart {
// frame: Frame,
container: DraggableContainer,
@@ -46,14 +48,15 @@ impl FlowChart {
0.,
Color32::PURPLE,
BG_STROKE,
egui::StrokeKind::Middle,
egui::StrokeKind::Outside,
);
ui.label("Tests");
ui.button("Test");
let _ = ui.button("Test");
})
}
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct DraggableContainer {
pub pos: egui::Pos2,
pub size: egui::Vec2,
+4
View File
@@ -1,6 +1,10 @@
#![warn(clippy::all, rust_2018_idioms)]
#![macro_use]
extern crate log;
mod app;
pub use app::TemplateApp;
mod config;
mod flowchart;
+1 -6
View File
@@ -6,17 +6,12 @@ use unshell_gui::TemplateApp;
// When compiling natively:
#[cfg(not(target_arch = "wasm32"))]
fn main() -> eframe::Result {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
pretty_env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let native_options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_inner_size([400.0, 300.0])
.with_min_inner_size([300.0, 220.0]),
// .with_icon(
// // NOTE: Adding an icon is optional
// eframe::icon_data::from_png_bytes(&include_bytes!("../assets/icon-256.png")[..])
// .expect("Failed to load icon"),
// ),
..Default::default()
};
eframe::run_native(
+1 -1
View File
@@ -21,7 +21,7 @@ bincode = "2.0.1"
unshell-obfuscate = {path = "../unshell-obfuscate"}
chrono = "0.4.42"
serde = {version = "1.0.228", features=["derive"]}
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
libc = "0.2.177"
rand = "0.9.2"
+1 -1
View File
@@ -21,7 +21,7 @@ pub struct PayloadConfig {
pub runtime_config: Vec<RuntimeConfig>,
}
#[derive(Debug, Clone, Encode, Decode)]
#[derive(Debug, Clone, Encode, Decode, serde::Deserialize, serde::Serialize)]
pub struct RuntimeConfig {
pub parent_component: String,
pub name: String,