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"
|
checksum = "d6041616acea41d67c4a984709ddab1587fd0b10efe5cc563fee954d2f011854"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clipboard-win",
|
"clipboard-win",
|
||||||
"core-graphics",
|
|
||||||
"image",
|
|
||||||
"log",
|
"log",
|
||||||
"objc",
|
"objc",
|
||||||
"objc-foundation",
|
"objc-foundation",
|
||||||
@ -1142,6 +1140,28 @@ dependencies = [
|
|||||||
"weezl",
|
"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]]
|
[[package]]
|
||||||
name = "gl_generator"
|
name = "gl_generator"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@ -2032,6 +2052,12 @@ dependencies = [
|
|||||||
"toml_edit",
|
"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]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.53"
|
version = "1.0.53"
|
||||||
@ -2733,11 +2759,11 @@ name = "ubusman"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arboard",
|
|
||||||
"async-ssh2-lite",
|
"async-ssh2-lite",
|
||||||
"directories-next",
|
"directories-next",
|
||||||
"eframe",
|
"eframe",
|
||||||
"egui",
|
"egui",
|
||||||
|
"git-version",
|
||||||
"hex",
|
"hex",
|
||||||
"image",
|
"image",
|
||||||
"lazy-regex",
|
"lazy-regex",
|
||||||
@ -2751,6 +2777,7 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
|
"version",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2807,6 +2834,12 @@ version = "0.8.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a449064fee414fcc201356a3e6c1510f6c8829ed28bb06b91c54ebe208ce065"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
@ -7,11 +7,11 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.70"
|
anyhow = "1.0.70"
|
||||||
arboard = "3.2.0"
|
|
||||||
async-ssh2-lite = { version = "0.4.5", features = ["async-io"] }
|
async-ssh2-lite = { version = "0.4.5", features = ["async-io"] }
|
||||||
directories-next = "2.0.0"
|
directories-next = "2.0.0"
|
||||||
eframe = "0.21.3"
|
eframe = "0.21.3"
|
||||||
egui = "0.21.0"
|
egui = "0.21.0"
|
||||||
|
git-version = "0.3.5"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
image = "0.24.6"
|
image = "0.24.6"
|
||||||
lazy-regex = "2.5.0"
|
lazy-regex = "2.5.0"
|
||||||
@ -25,3 +25,4 @@ syntect = "5.0.0"
|
|||||||
thiserror = "1.0.40"
|
thiserror = "1.0.40"
|
||||||
tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] }
|
||||||
toml = "0.7.3"
|
toml = "0.7.3"
|
||||||
|
version = "3.0.0"
|
||||||
|
237
src/app.rs
237
src/app.rs
@ -1,17 +1,19 @@
|
|||||||
use std::{
|
use std::{
|
||||||
net::{SocketAddr, SocketAddrV4},
|
net::{SocketAddr, SocketAddrV4},
|
||||||
sync::{mpsc::{Receiver, Sender}, Arc},
|
sync::{mpsc::{Receiver, Sender}, Arc},
|
||||||
vec, rc::Rc,
|
vec, rc::Rc, time::SystemTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_ssh2_lite::{AsyncIoTcpStream, AsyncSession};
|
use async_ssh2_lite::{AsyncIoTcpStream, AsyncSession};
|
||||||
use eframe::CreationContext;
|
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 lazy_regex::regex_replace_all;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use lazy_static::lazy_static;
|
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};
|
use crate::ubus::{self, escape_json};
|
||||||
|
|
||||||
@ -22,6 +24,10 @@ lazy_static! {
|
|||||||
.expect("Failed to load copy icon") as ColorImage;
|
.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> {
|
pub fn load_image_from_memory(image_data: &[u8]) -> Result<ColorImage, image::ImageError> {
|
||||||
let image = image::load_from_memory(image_data)?;
|
let image = image::load_from_memory(image_data)?;
|
||||||
let size = [image.width() as _, image.height() as _];
|
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 {
|
pub enum AsyncEvent {
|
||||||
Connect(Result<AsyncSession<AsyncIoTcpStream>>),
|
Connect(Result<AsyncSession<AsyncIoTcpStream>>),
|
||||||
Disconnect(Result<()>),
|
Disconnect(Result<()>),
|
||||||
@ -52,12 +53,14 @@ pub struct AppSettings {
|
|||||||
password: String,
|
password: String,
|
||||||
|
|
||||||
show_object_ids: bool,
|
show_object_ids: bool,
|
||||||
connect_immidiately: bool
|
connect_on_start: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
settings: AppSettings,
|
settings: AppSettings,
|
||||||
session: Option<AsyncSession<AsyncIoTcpStream>>,
|
session: Option<AsyncSession<AsyncIoTcpStream>>,
|
||||||
|
ubus_call_handle: Option<JoinHandle<()>>,
|
||||||
|
last_ubus_call_at: SystemTime,
|
||||||
|
|
||||||
selected_object: Option<Rc<ubus::Object>>,
|
selected_object: Option<Rc<ubus::Object>>,
|
||||||
selected_method: Option<String>,
|
selected_method: Option<String>,
|
||||||
@ -68,6 +71,7 @@ pub struct App {
|
|||||||
|
|
||||||
is_connecting: bool,
|
is_connecting: bool,
|
||||||
is_disconnecting: bool,
|
is_disconnecting: bool,
|
||||||
|
settings_open: bool,
|
||||||
|
|
||||||
tx: Sender<AsyncEvent>,
|
tx: Sender<AsyncEvent>,
|
||||||
rx: Receiver<AsyncEvent>,
|
rx: Receiver<AsyncEvent>,
|
||||||
@ -86,9 +90,11 @@ impl Default for App {
|
|||||||
username: "root".to_owned(),
|
username: "root".to_owned(),
|
||||||
password: "admin01".to_owned(),
|
password: "admin01".to_owned(),
|
||||||
show_object_ids: false,
|
show_object_ids: false,
|
||||||
connect_immidiately: true
|
connect_on_start: true
|
||||||
},
|
},
|
||||||
session: None,
|
session: None,
|
||||||
|
ubus_call_handle: None,
|
||||||
|
last_ubus_call_at: SystemTime::UNIX_EPOCH,
|
||||||
|
|
||||||
object_filter: "".into(),
|
object_filter: "".into(),
|
||||||
selected_object: None,
|
selected_object: None,
|
||||||
@ -99,6 +105,7 @@ impl Default for App {
|
|||||||
|
|
||||||
is_connecting: false,
|
is_connecting: false,
|
||||||
is_disconnecting: false,
|
is_disconnecting: false,
|
||||||
|
settings_open: false,
|
||||||
|
|
||||||
tx,
|
tx,
|
||||||
rx,
|
rx,
|
||||||
@ -150,7 +157,7 @@ fn json_layouter(ui: &egui::Ui, string: &str, wrap_width: f32) -> Arc<egui::Gall
|
|||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn init(&mut self, cc: &CreationContext) {
|
pub fn init(&mut self, cc: &CreationContext) {
|
||||||
if self.settings.connect_immidiately {
|
if self.settings.connect_on_start {
|
||||||
let username = &self.settings.username;
|
let username = &self.settings.username;
|
||||||
let password = &self.settings.password;
|
let password = &self.settings.password;
|
||||||
if username.is_empty() || password.is_empty() {
|
if username.is_empty() || password.is_empty() {
|
||||||
@ -190,6 +197,13 @@ impl App {
|
|||||||
serde_json::from_str(&stripped_payload)
|
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) {
|
fn handle_events(&mut self, _ctx: &egui::Context) {
|
||||||
use AsyncEvent::*;
|
use AsyncEvent::*;
|
||||||
|
|
||||||
@ -220,7 +234,8 @@ impl App {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Call(result) => {
|
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 tx = self.tx.clone();
|
||||||
let session = self.session.clone().unwrap();
|
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;
|
let result = ubus::call(&session, &object, &method, message.as_ref()).await;
|
||||||
tx.send(AsyncEvent::Call(result)).expect("Failed to send event");
|
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) {
|
fn start_list_objects(&self) {
|
||||||
@ -290,8 +307,10 @@ impl App {
|
|||||||
fn show_left_panel(&mut self, ui: &mut egui::Ui) {
|
fn show_left_panel(&mut self, ui: &mut egui::Ui) {
|
||||||
use egui::*;
|
use egui::*;
|
||||||
|
|
||||||
|
let mut match_exists = false;
|
||||||
ui.text_edit_singleline(&mut self.object_filter);
|
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() {
|
for obj in self.objects.iter() {
|
||||||
let shown_methods;
|
let shown_methods;
|
||||||
if obj.name.contains(&self.object_filter) {
|
if obj.name.contains(&self.object_filter) {
|
||||||
@ -305,6 +324,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if shown_methods.len() > 0 {
|
if shown_methods.len() > 0 {
|
||||||
|
match_exists = true;
|
||||||
let style = ui.style();
|
let style = ui.style();
|
||||||
let mut text = LayoutJob::default();
|
let mut text = LayoutJob::default();
|
||||||
text.append(&obj.name, 0.0, TextFormat {
|
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) {
|
fn show_right_panel(&mut self, ui: &mut egui::Ui) {
|
||||||
use egui::*;
|
use egui::*;
|
||||||
|
|
||||||
egui::ScrollArea::vertical()
|
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||||
.show(ui, |ui| {
|
ui.label("Address:");
|
||||||
ui.text_edit_singleline(&mut self.settings.address);
|
ui.text_edit_singleline(&mut self.settings.address);
|
||||||
// TODO: ui.text_edit_singleline(&mut self.port);
|
// TODO: ui.text_edit_singleline(&mut self.port);
|
||||||
|
ui.label("Username:");
|
||||||
ui.text_edit_singleline(&mut self.settings.username);
|
ui.text_edit_singleline(&mut self.settings.username);
|
||||||
|
ui.label("Password:");
|
||||||
ui.text_edit_singleline(&mut self.settings.password);
|
ui.text_edit_singleline(&mut self.settings.password);
|
||||||
|
ui.add_space(8.0);
|
||||||
if self.is_connecting {
|
if self.is_connecting {
|
||||||
ui.add_enabled(false, Button::new("Connecting..."));
|
ui.add_enabled(false, Button::new("Connecting..."));
|
||||||
} else if self.session.is_none() {
|
} else if self.session.is_none() {
|
||||||
@ -353,6 +391,21 @@ impl App {
|
|||||||
self.start_disconnect()
|
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,89 +459,96 @@ impl App {
|
|||||||
|
|
||||||
fn show_central_panel(&mut self, ui: &mut egui::Ui) {
|
fn show_central_panel(&mut self, ui: &mut egui::Ui) {
|
||||||
use egui::*;
|
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.horizontal(|ui| {
|
||||||
// Object dropdown
|
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("");
|
{
|
||||||
let object_combobox = egui::ComboBox::from_id_source("selected_object")
|
let object_name = self.selected_object.as_ref().map(|obj| obj.name.as_ref()).unwrap_or("");
|
||||||
.selected_text(object_name)
|
let object_combobox = egui::ComboBox::from_id_source("selected_object")
|
||||||
.width(200.0)
|
.selected_text(object_name)
|
||||||
.show_ui(ui, |ui| {
|
.width(200.0)
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
let mut selection = None;
|
||||||
|
for object in &self.objects {
|
||||||
|
ui.selectable_value(&mut selection, Some(object.clone()), &object.name);
|
||||||
|
}
|
||||||
|
return selection;
|
||||||
|
});
|
||||||
|
|
||||||
|
match object_combobox.inner {
|
||||||
|
Some(Some(object)) => {
|
||||||
|
self.selected_method = None;
|
||||||
|
self.selected_object = Some(object);
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method dropdown
|
||||||
|
{
|
||||||
|
let selected_method_name = self.selected_method.as_deref().unwrap_or("");
|
||||||
|
let method_combobox = ui.add_enabled_ui(self.selected_object.is_some(), |ui| {
|
||||||
let mut selection = None;
|
let mut selection = None;
|
||||||
for object in &self.objects {
|
egui::ComboBox::from_id_source("selected_method")
|
||||||
ui.selectable_value(&mut selection, Some(object.clone()), &object.name);
|
.selected_text(selected_method_name)
|
||||||
}
|
.width(200.0)
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
if let Some(object) = &self.selected_object {
|
||||||
|
for method in &object.methods {
|
||||||
|
let method_name = &method.0;
|
||||||
|
let mut label_response = ui.selectable_label(selected_method_name == method_name, method_name);
|
||||||
|
if label_response.clicked() && selected_method_name != method_name {
|
||||||
|
selection = Some(method_name.clone());
|
||||||
|
label_response.mark_changed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
return selection;
|
return selection;
|
||||||
});
|
});
|
||||||
|
|
||||||
match object_combobox.inner {
|
match (method_combobox.inner, &self.selected_object) {
|
||||||
Some(Some(object)) => {
|
(Some(method), Some(object)) => {
|
||||||
self.selected_method = None;
|
let method_params = object.methods.iter()
|
||||||
self.selected_object = Some(object);
|
.find(|(name, _)| name.eq(&method))
|
||||||
},
|
.map(|(_, params)| params)
|
||||||
_ => {}
|
.unwrap()
|
||||||
};
|
.iter()
|
||||||
}
|
.map(|(param_name, param_type)| (param_name.as_str(), *param_type))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
self.payload = App::create_default_payload(&method_params);
|
||||||
|
self.selected_method = Some(method);
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Method dropdown
|
if ui.add_enabled(call_enabled, Button::new("call")).clicked() {
|
||||||
{
|
|
||||||
let selected_method_name = self.selected_method.as_deref().unwrap_or("");
|
|
||||||
let method_combobox = ui.add_enabled_ui(self.selected_object.is_some(), |ui| {
|
|
||||||
let mut selection = None;
|
|
||||||
egui::ComboBox::from_id_source("selected_method")
|
|
||||||
.selected_text(selected_method_name)
|
|
||||||
.width(200.0)
|
|
||||||
.show_ui(ui, |ui| {
|
|
||||||
if let Some(object) = &self.selected_object {
|
|
||||||
for method in &object.methods {
|
|
||||||
let method_name = &method.0;
|
|
||||||
let mut label_response = ui.selectable_label(selected_method_name == method_name, method_name);
|
|
||||||
if label_response.clicked() && selected_method_name != method_name {
|
|
||||||
selection = Some(method_name.clone());
|
|
||||||
label_response.mark_changed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return selection;
|
|
||||||
});
|
|
||||||
|
|
||||||
match (method_combobox.inner, &self.selected_object) {
|
|
||||||
(Some(method), Some(object)) => {
|
|
||||||
let method_params = object.methods.iter()
|
|
||||||
.find(|(name, _)| name.eq(&method))
|
|
||||||
.map(|(_, params)| params)
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
|
||||||
.map(|(param_name, param_type)| (param_name.as_str(), *param_type))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
self.payload = App::create_default_payload(&method_params);
|
|
||||||
self.selected_method = Some(method);
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
let object_name = self.get_selected_object().unwrap().into();
|
let object_name = self.get_selected_object().unwrap().into();
|
||||||
let method_name = self.get_selected_method().unwrap().into();
|
let method_name = self.get_selected_method().unwrap().into();
|
||||||
let payload = self.get_payload().unwrap(); // TODO: handle parsing error
|
let payload = self.get_payload().unwrap(); // TODO: handle parsing error
|
||||||
self.start_call(object_name, method_name, Some(payload));
|
self.start_call(object_name, method_name, Some(payload));
|
||||||
// TODO: Block sending other requests
|
// 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() {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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_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));
|
||||||
|
ui.output_mut(|o| o.copied_text = cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if call_in_progress {
|
||||||
|
ui.spinner();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
@ -505,7 +565,8 @@ impl App {
|
|||||||
egui::CentralPanel::default()
|
egui::CentralPanel::default()
|
||||||
.show_inside(ui, |ui| {
|
.show_inside(ui, |ui| {
|
||||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||||
ui.add(
|
ui.add_enabled(
|
||||||
|
!call_in_progress,
|
||||||
egui::TextEdit::multiline(&mut self.payload)
|
egui::TextEdit::multiline(&mut self.payload)
|
||||||
.font(egui::TextStyle::Monospace) // for cursor height
|
.font(egui::TextStyle::Monospace) // for cursor height
|
||||||
.code_editor()
|
.code_editor()
|
||||||
@ -531,7 +592,7 @@ impl eframe::App for App {
|
|||||||
|
|
||||||
egui::SidePanel::right("right_panel")
|
egui::SidePanel::right("right_panel")
|
||||||
.resizable(true)
|
.resizable(true)
|
||||||
.width_range(100.0..=200.0)
|
.width_range(125.0..=200.0)
|
||||||
.show_inside(ui, |ui| self.show_right_panel(ui));
|
.show_inside(ui, |ui| self.show_right_panel(ui));
|
||||||
|
|
||||||
egui::CentralPanel::default()
|
egui::CentralPanel::default()
|
||||||
|
@ -29,9 +29,10 @@ fn main() {
|
|||||||
|
|
||||||
let mut app = App::default();
|
let mut app = App::default();
|
||||||
let mut options = eframe::NativeOptions::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");
|
let icon_data = load_icon_from_memory(include_bytes!("./icon.png")).expect("Failed to load icon data");
|
||||||
options.icon_data = Some(icon_data);
|
options.icon_data = Some(icon_data);
|
||||||
|
options.vsync = true;
|
||||||
|
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
"ubusman",
|
"ubusman",
|
||||||
|
Loading…
Reference in New Issue
Block a user