add settings window
This commit is contained in:
parent
09995db974
commit
3a7956f358
39
Cargo.lock
generated
39
Cargo.lock
generated
@ -155,8 +155,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6041616acea41d67c4a984709ddab1587fd0b10efe5cc563fee954d2f011854"
|
||||
dependencies = [
|
||||
"clipboard-win",
|
||||
"core-graphics",
|
||||
"image",
|
||||
"log",
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
@ -1142,6 +1140,28 @@ dependencies = [
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git-version"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6b0decc02f4636b9ccad390dcbe77b722a77efedfa393caf8379a51d5c61899"
|
||||
dependencies = [
|
||||
"git-version-macro",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git-version-macro"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe69f1cbdb6e28af2bac214e943b99ce8a0a06b447d15d3e61161b0423139f3f"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gl_generator"
|
||||
version = "0.14.0"
|
||||
@ -2032,6 +2052,12 @@ dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.20+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.53"
|
||||
@ -2733,11 +2759,11 @@ name = "ubusman"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arboard",
|
||||
"async-ssh2-lite",
|
||||
"directories-next",
|
||||
"eframe",
|
||||
"egui",
|
||||
"git-version",
|
||||
"hex",
|
||||
"image",
|
||||
"lazy-regex",
|
||||
@ -2751,6 +2777,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"toml",
|
||||
"version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2807,6 +2834,12 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a449064fee414fcc201356a3e6c1510f6c8829ed28bb06b91c54ebe208ce065"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
@ -7,11 +7,11 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.70"
|
||||
arboard = "3.2.0"
|
||||
async-ssh2-lite = { version = "0.4.5", features = ["async-io"] }
|
||||
directories-next = "2.0.0"
|
||||
eframe = "0.21.3"
|
||||
egui = "0.21.0"
|
||||
git-version = "0.3.5"
|
||||
hex = "0.4.3"
|
||||
image = "0.24.6"
|
||||
lazy-regex = "2.5.0"
|
||||
@ -25,3 +25,4 @@ syntect = "5.0.0"
|
||||
thiserror = "1.0.40"
|
||||
tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] }
|
||||
toml = "0.7.3"
|
||||
version = "3.0.0"
|
||||
|
109
src/app.rs
109
src/app.rs
@ -1,17 +1,19 @@
|
||||
use std::{
|
||||
net::{SocketAddr, SocketAddrV4},
|
||||
sync::{mpsc::{Receiver, Sender}, Arc},
|
||||
vec, rc::Rc,
|
||||
vec, rc::Rc, time::SystemTime,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use async_ssh2_lite::{AsyncIoTcpStream, AsyncSession};
|
||||
use eframe::CreationContext;
|
||||
use egui::{text::LayoutJob, Color32, ColorImage, TextureHandle, Vec2, Image};
|
||||
use egui::{text::LayoutJob, Color32, ColorImage, TextureHandle};
|
||||
use lazy_regex::regex_replace_all;
|
||||
use serde_json::Value;
|
||||
use lazy_static::lazy_static;
|
||||
use arboard::Clipboard;
|
||||
use tokio::task::JoinHandle;
|
||||
use git_version::git_version;
|
||||
use version::version;
|
||||
|
||||
use crate::ubus::{self, escape_json};
|
||||
|
||||
@ -22,6 +24,10 @@ lazy_static! {
|
||||
.expect("Failed to load copy icon") as ColorImage;
|
||||
}
|
||||
|
||||
const SOURCE_CODE_URL: &str = "https://example.com";
|
||||
const VERSION: &str = version!();
|
||||
const GIT_VERSION: &str = git_version!(args = ["--always", "--dirty=*"]);
|
||||
|
||||
pub fn load_image_from_memory(image_data: &[u8]) -> Result<ColorImage, image::ImageError> {
|
||||
let image = image::load_from_memory(image_data)?;
|
||||
let size = [image.width() as _, image.height() as _];
|
||||
@ -33,11 +39,6 @@ pub fn load_image_from_memory(image_data: &[u8]) -> Result<ColorImage, image::Im
|
||||
))
|
||||
}
|
||||
|
||||
fn set_clipboard(text: &str) {
|
||||
let mut clipboard = Clipboard::new().unwrap();
|
||||
clipboard.set_text(text).unwrap();
|
||||
}
|
||||
|
||||
pub enum AsyncEvent {
|
||||
Connect(Result<AsyncSession<AsyncIoTcpStream>>),
|
||||
Disconnect(Result<()>),
|
||||
@ -52,12 +53,14 @@ pub struct AppSettings {
|
||||
password: String,
|
||||
|
||||
show_object_ids: bool,
|
||||
connect_immidiately: bool
|
||||
connect_on_start: bool
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
settings: AppSettings,
|
||||
session: Option<AsyncSession<AsyncIoTcpStream>>,
|
||||
ubus_call_handle: Option<JoinHandle<()>>,
|
||||
last_ubus_call_at: SystemTime,
|
||||
|
||||
selected_object: Option<Rc<ubus::Object>>,
|
||||
selected_method: Option<String>,
|
||||
@ -68,6 +71,7 @@ pub struct App {
|
||||
|
||||
is_connecting: bool,
|
||||
is_disconnecting: bool,
|
||||
settings_open: bool,
|
||||
|
||||
tx: Sender<AsyncEvent>,
|
||||
rx: Receiver<AsyncEvent>,
|
||||
@ -86,9 +90,11 @@ impl Default for App {
|
||||
username: "root".to_owned(),
|
||||
password: "admin01".to_owned(),
|
||||
show_object_ids: false,
|
||||
connect_immidiately: true
|
||||
connect_on_start: true
|
||||
},
|
||||
session: None,
|
||||
ubus_call_handle: None,
|
||||
last_ubus_call_at: SystemTime::UNIX_EPOCH,
|
||||
|
||||
object_filter: "".into(),
|
||||
selected_object: None,
|
||||
@ -99,6 +105,7 @@ impl Default for App {
|
||||
|
||||
is_connecting: false,
|
||||
is_disconnecting: false,
|
||||
settings_open: false,
|
||||
|
||||
tx,
|
||||
rx,
|
||||
@ -150,7 +157,7 @@ fn json_layouter(ui: &egui::Ui, string: &str, wrap_width: f32) -> Arc<egui::Gall
|
||||
|
||||
impl App {
|
||||
pub fn init(&mut self, cc: &CreationContext) {
|
||||
if self.settings.connect_immidiately {
|
||||
if self.settings.connect_on_start {
|
||||
let username = &self.settings.username;
|
||||
let password = &self.settings.password;
|
||||
if username.is_empty() || password.is_empty() {
|
||||
@ -190,6 +197,13 @@ impl App {
|
||||
serde_json::from_str(&stripped_payload)
|
||||
}
|
||||
|
||||
pub fn is_ubus_call_in_progress(&self) -> bool {
|
||||
if let Some(handle) = &self.ubus_call_handle {
|
||||
return !handle.is_finished();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn handle_events(&mut self, _ctx: &egui::Context) {
|
||||
use AsyncEvent::*;
|
||||
|
||||
@ -220,7 +234,8 @@ impl App {
|
||||
},
|
||||
|
||||
Call(result) => {
|
||||
self.response = Some(result)
|
||||
self.response = Some(result);
|
||||
self.ubus_call_handle = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -267,10 +282,12 @@ impl App {
|
||||
|
||||
let tx = self.tx.clone();
|
||||
let session = self.session.clone().unwrap();
|
||||
tokio::spawn(async move {
|
||||
let handle = tokio::spawn(async move {
|
||||
let result = ubus::call(&session, &object, &method, message.as_ref()).await;
|
||||
tx.send(AsyncEvent::Call(result)).expect("Failed to send event");
|
||||
});
|
||||
self.ubus_call_handle = Some(handle);
|
||||
self.last_ubus_call_at = SystemTime::now()
|
||||
}
|
||||
|
||||
fn start_list_objects(&self) {
|
||||
@ -290,8 +307,10 @@ impl App {
|
||||
fn show_left_panel(&mut self, ui: &mut egui::Ui) {
|
||||
use egui::*;
|
||||
|
||||
let mut match_exists = false;
|
||||
ui.text_edit_singleline(&mut self.object_filter);
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.add_space(8.0);
|
||||
ScrollArea::vertical().show(ui, |ui| {
|
||||
for obj in self.objects.iter() {
|
||||
let shown_methods;
|
||||
if obj.name.contains(&self.object_filter) {
|
||||
@ -305,6 +324,7 @@ impl App {
|
||||
}
|
||||
|
||||
if shown_methods.len() > 0 {
|
||||
match_exists = true;
|
||||
let style = ui.style();
|
||||
let mut text = LayoutJob::default();
|
||||
text.append(&obj.name, 0.0, TextFormat {
|
||||
@ -330,17 +350,35 @@ impl App {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if !match_exists {
|
||||
ui.label("no matches :(");
|
||||
}
|
||||
}
|
||||
|
||||
fn show_settings(ui: &mut egui::Ui, settings: &mut AppSettings) {
|
||||
ui.checkbox(&mut settings.show_object_ids, "Show object IDs");
|
||||
ui.checkbox(&mut settings.connect_on_start, "Connect on start");
|
||||
ui.add_space(16.0);
|
||||
ui.label(format!("Version: {}-{}", VERSION, GIT_VERSION));
|
||||
ui.label("Author: Rokas Puzonas");
|
||||
if ui.link("Source code").clicked() {
|
||||
ui.output_mut(|o| o.open_url(SOURCE_CODE_URL));
|
||||
}
|
||||
}
|
||||
|
||||
fn show_right_panel(&mut self, ui: &mut egui::Ui) {
|
||||
use egui::*;
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
.show(ui, |ui| {
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.label("Address:");
|
||||
ui.text_edit_singleline(&mut self.settings.address);
|
||||
// TODO: ui.text_edit_singleline(&mut self.port);
|
||||
ui.label("Username:");
|
||||
ui.text_edit_singleline(&mut self.settings.username);
|
||||
ui.label("Password:");
|
||||
ui.text_edit_singleline(&mut self.settings.password);
|
||||
ui.add_space(8.0);
|
||||
if self.is_connecting {
|
||||
ui.add_enabled(false, Button::new("Connecting..."));
|
||||
} else if self.session.is_none() {
|
||||
@ -353,6 +391,21 @@ impl App {
|
||||
self.start_disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
if ui.button("Settings").clicked() {
|
||||
self.settings_open = !self.settings_open;
|
||||
}
|
||||
let window = egui::Window::new("Settings")
|
||||
.resizable(false)
|
||||
.collapsible(false)
|
||||
.anchor(Align2::CENTER_CENTER, Vec2::ZERO)
|
||||
.open(&mut self.settings_open);
|
||||
window.show(ui.ctx(), |ui| App::show_settings(ui, &mut self.settings));
|
||||
|
||||
ui.add_space(32.0);
|
||||
ui.label("TODO: Add saving calls");
|
||||
ui.label("TODO: Add listen logs");
|
||||
ui.label("TODO: Add monitor logs");
|
||||
});
|
||||
}
|
||||
|
||||
@ -406,7 +459,12 @@ impl App {
|
||||
|
||||
fn show_central_panel(&mut self, ui: &mut egui::Ui) {
|
||||
use egui::*;
|
||||
let time_since_last_call = self.last_ubus_call_at.elapsed().expect("Failed to get time since last ubus call");
|
||||
let call_in_progress = self.is_ubus_call_in_progress() && time_since_last_call.as_millis() >= 100;
|
||||
let call_enabled = self.selected_object.is_some() && self.selected_method.is_some();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_enabled_ui(!call_in_progress, |ui| {
|
||||
// Object dropdown
|
||||
{
|
||||
let object_name = self.selected_object.as_ref().map(|obj| obj.name.as_ref()).unwrap_or("");
|
||||
@ -469,26 +527,28 @@ impl App {
|
||||
};
|
||||
}
|
||||
|
||||
let call_enabled = self.selected_object.is_some() && self.selected_method.is_some();
|
||||
ui.add_enabled_ui(call_enabled, |ui| {
|
||||
if ui.button("call").clicked() {
|
||||
if ui.add_enabled(call_enabled, Button::new("call")).clicked() {
|
||||
let object_name = self.get_selected_object().unwrap().into();
|
||||
let method_name = self.get_selected_method().unwrap().into();
|
||||
let payload = self.get_payload().unwrap(); // TODO: handle parsing error
|
||||
self.start_call(object_name, method_name, Some(payload));
|
||||
// TODO: Block sending other requests
|
||||
}
|
||||
});
|
||||
|
||||
let copy_icon = self.copy_texture.as_ref().expect("Copy icon not loaded");
|
||||
let copy_button = Button::image_and_text(copy_icon.id(), Vec2::new(10.0, 10.0), "copy");
|
||||
if ui.add(copy_button).clicked() {
|
||||
if ui.add_enabled(call_enabled, copy_button).clicked() {
|
||||
let object_name = self.get_selected_object().unwrap();
|
||||
let method_name = self.get_selected_method().unwrap();
|
||||
let payload = self.get_payload().unwrap(); // TODO: handle parsing error
|
||||
let cmd = format!("ubus call {} {} {}", object_name, method_name, escape_json(&payload));
|
||||
set_clipboard(&cmd);
|
||||
ui.output_mut(|o| o.copied_text = cmd);
|
||||
}
|
||||
|
||||
if call_in_progress {
|
||||
ui.spinner();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
@ -505,7 +565,8 @@ impl App {
|
||||
egui::CentralPanel::default()
|
||||
.show_inside(ui, |ui| {
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.add(
|
||||
ui.add_enabled(
|
||||
!call_in_progress,
|
||||
egui::TextEdit::multiline(&mut self.payload)
|
||||
.font(egui::TextStyle::Monospace) // for cursor height
|
||||
.code_editor()
|
||||
@ -531,7 +592,7 @@ impl eframe::App for App {
|
||||
|
||||
egui::SidePanel::right("right_panel")
|
||||
.resizable(true)
|
||||
.width_range(100.0..=200.0)
|
||||
.width_range(125.0..=200.0)
|
||||
.show_inside(ui, |ui| self.show_right_panel(ui));
|
||||
|
||||
egui::CentralPanel::default()
|
||||
|
@ -29,9 +29,10 @@ fn main() {
|
||||
|
||||
let mut app = App::default();
|
||||
let mut options = eframe::NativeOptions::default();
|
||||
options.min_window_size = Some(Vec2::new(900.0, 500.0));
|
||||
options.min_window_size = Some(Vec2::new(920.0, 500.0));
|
||||
let icon_data = load_icon_from_memory(include_bytes!("./icon.png")).expect("Failed to load icon data");
|
||||
options.icon_data = Some(icon_data);
|
||||
options.vsync = true;
|
||||
|
||||
eframe::run_native(
|
||||
"ubusman",
|
||||
|
Loading…
Reference in New Issue
Block a user