feat: reworked UI for size/spacing/resizing/responsivness/alignment/coherence on every panels.

feat: reduction in code dupplication related to UI
feat: update header of XvB tab
feat: upgrade egui to git main
feat: upgrade deps
This commit is contained in:
Cyrix126 2024-12-11 18:50:40 +01:00
parent 55b39cf2c3
commit 50b3336156
38 changed files with 3477 additions and 4101 deletions

601
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -31,8 +31,11 @@ benri = "0.1.12"
bytes = "1.8.0" bytes = "1.8.0"
dirs = "5.0.1" dirs = "5.0.1"
#-------------------------------------------------------------------------------- #--------------------------------------------------------------------------------
egui = "0.29.1" # egui = "0.29.1"
egui_extras = {version="0.29.1", features = ["image"] } egui = {git="https://github.com/emilk/egui"}
# egui_extras = {version="0.29.1", features = ["image"] }
egui_extras = {git="https://github.com/emilk/egui", features = ["image"] }
## 2023-12-28: https://github.com/hinto-janai/gupax/issues/68 ## 2023-12-28: https://github.com/hinto-janai/gupax/issues/68
## ##
## 2024-03-18: Both `glow` and `wgpu` seem to crash: ## 2024-03-18: Both `glow` and `wgpu` seem to crash:
@ -75,7 +78,8 @@ enclose = "1.2.0"
bounded-vec-deque = {version="0.1.1", default-features=false} bounded-vec-deque = {version="0.1.1", default-features=false}
cfg-if = "1.0" cfg-if = "1.0"
flexi_logger = "0.29" flexi_logger = "0.29"
eframe = {version="0.29.1", features=["wgpu"]} # eframe = {version="0.29.1", features=["wgpu"]}
eframe = {git="https://github.com/emilk/egui", features=["wgpu"]}
strum = {version="0.26", features=["derive"]} strum = {version="0.26", features=["derive"]}
# Unix dependencies # Unix dependencies
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
@ -97,7 +101,8 @@ sudo = "0.6.0"
# linked as well which causes problems, so statically link it. # linked as well which causes problems, so statically link it.
lzma-sys = { version = "0.1", features = ["static"] } lzma-sys = { version = "0.1", features = ["static"] }
[dev-dependencies] [dev-dependencies]
egui = {version="0.29.1", features=["callstack"]} # egui = {version="0.29.1", features=["callstack"]}
egui = {git="https://github.com/emilk/egui", features=["callstack"]}
# [target.'cfg(not(target_os = "macos"))'.dependencies] # [target.'cfg(not(target_os = "macos"))'.dependencies]
# tls-api-native-tls = "0.9.0" # tls-api-native-tls = "0.9.0"
@ -107,7 +112,7 @@ egui = {version="0.29.1", features=["callstack"]}
# glow start on windows but not wgpu # glow start on windows but not wgpu
# need the same version that eframe is using with egui_wgpu # need the same version that eframe is using with egui_wgpu
# feature angle to enable support for old cpu on Windows # feature angle to enable support for old cpu on Windows
wgpu = {version = "22.1", features=["angle"]} wgpu = {version = "23.0", features=["angle"]}
zip = "2.2.0" zip = "2.2.0"
is_elevated = "0.1.2" is_elevated = "0.1.2"

View file

@ -1,5 +1,13 @@
use crate::APP_DEFAULT_HEIGHT; use crate::APP_DEFAULT_HEIGHT;
use crate::APP_DEFAULT_WIDTH; use crate::APP_DEFAULT_WIDTH;
use crate::GUPAX_TAB_ABOUT;
use crate::GUPAX_TAB_GUPAX;
use crate::GUPAX_TAB_NODE;
use crate::GUPAX_TAB_P2POOL;
use crate::GUPAX_TAB_STATUS;
use crate::GUPAX_TAB_XMRIG;
use crate::GUPAX_TAB_XMRIG_PROXY;
use crate::GUPAX_TAB_XVB;
use crate::GUPAX_VERSION; use crate::GUPAX_VERSION;
use crate::OS; use crate::OS;
use crate::cli::Cli; use crate::cli::Cli;
@ -47,6 +55,7 @@ use log::debug;
use log::error; use log::error;
use log::info; use log::info;
use log::warn; use log::warn;
use panels::middle::common::list_poolnode::PoolNode;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use std::path::PathBuf; use std::path::PathBuf;
@ -87,10 +96,10 @@ pub struct App {
pub update: Arc<Mutex<Update>>, // State for update data [update.rs] pub update: Arc<Mutex<Update>>, // State for update data [update.rs]
pub file_window: Arc<Mutex<FileWindow>>, // State for the path selector in [Gupax] pub file_window: Arc<Mutex<FileWindow>>, // State for the path selector in [Gupax]
pub ping: Arc<Mutex<Ping>>, // Ping data found in [node.rs] pub ping: Arc<Mutex<Ping>>, // Ping data found in [node.rs]
pub og_node_vec: Vec<(String, Node)>, // Manual Node database pub og_node_vec: Vec<(String, PoolNode)>, // Manual Node database
pub node_vec: Vec<(String, Node)>, // Manual Node database pub node_vec: Vec<(String, PoolNode)>, // Manual Node database
pub og_pool_vec: Vec<(String, Pool)>, // Manual Pool database pub og_pool_vec: Vec<(String, PoolNode)>, // Manual Pool database
pub pool_vec: Vec<(String, Pool)>, // Manual Pool database pub pool_vec: Vec<(String, PoolNode)>, // Manual Pool database
pub diff: bool, // This bool indicates state changes pub diff: bool, // This bool indicates state changes
// Restart state: // Restart state:
// If Gupax updated itself, this represents that the // If Gupax updated itself, this represents that the
@ -136,7 +145,7 @@ pub struct App {
// Static stuff // Static stuff
pub benchmarks: Vec<Benchmark>, // XMRig CPU benchmarks pub benchmarks: Vec<Benchmark>, // XMRig CPU benchmarks
pub pid: sysinfo::Pid, // Gupax's PID pub pid: sysinfo::Pid, // Gupax's PID
pub max_threads: usize, // Max amount of detected system threads pub max_threads: u16, // Max amount of detected system threads
pub now: Instant, // Internal timer pub now: Instant, // Internal timer
pub exe: String, // Path for [Gupax] binary pub exe: String, // Path for [Gupax] binary
pub dir: String, // Directory [Gupax] binary is in pub dir: String, // Directory [Gupax] binary is in
@ -311,7 +320,7 @@ impl App {
pub_sys, pub_sys,
benchmarks, benchmarks,
pid, pid,
max_threads: benri::threads!(), max_threads: benri::threads!() as u16,
now, now,
admin: false, admin: false,
exe: String::new(), exe: String::new(),
@ -511,65 +520,83 @@ impl App {
} }
// Handle [node_vec] overflow // Handle [node_vec] overflow
info!("App Init | Handling [node_vec] overflow"); info!("App Init | Handling [node_vec] overflow");
if og.p2pool.selected_index > app.og_node_vec.len() { if og.p2pool.selected_node.index > app.og_node_vec.len() {
warn!( warn!(
"App | Overflowing manual node index [{} > {}]", "App | Overflowing manual node index [{} > {}]",
og.p2pool.selected_index, og.p2pool.selected_node.index,
app.og_node_vec.len() app.og_node_vec.len()
); );
let (name, node) = match app.og_node_vec.first() { let (name, node) = match app.og_node_vec.first() {
Some(zero) => zero.clone(), Some(zero) => zero.clone(),
None => Node::new_tuple(), None => Node::new_tuple(),
}; };
og.p2pool.selected_index = 0; og.p2pool.selected_node.index = 0;
og.p2pool.selected_name.clone_from(&name); og.p2pool.selected_node.name.clone_from(&name);
og.p2pool.selected_ip.clone_from(&node.ip); og.p2pool
og.p2pool.selected_rpc.clone_from(&node.rpc); .selected_node
og.p2pool.selected_zmq.clone_from(&node.zmq); .ip
app.state.p2pool.selected_index = 0; .clone_from(&node.ip().to_string());
app.state.p2pool.selected_name = name; og.p2pool
app.state.p2pool.selected_ip = node.ip; .selected_node
app.state.p2pool.selected_rpc = node.rpc; .rpc
app.state.p2pool.selected_zmq = node.zmq; .clone_from(&node.port().to_string());
og.p2pool
.selected_node
.zmq_rig
.clone_from(&node.custom().to_string());
app.state.p2pool.selected_node.index = 0;
app.state.p2pool.selected_node.name = name;
app.state.p2pool.selected_node.ip = node.ip().to_string();
app.state.p2pool.selected_node.rpc = node.port().to_string();
app.state.p2pool.selected_node.zmq_rig = node.custom().to_string();
} }
// Handle [pool_vec] overflow // Handle [pool_vec] overflow
info!("App Init | Handling [pool_vec] overflow..."); info!("App Init | Handling [pool_vec] overflow...");
if og.xmrig.selected_index > app.og_pool_vec.len() { if og.xmrig.selected_pool.index > app.og_pool_vec.len() {
warn!( warn!(
"App | Overflowing manual pool index [{} > {}], resetting to 1", "App | Overflowing manual pool index [{} > {}], resetting to 1",
og.xmrig.selected_index, og.xmrig.selected_pool.index,
app.og_pool_vec.len() app.og_pool_vec.len()
); );
let (name, pool) = match app.og_pool_vec.first() { let (name, pool) = match app.og_pool_vec.first() {
Some(zero) => zero.clone(), Some(zero) => zero.clone(),
None => Pool::new_tuple(), None => Pool::new_tuple(),
}; };
og.xmrig.selected_index = 0; og.xmrig.selected_pool.index = 0;
og.xmrig.selected_name.clone_from(&name); og.xmrig.selected_pool.name.clone_from(&name);
og.xmrig.selected_ip.clone_from(&pool.ip); og.xmrig.selected_pool.ip.clone_from(&pool.ip().to_string());
og.xmrig.selected_port.clone_from(&pool.port); og.xmrig
app.state.xmrig.selected_index = 0; .selected_pool
app.state.xmrig.selected_name = name; .rpc
app.state.xmrig.selected_ip = pool.ip; .clone_from(&pool.port().to_string());
app.state.xmrig.selected_port = pool.port; app.state.xmrig.selected_pool.index = 0;
if og.xmrig_proxy.selected_index > app.og_pool_vec.len() { app.state.xmrig.selected_pool.name = name;
app.state.xmrig.selected_pool.ip = pool.ip().to_string();
app.state.xmrig.selected_pool.rpc = pool.port().to_string();
if og.xmrig_proxy.selected_pool.index > app.og_pool_vec.len() {
warn!( warn!(
"App | Overflowing manual pool index [{} > {}], resetting to 1", "App | Overflowing manual pool index [{} > {}], resetting to 1",
og.xmrig_proxy.selected_index, og.xmrig_proxy.selected_pool.index,
app.og_pool_vec.len() app.og_pool_vec.len()
); );
let (name, pool) = match app.og_pool_vec.first() { let (name, pool) = match app.og_pool_vec.first() {
Some(zero) => zero.clone(), Some(zero) => zero.clone(),
None => Pool::new_tuple(), None => Pool::new_tuple(),
}; };
og.xmrig_proxy.selected_index = 0; og.xmrig_proxy.selected_pool.index = 0;
og.xmrig_proxy.selected_name.clone_from(&name); og.xmrig_proxy.selected_pool.name.clone_from(&name);
og.xmrig_proxy.selected_ip.clone_from(&pool.ip); og.xmrig_proxy
og.xmrig_proxy.selected_port.clone_from(&pool.port); .selected_pool
app.state.xmrig_proxy.selected_index = 0; .ip
app.state.xmrig_proxy.selected_name = name; .clone_from(&pool.ip().to_string());
app.state.xmrig_proxy.selected_ip = pool.ip; og.xmrig_proxy
app.state.xmrig_proxy.selected_port = pool.port; .selected_pool
.rpc
.clone_from(&pool.port().to_string());
app.state.xmrig_proxy.selected_pool.index = 0;
app.state.xmrig_proxy.selected_pool.name = name;
app.state.xmrig_proxy.selected_pool.ip = pool.ip().to_string();
app.state.xmrig_proxy.selected_pool.rpc = pool.port().to_string();
} }
} }
@ -648,7 +675,7 @@ impl App {
#[cold] #[cold]
#[inline(never)] #[inline(never)]
pub fn gather_backup_hosts(&self) -> Option<Vec<Node>> { pub fn gather_backup_hosts(&self) -> Option<Vec<PoolNode>> {
if !self.state.p2pool.backup_host { if !self.state.p2pool.backup_host {
return None; return None;
} }
@ -695,7 +722,7 @@ impl App {
zmq: zmq.into(), zmq: zmq.into(),
}; };
vec.push(node); vec.push(PoolNode::Node(node));
} }
if vec.is_empty() { if vec.is_empty() {
@ -747,6 +774,18 @@ impl Tab {
Tab::Xvb => Some(ProcessName::Xvb), Tab::Xvb => Some(ProcessName::Xvb),
} }
} }
pub fn msg_default_tab(&self) -> &str {
match self {
Tab::About => GUPAX_TAB_ABOUT,
Tab::Status => GUPAX_TAB_STATUS,
Tab::Gupax => GUPAX_TAB_GUPAX,
Tab::Node => GUPAX_TAB_NODE,
Tab::P2pool => GUPAX_TAB_P2POOL,
Tab::Xmrig => GUPAX_TAB_XMRIG,
Tab::XmrigProxy => GUPAX_TAB_XMRIG_PROXY,
Tab::Xvb => GUPAX_TAB_XVB,
}
}
} }
//---------------------------------------------------------------------------------------------------- [Restart] Enum //---------------------------------------------------------------------------------------------------- [Restart] Enum
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]

View file

@ -207,16 +207,30 @@ impl crate::app::App {
let restart_msg = format!("Restart {}", name); let restart_msg = format!("Restart {}", name);
if process.waiting { if process.waiting {
ui.add_enabled_ui(false, |ui| { ui.add_enabled_ui(false, |ui| {
ui.add_sized(size, Button::new("")) ui.add_sized(size, Button::new(""))
.on_disabled_hover_text(process.run_middle_msg()); .on_disabled_hover_text(process.run_middle_msg());
ui.add(Separator::default().grow(0.0)); ui.add(Separator::default().grow(0.0));
ui.add_sized(size, Button::new("")) ui.add_sized(size, Button::new(""))
.on_disabled_hover_text(process.run_middle_msg()); .on_disabled_hover_text(process.run_middle_msg());
ui.add(Separator::default().grow(0.0)); ui.add(Separator::default().grow(0.0));
ui.add_sized(size, Button::new("")) ui.add_sized(size, Button::new(""))
.on_disabled_hover_text(process.run_middle_msg()); .on_disabled_hover_text(process.run_middle_msg());
}); });
} else if process.alive { } else if process.alive {
ui.add_enabled_ui(false, |ui| {
ui.add_sized(size, Button::new(""))
.on_disabled_hover_text(start_msg)
});
ui.add(Separator::default().grow(0.0));
if key.is_down() && !wants_input
|| ui
.add_sized(size, Button::new(""))
.on_hover_text(stop_msg)
.clicked()
{
process.stop(&self.helper);
}
ui.add(Separator::default().grow(0.0));
if key.is_up() && !wants_input if key.is_up() && !wants_input
|| ui || ui
.add_sized(size, Button::new("")) .add_sized(size, Button::new(""))
@ -274,29 +288,7 @@ impl crate::app::App {
} }
} }
} }
ui.add(Separator::default().grow(0.0));
if key.is_down() && !wants_input
|| ui
.add_sized(size, Button::new(""))
.on_hover_text(stop_msg)
.clicked()
{
process.stop(&self.helper);
}
ui.add(Separator::default().grow(0.0));
ui.add_enabled_ui(false, |ui| {
ui.add_sized(size, Button::new(""))
.on_disabled_hover_text(start_msg)
});
} else { } else {
ui.add_enabled_ui(false, |ui| {
ui.add_sized(size, Button::new(""))
.on_disabled_hover_text(restart_msg);
ui.add(Separator::default().grow(0.0));
ui.add_sized(size, Button::new(""))
.on_disabled_hover_text(stop_msg);
ui.add(Separator::default().grow(0.0));
});
let text_err = self.start_ready(process).err().unwrap_or_default(); let text_err = self.start_ready(process).err().unwrap_or_default();
let ui_enabled = text_err.is_empty(); let ui_enabled = text_err.is_empty();
ui.add_enabled_ui(ui_enabled, |ui| { ui.add_enabled_ui(ui_enabled, |ui| {
@ -354,6 +346,14 @@ impl crate::app::App {
} }
} }
}); });
ui.add_enabled_ui(false, |ui| {
ui.add_sized(size, Button::new(""))
.on_disabled_hover_text(stop_msg);
ui.add(Separator::default().grow(0.0));
ui.add_sized(size, Button::new(""))
.on_disabled_hover_text(restart_msg);
ui.add(Separator::default().grow(0.0));
});
} }
}); });
} }

View file

@ -0,0 +1,73 @@
use std::sync::{Arc, Mutex};
use egui::{Label, TextEdit, TextStyle, Ui};
use crate::{DARK_GRAY, helper::Process, miscs::height_txt_before_button, regex::num_lines};
pub fn console(ui: &mut Ui, text: &str) {
let nb_lines = num_lines(text);
let height = ui.available_height() / 2.8;
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
ui.style_mut().override_text_style = Some(TextStyle::Small);
egui::ScrollArea::vertical()
.stick_to_bottom(true)
.max_width(ui.available_width())
.max_height(height)
.auto_shrink([false; 2])
// .show_viewport(ui, |ui, _| {
.show_rows(
ui,
ui.text_style_height(&TextStyle::Small),
nb_lines,
|ui, row_range| {
for i in row_range {
if let Some(line) = text.lines().nth(i) {
ui.label(line);
}
}
},
);
});
}
// input args
pub fn input_args_field(
ui: &mut Ui,
buffer: &mut String,
process: &Arc<Mutex<Process>>,
hint: &str,
hover: &str,
) {
ui.style_mut().spacing.text_edit_width = ui.available_width();
let response = ui
.add(TextEdit::hint_text(TextEdit::singleline(buffer), hint))
.on_hover_text(hover);
// If the user pressed enter, dump buffer contents into the process STDIN
if response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
response.request_focus(); // Get focus back
let buffer = std::mem::take(buffer); // Take buffer
let mut process = process.lock().unwrap();
if process.is_alive() {
process.input.push(buffer);
} // Push only if alive
}
}
// Command arguments
pub fn start_options_field(ui: &mut Ui, arguments: &mut String, hint: &str, hover: &str) {
ui.group(|ui| {
ui.horizontal(|ui| {
ui.add_sized(
[0.0, height_txt_before_button(ui, &TextStyle::Body)],
Label::new("Command arguments:"),
);
ui.style_mut().spacing.text_edit_width = ui.available_width();
ui.add(TextEdit::hint_text(TextEdit::singleline(arguments), hint))
.on_hover_text(hover);
arguments.truncate(1024);
})
});
if !arguments.is_empty() {
ui.disable();
}
}

View file

@ -0,0 +1,83 @@
use egui::{Hyperlink, Image, Separator, TextStyle, TextWrapMode, Ui};
use log::debug;
use crate::SPACE;
/// logo first, first hyperlink will be the header, description under.
/// will take care of centering widgets if boerder weight is more than 0.
#[allow(clippy::needless_range_loop)]
pub fn header_tab(
ui: &mut Ui,
logo: Option<Image>,
// text, link, hover text
links: &[(&str, &str, &str)],
subtitle: Option<String>,
one_line_center: bool,
) {
// width - logo and links and separators divided by double the size of logo (can't know width of links).
ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
ui.style_mut().override_text_style = Some(TextStyle::Heading);
ui.add_space(SPACE);
if one_line_center {
let height = 64.0;
let nb_links = links.len();
let border_weight = ((ui.available_width()
- ((height * 4.0 * nb_links as f32) + if logo.is_some() { height * 2.0 } else { 0.0 }))
/ (height * 2.0))
.max(0.0) as usize;
// nb_columns add logo if exist plus number of links with separator for each + number of column for border space
let nb_columns = if logo.is_some() { 1 } else { 0 } + (links.len() * 2) + border_weight * 2;
ui.columns(nb_columns, |col| {
// first column for left border
for n in 0..(border_weight) {
col[n].vertical_centered(|ui| ui.add_space(0.0));
debug!("left side space: {}", n);
}
// jump first column, stop less 2, because begin at 0 and end with space column.
let mut nb_col = border_weight;
if let Some(logo) = logo {
debug!("logo: {}", nb_col);
col[nb_col].vertical_centered(|ui| ui.add_sized([height, height], logo));
nb_col += 1;
}
for link in links {
debug!("separator: {}", nb_col);
col[nb_col].vertical_centered(|ui| {
ui.add_sized(
[height / 8.0, height],
Separator::default().vertical().spacing(height / 8.0),
)
});
nb_col += 1;
debug!("link: {}", nb_col);
col[nb_col].vertical_centered(|ui| {
ui.add_sized(
[ui.available_width(), height],
Hyperlink::from_label_and_url(link.0, link.1),
);
});
nb_col += 1;
}
for n in nb_col..(nb_col + border_weight) {
debug!("right side border space: {}", n);
col[n].vertical_centered(|ui| ui.add_space(0.0));
}
});
} else {
// top down
ui.vertical_centered(|ui| {
if let Some(source) = logo {
ui.add(source);
}
for link in links {
ui.hyperlink_to(link.0, link.1);
}
ui.style_mut().override_text_style = Some(TextStyle::Body);
});
}
if let Some(desc) = subtitle {
ui.label(desc);
}
ui.add_space(SPACE);
}

View file

@ -0,0 +1,330 @@
use egui::{Button, ComboBox, RichText, SelectableLabel, Ui};
use log::{debug, info};
use crate::{
LIST_ADD, LIST_CLEAR, LIST_DELETE, LIST_SAVE,
disk::{node::Node, pool::Pool, state::SelectedPoolNode},
};
#[derive(Clone, Debug, PartialEq)]
pub enum PoolNode {
Node(Node),
Pool(Pool),
}
impl PoolNode {
pub fn ip(&self) -> &str {
match &self {
PoolNode::Node(n) => &n.ip,
PoolNode::Pool(p) => &p.ip,
}
}
pub fn port(&self) -> &str {
match &self {
PoolNode::Node(n) => &n.rpc,
PoolNode::Pool(p) => &p.port,
}
}
pub fn custom(&self) -> &str {
match &self {
PoolNode::Node(n) => &n.zmq,
PoolNode::Pool(p) => &p.rig,
}
}
pub fn custom_name(&self) -> &str {
match &self {
PoolNode::Node(_) => "ZMQ",
PoolNode::Pool(_) => "rig",
}
}
fn set_ip(&mut self, new_ip: String) {
match self {
PoolNode::Node(n) => n.ip = new_ip,
PoolNode::Pool(p) => p.ip = new_ip,
}
}
fn set_port(&mut self, new_port: String) {
match self {
PoolNode::Node(n) => n.rpc = new_port,
PoolNode::Pool(p) => p.port = new_port,
}
}
fn set_custom(&mut self, new_custom: String) {
match self {
PoolNode::Node(n) => n.zmq = new_custom,
PoolNode::Pool(p) => p.rig = new_custom,
}
}
// pub fn from_vec_node(vec_node: Vec<(String, Node)>) -> Vec<(String, Self)> {
// vec_node
// .into_iter()
// .map(|(name, node)| (name, PoolNode::Node(node)))
// .collect()
// }
// pub fn from_vec_pool(vec_node: Vec<(String, Pool)>) -> Vec<(String, Self)> {
// vec_node
// .into_iter()
// .map(|(name, pool)| (name, PoolNode::Pool(pool)))
// .collect()
// }
}
/// compatible for P2Pool and Xmrig/Proxy
/// current is (name, ip, port, zmq/rig)
pub fn list_poolnode(
ui: &mut Ui,
current: &mut (&mut String, &mut String, &mut String, &mut String),
selected: &mut SelectedPoolNode,
node_vec: &mut Vec<(String, PoolNode)>,
incorrect_input: bool,
) {
ui.vertical(|ui| {
ui.spacing_mut().item_spacing.y = 0.0;
// ui.spacing_mut().button_padding.x = ui.available_width() / 2.0;
let width = ui.available_width();
// [Manual node selection]
// [Ping List]
debug!("P2Pool Tab | Rendering [Node List]");
// [Menu]
menu_list_node(ui, node_vec, width, selected, current);
let node_vec_len = node_vec.len();
// [Add/Save]
ui.horizontal(|ui| {
add_save_node(
ui,
selected,
node_vec,
current,
node_vec_len,
incorrect_input,
);
});
// [Delete]
ui.horizontal(|ui| {
delete_node(ui, selected, node_vec, current, node_vec_len);
});
// [Clear]
ui.horizontal(|ui| {
clear_node(ui, current);
});
});
}
// slider H/s
fn clear_node(ui: &mut Ui, current: &mut (&mut String, &mut String, &mut String, &mut String)) {
ui.add_enabled_ui(
!current.0.is_empty()
|| !current.1.is_empty()
|| !current.2.is_empty()
|| !current.3.is_empty(),
|ui| {
if ui
.add_sized([ui.available_width(), 0.0], Button::new("Clear"))
.on_hover_text(LIST_CLEAR)
.clicked()
{
current.0.clear();
current.1.clear();
current.2.clear();
current.3.clear();
}
},
);
}
fn menu_list_node(
ui: &mut Ui,
node_vec: &mut [(String, PoolNode)],
width: f32,
selected: &mut SelectedPoolNode,
current: &mut (&mut String, &mut String, &mut String, &mut String),
) {
let text = RichText::new(format!("{}. {}", selected.index + 1, selected.name));
ComboBox::from_id_salt("manual_nodes")
.selected_text(text)
.width(width)
.show_ui(ui, |ui| {
for (n, (name, node)) in node_vec.iter().enumerate() {
let text = RichText::new(format!(
"{}. {}\n IP: {}\n RPC: {}\n {}: {}",
n + 1,
name,
node.ip(),
node.port(),
node.custom_name(),
node.custom()
));
if ui
.add(SelectableLabel::new(selected.name == **name, text))
.clicked()
{
selected.index = n;
let node = node.clone();
selected.name.clone_from(name);
selected.ip.clone_from(&node.ip().to_string());
selected.rpc.clone_from(&node.port().to_string());
selected.zmq_rig.clone_from(&node.custom().to_string());
current.0.clone_from(name);
*current.1 = node.ip().to_string();
*current.2 = node.port().to_string();
*current.3 = node.custom().to_string();
}
}
});
}
fn add_save_node(
ui: &mut Ui,
selected: &mut SelectedPoolNode,
node_vec: &mut Vec<(String, PoolNode)>,
current: &mut (&mut String, &mut String, &mut String, &mut String),
node_vec_len: usize,
incorrect_input: bool,
) {
// list should never be empty unless state edited by hand.
let is_node = matches!(node_vec[0].1, PoolNode::Node(_));
// [Add/Save]
let mut exists = false;
let mut save_diff = true;
let mut existing_index = 0;
for (name, node) in node_vec.iter() {
if *name == *current.0 {
exists = true;
if *current.1 == node.ip() && *current.2 == node.port() && *current.3 == node.custom() {
save_diff = false;
}
break;
}
existing_index += 1;
}
let text = if exists { LIST_SAVE } else { LIST_ADD };
let text = format!(
"{}\n Currently selected node: {}. {}\n Current amount of {}: {}/1000",
text,
selected.index + 1,
selected.name,
if is_node { "nodes" } else { "pools" },
node_vec_len
);
// If the node already exists, show [Save] and mutate the already existing node
if exists {
ui.add_enabled_ui(!incorrect_input && save_diff, |ui| {
if ui
.add_sized([ui.available_width(), 0.0], Button::new("Save"))
.on_hover_text(text)
.clicked()
{
let ip = current.1.clone();
let rpc = current.2.clone();
// zmq can be rig in case of Pool
let zmq = current.3.clone();
let poolnode = &mut node_vec[existing_index].1;
poolnode.set_ip(ip);
poolnode.set_port(rpc);
poolnode.set_custom(zmq);
info!(
"Node | S | [index: {}, name: \"{}\", ip: \"{}\", rpc: {}, {}: {}]",
existing_index + 1,
current.0,
current.1,
current.2,
poolnode.custom_name(),
current.3
);
selected.index = existing_index;
selected.ip.clone_from(current.1);
selected.rpc.clone_from(current.2);
selected.zmq_rig.clone_from(current.3);
}
});
// Else, add to the list
} else {
ui.add_enabled_ui(!incorrect_input && node_vec_len < 1000, |ui| {
if ui
.add_sized([ui.available_width(), 0.0], Button::new("Add"))
.on_hover_text(text)
.clicked()
{
let ip = current.1.clone();
let rpc = current.2.clone();
// zmq can be rig in case of Pool
let zmq = current.3.clone();
let poolnode = match node_vec[existing_index].1 {
PoolNode::Node(_) => PoolNode::Node(Node { ip, rpc, zmq }),
PoolNode::Pool(_) => PoolNode::Pool(Pool {
rig: zmq,
ip,
port: rpc,
}),
};
info!(
"Node | A | [index: {}, name: \"{}\", ip: \"{}\", rpc: {}, {}: {}]",
node_vec_len,
current.0,
current.1,
current.2,
poolnode.custom_name(),
current.3
);
node_vec.push((current.0.clone(), poolnode));
selected.index = node_vec_len;
selected.name.clone_from(current.0);
selected.ip.clone_from(current.1);
selected.rpc.clone_from(current.2);
selected.zmq_rig.clone_from(current.3);
}
});
}
}
fn delete_node(
ui: &mut Ui,
selected: &mut SelectedPoolNode,
node_vec: &mut Vec<(String, PoolNode)>,
current: &mut (&mut String, &mut String, &mut String, &mut String),
node_vec_len: usize,
) {
ui.add_enabled_ui(node_vec_len > 1, |ui| {
let text = format!(
"{}\n Currently selected node: {}. {}\n Current amount of nodes: {}/1000",
LIST_DELETE,
selected.index + 1,
selected.name,
node_vec_len
);
if ui
.add_sized([ui.available_width(), 0.0], Button::new("Delete"))
.on_hover_text(text)
.clicked()
{
let new_name;
let new_node;
match selected.index {
0 => {
new_name = node_vec[1].0.clone();
new_node = node_vec[1].1.clone();
node_vec.remove(0);
}
_ => {
node_vec.remove(selected.index);
selected.index -= 1;
new_name = node_vec[selected.index].0.clone();
new_node = node_vec[selected.index].1.clone();
}
};
selected.name.clone_from(&new_name);
selected.ip = new_node.ip().to_string();
selected.rpc = new_node.port().to_string();
selected.zmq_rig = new_node.custom().to_string();
*current.0 = new_name;
*current.1 = new_node.ip().to_string();
*current.2 = new_node.port().to_string();
*current.3 = new_node.custom().to_string();
info!(
"Node | D | [index: {}, name: \"{}\", ip: \"{}\", port: {}, {}: {}]",
selected.index,
selected.name,
selected.ip,
selected.rpc,
new_node.custom_name(),
selected.zmq_rig
);
}
});
}

View file

@ -0,0 +1,4 @@
pub mod console;
pub mod header_tab;
pub mod list_poolnode;
pub mod state_edit_field;

View file

@ -0,0 +1,212 @@
use std::ops::RangeInclusive;
use std::sync::{Arc, Mutex};
use egui::{Color32, Label, RichText, Slider, TextEdit};
use egui::{TextStyle, Ui};
use crate::components::gupax::{FileType, FileWindow};
use crate::disk::state::Gupax;
use crate::miscs::height_txt_before_button;
use crate::regex::Regexes;
use crate::{
GREEN, GUPAX_SELECT, LIGHT_GRAY, NODE_DB_DIR, NODE_DB_PATH_EMPTY, NODE_PATH_OK, RED, SPACE,
};
pub fn slider_state_field(
ui: &mut Ui,
description: &str,
hover_msg: &str,
field: &mut u16,
range: RangeInclusive<u16>,
) {
ui.horizontal(|ui| {
ui.add_sized(
[0.0, height_txt_before_button(ui, &TextStyle::Body)],
Label::new(description),
);
// not sure what's the right calculation to make
ui.style_mut().spacing.slider_width = (ui.available_width()
- ui.spacing().item_spacing.x * 4.0
- ui.spacing().scroll.bar_width
- SPACE * 2.0
+ 2.0)
.max(80.0);
ui.add_sized(
[0.0, height_txt_before_button(ui, &TextStyle::Body)],
Slider::new(field, range),
)
.on_hover_text(hover_msg);
});
}
pub struct StateTextEdit<'a> {
description: &'a str,
max_ch: u8,
help_msg: &'a str,
validations: &'a [fn(&str) -> bool],
text_edit_width: f32,
text_style: TextStyle,
}
#[allow(unused)]
impl<'a> StateTextEdit<'a> {
pub fn new(ui: &Ui) -> Self {
StateTextEdit {
description: "",
max_ch: 30,
help_msg: "",
validations: &[],
text_edit_width: ui.text_style_height(&TextStyle::Body) * 18.0,
text_style: TextStyle::Body,
}
}
pub fn build(self, ui: &mut Ui, state_field: &mut String) -> bool {
let mut valid = false;
ui.horizontal_centered(|ui| {
let color;
let symbol;
let mut input_validated = true;
let len;
let inside_space;
for v in self.validations {
if !v(state_field) {
input_validated = false;
}
}
if state_field.is_empty() {
symbol = "";
color = Color32::LIGHT_GRAY;
} else if input_validated {
symbol = "";
color = Color32::from_rgb(100, 230, 100);
valid = true;
} else {
symbol = "";
color = Color32::from_rgb(230, 50, 50);
}
match self.max_ch {
x if x >= 100 => {
len = format!("{:03}", state_field.len());
inside_space = "";
}
10..99 => {
len = format!("{:02}", state_field.len());
inside_space = " ";
}
_ => {
len = format!("{}", state_field.len());
inside_space = " ";
}
}
let text = format!(
"{}[{}{}/{}{}]{}",
self.description, inside_space, len, self.max_ch, inside_space, symbol
);
ui.add_sized(
[0.0, height_txt_before_button(ui, &self.text_style)],
Label::new(RichText::new(text).color(color)),
);
// allocate the size to leave half of the total width free.
ui.spacing_mut().text_edit_width = self.text_edit_width;
ui.text_edit_singleline(state_field)
.on_hover_text(self.help_msg);
state_field.truncate(self.max_ch.into());
});
valid
}
pub fn description(mut self, description: &'a str) -> Self {
self.description = description;
self
}
pub fn max_ch(mut self, max_ch: u8) -> Self {
self.max_ch = max_ch;
self
}
pub fn help_msg(mut self, help_msg: &'a str) -> Self {
self.help_msg = help_msg;
self
}
pub fn validations(mut self, validations: &'a [fn(&str) -> bool]) -> Self {
self.validations = validations;
self
}
pub fn text_style(mut self, text_style: TextStyle) -> Self {
self.text_style = text_style;
self
}
pub fn text_edit_width(mut self, text_edit_width: f32) -> Self {
self.text_edit_width = text_edit_width;
self
}
pub fn text_edit_width_half_left(mut self, ui: &Ui) -> Self {
self.text_edit_width = ui.available_width() / 2.0;
self
}
pub fn text_edit_width_same_as_max_ch(mut self, ui: &Ui) -> Self {
self.text_edit_width = ui.text_style_height(&self.text_style) * self.max_ch as f32;
self
}
}
// path to choose
pub fn path_db_field(ui: &mut Ui, path: &mut String, file_window: &Arc<Mutex<FileWindow>>) {
ui.horizontal(|ui| {
let symbol;
let color;
let hover;
if path.is_empty() {
symbol = "";
color = LIGHT_GRAY;
hover = NODE_DB_PATH_EMPTY;
} else if !Gupax::path_is_dir(path) {
symbol = "";
color = RED;
hover = NODE_DB_DIR;
} else {
symbol = "";
color = GREEN;
hover = NODE_PATH_OK;
}
let text = ["Node Database Directory ", symbol].concat();
ui.add_sized(
[0.0, height_txt_before_button(ui, &TextStyle::Body)],
Label::new(RichText::new(text).color(color)),
);
let window_busy = file_window.lock().unwrap().thread;
ui.add_enabled_ui(!window_busy, |ui| {
if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() {
Gupax::spawn_file_window_thread(file_window, FileType::NodeDB);
}
ui.spacing_mut().text_edit_width = ui.available_width();
ui.text_edit_singleline(path).on_hover_text(hover);
});
});
}
pub fn monero_address_field(address: &mut String, ui: &mut Ui, hover: &str) {
ui.group(|ui| {
let text;
let color;
let len = format!("{:02}", address.len());
if address.is_empty() {
text = format!("Monero Address [{}/95] ", len);
color = Color32::LIGHT_GRAY;
} else if Regexes::addr_ok(address) {
text = format!("Monero Address [{}/95] ✔", len);
color = Color32::from_rgb(100, 230, 100);
} else {
text = format!("Monero Address [{}/95] ❌", len);
color = Color32::from_rgb(230, 50, 50);
}
ui.style_mut().spacing.text_edit_width = ui.available_width();
ui.vertical_centered(|ui| {
ui.label(RichText::new(text).color(color));
// ui.set_max_width(95.0 * 3.0);
ui.add_space(SPACE);
ui.add(
TextEdit::hint_text(TextEdit::singleline(address), "4...")
.horizontal_align(egui::Align::Center),
)
.on_hover_text(hover);
address.truncate(95);
});
});
}

View file

@ -5,10 +5,13 @@ use crate::components::gupax::*;
use crate::components::update::Update; use crate::components::update::Update;
use crate::components::update::check_binary_path; use crate::components::update::check_binary_path;
use crate::disk::state::*; use crate::disk::state::*;
use crate::miscs::height_txt_before_button;
use log::debug; use log::debug;
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
use strum::EnumCount;
use strum::IntoEnumIterator;
impl Gupax { impl Gupax {
#[inline(always)] // called once #[inline(always)] // called once
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -20,7 +23,6 @@ impl Gupax {
file_window: &Arc<Mutex<FileWindow>>, file_window: &Arc<Mutex<FileWindow>>,
error_state: &mut ErrorState, error_state: &mut ErrorState,
restart: &Arc<Mutex<Restart>>, restart: &Arc<Mutex<Restart>>,
size: Vec2,
_frame: &mut eframe::Frame, _frame: &mut eframe::Frame,
_ctx: &egui::Context, _ctx: &egui::Context,
ui: &mut egui::Ui, ui: &mut egui::Ui,
@ -28,33 +30,30 @@ impl Gupax {
) { ) {
// Update button + Progress bar // Update button + Progress bar
debug!("Gupaxx Tab | Rendering [Update] button + progress bar"); debug!("Gupaxx Tab | Rendering [Update] button + progress bar");
let height_font = ui.text_style_height(&TextStyle::Body);
egui::ScrollArea::vertical().show(ui, |ui| { egui::ScrollArea::vertical().show(ui, |ui| {
ui.style_mut().spacing.item_spacing = [height_font, height_font].into();
ui.group(|ui| { ui.group(|ui| {
let button = if self.simple {
size.y / 5.0
} else {
size.y / 15.0
};
let height = if self.simple {
size.y / 5.0
} else {
size.y / 10.0
};
let width = size.x - SPACE;
let updating = *update.lock().unwrap().updating.lock().unwrap(); let updating = *update.lock().unwrap().updating.lock().unwrap();
ui.vertical(|ui| { ui.vertical_centered(|ui| {
ui.add_space(height_font);
ui.style_mut().spacing.button_padding = ui.style().spacing.button_padding * 3.0;
// If [Gupax] is being built for a Linux distro, // If [Gupax] is being built for a Linux distro,
// disable built-in updating completely. // disable built-in updating completely.
#[cfg(feature = "distro")] #[cfg(feature = "distro")]
ui.disable(); ui.disable();
#[cfg(feature = "distro")] #[cfg(feature = "distro")]
ui.add_sized([width, button], Button::new("Updates are disabled")) // ui.add_sized([width, button], Button::new("Updates are disabled"))
// .on_disabled_hover_text(DISTRO_NO_UPDATE);
ui.button("Updates are disabled")
.on_disabled_hover_text(DISTRO_NO_UPDATE); .on_disabled_hover_text(DISTRO_NO_UPDATE);
#[cfg(not(feature = "distro"))] #[cfg(not(feature = "distro"))]
ui.add_enabled_ui(!updating && *restart.lock().unwrap() == Restart::No, |ui| { ui.add_enabled_ui(!updating && *restart.lock().unwrap() == Restart::No, |ui| {
#[cfg(not(feature = "distro"))] #[cfg(not(feature = "distro"))]
// if ui
// .add_sized([width, button], Button::new("Check for updates"))
if ui if ui
.add_sized([width, button], Button::new("Check for updates")) .button("Check for updates")
.on_hover_text(GUPAX_UPDATE) .on_hover_text(GUPAX_UPDATE)
.clicked() .clicked()
{ {
@ -68,8 +67,6 @@ impl Gupax {
); );
} }
}); });
});
ui.vertical(|ui| {
ui.add_enabled_ui(updating, |ui| { ui.add_enabled_ui(updating, |ui| {
let prog = *update.lock().unwrap().prog.lock().unwrap(); let prog = *update.lock().unwrap().prog.lock().unwrap();
let msg = format!( let msg = format!(
@ -78,479 +75,346 @@ impl Gupax {
prog, prog,
"%" "%"
); );
ui.add_sized([width, height * 1.4], Label::new(RichText::new(msg))); ui.label(msg);
let height = height / 2.0;
let size = vec2(width, height);
if updating { if updating {
ui.add_sized(size, Spinner::new().size(height)); ui.spinner();
} else { } else {
ui.add_sized(size, Label::new("...")); ui.label("...");
} }
ui.add_sized( ui.add(ProgressBar::new(
size, update.lock().unwrap().prog.lock().unwrap().round() / 100.0,
ProgressBar::new( ));
update.lock().unwrap().prog.lock().unwrap().round() / 100.0,
),
);
}); });
}); });
}); });
debug!("Gupaxx Tab | Rendering bool buttons"); // debug!("Gupaxx Tab | Rendering bool buttons");
ui.horizontal(|ui| { ui.group(|ui| {
ui.group(|ui| { ui.vertical_centered(|ui| {
egui::ScrollArea::horizontal().show(ui, |ui| { ui.add(Label::new(
let width = (size.x - SPACE * 17.0) / 8.0; RichText::new("Default Behaviour")
let height = if self.simple { .underline()
size.y / 10.0 .color(LIGHT_GRAY),
} else { ))
size.y / 15.0
};
let size = vec2(width, height);
ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
ui.add_sized(size, Checkbox::new(&mut self.auto_update, "Auto-Update"))
.on_hover_text(GUPAX_AUTO_UPDATE);
ui.separator();
ui.add_sized(size, Checkbox::new(&mut self.bundled, "Bundle"))
.on_hover_text(GUPAX_BUNDLED_UPDATE);
ui.separator();
ui.add_sized(size, Checkbox::new(&mut self.auto_node, "Auto-Node"))
.on_hover_text(GUPAX_AUTO_NODE);
ui.separator();
ui.add_sized(size, Checkbox::new(&mut self.auto_p2pool, "Auto-P2Pool"))
.on_hover_text(GUPAX_AUTO_P2POOL);
ui.separator();
ui.add_sized(size, Checkbox::new(&mut self.auto_xmrig, "Auto-XMRig"))
.on_hover_text(GUPAX_AUTO_XMRIG);
ui.separator();
ui.add_sized(size, Checkbox::new(&mut self.auto_xp, "Auto-Proxy"))
.on_hover_text(GUPAX_AUTO_XMRIG_PROXY);
ui.separator();
ui.add_sized(size, Checkbox::new(&mut self.auto_xvb, "Auto-XvB"))
.on_hover_text(GUPAX_AUTO_XVB);
ui.separator();
ui.add_sized(
size,
Checkbox::new(&mut self.ask_before_quit, "Confirm quit"),
)
.on_hover_text(GUPAX_ASK_BEFORE_QUIT);
ui.separator();
ui.add_sized(
size,
Checkbox::new(&mut self.save_before_quit, "Save on quit"),
)
.on_hover_text(GUPAX_SAVE_BEFORE_QUIT);
});
}); });
ui.separator();
self.horizontal_flex_auto_start(ui, AutoStart::ALL);
}); });
if self.simple { if self.simple {
return; return;
} }
debug!("Gupaxx Tab | Rendering P2Pool/XMRig path selection"); debug!("Gupaxx Tab | Rendering Node/P2Pool/XMRig/XMRig-Proxy path selection");
// P2Pool/XMRig binary path selection
// need to clone bool so file_window is not locked across a thread // need to clone bool so file_window is not locked across a thread
let window_busy = file_window.lock().unwrap().thread.to_owned(); let window_busy = file_window.lock().unwrap().thread.to_owned();
let height = size.y / 28.0;
let text_edit = (ui.available_width() / 10.0) - SPACE;
ui.group(|ui| { ui.group(|ui| {
ui.add_sized( ui.push_id(2, |ui| {
[ui.available_width(), height / 2.0], ui.vertical_centered(|ui| {
Label::new( ui.add(Label::new(
RichText::new("Node/P2Pool/XMRig/XMRig-Proxy PATHs") RichText::new("Node/P2Pool/XMRig/XMRig-Proxy PATHs")
.underline() .underline()
.color(LIGHT_GRAY), .color(LIGHT_GRAY),
), ))
) .on_hover_text("Gupaxx is online");
.on_hover_text("Gupaxx is online"); });
ui.separator(); ui.separator();
ui.horizontal(|ui| { ScrollArea::horizontal().show(ui, |ui| {
if self.node_path.is_empty() { ui.vertical(|ui| {
ui.add_sized( BundledProcess::iter().for_each(|name| {
[text_edit, height], path_binary(
Label::new(RichText::new("Node Binary Path ").color(LIGHT_GRAY)), self.path_binary(&name),
) name.process_name(),
.on_hover_text(NODE_PATH_EMPTY); ui,
} else if !Self::path_is_file(&self.node_path) { window_busy,
ui.add_sized( file_window,
[text_edit, height], )
Label::new(RichText::new("Node Binary Path ❌").color(RED)), });
) });
.on_hover_text(NODE_PATH_NOT_FILE);
} else if !check_binary_path(&self.node_path, ProcessName::Node) {
ui.add_sized(
[text_edit, height],
Label::new(RichText::new("Node Binary Path ❌").color(RED)),
)
.on_hover_text(NODE_PATH_NOT_VALID);
} else {
ui.add_sized(
[text_edit, height],
Label::new(RichText::new("Node Binary Path ✔").color(GREEN)),
)
.on_hover_text(NODE_PATH_OK);
}
ui.spacing_mut().text_edit_width = ui.available_width() - SPACE;
ui.add_enabled_ui(!window_busy, |ui| {
if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() {
Self::spawn_file_window_thread(file_window, FileType::Node);
}
ui.add_sized(
[ui.available_width(), height],
TextEdit::singleline(&mut self.node_path),
)
.on_hover_text(GUPAX_PATH_NODE);
});
});
ui.horizontal(|ui| {
if self.p2pool_path.is_empty() {
ui.add_sized(
[text_edit, height],
Label::new(RichText::new("P2Pool Binary Path ").color(LIGHT_GRAY)),
)
.on_hover_text(P2POOL_PATH_EMPTY);
} else if !Self::path_is_file(&self.p2pool_path) {
ui.add_sized(
[text_edit, height],
Label::new(RichText::new("P2Pool Binary Path ❌").color(RED)),
)
.on_hover_text(P2POOL_PATH_NOT_FILE);
} else if !check_binary_path(&self.p2pool_path, ProcessName::P2pool) {
ui.add_sized(
[text_edit, height],
Label::new(RichText::new("P2Pool Binary Path ❌").color(RED)),
)
.on_hover_text(P2POOL_PATH_NOT_VALID);
} else {
ui.add_sized(
[text_edit, height],
Label::new(RichText::new("P2Pool Binary Path ✔").color(GREEN)),
)
.on_hover_text(P2POOL_PATH_OK);
}
ui.spacing_mut().text_edit_width = ui.available_width() - SPACE;
ui.add_enabled_ui(!window_busy, |ui| {
if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() {
Self::spawn_file_window_thread(file_window, FileType::P2pool);
}
ui.add_sized(
[ui.available_width(), height],
TextEdit::singleline(&mut self.p2pool_path),
)
.on_hover_text(GUPAX_PATH_P2POOL);
});
});
ui.horizontal(|ui| {
if self.xmrig_path.is_empty() {
ui.add_sized(
[text_edit, height],
Label::new(RichText::new("XMRig Binary Path ").color(LIGHT_GRAY)),
)
.on_hover_text(XMRIG_PATH_EMPTY);
} else if !Self::path_is_file(&self.xmrig_path) {
ui.add_sized(
[text_edit, height],
Label::new(RichText::new("XMRig Binary Path ❌").color(RED)),
)
.on_hover_text(XMRIG_PATH_NOT_FILE);
} else if !check_binary_path(&self.xmrig_path, ProcessName::Xmrig) {
ui.add_sized(
[text_edit, height],
Label::new(RichText::new("XMRig Binary Path ❌").color(RED)),
)
.on_hover_text(XMRIG_PATH_NOT_VALID);
} else {
ui.add_sized(
[text_edit, height],
Label::new(RichText::new("XMRig Binary Path ✔").color(GREEN)),
)
.on_hover_text(XMRIG_PATH_OK);
}
ui.spacing_mut().text_edit_width = ui.available_width() - SPACE;
ui.add_enabled_ui(!window_busy, |ui| {
if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() {
Self::spawn_file_window_thread(file_window, FileType::Xmrig);
}
ui.add_sized(
[ui.available_width(), height],
TextEdit::singleline(&mut self.xmrig_path),
)
.on_hover_text(GUPAX_PATH_XMRIG);
});
});
ui.horizontal(|ui| {
if self.xmrig_proxy_path.is_empty() {
ui.add_sized(
[text_edit, height],
Label::new(
RichText::new("XMRig-Proxy Binary Path ").color(LIGHT_GRAY),
),
)
.on_hover_text(XMRIG_PROXY_PATH_EMPTY);
} else if !Self::path_is_file(&self.xmrig_proxy_path) {
ui.add_sized(
[text_edit, height],
Label::new(RichText::new("XMRig-Proxy Binary Path ❌").color(RED)),
)
.on_hover_text(XMRIG_PROXY_PATH_NOT_FILE);
} else if !crate::components::update::check_binary_path(
&self.xmrig_proxy_path,
ProcessName::XmrigProxy,
) {
ui.add_sized(
[text_edit, height],
Label::new(RichText::new("XMRig-Proxy Binary Path ❌").color(RED)),
)
.on_hover_text(XMRIG_PROXY_PATH_NOT_VALID);
} else {
ui.add_sized(
[text_edit, height],
Label::new(RichText::new("XMRig-Proxy Binary Path ✔").color(GREEN)),
)
.on_hover_text(XMRIG_PROXY_PATH_OK);
}
ui.spacing_mut().text_edit_width = ui.available_width() - SPACE;
ui.add_enabled_ui(!window_busy, |ui| {
if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() {
Self::spawn_file_window_thread(file_window, FileType::XmrigProxy);
}
ui.add_sized(
[ui.available_width(), height],
TextEdit::singleline(&mut self.xmrig_proxy_path),
)
.on_hover_text(GUPAX_PATH_XMRIG_PROXY);
}); });
}); });
let mut guard = file_window.lock().unwrap();
if guard.picked_p2pool {
self.p2pool_path.clone_from(&guard.p2pool_path);
guard.picked_p2pool = false;
}
if guard.picked_xmrig {
self.xmrig_path.clone_from(&guard.xmrig_path);
guard.picked_xmrig = false;
}
if guard.picked_xp {
self.xmrig_proxy_path.clone_from(&guard.xmrig_proxy_path);
guard.picked_xp = false;
}
if guard.picked_node {
self.node_path.clone_from(&guard.node_path);
guard.picked_node = false;
}
drop(guard);
}); });
let mut guard = file_window.lock().unwrap();
if guard.picked_p2pool {
self.p2pool_path.clone_from(&guard.p2pool_path);
guard.picked_p2pool = false;
}
if guard.picked_xmrig {
self.xmrig_path.clone_from(&guard.xmrig_path);
guard.picked_xmrig = false;
}
if guard.picked_xp {
self.xmrig_proxy_path.clone_from(&guard.xmrig_proxy_path);
guard.picked_xp = false;
}
if guard.picked_node {
self.node_path.clone_from(&guard.node_path);
guard.picked_node = false;
}
drop(guard);
let height = ui.available_height() / 6.0;
// Saved [Tab] // Saved [Tab]
debug!("Gupaxx Tab | Rendering [Tab] selector"); debug!("Gupaxx Tab | Rendering [Tab] selector");
ui.group(|ui| { ui.group(|ui| {
let width = (size.x / 7.0) - (SPACE * 1.93); ui.vertical_centered(|ui| {
let size = vec2(width, height); ui.add(Label::new(
ui.add_sized( RichText::new("Default Tab").underline().color(LIGHT_GRAY),
[ui.available_width(), height / 2.0], ))
Label::new(RichText::new("Default Tab").underline().color(LIGHT_GRAY)), .on_hover_text(GUPAX_TAB);
) });
.on_hover_text(GUPAX_TAB);
ui.separator(); ui.separator();
ui.horizontal(|ui| { ui.push_id(1, |ui| {
if ui ScrollArea::horizontal().show(ui, |ui| {
.add_sized(size, SelectableLabel::new(self.tab == Tab::About, "About")) ui.horizontal(|ui| {
.on_hover_text(GUPAX_TAB_ABOUT) let width = (ui.available_width() / Tab::COUNT as f32)
.clicked() - (ui.spacing().button_padding.y * 2.0
{ + ui.spacing().item_spacing.x)
self.tab = Tab::About; - SPACE;
} Tab::iter().enumerate().for_each(|(count, tab)| {
ui.separator(); if ui
if ui .add_sized(
.add_sized( [width, height_txt_before_button(ui, &TextStyle::Button)],
size, SelectableLabel::new(self.tab == tab, tab.to_string()),
SelectableLabel::new(self.tab == Tab::Status, "Status"), )
) .on_hover_text(tab.msg_default_tab())
.on_hover_text(GUPAX_TAB_STATUS) .clicked()
.clicked() {
{ self.tab = tab;
self.tab = Tab::Status; }
}
ui.separator(); if count + 1 != Tab::COUNT {
if ui ui.separator();
.add_sized(size, SelectableLabel::new(self.tab == Tab::Gupax, "Gupaxx")) }
.on_hover_text(GUPAX_TAB_GUPAX) })
.clicked() });
{ });
self.tab = Tab::Gupax; });
}
ui.separator();
if ui
.add_sized(size, SelectableLabel::new(self.tab == Tab::Node, "Node"))
.on_hover_text(GUPAX_TAB_NODE)
.clicked()
{
self.tab = Tab::Node;
}
ui.separator();
if ui
.add_sized(
size,
SelectableLabel::new(self.tab == Tab::P2pool, "P2Pool"),
)
.on_hover_text(GUPAX_TAB_P2POOL)
.clicked()
{
self.tab = Tab::P2pool;
}
ui.separator();
if ui
.add_sized(size, SelectableLabel::new(self.tab == Tab::Xmrig, "XMRig"))
.on_hover_text(GUPAX_TAB_XMRIG)
.clicked()
{
self.tab = Tab::Xmrig;
}
if ui
.add_sized(size, SelectableLabel::new(self.tab == Tab::Xvb, "XvB"))
.on_hover_text(GUPAX_TAB_XVB)
.clicked()
{
self.tab = Tab::Xvb;
}
})
}); });
// Gupax App resolution sliders // Gupax App resolution sliders
debug!("Gupaxx Tab | Rendering resolution sliders"); debug!("Gupaxx Tab | Rendering resolution sliders");
ui.group(|ui| { ui.group(|ui| {
ui.add_sized( ui.vertical_centered(|ui| {
[ui.available_width(), height / 2.0], ui.add(Label::new(
Label::new( RichText::new("Width/Height/Scaling Adjustment")
RichText::new("Width/Height Adjust")
.underline() .underline()
.color(LIGHT_GRAY), .color(LIGHT_GRAY),
), ))
) .on_hover_text(GUPAX_ADJUST);
.on_hover_text(GUPAX_ADJUST); ui.separator();
ui.separator();
ui.vertical(|ui| {
let width = size.x / 10.0;
ui.spacing_mut().icon_width = width / 25.0;
ui.spacing_mut().slider_width = width * 7.6;
match self.ratio {
Ratio::None => (),
Ratio::Width => {
let width = self.selected_width as f64;
let height = (width / 1.333).round();
self.selected_height = height as u16;
}
Ratio::Height => {
let height = self.selected_height as f64;
let width = (height * 1.333).round();
self.selected_width = width as u16;
}
}
let height = height / 3.5;
let size = vec2(width, height);
ui.horizontal(|ui| {
ui.add_enabled_ui(self.ratio != Ratio::Height, |ui| {
ui.add_sized(
size,
Label::new(format!(
" Width [{}-{}]:",
APP_MIN_WIDTH as u16, APP_MAX_WIDTH as u16
)),
);
ui.add_sized(
size,
Slider::new(
&mut self.selected_width,
APP_MIN_WIDTH as u16..=APP_MAX_WIDTH as u16,
),
)
.on_hover_text(GUPAX_WIDTH);
});
});
ui.horizontal(|ui| {
ui.add_enabled_ui(self.ratio != Ratio::Width, |ui| {
ui.add_sized(
size,
Label::new(format!(
"Height [{}-{}]:",
APP_MIN_HEIGHT as u16, APP_MAX_HEIGHT as u16
)),
);
ui.add_sized(
size,
Slider::new(
&mut self.selected_height,
APP_MIN_HEIGHT as u16..=APP_MAX_HEIGHT as u16,
),
)
.on_hover_text(GUPAX_HEIGHT);
});
});
ui.horizontal(|ui| {
ui.add_sized(
size,
Label::new(format!("Scaling [{APP_MIN_SCALE}..{APP_MAX_SCALE}]:")),
);
ui.add_sized(
size,
Slider::new(&mut self.selected_scale, APP_MIN_SCALE..=APP_MAX_SCALE)
.step_by(0.1),
)
.on_hover_text(GUPAX_SCALE);
});
}); });
ui.style_mut().override_text_style = Some(egui::TextStyle::Button);
ui.separator();
// Width/Height locks
ui.horizontal(|ui| { ui.horizontal(|ui| {
use Ratio::*; ScrollArea::horizontal().show(ui, |ui| {
let width = (size.x / 4.0) - (SPACE * 1.5); ui.vertical(|ui| {
let size = vec2(width, height); match self.ratio {
if ui Ratio::None => (),
.add_sized( Ratio::Width => {
size, let width = self.selected_width as f64;
SelectableLabel::new(self.ratio == Width, "Lock to width"), let height = (width / 1.333).round();
) self.selected_height = height as u16;
.on_hover_text(GUPAX_LOCK_WIDTH) }
.clicked() Ratio::Height => {
{ let height = self.selected_height as f64;
self.ratio = Width; let width = (height * 1.333).round();
} self.selected_width = width as u16;
ui.separator(); }
if ui }
.add_sized( // let height = height / 3.5;
size, // let size = vec2(width, height);
SelectableLabel::new(self.ratio == Height, "Lock to height"), ui.horizontal(|ui| {
) ui.add_enabled_ui(self.ratio != Ratio::Height, |ui| {
.on_hover_text(GUPAX_LOCK_HEIGHT) ui.label(format!(
.clicked() " Width [{}-{}]:",
{ APP_MIN_WIDTH as u16, APP_MAX_WIDTH as u16
self.ratio = Height; ));
} ui.add(Slider::new(
ui.separator(); &mut self.selected_width,
if ui APP_MIN_WIDTH as u16..=APP_MAX_WIDTH as u16,
.add_sized(size, SelectableLabel::new(self.ratio == None, "No lock")) ))
.on_hover_text(GUPAX_NO_LOCK) .on_hover_text(GUPAX_WIDTH);
.clicked() });
{ });
self.ratio = None; ui.horizontal(|ui| {
} ui.add_enabled_ui(self.ratio != Ratio::Width, |ui| {
if ui ui.label(format!(
.add_sized(size, Button::new("Set")) " Height [{}-{}]:",
.on_hover_text(GUPAX_SET) APP_MIN_HEIGHT as u16, APP_MAX_HEIGHT as u16
.clicked() ));
{ ui.add(Slider::new(
let size = &mut self.selected_height,
Vec2::new(self.selected_width as f32, self.selected_height as f32); APP_MIN_HEIGHT as u16..=APP_MAX_HEIGHT as u16,
ui.ctx() ))
.send_viewport_cmd(egui::viewport::ViewportCommand::InnerSize(size)); .on_hover_text(GUPAX_HEIGHT);
*must_resize = true; });
} });
}) ui.horizontal(|ui| {
ui.label(format!(" Scaling [{APP_MIN_SCALE}..{APP_MAX_SCALE}]:"));
ui.add(
Slider::new(
&mut self.selected_scale,
APP_MIN_SCALE..=APP_MAX_SCALE,
)
.step_by(0.1),
)
.on_hover_text(GUPAX_SCALE);
});
});
ui.style_mut().override_text_style = Some(egui::TextStyle::Button);
ui.separator();
// Width/Height locks
ui.vertical(|ui| {
use Ratio::*;
ui.horizontal(|ui| {
if ui
.selectable_label(self.ratio == Width, "Lock to width")
.on_hover_text(GUPAX_LOCK_WIDTH)
.clicked()
{
self.ratio = Width;
}
ui.separator();
if ui
.selectable_label(self.ratio == Height, "Lock to height")
.on_hover_text(GUPAX_LOCK_HEIGHT)
.clicked()
{
self.ratio = Height;
}
ui.separator();
if ui
.selectable_label(self.ratio == None, "No lock")
.on_hover_text(GUPAX_NO_LOCK)
.clicked()
{
self.ratio = None;
}
ui.separator();
if ui.button("Set").on_hover_text(GUPAX_SET).clicked() {
let size = Vec2::new(
self.selected_width as f32,
self.selected_height as f32,
);
ui.ctx().send_viewport_cmd(
egui::viewport::ViewportCommand::InnerSize(size),
);
*must_resize = true;
}
});
});
})
});
});
});
}
/// widget: AutoStart variant and selectable label (true) or checkbox (false)
pub fn horizontal_flex_auto_start(&mut self, ui: &mut Ui, auto_starts: &[AutoStart]) {
let text_style = TextStyle::Button;
// let height = ui.style().text_styles.get(&text_style).unwrap().size;
ui.style_mut().override_text_style = Some(text_style);
// width = (width - / number of tab) - (space between widget * 2.0 + space of separator / 2.0)
// ui.style_mut().spacing.item_spacing.x = 4.0;
let spacing = 2.0;
// ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| {
// ui.horizontal(|ui| {
ScrollArea::horizontal().show(ui, |ui| {
ui.with_layout(egui::Layout::left_to_right(egui::Align::Min), |ui| {
let width = (((ui.available_width()) / auto_starts.len() as f32)
- ((ui.style().spacing.item_spacing.x * 2.0) + (spacing / 2.0)))
.max(0.0);
// TODO: calculate minimum width needed, if ui.available width is less, show items on two lines, then on 3 etc..
// checkbox padding + item spacing + text + separator
let size = [width, 0.0];
let len = auto_starts.iter().len();
for (count, auto) in auto_starts.iter().enumerate() {
ui.horizontal(|ui| {
ui.vertical(|ui| {
ui.horizontal(|ui| {
let mut is_checked = self.auto.is_enabled(auto);
let widget = Checkbox::new(&mut is_checked, auto.to_string());
if ui
.add_sized(size, widget)
.on_hover_text(auto.help_msg())
.clicked()
{
self.auto.enable(auto, is_checked);
}
});
// add a space to prevent selectable button to be at the same line as the end of the top bar. Make it the same spacing as separators.
ui.add_space(spacing * 4.0);
});
if count + 1 != len {
ui.add(Separator::default().spacing(spacing).vertical());
}
});
}
}); });
}); });
} }
} }
fn path_binary(
path: &mut String,
name: ProcessName,
ui: &mut Ui,
window_busy: bool,
file_window: &Arc<Mutex<FileWindow>>,
) {
// align correctly even with different length of name by adapting the space just after.
let flex_space = " ".repeat(
ProcessName::iter()
.enumerate()
.max_by(|(_, a), (_, b)| {
a.to_string()
.len()
.partial_cmp(&b.to_string().len())
.expect("ProcessName should have values")
})
.expect("Iterator cant' be empty")
.1
.to_string()
.len()
- name.to_string().len()
+ 1,
);
let msg = format!(" {name}{flex_space}Binary Path");
// need to precise the height of text or there will be an misalignment with the button if it's bigger than the text.
let height =
(ui.style().spacing.button_padding.y * 2.0) + ui.text_style_height(&TextStyle::Body);
ui.horizontal(|ui| {
if path.is_empty() {
ui.add_sized(
[0.0, height],
Label::new(RichText::new(msg + " ").color(LIGHT_GRAY)),
)
.on_hover_text(name.msg_binary_path_empty());
} else if !Gupax::path_is_file(path) {
ui.add_sized(
[0.0, height],
Label::new(RichText::new(msg + "").color(RED)),
)
.on_hover_text(name.msg_binary_path_not_file());
} else if !check_binary_path(path, name) {
ui.add_sized(
[0.0, height],
Label::new(RichText::new(msg + "").color(RED)),
)
.on_hover_text(name.msg_binary_path_invalid());
} else {
ui.add_sized(
[0.0, height],
Label::new(RichText::new(msg + "").color(GREEN)),
)
.on_hover_text(name.msg_binary_path_ok());
}
ui.spacing_mut().text_edit_width = (ui.available_width() - SPACE).max(0.0);
ui.add_enabled_ui(!window_busy, |ui| {
if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() {
Gupax::spawn_file_window_thread(
file_window,
name.file_type()
.expect("XvB process should not be called in a function related to path"),
);
}
ui.text_edit_singleline(path)
.on_hover_text(name.msg_path_edit());
});
});
}

View file

@ -1,11 +1,15 @@
use crate::app::Tab; use crate::app::Tab;
use crate::app::eframe_impl::ProcessStatesGui; use crate::app::eframe_impl::ProcessStatesGui;
use crate::app::keys::KeyPressed; use crate::app::keys::KeyPressed;
use crate::components::gupax::FileWindow;
use crate::helper::ProcessName; use crate::helper::ProcessName;
use crate::regex::REGEXES;
use crate::utils::constants::*; use crate::utils::constants::*;
use common::state_edit_field::StateTextEdit;
use egui::*; use egui::*;
use log::debug; use log::debug;
mod about; mod about;
pub mod common;
mod gupax; mod gupax;
mod node; mod node;
mod p2pool; mod p2pool;
@ -47,7 +51,6 @@ impl crate::app::App {
self.max_threads, self.max_threads,
&self.gupax_p2pool_api, &self.gupax_p2pool_api,
&self.benchmarks, &self.benchmarks,
self.size,
ctx, ctx,
ui, ui,
); );
@ -62,7 +65,6 @@ impl crate::app::App {
&self.file_window, &self.file_window,
&mut self.error_state, &mut self.error_state,
&self.restart, &self.restart,
self.size,
frame, frame,
ctx, ctx,
ui, ui,
@ -76,7 +78,6 @@ impl crate::app::App {
&self.node, &self.node,
&self.node_api, &self.node_api,
&mut self.node_stdin, &mut self.node_stdin,
self.size,
&self.file_window, &self.file_window,
ui, ui,
); );
@ -91,7 +92,6 @@ impl crate::app::App {
&self.p2pool, &self.p2pool,
&self.p2pool_api, &self.p2pool_api,
&mut self.p2pool_stdin, &mut self.p2pool_stdin,
self.size,
ctx, ctx,
ui, ui,
); );
@ -104,7 +104,6 @@ impl crate::app::App {
&self.xmrig, &self.xmrig,
&self.xmrig_api, &self.xmrig_api,
&mut self.xmrig_stdin, &mut self.xmrig_stdin,
self.size,
ctx, ctx,
ui, ui,
); );
@ -117,7 +116,6 @@ impl crate::app::App {
&mut self.pool_vec, &mut self.pool_vec,
&self.xmrig_proxy_api, &self.xmrig_proxy_api,
&mut self.xmrig_proxy_stdin, &mut self.xmrig_proxy_stdin,
self.size,
ui, ui,
); );
} }
@ -125,7 +123,6 @@ impl crate::app::App {
debug!("App | Entering [XvB] Tab"); debug!("App | Entering [XvB] Tab");
crate::disk::state::Xvb::show( crate::disk::state::Xvb::show(
&mut self.state.xvb, &mut self.state.xvb,
self.size,
&self.state.p2pool.address, &self.state.p2pool.address,
ctx, ctx,
ui, ui,
@ -139,3 +136,49 @@ impl crate::app::App {
}); });
} }
} }
// Common widgets that will appears on multiple panels.
// header
// console
// sliders in/out peers/log
// menu node
// premade state edit field
// return boolean to know if the field input is validated.
fn rpc_port_field(field: &mut String, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" RPC PORT ")
.max_ch(5)
.help_msg(NODE_API_PORT)
.validations(&[|x| REGEXES.port.is_match(x)])
.build(ui, field)
}
fn zmq_port_field(field: &mut String, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" ZMQ PORT ")
.max_ch(5)
.help_msg(NODE_ZMQ_PORT)
.validations(&[|x| REGEXES.port.is_match(x)])
.build(ui, field)
}
fn rpc_bind_field(field: &mut String, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description("RPC BIND IP ")
.max_ch(255)
.help_msg(NODE_API_BIND)
.validations(&[|x| REGEXES.ipv4.is_match(x), |x| REGEXES.domain.is_match(x)])
.build(ui, field)
}
fn zmq_bind_field(field: &mut String, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description("API BIND IP ")
.max_ch(255)
.help_msg(NODE_ZMQ_BIND)
.validations(&[|x| REGEXES.ipv4.is_match(x), |x| REGEXES.domain.is_match(x)])
.build(ui, field)
}

View file

@ -1,21 +1,19 @@
use crate::app::panels::middle::common::console::{console, input_args_field, start_options_field};
use crate::app::panels::middle::common::state_edit_field::{path_db_field, slider_state_field};
use crate::app::panels::middle::{rpc_bind_field, rpc_port_field, zmq_bind_field, zmq_port_field};
use crate::{ use crate::{
GUPAX_SELECT, NODE_API_BIND, NODE_API_PORT, NODE_ARGUMENTS, NODE_DB_DIR, NODE_DB_PATH_EMPTY, NODE_ARGUMENTS, NODE_DNS_BLOCKLIST, NODE_DNS_CHECKPOINT, NODE_INPUT, NODE_PRUNNING, NODE_URL,
NODE_DNS_BLOCKLIST, NODE_DNS_CHECKPOINT, NODE_INPUT, NODE_PATH_OK, NODE_PRUNNING, NODE_URL,
NODE_ZMQ_BIND, NODE_ZMQ_PORT,
}; };
use egui::{Color32, Label, RichText, Slider, TextEdit, TextStyle, Ui, Vec2}; use egui::{Label, TextStyle};
use regex::Regex;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use log::debug; use log::debug;
use crate::components::gupax::{FileType, FileWindow}; use crate::components::gupax::FileWindow;
use crate::disk::state::{Gupax, Node}; use crate::disk::state::Node;
use crate::helper::Process; use crate::helper::Process;
use crate::helper::node::PubNodeApi; use crate::helper::node::PubNodeApi;
use crate::regex::{REGEXES, num_lines}; use crate::{P2POOL_IN, P2POOL_LOG, P2POOL_OUT, SPACE};
use crate::utils::constants::DARK_GRAY;
use crate::{GREEN, LIGHT_GRAY, P2POOL_IN, P2POOL_LOG, P2POOL_OUT, RED, SPACE};
impl Node { impl Node {
#[inline(always)] // called once #[inline(always)] // called once
@ -24,102 +22,47 @@ impl Node {
process: &Arc<Mutex<Process>>, process: &Arc<Mutex<Process>>,
api: &Arc<Mutex<PubNodeApi>>, api: &Arc<Mutex<PubNodeApi>>,
buffer: &mut String, buffer: &mut String,
size: Vec2,
file_window: &Arc<Mutex<FileWindow>>, file_window: &Arc<Mutex<FileWindow>>,
ui: &mut egui::Ui, ui: &mut egui::Ui,
) { ) {
let width = size.x; ui.style_mut().override_text_style = Some(TextStyle::Body);
let height = size.y; ui.vertical_centered(|ui| {
let space_h = height / 48.0; ui.add_space(SPACE);
let text_height = size.y / 25.0; ui.style_mut().override_text_style = Some(TextStyle::Heading);
let txt_description_width = size.x * 0.1; ui.hyperlink_to("Monerod", NODE_URL);
ui.style_mut().override_text_style = None;
ui.add(Label::new("C++ Monero Node"));
ui.add_space(SPACE);
});
// console output for log
debug!("Node Tab | Rendering [Console]");
egui::ScrollArea::vertical().show(ui, |ui| { egui::ScrollArea::vertical().show(ui, |ui| {
ui.vertical_centered(|ui| { let text = &api.lock().unwrap().output;
ui.add_space(space_h);
ui.style_mut().override_text_style = Some(TextStyle::Heading);
ui.hyperlink_to("Monerod", NODE_URL);
ui.style_mut().override_text_style = Some(TextStyle::Body);
ui.add(Label::new("C++ Monero Node"));
ui.add_space(space_h);
});
// console output for log
debug!("Node Tab | Rendering [Console]");
ui.group(|ui| { ui.group(|ui| {
let text = &api.lock().unwrap().output; console(ui, text);
let nb_lines = num_lines(text); if !self.simple {
let height = size.y / 2.8; ui.separator();
let width = (size.x - (space_h / 2.0)).max(0.0); input_args_field(
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| { ui,
ui.style_mut().override_text_style = Some(TextStyle::Small); buffer,
egui::ScrollArea::vertical() process,
.stick_to_bottom(true) r#"Commands: help, status, set_log <level>, diff"#,
.max_width(width) NODE_INPUT,
.max_height(height) );
.auto_shrink([false; 2]) }
// .show_viewport(ui, |ui, _| {
.show_rows(
ui,
ui.text_style_height(&TextStyle::Small),
nb_lines,
|ui, row_range| {
for i in row_range {
if let Some(line) = text.lines().nth(i) {
ui.label(line);
}
}
},
);
});
}); });
//---------------------------------------------------------------------------------------------------- [Advanced] Console //---------------------------------------------------------------------------------------------------- [Advanced] Console
if !self.simple { if !self.simple {
ui.separator();
let response = ui
.add_sized(
[width, text_height],
TextEdit::hint_text(
TextEdit::singleline(buffer),
r#"Commands: help, status, set_log <level>, diff"#,
),
)
.on_hover_text(NODE_INPUT);
// If the user pressed enter, dump buffer contents into the process STDIN
if response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
response.request_focus(); // Get focus back
let buffer = std::mem::take(buffer); // Take buffer
let mut process = process.lock().unwrap(); // Lock
if process.is_alive() {
process.input.push(buffer);
} // Push only if alive
}
//---------------------------------------------------------------------------------------------------- Arguments //---------------------------------------------------------------------------------------------------- Arguments
debug!("Node Tab | Rendering [Arguments]"); debug!("Node Tab | Rendering [Arguments]");
ui.group(|ui| { start_options_field(
ui.horizontal(|ui| { ui,
ui.add_sized( &mut self.arguments,
[txt_description_width, text_height], r#"--zmq-pub tcp://127.0.0.1:18081"#,
Label::new("Command arguments:"), NODE_ARGUMENTS,
); );
ui.add_sized(
[ui.available_width(), text_height],
TextEdit::hint_text(
TextEdit::singleline(&mut self.arguments),
r#"--zmq-pub tcp://127.0.0.1:18081"#,
),
)
.on_hover_text(NODE_ARGUMENTS);
self.arguments.truncate(1024);
})
});
if !self.arguments.is_empty() {
ui.disable();
}
//---------------------------------------------------------------------------------------------------- Prunned checkbox //---------------------------------------------------------------------------------------------------- Prunned checkbox
ui.add_space(space_h); ui.add_space(SPACE);
ui.style_mut().spacing.icon_width_inner = width / 45.0;
ui.style_mut().spacing.icon_width = width / 35.0;
ui.style_mut().spacing.icon_spacing = space_h;
debug!("Node Tab | Rendering DNS and Prunning buttons"); debug!("Node Tab | Rendering DNS and Prunning buttons");
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.group(|ui| { ui.group(|ui| {
@ -134,276 +77,69 @@ impl Node {
}); });
}); });
ui.add_space(space_h); ui.add_space(SPACE);
// idea // // idea
// need to warn the user if local firewall is blocking port // // need to warn the user if local firewall is blocking port
// need to warn the user if NAT is blocking port // // need to warn the user if NAT is blocking port
// need to show local ip address // // need to show local ip address
// need to show public ip // // need to show public ip
// text edit width is 4x bigger than description. Which makes half of the total width on screen less a space.
// (width - (width - ui.available_width()) - (ui.spacing().item_spacing.x * 4.5))
// / 2.0;
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.group(|ui| { egui::ScrollArea::horizontal().show(ui, |ui| {
ui.vertical(|ui| {
rpc_bind_field(self, ui, txt_description_width, text_height, width);
rpc_port_field(self, ui, txt_description_width, text_height, width);
ui.add_space(space_h);
zmq_bind_field(self, ui, txt_description_width, text_height, width);
zmq_port_field(self, ui, txt_description_width, text_height, width);
});
});
//---------------------------------------------------------------------------------------------------- In/Out peers
debug!("Node Tab | Rendering sliders elements");
ui.vertical(|ui| {
ui.group(|ui| { ui.group(|ui| {
ui.style_mut().override_text_style = Some(TextStyle::Small); ui.vertical(|ui| {
ui.horizontal(|ui| { rpc_bind_field(&mut self.api_ip, ui);
// ui.label("Out peers [10-450]:"); rpc_port_field(&mut self.api_port, ui);
ui.add_sized( ui.add_space(SPACE);
[txt_description_width, text_height], zmq_bind_field(&mut self.zmq_ip, ui);
Label::new("Out peers [2-450]:"), zmq_port_field(&mut self.zmq_port, ui);
);
// not sure what's the right calculation to make
ui.style_mut().spacing.slider_width = (ui.available_width()
- ui.spacing().item_spacing.x * 4.0
- ui.spacing().scroll.bar_width
- (SPACE * 2.0))
.max(0.0);
ui.add(Slider::new(&mut self.out_peers, 2..=450))
.on_hover_text(P2POOL_OUT);
// ui.add_space(ui.available_width() - 4.0);
}); });
ui.horizontal(|ui| { });
// ui.label("In peers [10-450]:");
ui.add_sized( //---------------------------------------------------------------------------------------------------- In/Out peers
[txt_description_width, text_height], debug!("Node Tab | Rendering sliders elements");
Label::new("In peers [2-450]:"), ui.vertical(|ui| {
ui.group(|ui| {
ui.add_space(SPACE);
slider_state_field(
ui,
"Out peers [2-450]:",
P2POOL_OUT,
&mut self.out_peers,
2..=450,
); );
ui.style_mut().spacing.slider_width = (ui.available_width() ui.add_space(SPACE);
- ui.spacing().item_spacing.x * 4.0 slider_state_field(
- ui.spacing().scroll.bar_width ui,
- (SPACE * 2.0)) "In peers [2-450]:",
.max(0.0); P2POOL_IN,
ui.add(Slider::new(&mut self.in_peers, 2..=450)) &mut self.in_peers,
.on_hover_text(P2POOL_IN); 2..=450,
});
ui.horizontal(|ui| {
// ui.label("Log level [ 0-4 ]:");
ui.add_sized(
[txt_description_width, text_height],
Label::new("Log level [ 0-4 ] :"),
); );
ui.style_mut().spacing.slider_width = (ui.available_width() ui.add_space(SPACE);
- ui.spacing().item_spacing.x * 4.0 slider_state_field(
- ui.spacing().scroll.bar_width ui,
- (SPACE * 2.0)) "Log level [ 0-4 ]:",
.max(0.0); P2POOL_LOG,
ui.add(Slider::new(&mut self.log_level, 0..=4)) &mut self.log_level,
.on_hover_text(P2POOL_LOG); 0..=6,
);
ui.add_space(SPACE);
}); });
}); });
}); });
}); });
//---------------------------------------------------------------------------------------------------- DB path //---------------------------------------------------------------------------------------------------- DB path
ui.add_space(space_h); ui.add_space(SPACE);
ui.group(|ui| { ui.group(|ui| {
path_db_field(self, ui, txt_description_width, text_height, file_window); path_db_field(ui, &mut self.path_db, file_window);
let mut guard = file_window.lock().unwrap();
if guard.picked_nodedb {
self.path_db.clone_from(&guard.nodedb_path);
guard.picked_nodedb = false;
}
}); });
ui.add_space(space_h); ui.add_space(SPACE);
} }
}); });
} }
} }
fn rpc_bind_field(
state: &mut Node,
ui: &mut Ui,
txt_description_width: f32,
text_height: f32,
width: f32,
) {
state_edit_field(
&mut state.api_ip,
ui,
txt_description_width,
text_height,
width,
"RPC BIND IP ",
255,
NODE_API_BIND,
vec![&REGEXES.ipv4, &REGEXES.domain],
);
}
fn rpc_port_field(
state: &mut Node,
ui: &mut Ui,
txt_description_width: f32,
text_height: f32,
width: f32,
) {
state_edit_field(
&mut state.api_port,
ui,
txt_description_width,
text_height,
width,
" RPC PORT ",
5,
NODE_API_PORT,
vec![&REGEXES.port],
);
}
fn zmq_bind_field(
state: &mut Node,
ui: &mut Ui,
txt_description_width: f32,
text_height: f32,
width: f32,
) {
state_edit_field(
&mut state.zmq_ip,
ui,
txt_description_width,
text_height,
width,
"API BIND IP ",
255,
NODE_ZMQ_BIND,
vec![&REGEXES.ipv4, &REGEXES.domain],
);
}
fn zmq_port_field(
state: &mut Node,
ui: &mut Ui,
txt_description_width: f32,
text_height: f32,
width: f32,
) {
state_edit_field(
&mut state.zmq_port,
ui,
txt_description_width,
text_height,
width,
" ZMQ PORT ",
5,
NODE_ZMQ_PORT,
vec![&REGEXES.port],
);
}
fn path_db_field(
state: &mut Node,
ui: &mut Ui,
txt_description_width: f32,
text_height: f32,
file_window: &Arc<Mutex<FileWindow>>,
) {
ui.horizontal(|ui| {
let symbol;
let color;
let hover;
if state.path_db.is_empty() {
symbol = "";
color = LIGHT_GRAY;
hover = NODE_DB_PATH_EMPTY;
} else if !Gupax::path_is_dir(&state.path_db) {
symbol = "";
color = RED;
hover = NODE_DB_DIR;
} else {
symbol = "";
color = GREEN;
hover = NODE_PATH_OK;
}
let text = ["Node Database Directory ", symbol].concat();
ui.add_sized(
[txt_description_width, text_height],
Label::new(RichText::new(text).color(color)),
);
ui.spacing_mut().text_edit_width =
(ui.available_width() - (ui.spacing().item_spacing.x * 8.0) - SPACE * 2.0).max(0.0);
let window_busy = file_window.lock().unwrap().thread;
ui.add_enabled_ui(!window_busy, |ui| {
if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() {
Gupax::spawn_file_window_thread(file_window, FileType::NodeDB);
}
ui.text_edit_singleline(&mut state.path_db)
.on_hover_text(hover);
});
});
let mut guard = file_window.lock().unwrap();
if guard.picked_nodedb {
state.path_db.clone_from(&guard.nodedb_path);
guard.picked_nodedb = false;
}
}
#[allow(clippy::too_many_arguments)]
fn state_edit_field(
state_field: &mut String,
ui: &mut Ui,
txt_description_width: f32,
text_height: f32,
width: f32,
description: &str,
max_ch: u8,
help_msg: &str,
validations: Vec<&Regex>,
) {
ui.horizontal(|ui| {
let color;
let symbol;
let mut input_validated = true;
let len;
let inside_space;
for v in validations {
if !v.is_match(state_field) {
input_validated = false;
}
}
if state_field.is_empty() {
symbol = "";
color = Color32::LIGHT_GRAY;
} else if input_validated {
symbol = "";
color = Color32::from_rgb(100, 230, 100);
} else {
symbol = "";
color = Color32::from_rgb(230, 50, 50);
}
match max_ch {
x if x >= 100 => {
len = format!("{:03}", state_field.len());
inside_space = "";
}
10..99 => {
len = format!("{:02}", state_field.len());
inside_space = " ";
}
_ => {
len = format!("{}", state_field.len());
inside_space = " ";
}
}
let text = format!(
"{}[{}{}/{}{}]{}",
description, inside_space, len, max_ch, inside_space, symbol
);
ui.add_sized(
[txt_description_width, text_height],
Label::new(RichText::new(text).color(color)),
);
// allocate the size to leave half of the total width free.
ui.spacing_mut().text_edit_width = ((width / 2.0)
- (width - ui.available_width() - ui.spacing().scroll.bar_width)
- ui.spacing().item_spacing.x * 2.5)
.max(0.0);
ui.text_edit_singleline(state_field).on_hover_text(help_msg);
state_field.truncate(max_ch.into());
});
}

View file

@ -1,313 +1,154 @@
use crate::disk::node::Node; use crate::app::panels::middle::common::list_poolnode::{PoolNode, list_poolnode};
use crate::app::panels::middle::common::state_edit_field::{StateTextEdit, slider_state_field};
use crate::miscs::height_txt_before_button;
use crate::{disk::state::P2pool, utils::regex::REGEXES}; use crate::{disk::state::P2pool, utils::regex::REGEXES};
use egui::Checkbox;
use egui::Slider;
use egui::{Button, Vec2};
use crate::constants::*; use crate::constants::*;
use egui::{Color32, ComboBox, Label, RichText, SelectableLabel, Ui}; use egui::{Checkbox, SelectableLabel, Ui};
use log::*; use log::*;
impl P2pool { impl P2pool {
pub(super) fn advanced( pub(super) fn advanced(&mut self, ui: &mut Ui, node_vec: &mut Vec<(String, PoolNode)>) {
&mut self, // let height = size.y / 16.0;
ui: &mut Ui, // let space_h = size.y / 128.0;
size: Vec2,
text_edit: f32,
node_vec: &mut Vec<(String, Node)>,
) {
let height = size.y / 16.0;
let space_h = size.y / 128.0;
debug!("P2Pool Tab | Rendering [Node List] elements"); debug!("P2Pool Tab | Rendering [Node List] elements");
let mut incorrect_input = false; // This will disable [Add/Delete] on bad input let mut incorrect_input = false; // This will disable [Add/Delete] on bad input
// [Monero node IP/RPC/ZMQ] // [Monero node IP/RPC/ZMQ]
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.group(|ui| { ui.group(|ui| {
let width = size.x/10.0; // let width = size.x/10.0;
ui.vertical(|ui| { ui.vertical(|ui| {
ui.spacing_mut().text_edit_width = width*3.32; if !self.name_field(ui) {
ui.horizontal(|ui| { incorrect_input = false;
let text; }
let color; if !self.ip_field(ui) {
let len = format!("{:02}", self.name.len()); incorrect_input = false;
if self.name.is_empty() { }
text = format!("Name [ {}/30 ]", len); if !self.rpc_port_field(ui) {
color = Color32::LIGHT_GRAY; incorrect_input = false;
incorrect_input = true; }
} else if REGEXES.name.is_match(&self.name) { if !self.zmq_port_field(ui) {
text = format!("Name [ {}/30 ]✔", len); incorrect_input = false;
color = Color32::from_rgb(100, 230, 100); }
} else { });
text = format!("Name [ {}/30 ]❌", len); list_poolnode(
color = Color32::from_rgb(230, 50, 50); ui,
incorrect_input = true; &mut (&mut self.name, &mut self.ip, &mut self.rpc, &mut self.zmq),
} &mut self.selected_node,
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color))); node_vec,
ui.text_edit_singleline(&mut self.name).on_hover_text(P2POOL_NAME); incorrect_input,
self.name.truncate(30); );
}); });
ui.horizontal(|ui| { });
let text;
let color;
let len = format!("{:03}", self.ip.len());
if self.ip.is_empty() {
text = format!(" IP [{}/255]", len);
color = Color32::LIGHT_GRAY;
incorrect_input = true;
} else if self.ip == "localhost" || REGEXES.ipv4.is_match(&self.ip) || REGEXES.domain.is_match(&self.ip) {
text = format!(" IP [{}/255]✔", len);
color = Color32::from_rgb(100, 230, 100);
} else {
text = format!(" IP [{}/255]❌", len);
color = Color32::from_rgb(230, 50, 50);
incorrect_input = true;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.ip).on_hover_text(P2POOL_NODE_IP);
self.ip.truncate(255);
});
ui.horizontal(|ui| {
let text;
let color;
let len = self.rpc.len();
if self.rpc.is_empty() {
text = format!(" RPC [ {}/5 ]", len);
color = Color32::LIGHT_GRAY;
incorrect_input = true;
} else if REGEXES.port.is_match(&self.rpc) {
text = format!(" RPC [ {}/5 ]✔", len);
color = Color32::from_rgb(100, 230, 100);
} else {
text = format!(" RPC [ {}/5 ]❌", len);
color = Color32::from_rgb(230, 50, 50);
incorrect_input = true;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.rpc).on_hover_text(P2POOL_RPC_PORT);
self.rpc.truncate(5);
});
ui.horizontal(|ui| {
let text;
let color;
let len = self.zmq.len();
if self.zmq.is_empty() {
text = format!(" ZMQ [ {}/5 ]", len);
color = Color32::LIGHT_GRAY;
incorrect_input = true;
} else if REGEXES.port.is_match(&self.zmq) {
text = format!(" ZMQ [ {}/5 ]✔", len);
color = Color32::from_rgb(100, 230, 100);
} else {
text = format!(" ZMQ [ {}/5 ]❌", len);
color = Color32::from_rgb(230, 50, 50);
incorrect_input = true;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.zmq).on_hover_text(P2POOL_ZMQ_PORT);
self.zmq.truncate(5);
});
});
ui.vertical(|ui| {
let width = ui.available_width();
ui.add_space(1.0);
// [Manual node selection]
ui.spacing_mut().slider_width = width - 8.0;
ui.spacing_mut().icon_width = width / 25.0;
// [Ping List]
debug!("P2Pool Tab | Rendering [Node List]");
let text = RichText::new(format!("{}. {}", self.selected_index+1, self.selected_name));
ComboBox::from_id_salt("manual_nodes").selected_text(text).width(width).show_ui(ui, |ui| {
for (n, (name, node)) in node_vec.iter().enumerate() {
let text = RichText::new(format!("{}. {}\n IP: {}\n RPC: {}\n ZMQ: {}", n+1, name, node.ip, node.rpc, node.zmq));
if ui.add(SelectableLabel::new(self.selected_name == *name, text)).clicked() {
self.selected_index = n;
let node = node.clone();
self.selected_name.clone_from(name);
self.selected_ip.clone_from(&node.ip);
self.selected_rpc.clone_from(&node.rpc);
self.selected_zmq.clone_from(&node.zmq);
self.name.clone_from(name);
self.ip = node.ip;
self.rpc = node.rpc;
self.zmq = node.zmq;
}
}
});
// [Add/Save]
let node_vec_len = node_vec.len();
let mut exists = false;
let mut save_diff = true;
let mut existing_index = 0;
for (name, node) in node_vec.iter() {
if *name == self.name {
exists = true;
if self.ip == node.ip && self.rpc == node.rpc && self.zmq == node.zmq {
save_diff = false;
}
break
}
existing_index += 1;
}
ui.horizontal(|ui| {
let text = if exists { LIST_SAVE } else { LIST_ADD };
let text = format!("{}\n Currently selected node: {}. {}\n Current amount of nodes: {}/1000", text, self.selected_index+1, self.selected_name, node_vec_len);
// If the node already exists, show [Save] and mutate the already existing node
if exists {
ui.add_enabled_ui(!incorrect_input && save_diff, |ui|{
if ui.add_sized([width, text_edit], Button::new("Save")).on_hover_text(text).clicked() {
let node = Node {
ip: self.ip.clone(),
rpc: self.rpc.clone(),
zmq: self.zmq.clone(),
};
node_vec[existing_index].1 = node;
self.selected_index = existing_index;
self.selected_ip.clone_from(&self.ip);
self.selected_rpc.clone_from(&self.rpc);
self.selected_zmq.clone_from(&self.zmq);
info!("Node | S | [index: {}, name: \"{}\", ip: \"{}\", rpc: {}, zmq: {}]", existing_index+1, self.name, self.ip, self.rpc, self.zmq);
}
});
// Else, add to the list
} else {
ui.add_enabled_ui(!incorrect_input && node_vec_len < 1000, |ui| {
if ui.add_sized([width, text_edit], Button::new("Add")).on_hover_text(text).clicked() {
let node = Node {
ip: self.ip.clone(),
rpc: self.rpc.clone(),
zmq: self.zmq.clone(),
};
node_vec.push((self.name.clone(), node));
self.selected_index = node_vec_len;
self.selected_name.clone_from(&self.name);
self.selected_ip.clone_from(&self.ip);
self.selected_rpc.clone_from(&self.rpc);
self.selected_zmq.clone_from(&self.zmq);
info!("Node | A | [index: {}, name: \"{}\", ip: \"{}\", rpc: {}, zmq: {}]", node_vec_len, self.name, self.ip, self.rpc, self.zmq);
}
});
}
});
// [Delete]
ui.horizontal(|ui| {
ui.add_enabled_ui(node_vec_len > 1, |ui|{
let text = format!("{}\n Currently selected node: {}. {}\n Current amount of nodes: {}/1000", LIST_DELETE, self.selected_index+1, self.selected_name, node_vec_len);
if ui.add_sized([width, text_edit], Button::new("Delete")).on_hover_text(text).clicked() {
let new_name;
let new_node;
match self.selected_index {
0 => {
new_name = node_vec[1].0.clone();
new_node = node_vec[1].1.clone();
node_vec.remove(0);
}
_ => {
node_vec.remove(self.selected_index);
self.selected_index -= 1;
new_name = node_vec[self.selected_index].0.clone();
new_node = node_vec[self.selected_index].1.clone();
}
};
self.selected_name.clone_from(&new_name);
self.selected_ip.clone_from(&new_node.ip);
self.selected_rpc.clone_from(&new_node.rpc);
self.selected_zmq.clone_from(&new_node.zmq);
self.name = new_name;
self.ip = new_node.ip;
self.rpc = new_node.rpc;
self.zmq = new_node.zmq;
info!("Node | D | [index: {}, name: \"{}\", ip: \"{}\", rpc: {}, zmq: {}]", self.selected_index, self.selected_name, self.selected_ip, self.selected_rpc, self.selected_zmq);
}
});
});
ui.horizontal(|ui| {
ui.add_enabled_ui(!self.name.is_empty() || !self.ip.is_empty() || !self.rpc.is_empty() || !self.zmq.is_empty(), |ui|{
if ui.add_sized([width, text_edit], Button::new("Clear")).on_hover_text(LIST_CLEAR).clicked() {
self.name.clear();
self.ip.clear();
self.rpc.clear();
self.zmq.clear();
}
});
});
});
});
});
// ui.add_space(space_h); // ui.add_space(space_h);
debug!("P2Pool Tab | Rendering [Main/Mini/Peers/Log] elements"); debug!("P2Pool Tab | Rendering [Main/Mini/Peers/Log] elements");
// [Main/Mini] // [Main/Mini]
ui.horizontal(|ui| { ui.horizontal(|ui| {
let height = height / 4.0; // let height = height / 4.0;
ui.group(|ui| { ui.group(|ui| {
ui.horizontal(|ui| { ui.vertical(|ui| {
let width = (size.x / 4.0) - SPACE; let height = height_txt_before_button(ui, &egui::TextStyle::Button) * 1.9;
let height = height + space_h; ui.horizontal(|ui| {
if ui let width = (ui.available_width() / 4.0) - SPACE;
.add_sized( if ui
[width, height], // if ui.add_sized(, )
SelectableLabel::new(!self.mini, "P2Pool Main"), // .selectable_label(!self.mini, "P2Pool Main")
.add_sized(
[width, height],
SelectableLabel::new(!self.mini, "P2Pool Main"),
)
.on_hover_text(P2POOL_MAIN)
.clicked()
{
self.mini = false;
}
if ui
// .selectable_label(!self.mini, "P2Pool Mini")
// if ui
.add_sized(
[width, height],
SelectableLabel::new(self.mini, "P2Pool Mini"),
)
.on_hover_text(P2POOL_MINI)
.clicked()
{
self.mini = true;
}
});
debug!("P2Pool Tab | Rendering Backup host button");
ui.group(|ui| {
// [Backup host]
ui.add_sized(
[(ui.available_width() / 2.0) - (SPACE * 2.0), height],
Checkbox::new(&mut self.backup_host, "Backup host"),
) )
.on_hover_text(P2POOL_MAIN) // ui.checkbox(&mut self.backup_host, "Backup host")
.clicked() .on_hover_text(P2POOL_BACKUP_HOST_ADVANCED);
{ });
self.mini = false; });
}
if ui
.add_sized(
[width, height],
SelectableLabel::new(self.mini, "P2Pool Mini"),
)
.on_hover_text(P2POOL_MINI)
.clicked()
{
self.mini = true;
}
})
}); });
// [Out/In Peers] + [Log Level] // [Out/In Peers] + [Log Level]
ui.group(|ui| { ui.group(|ui| {
ui.vertical(|ui| { ui.vertical(|ui| {
let text = (ui.available_width() / 10.0) - SPACE; ui.add_space(SPACE);
let width = (text * 8.0) - SPACE; slider_state_field(
let height = height / 3.0; ui,
ui.style_mut().spacing.slider_width = width / 1.1; "Out peers [2-450]:",
ui.style_mut().spacing.interact_size.y = height; P2POOL_OUT,
ui.style_mut().override_text_style = Some(egui::TextStyle::Small); &mut self.out_peers,
ui.horizontal(|ui| { 2..=450,
ui.add_sized([text, height], Label::new("Out peers [10-450]:")); );
ui.add_sized([width, height], Slider::new(&mut self.out_peers, 10..=450)) ui.add_space(SPACE);
.on_hover_text(P2POOL_OUT); slider_state_field(
ui.add_space(ui.available_width() - 4.0); ui,
}); "In peers [2-450]:",
ui.horizontal(|ui| { P2POOL_IN,
ui.add_sized([text, height], Label::new(" In peers [10-450]:")); &mut self.in_peers,
ui.add_sized([width, height], Slider::new(&mut self.in_peers, 10..=450)) 2..=450,
.on_hover_text(P2POOL_IN); );
}); ui.add_space(SPACE);
ui.horizontal(|ui| { slider_state_field(
ui.add_sized([text, height], Label::new(" Log level [0-6]:")); ui,
ui.add_sized([width, height], Slider::new(&mut self.log_level, 0..=6)) "Log level [ 0-6 ]:",
.on_hover_text(P2POOL_LOG); P2POOL_LOG,
}); &mut self.log_level,
0..=6,
);
}) })
}); });
}); });
}
debug!("P2Pool Tab | Rendering Backup host button"); fn name_field(&mut self, ui: &mut Ui) -> bool {
ui.group(|ui| { StateTextEdit::new(ui)
let width = size.x - SPACE; .description(" Name ")
let height = ui.available_height(); .max_ch(30)
ui.style_mut().spacing.icon_width = height; .help_msg(P2POOL_NAME)
ui.style_mut().spacing.icon_width_inner = height * 0.9; .validations(&[|x| REGEXES.name.is_match(x)])
// [Backup host] .build(ui, &mut self.name)
ui.add_sized( }
[width, height], fn rpc_port_field(&mut self, ui: &mut Ui) -> bool {
Checkbox::new(&mut self.backup_host, "Backup host"), StateTextEdit::new(ui)
) .description(" RPC PORT ")
.on_hover_text(P2POOL_BACKUP_HOST_ADVANCED); .max_ch(5)
}); .help_msg(P2POOL_RPC_PORT)
.validations(&[|x| REGEXES.port.is_match(x)])
.build(ui, &mut self.rpc)
}
fn zmq_port_field(&mut self, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" ZMQ PORT ")
.max_ch(5)
.help_msg(P2POOL_ZMQ_PORT)
.validations(&[|x| REGEXES.port.is_match(x)])
.build(ui, &mut self.zmq)
}
fn ip_field(&mut self, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" IP ")
.max_ch(255)
.help_msg(P2POOL_NODE_IP)
.validations(&[|x| REGEXES.ipv4.is_match(x), |x| REGEXES.domain.is_match(x)])
.build(ui, &mut self.ip)
} }
} }

View file

@ -1,7 +1,6 @@
use crate::disk::node::Node; use crate::app::panels::middle::common::console::{console, input_args_field, start_options_field};
use crate::disk::state::{P2pool, State}; use crate::disk::state::{P2pool, State};
use crate::helper::p2pool::PubP2poolApi; use crate::helper::p2pool::PubP2poolApi;
use crate::regex::num_lines;
// Gupax - GUI Uniting P2Pool And XMRig // Gupax - GUI Uniting P2Pool And XMRig
// //
// Copyright (c) 2022-2023 hinto-janai // Copyright (c) 2022-2023 hinto-janai
@ -18,12 +17,13 @@ use crate::regex::num_lines;
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{components::node::*, constants::*, helper::*, utils::regex::Regexes}; use crate::{components::node::*, constants::*, helper::*};
use egui::{Color32, Label, RichText, TextEdit, TextStyle, Vec2, vec2};
use log::*; use log::*;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use super::common::list_poolnode::PoolNode;
mod advanced; mod advanced;
mod simple; mod simple;
@ -32,143 +32,51 @@ impl P2pool {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn show( pub fn show(
&mut self, &mut self,
node_vec: &mut Vec<(String, Node)>, node_vec: &mut Vec<(String, PoolNode)>,
_og: &Arc<Mutex<State>>, _og: &Arc<Mutex<State>>,
ping: &Arc<Mutex<Ping>>, ping: &Arc<Mutex<Ping>>,
process: &Arc<Mutex<Process>>, process: &Arc<Mutex<Process>>,
api: &Arc<Mutex<PubP2poolApi>>, api: &Arc<Mutex<PubP2poolApi>>,
buffer: &mut String, buffer: &mut String,
size: Vec2,
_ctx: &egui::Context, _ctx: &egui::Context,
ui: &mut egui::Ui, ui: &mut egui::Ui,
) { ) {
let height = size.y;
let width = size.x;
let text_edit = size.y / 25.0;
//---------------------------------------------------------------------------------------------------- [Simple] Console //---------------------------------------------------------------------------------------------------- [Simple] Console
// debug!("P2Pool Tab | Rendering [Console]"); // debug!("P2Pool Tab | Rendering [Console]");
egui::ScrollArea::vertical().show(ui, |ui| { egui::ScrollArea::vertical().show(ui, |ui| {
let text = &api.lock().unwrap().output;
ui.group(|ui| { ui.group(|ui| {
let text = &api.lock().unwrap().output; console(ui, text);
let nb_lines = num_lines(text);
let (height, width) = if self.simple {
((size.y * 0.38) - SPACE, size.x - SPACE)
} else {
(
if size.y < 600.0 {
size.y * 0.22 - SPACE
} else {
size.y * 0.36 - SPACE
},
width - SPACE,
)
};
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
egui::ScrollArea::vertical()
.stick_to_bottom(true)
.max_width(width)
.max_height(height)
.auto_shrink([false; 2])
// .show_viewport(ui, |ui, _| {
.show_rows(
ui,
ui.text_style_height(&TextStyle::Small),
nb_lines,
|ui, row_range| {
for i in row_range {
if let Some(line) = text.lines().nth(i) {
ui.label(line);
}
}
},
);
});
if !self.simple { if !self.simple {
//---------------------------------------------------------------------------------------------------- [Advanced] Console
ui.separator(); ui.separator();
let response = ui input_args_field(
.add_sized( ui,
[width, text_edit], buffer,
TextEdit::hint_text( process,
TextEdit::singleline(buffer), r#"Type a command (e.g "help" or "status") and press Enter"#,
r#"Type a command (e.g "help" or "status") and press Enter"#, P2POOL_INPUT,
), );
)
.on_hover_text(P2POOL_INPUT);
// If the user pressed enter, dump buffer contents into the process STDIN
if response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
response.request_focus(); // Get focus back
let buffer = std::mem::take(buffer); // Take buffer
let mut process = process.lock().unwrap(); // Lock
if process.is_alive() {
process.input.push(buffer);
} // Push only if alive
}
} }
}); });
//---------------------------------------------------------------------------------------------------- Args
if !self.simple { if !self.simple {
debug!("P2Pool Tab | Rendering [Arguments]"); start_options_field(
ui.group(|ui| { ui,
ui.horizontal(|ui| { &mut self.arguments,
let width = (width / 10.0) - SPACE; r#"--wallet <...> --host <...>"#,
ui.add_sized([width, text_edit], Label::new("Command arguments:")); P2POOL_ARGUMENTS,
ui.add_sized(
[ui.available_width(), text_edit],
TextEdit::hint_text(
TextEdit::singleline(&mut self.arguments),
r#"--wallet <...> --host <...>"#,
),
)
.on_hover_text(P2POOL_ARGUMENTS);
self.arguments.truncate(1024);
})
});
if !self.arguments.is_empty() {
ui.disable()
}
}
//---------------------------------------------------------------------------------------------------- Address
debug!("P2Pool Tab | Rendering [Address]");
ui.group(|ui| {
let width = width - SPACE;
ui.spacing_mut().text_edit_width = (width) - (SPACE * 3.0);
let text;
let color;
let len = format!("{:02}", self.address.len());
if self.address.is_empty() {
text = format!("Monero Address [{}/95] ", len);
color = Color32::LIGHT_GRAY;
} else if Regexes::addr_ok(&self.address) {
text = format!("Monero Address [{}/95] ✔", len);
color = Color32::from_rgb(100, 230, 100);
} else {
text = format!("Monero Address [{}/95] ❌", len);
color = Color32::from_rgb(230, 50, 50);
}
ui.add_sized(
[width, text_edit],
Label::new(RichText::new(text).color(color)),
); );
ui.add_sized( }
[width, text_edit], debug!("P2Pool Tab | Rendering [Address]");
TextEdit::hint_text(TextEdit::singleline(&mut self.address), "4..."), crate::app::panels::middle::common::state_edit_field::monero_address_field(
) &mut self.address,
.on_hover_text(P2POOL_ADDRESS); ui,
self.address.truncate(95); P2POOL_ADDRESS,
}); );
// let height = ui.available_height();
let size = vec2(width, height);
if self.simple { if self.simple {
//---------------------------------------------------------------------------------------------------- Simple self.simple(ui, ping);
self.simple(ui, size, ping);
//---------------------------------------------------------------------------------------------------- Advanced
} else { } else {
self.advanced(ui, size, text_edit, node_vec); self.advanced(ui, node_vec);
} }
}); });
} }

View file

@ -1,47 +1,32 @@
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
use crate::app::panels::middle::Hyperlink;
use crate::app::panels::middle::ProgressBar; use crate::app::panels::middle::ProgressBar;
use crate::app::panels::middle::Spinner;
use crate::components::node::Ping; use crate::components::node::Ping;
use crate::components::node::RemoteNode; use crate::components::node::RemoteNode;
use crate::components::node::format_ip_location; use crate::components::node::format_ip_location;
use crate::components::node::format_ms; use crate::components::node::format_ms;
use crate::disk::state::P2pool; use crate::disk::state::P2pool;
use crate::miscs::height_txt_before_button;
use egui::Button; use egui::Button;
use egui::Checkbox; use egui::Checkbox;
use egui::Vec2; use egui::ScrollArea;
use egui::TextStyle;
use egui::TextWrapMode;
use egui::vec2; use egui::vec2;
use crate::constants::*; use crate::constants::*;
use egui::{Color32, ComboBox, Label, RichText, Ui}; use egui::{Color32, ComboBox, RichText, Ui};
use log::*; use log::*;
impl P2pool { impl P2pool {
pub(super) fn simple(&mut self, ui: &mut Ui, size: Vec2, ping: &Arc<Mutex<Ping>>) { pub(super) fn simple(&mut self, ui: &mut Ui, ping: &Arc<Mutex<Ping>>) {
// [Node]
let height = size.y / 13.0;
let space_h = size.y / 96.0;
ui.spacing_mut().slider_width = (size.x - 16.0).max(0.0);
ui.spacing_mut().icon_width = size.x / 25.0;
// [Auto-select] if we haven't already.
// Using [Arc<Mutex<Ping>>] as an intermediary here
// saves me the hassle of wrapping [state: State] completely
// and [.lock().unwrap()]ing it everywhere.
// Two atomic bools = enough to represent this data
// local or remote
// button bool
ui.vertical_centered(|ui|{ ui.vertical_centered(|ui|{
ui.add_space(space_h); ui.add_space(SPACE);
ui.checkbox(&mut self.local_node, "Use a local node").on_hover_text("If checked (recommended), p2pool will automatically use the local node.\nCheck the Node tab to start a local node.\nIf unchecked, p2pool will attempt to use a remote node."); ui.checkbox(&mut self.local_node, "Use a local node").on_hover_text("If checked (recommended), p2pool will automatically use the local node.\nCheck the Node tab to start a local node.\nIf unchecked, p2pool will attempt to use a remote node.");
}); });
ui.add_space(space_h * 2.0); ui.add_space(SPACE * 2.0);
// if checked, use only local node // if checked, use only local node
// if unchecked, show remote nodes. // if unchecked, show remote nodes.
// disable remote if local is checked. // disable remote if local is checked.
let visible = !self.local_node; let visible = !self.local_node;
debug!("P2Pool Tab | Running [auto-select] check"); debug!("P2Pool Tab | Running [auto-select] check");
@ -54,7 +39,6 @@ impl P2pool {
} }
drop(ping); drop(ping);
} }
ui.add_enabled_ui(visible, |ui| { ui.add_enabled_ui(visible, |ui| {
ui.vertical(|ui| { ui.vertical(|ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
@ -74,9 +58,11 @@ impl P2pool {
debug!("P2Pool Tab | Rendering [ComboBox] of Remote Nodes"); debug!("P2Pool Tab | Rendering [ComboBox] of Remote Nodes");
let ip_location = format_ip_location(&self.node, false); let ip_location = format_ip_location(&self.node, false);
let text = RichText::new(format!("{}ms | {}", ms, ip_location)).color(color); let text = RichText::new(format!("{}ms | {}", ms, ip_location)).color(color);
ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
ui.spacing_mut().item_spacing.y = 0.0;
ComboBox::from_id_salt("remote_nodes") ComboBox::from_id_salt("remote_nodes")
.selected_text(text) .selected_text(text)
.width(size.x) .width(ui.available_width())
.show_ui(ui, |ui| { .show_ui(ui, |ui| {
for data in ping.lock().unwrap().nodes.iter() { for data in ping.lock().unwrap().nodes.iter() {
let ms = format_ms(data.ms); let ms = format_ms(data.ms);
@ -87,123 +73,151 @@ impl P2pool {
} }
}); });
}); });
ui.add_space(SPACE);
ui.add_space(space_h);
debug!("P2Pool Tab | Rendering [Select fastest ... Ping] buttons"); debug!("P2Pool Tab | Rendering [Select fastest ... Ping] buttons");
ui.horizontal(|ui| { ScrollArea::horizontal()
let width = ((size.x / 5.0) - 6.0).max(0.0); .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden)
let size = vec2(width, height); .id_salt("horizontal")
// [Select random node] .show(ui, |ui| {
if ui ui.style_mut().override_text_style = Some(egui::TextStyle::Button);
.add_sized(size, Button::new("Select random node")) ui.horizontal(|ui| {
.on_hover_text(P2POOL_SELECT_RANDOM) ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
.clicked() // ui.columns_const(|[col1, col2, col3, col4, col5]| {
{ let width = ((ui.available_width() / 5.0)
self.node = RemoteNode::get_random(&self.node); - (ui.spacing().item_spacing.x * (4.0 / 5.0)))
} .max(20.0);
// [Select fastest node] let height = height_txt_before_button(ui, &TextStyle::Button) * 2.0;
if ui // [Select random node]
.add_sized(size, Button::new("Select fastest node")) ui.style_mut().override_text_valign = Some(egui::Align::Center);
.on_hover_text(P2POOL_SELECT_FASTEST) if ui
.clicked() .add_sized([width, height], Button::new("Select random node"))
&& ping.lock().unwrap().pinged .on_hover_text(P2POOL_SELECT_RANDOM)
{ .clicked()
self.node = ping.lock().unwrap().fastest.to_string(); {
} self.node = RemoteNode::get_random(&self.node);
// [Ping Button]
ui.add_enabled_ui(!ping.lock().unwrap().pinging, |ui| {
if ui
.add_sized(size, Button::new("Ping remote nodes"))
.on_hover_text(P2POOL_PING)
.clicked()
{
Ping::spawn_thread(ping);
}
});
// [Last <-]
if ui
.add_sized(size, Button::new("⬅ Last"))
.on_hover_text(P2POOL_SELECT_LAST)
.clicked()
{
let ping = ping.lock().unwrap();
match ping.pinged {
true => {
self.node = RemoteNode::get_last_from_ping(&self.node, &ping.nodes)
} }
false => self.node = RemoteNode::get_last(&self.node), // [Select fastest node]
} if ui
drop(ping); .add_sized([width, height], Button::new("Select fastest node"))
} .on_hover_text(P2POOL_SELECT_FASTEST)
// [Next ->] .clicked()
if ui && ping.lock().unwrap().pinged
.add_sized(size, Button::new("Next ➡")) {
.on_hover_text(P2POOL_SELECT_NEXT) self.node = ping.lock().unwrap().fastest.to_string();
.clicked()
{
let ping = ping.lock().unwrap();
match ping.pinged {
true => {
self.node = RemoteNode::get_next_from_ping(&self.node, &ping.nodes)
} }
false => self.node = RemoteNode::get_next(&self.node), // [Ping Button]
} ui.add_enabled_ui(!ping.lock().unwrap().pinging, |ui| {
drop(ping); if ui
} .add_sized([width, height], Button::new("Ping remote nodes"))
}); .on_hover_text(P2POOL_PING)
.clicked()
{
Ping::spawn_thread(ping);
}
});
// [Last <-]
if ui
.add_sized([width, height], Button::new("⬅ Last"))
.on_hover_text(P2POOL_SELECT_LAST)
.clicked()
{
let ping = ping.lock().unwrap();
match ping.pinged {
true => {
self.node =
RemoteNode::get_last_from_ping(&self.node, &ping.nodes)
}
false => self.node = RemoteNode::get_last(&self.node),
}
drop(ping);
}
// [Next ->]
if ui
.add_sized([width, height], Button::new("Next ➡"))
.on_hover_text(P2POOL_SELECT_NEXT)
.clicked()
{
let ping = ping.lock().unwrap();
match ping.pinged {
true => {
self.node =
RemoteNode::get_next_from_ping(&self.node, &ping.nodes)
}
false => self.node = RemoteNode::get_next(&self.node),
}
drop(ping);
}
});
ui.vertical(|ui| { ui.vertical_centered(|ui| {
let height = height / 2.0; // let height = height / 2.0;
let pinging = ping.lock().unwrap().pinging; let pinging = ping.lock().unwrap().pinging;
ui.add_enabled_ui(pinging, |ui| { ui.add_enabled_ui(pinging, |ui| {
let prog = ping.lock().unwrap().prog.round(); let prog = ping.lock().unwrap().prog.round();
let msg = let msg = RichText::new(format!(
RichText::new(format!("{} ... {}%", ping.lock().unwrap().msg, prog)); "{} ... {}%",
let height = height / 1.25; ping.lock().unwrap().msg,
let size = vec2(size.x, height); prog
ui.add_space(space_h); ));
ui.add_sized(size, Label::new(msg)); // let height = height / 1.25;
ui.add_space(space_h); // let size = vec2(size.x, height);
if pinging { ui.add_space(SPACE);
ui.add_sized(size, Spinner::new().size(height)); ui.label(msg);
} else { ui.add_space(SPACE);
ui.add_sized(size, Label::new("...")); if pinging {
} ui.spinner();
ui.add_sized(size, ProgressBar::new(prog.round() / 100.0)); } else {
ui.add_space(space_h); ui.label("...");
}
ui.add(ProgressBar::new(prog.round() / 100.0));
ui.add_space(SPACE);
});
});
debug!("P2Pool Tab | Rendering [Auto-*] buttons");
ui.group(|ui| {
ui.horizontal(|ui| {
let width =
(((ui.available_width() - ui.spacing().item_spacing.x) / 3.0)
- SPACE * 1.5)
.max(ui.text_style_height(&TextStyle::Button) * 7.0);
let size = vec2(
width,
height_txt_before_button(ui, &TextStyle::Button) * 2.0,
);
// [Auto-node]
ui.add_sized(
size,
Checkbox::new(&mut self.auto_select, "Auto-select"),
)
// ui.checkbox(&mut self.auto_select, "Auto-select")
.on_hover_text(P2POOL_AUTO_SELECT);
ui.separator();
// [Auto-node]
ui.add_sized(size, Checkbox::new(&mut self.auto_ping, "Auto-ping"))
// ui.checkbox(&mut self.auto_ping, "Auto-ping")
.on_hover_text(P2POOL_AUTO_NODE);
ui.separator();
// [Backup host]
ui.add_sized(
size,
Checkbox::new(&mut self.backup_host, "Backup host"),
)
// ui.checkbox(&mut self.backup_host, "Backup host")
.on_hover_text(P2POOL_BACKUP_HOST_SIMPLE);
})
});
}); });
});
}); });
debug!("P2Pool Tab | Rendering [Auto-*] buttons");
ui.group(|ui| {
ui.horizontal(|ui| {
let width = ((size.x / 3.0) - (SPACE * 1.75)).max(0.0);
let size = vec2(width, height);
// [Auto-node]
ui.add_sized(size, Checkbox::new(&mut self.auto_select, "Auto-select"))
.on_hover_text(P2POOL_AUTO_SELECT);
ui.separator();
// [Auto-node]
ui.add_sized(size, Checkbox::new(&mut self.auto_ping, "Auto-ping"))
.on_hover_text(P2POOL_AUTO_NODE);
ui.separator();
// [Backup host]
ui.add_sized(size, Checkbox::new(&mut self.backup_host, "Backup host"))
.on_hover_text(P2POOL_BACKUP_HOST_SIMPLE);
})
});
debug!("P2Pool Tab | Rendering warning text"); debug!("P2Pool Tab | Rendering warning text");
ui.add_sized( ui.add_space(SPACE);
[size.x, height / 2.0], ui.vertical_centered(|ui| {
Hyperlink::from_label_and_url( ui.hyperlink_to(
"WARNING: It is recommended to run/use your own Monero Node (hover for details)", "WARNING: It is recommended to run/use your own Monero Node (hover for details)",
"https://github.com/Cyrix126/gupaxx#running-a-local-monero-node", "https://github.com/Cyrix126/gupaxx#running-a-local-monero-node",
), )
) .on_hover_text(P2POOL_COMMUNITY_NODE_WARNING);
.on_hover_text(P2POOL_COMMUNITY_NODE_WARNING); });
}); });
} }
} }

View file

@ -1,7 +1,7 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::{app::Benchmark, disk::state::Status, helper::xrig::xmrig::PubXmrigApi}; use crate::{app::Benchmark, disk::state::Status, helper::xrig::xmrig::PubXmrigApi};
use egui::{Hyperlink, ProgressBar, ScrollArea, Spinner, Vec2}; use egui::{ProgressBar, ScrollArea, TextWrapMode};
use egui_extras::{Column, TableBuilder}; use egui_extras::{Column, TableBuilder};
use readable::num::{Float, Percent, Unsigned}; use readable::num::{Float, Percent, Unsigned};
@ -11,85 +11,56 @@ use log::*;
impl Status { impl Status {
pub(super) fn benchmarks( pub(super) fn benchmarks(
&mut self, &mut self,
size: Vec2,
ui: &mut egui::Ui, ui: &mut egui::Ui,
benchmarks: &[Benchmark], benchmarks: &[Benchmark],
xmrig_alive: bool, xmrig_alive: bool,
xmrig_api: &Arc<Mutex<PubXmrigApi>>, xmrig_api: &Arc<Mutex<PubXmrigApi>>,
) { ) {
debug!("Status Tab | Rendering [Benchmarks]"); debug!("Status Tab | Rendering [Benchmarks]");
let text = size.y / 20.0; let text = ui.text_style_height(&egui::TextStyle::Body);
let double = text * 2.0; let double = text * 2.0;
let log = size.y / 3.0;
let width = size.x; let width = ui.available_width();
// [0], The user's CPU (most likely). // [0], The user's CPU (most likely).
let cpu = &benchmarks[0]; let cpu = &benchmarks[0];
ui.horizontal(|ui| { ui.horizontal(|ui| {
let width = (width / 2.0) - (SPACE * 1.666);
let min_height = log;
ui.group(|ui| { ui.group(|ui| {
ui.vertical(|ui| { ui.set_max_width(ui.available_width() / 2.0);
ui.set_min_height(min_height); ui.vertical_centered(|ui| {
ui.add_sized( ui.label(RichText::new("Your CPU").underline().color(BONE))
[width, text], .on_hover_text(STATUS_SUBMENU_YOUR_CPU);
Label::new(RichText::new("Your CPU").underline().color(BONE)), ui.label(cpu.cpu.as_str());
) ui.label(RichText::new("Total Banchmarks").underline().color(BONE))
.on_hover_text(STATUS_SUBMENU_YOUR_CPU); .on_hover_text(STATUS_SUBMENU_YOUR_BENCHMARKS);
ui.add_sized([width, text], Label::new(cpu.cpu.as_str())); ui.label(format!("{}", cpu.benchmarks));
ui.add_sized( // ui.add_sized([width, text], Label::new(format!("{}", cpu.benchmarks)));
[width, text], ui.label(RichText::new("Rank").underline().color(BONE))
Label::new(RichText::new("Total Benchmarks").underline().color(BONE)), .on_hover_text(STATUS_SUBMENU_YOUR_RANK);
) ui.label(format!("{}/{}", cpu.rank, &benchmarks.len()));
.on_hover_text(STATUS_SUBMENU_YOUR_BENCHMARKS);
ui.add_sized([width, text], Label::new(format!("{}", cpu.benchmarks)));
ui.add_sized(
[width, text],
Label::new(RichText::new("Rank").underline().color(BONE)),
)
.on_hover_text(STATUS_SUBMENU_YOUR_RANK);
ui.add_sized(
[width, text],
Label::new(format!("{}/{}", cpu.rank, &benchmarks.len())),
);
}) })
}); });
ui.group(|ui| { ui.group(|ui| {
ui.vertical(|ui| { ui.vertical_centered(|ui| {
ui.set_min_height(min_height); ui.label(RichText::new("High Hashrate").underline().color(BONE))
ui.add_sized( .on_hover_text(STATUS_SUBMENU_YOUR_HIGH);
[width, text], ui.label(format!("{} H/s", Float::from_0(cpu.high.into())));
Label::new(RichText::new("High Hashrate").underline().color(BONE)), ui.label(RichText::new("Average Hashrate").underline().color(BONE))
) .on_hover_text(STATUS_SUBMENU_YOUR_AVERAGE);
.on_hover_text(STATUS_SUBMENU_YOUR_HIGH); ui.label(format!("{} H/s", Float::from_0(cpu.average.into())));
ui.add_sized( ui.label(RichText::new("Low Hashrate").underline().color(BONE))
[width, text], .on_hover_text(STATUS_SUBMENU_YOUR_LOW);
Label::new(format!("{} H/s", Float::from_0(cpu.high.into()))), ui.label(RichText::new(format!(
); "{} H/s",
ui.add_sized( Float::from_0(cpu.low.into())
[width, text], )));
Label::new(RichText::new("Average Hashrate").underline().color(BONE)),
)
.on_hover_text(STATUS_SUBMENU_YOUR_AVERAGE);
ui.add_sized(
[width, text],
Label::new(format!("{} H/s", Float::from_0(cpu.average.into()))),
);
ui.add_sized(
[width, text],
Label::new(RichText::new("Low Hashrate").underline().color(BONE)),
)
.on_hover_text(STATUS_SUBMENU_YOUR_LOW);
ui.add_sized(
[width, text],
Label::new(format!("{} H/s", Float::from_0(cpu.low.into()))),
);
}) })
}) })
}); });
// User's CPU hashrate comparison (if XMRig is alive). // User's CPU hashrate comparison (if XMRig is alive).
ui.scope(|ui| { // User's CPU hashrate comparison (if XMRig is alive).
ui.vertical_centered(|ui| {
ui.add_space(SPACE);
if xmrig_alive { if xmrig_alive {
let api = xmrig_api.lock().unwrap(); let api = xmrig_api.lock().unwrap();
let percent = (api.hashrate_raw / cpu.high) * 100.0; let percent = (api.hashrate_raw / cpu.high) * 100.0;
@ -102,11 +73,11 @@ impl Status {
human, api.hashrate human, api.hashrate
)), )),
); );
ui.add_sized([width, text], ProgressBar::new(1.0)); ui.add(ProgressBar::new(1.0));
} else if api.hashrate_raw == 0.0 { } else if api.hashrate_raw == 0.0 {
ui.add_sized([width, text], Label::new("Measuring hashrate...")); ui.label("Measuring hashrate...");
ui.add_sized([width, text], Spinner::new().size(text)); ui.spinner();
ui.add_sized([width, text], ProgressBar::new(0.0)); ui.add(ProgressBar::new(0.0));
} else { } else {
ui.add_sized( ui.add_sized(
[width, double], [width, double],
@ -115,7 +86,7 @@ impl Status {
human, api.hashrate human, api.hashrate
)), )),
); );
ui.add_sized([width, text], ProgressBar::new(percent / 100.0)); ui.add(ProgressBar::new(percent / 100.0));
} }
} else { } else {
ui.add_enabled_ui(xmrig_alive, |ui| { ui.add_enabled_ui(xmrig_alive, |ui| {
@ -123,22 +94,21 @@ impl Status {
[width, double], [width, double],
Label::new("XMRig is offline. Hashrate cannot be determined."), Label::new("XMRig is offline. Hashrate cannot be determined."),
); );
ui.add_sized([width, text], ProgressBar::new(0.0)); ui.add(ProgressBar::new(0.0));
}); });
} }
ui.add_space(SPACE);
// Comparison
ui.group(|ui| {
ui.hyperlink_to("Other CPUs", "https://xmrig.com/benchmark")
.on_hover_text(STATUS_SUBMENU_OTHER_CPUS);
});
}); });
// Comparison // Comparison
ui.group(|ui| {
ui.add_sized(
[width, text],
Hyperlink::from_label_and_url("Other CPUs", "https://xmrig.com/benchmark"),
)
.on_hover_text(STATUS_SUBMENU_OTHER_CPUS);
});
let width_column = width / 20.0; let width_column = width / 20.0;
let (cpu, bar, high, average, low, rank, bench) = ( let (cpu, bar, high, average, low, rank, bench) = (
width_column * 10.0, width_column * 6.0,
width_column * 3.0, width_column * 3.0,
width_column * 2.0, width_column * 2.0,
width_column * 2.0, width_column * 2.0,
@ -146,6 +116,7 @@ impl Status {
width_column, width_column,
width_column * 2.0, width_column * 2.0,
); );
ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
ScrollArea::horizontal().show(ui, |ui| { ScrollArea::horizontal().show(ui, |ui| {
TableBuilder::new(ui) TableBuilder::new(ui)
.columns(Column::auto(), 7) .columns(Column::auto(), 7)

View file

@ -15,8 +15,6 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
use egui::Vec2;
use crate::{ use crate::{
app::{Benchmark, eframe_impl::ProcessStatesGui}, app::{Benchmark, eframe_impl::ProcessStatesGui},
disk::{gupax_p2pool_api::GupaxP2poolApi, state::Status, status::*}, disk::{gupax_p2pool_api::GupaxP2poolApi, state::Status, status::*},
@ -51,10 +49,9 @@ impl Status {
p2pool_img: &Arc<Mutex<ImgP2pool>>, p2pool_img: &Arc<Mutex<ImgP2pool>>,
xmrig_img: &Arc<Mutex<ImgXmrig>>, xmrig_img: &Arc<Mutex<ImgXmrig>>,
states: &ProcessStatesGui, states: &ProcessStatesGui,
max_threads: usize, max_threads: u16,
gupax_p2pool_api: &Arc<Mutex<GupaxP2poolApi>>, gupax_p2pool_api: &Arc<Mutex<GupaxP2poolApi>>,
benchmarks: &[Benchmark], benchmarks: &[Benchmark],
size: Vec2,
_ctx: &egui::Context, _ctx: &egui::Context,
ui: &mut egui::Ui, ui: &mut egui::Ui,
) { ) {
@ -62,7 +59,6 @@ impl Status {
if self.submenu == Submenu::Processes { if self.submenu == Submenu::Processes {
self.processes( self.processes(
sys, sys,
size,
ui, ui,
node_api, node_api,
p2pool_api, p2pool_api,
@ -77,7 +73,6 @@ impl Status {
//---------------------------------------------------------------------------------------------------- [P2Pool] //---------------------------------------------------------------------------------------------------- [P2Pool]
} else if self.submenu == Submenu::P2pool { } else if self.submenu == Submenu::P2pool {
self.p2pool( self.p2pool(
size,
ui, ui,
gupax_p2pool_api, gupax_p2pool_api,
states.is_alive(ProcessName::P2pool), states.is_alive(ProcessName::P2pool),
@ -86,7 +81,6 @@ impl Status {
//---------------------------------------------------------------------------------------------------- [Benchmarks] //---------------------------------------------------------------------------------------------------- [Benchmarks]
} else if self.submenu == Submenu::Benchmarks { } else if self.submenu == Submenu::Benchmarks {
self.benchmarks( self.benchmarks(
size,
ui, ui,
benchmarks, benchmarks,
states.is_alive(ProcessName::Xmrig), states.is_alive(ProcessName::Xmrig),

View file

@ -1,7 +1,8 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use egui::{Label, RichText, SelectableLabel, Slider, TextEdit, Vec2}; use egui::{Label, RichText, ScrollArea, SelectableLabel, Separator, Slider, TextStyle};
use readable::num::Unsigned; use readable::num::Unsigned;
use strum::{EnumCount, IntoEnumIterator};
use crate::{ use crate::{
disk::{ disk::{
@ -16,445 +17,345 @@ use crate::{
impl Status { impl Status {
pub fn p2pool( pub fn p2pool(
&mut self, &mut self,
size: Vec2,
ui: &mut egui::Ui, ui: &mut egui::Ui,
gupax_p2pool_api: &Arc<Mutex<GupaxP2poolApi>>, gupax_p2pool_api: &Arc<Mutex<GupaxP2poolApi>>,
p2pool_alive: bool, p2pool_alive: bool,
p2pool_api: &Arc<Mutex<PubP2poolApi>>, p2pool_api: &Arc<Mutex<PubP2poolApi>>,
) { ) {
let api = gupax_p2pool_api.lock().unwrap(); let api = gupax_p2pool_api.lock().unwrap();
let height = size.y; // let height = size.y;
let width = size.x; // let width = size.x;
let text = height / 25.0; // let text = height / 25.0;
let log = height / 2.8; // let log = height / 2.8;
// Payout Text + PayoutView buttons // Payout Text + PayoutView buttons
ui.group(|ui| { ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
ui.horizontal(|ui| { ui.style_mut().override_text_style = Some(TextStyle::Body);
let width = (width / 3.0) - (SPACE * 4.0); let size_text = ui.text_style_height(&TextStyle::Body);
ui.add_sized( let height = (ui.style().spacing.button_padding.y * 2.0) + size_text;
[width, text], ScrollArea::vertical().show(ui, |ui| {
Label::new( ui.group(|ui| {
RichText::new(format!("Total Payouts: {}", api.payout)) ScrollArea::horizontal().show(ui, |ui| {
.underline() ui.horizontal(|ui| {
.color(LIGHT_GRAY), let width =
), ((ui.available_width() / 3.0) - (SPACE * 4.0)).max(size_text * 9.0);
) let width_button =
.on_hover_text(STATUS_SUBMENU_PAYOUT); ((ui.available_width() / 3.0 / 4.0) - SPACE).max(size_text * 4.0);
ui.separator(); ui.add_sized(
ui.add_sized( [width, height],
[width, text], Label::new(
Label::new( RichText::new(format!("Total Payouts: {}", api.payout))
RichText::new(format!("Total XMR: {}", api.xmr)) .underline()
.underline() .color(LIGHT_GRAY),
.color(LIGHT_GRAY),
),
)
.on_hover_text(STATUS_SUBMENU_XMR);
let width = width / 4.0;
ui.separator();
if ui
.add_sized(
[width, text],
SelectableLabel::new(self.payout_view == PayoutView::Latest, "Latest"),
)
.on_hover_text(STATUS_SUBMENU_LATEST)
.clicked()
{
self.payout_view = PayoutView::Latest;
}
ui.separator();
if ui
.add_sized(
[width, text],
SelectableLabel::new(self.payout_view == PayoutView::Oldest, "Oldest"),
)
.on_hover_text(STATUS_SUBMENU_OLDEST)
.clicked()
{
self.payout_view = PayoutView::Oldest;
}
ui.separator();
if ui
.add_sized(
[width, text],
SelectableLabel::new(self.payout_view == PayoutView::Biggest, "Biggest"),
)
.on_hover_text(STATUS_SUBMENU_BIGGEST)
.clicked()
{
self.payout_view = PayoutView::Biggest;
}
ui.separator();
if ui
.add_sized(
[width, text],
SelectableLabel::new(self.payout_view == PayoutView::Smallest, "Smallest"),
)
.on_hover_text(STATUS_SUBMENU_SMALLEST)
.clicked()
{
self.payout_view = PayoutView::Smallest;
}
});
ui.separator();
// Actual logs
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
egui::ScrollArea::vertical()
.stick_to_bottom(self.payout_view == PayoutView::Oldest)
.max_width(width)
.max_height(log)
.auto_shrink([false; 2])
.show_viewport(ui, |ui, _| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Body);
match self.payout_view {
PayoutView::Latest => ui.add_sized(
[width, log],
TextEdit::multiline(&mut api.log_rev.as_str()),
), ),
PayoutView::Oldest => ui.add_sized( )
[width, log], .on_hover_text(STATUS_SUBMENU_PAYOUT);
TextEdit::multiline(&mut api.log.as_str()), ui.add(Separator::default().vertical());
ui.add_sized(
[width, height],
Label::new(
RichText::new(format!("Total XMR: {}", api.xmr))
.underline()
.color(LIGHT_GRAY),
), ),
PayoutView::Biggest => ui.add_sized( )
[width, log], .on_hover_text(STATUS_SUBMENU_XMR);
TextEdit::multiline(&mut api.payout_high.as_str()), // });
), ui.add(Separator::default().vertical());
PayoutView::Smallest => ui.add_sized( PayoutView::iter().enumerate().for_each(|(count, p)| {
[width, log], if ui
TextEdit::multiline(&mut api.payout_low.as_str()), .add_sized(
), [width_button, height],
}; SelectableLabel::new(self.payout_view == p, p.to_string()),
)
.on_hover_text(p.msg_help())
.clicked()
{
self.payout_view = p;
}
if count + 1 < PayoutView::COUNT {
ui.add(Separator::default().vertical());
}
});
}); });
});
});
drop(api);
// Payout/Share Calculator
let button = (width / 20.0) - (SPACE * 1.666);
ui.group(|ui| {
ui.horizontal(|ui| {
ui.set_min_width(width - SPACE);
if ui
.add_sized(
[button * 2.0, text],
SelectableLabel::new(!self.manual_hash, "Automatic"),
)
.on_hover_text(STATUS_SUBMENU_AUTOMATIC)
.clicked()
{
self.manual_hash = false;
}
ui.separator();
if ui
.add_sized(
[button * 2.0, text],
SelectableLabel::new(self.manual_hash, "Manual"),
)
.on_hover_text(STATUS_SUBMENU_MANUAL)
.clicked()
{
self.manual_hash = true;
}
ui.separator();
ui.add_enabled_ui(self.manual_hash, |ui| {
if ui
.add_sized(
[button, text],
SelectableLabel::new(self.hash_metric == Hash::Hash, "Hash"),
)
.on_hover_text(STATUS_SUBMENU_HASH)
.clicked()
{
self.hash_metric = Hash::Hash;
}
ui.separator();
if ui
.add_sized(
[button, text],
SelectableLabel::new(self.hash_metric == Hash::Kilo, "Kilo"),
)
.on_hover_text(STATUS_SUBMENU_KILO)
.clicked()
{
self.hash_metric = Hash::Kilo;
}
ui.separator();
if ui
.add_sized(
[button, text],
SelectableLabel::new(self.hash_metric == Hash::Mega, "Mega"),
)
.on_hover_text(STATUS_SUBMENU_MEGA)
.clicked()
{
self.hash_metric = Hash::Mega;
}
ui.separator();
if ui
.add_sized(
[button, text],
SelectableLabel::new(self.hash_metric == Hash::Giga, "Giga"),
)
.on_hover_text(STATUS_SUBMENU_GIGA)
.clicked()
{
self.hash_metric = Hash::Giga;
}
ui.separator();
ui.spacing_mut().slider_width = button * 11.5;
ui.add_sized(
[button * 14.0, text],
Slider::new(&mut self.hashrate, 1.0..=1_000.0),
);
}); });
}) // ui.separator();
}); // Actual logs
// Actual stats egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
ui.add_enabled_ui(p2pool_alive, |ui| { egui::ScrollArea::vertical()
let text = height / 25.0; .stick_to_bottom(self.payout_view == PayoutView::Oldest)
let width = (width / 3.0) - (SPACE * 1.666); .max_width(ui.available_width())
let min_height = ui.available_height() / 1.3; .max_height(ui.available_height() / 2.8)
let api = p2pool_api.lock().unwrap(); .auto_shrink([false; 2])
ui.horizontal(|ui| { .show_viewport(ui, |ui, _| {
ui.group(|ui| { ui.style_mut().override_text_style = Some(egui::TextStyle::Body);
ui.vertical(|ui| { ui.style_mut().spacing.text_edit_width = ui.available_width();
ui.set_min_height(min_height); match self.payout_view {
ui.add_sized( PayoutView::Latest => {
[width, text], ui.text_edit_multiline(&mut api.log_rev.as_str())
Label::new(RichText::new("Monero Difficulty").underline().color(BONE)), }
) PayoutView::Oldest => ui.text_edit_multiline(&mut api.log.as_str()),
.on_hover_text(STATUS_SUBMENU_MONERO_DIFFICULTY); PayoutView::Biggest => {
ui.add_sized([width, text], Label::new(api.monero_difficulty.as_str())); ui.text_edit_multiline(&mut api.payout_high.as_str())
ui.add_sized( }
[width, text], PayoutView::Smallest => {
Label::new(RichText::new("Monero Hashrate").underline().color(BONE)), ui.text_edit_multiline(&mut api.payout_low.as_str())
) }
.on_hover_text(STATUS_SUBMENU_MONERO_HASHRATE); };
ui.add_sized([width, text], Label::new(api.monero_hashrate.as_str())); });
ui.add_sized(
[width, text],
Label::new(RichText::new("P2Pool Difficulty").underline().color(BONE)),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_DIFFICULTY);
ui.add_sized([width, text], Label::new(api.p2pool_difficulty.as_str()));
ui.add_sized(
[width, text],
Label::new(RichText::new("P2Pool Hashrate").underline().color(BONE)),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_HASHRATE);
ui.add_sized([width, text], Label::new(api.p2pool_hashrate.as_str()));
})
});
ui.group(|ui| {
ui.vertical(|ui| {
ui.set_min_height(min_height);
if self.manual_hash {
let hashrate =
Hash::convert_to_hash(self.hashrate, self.hash_metric) as u64;
let p2pool_share_mean = PubP2poolApi::calculate_share_or_block_time(
hashrate,
api.p2pool_difficulty_u64,
);
let solo_block_mean = PubP2poolApi::calculate_share_or_block_time(
hashrate,
api.monero_difficulty_u64,
);
ui.add_sized(
[width, text],
Label::new(
RichText::new("Manually Inputted Hashrate")
.underline()
.color(BONE),
),
);
ui.add_sized(
[width, text],
Label::new(format!("{} H/s", Unsigned::from(hashrate))),
);
ui.add_sized(
[width, text],
Label::new(
RichText::new("P2Pool Block Mean").underline().color(BONE),
),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_BLOCK_MEAN);
ui.add_sized(
[width, text],
Label::new(api.p2pool_block_mean.to_string()),
);
ui.add_sized(
[width, text],
Label::new(
RichText::new("Your P2Pool Share Mean")
.underline()
.color(BONE),
),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_SHARE_MEAN);
ui.add_sized([width, text], Label::new(p2pool_share_mean.to_string()));
ui.add_sized(
[width, text],
Label::new(
RichText::new("Your Solo Block Mean")
.underline()
.color(BONE),
),
)
.on_hover_text(STATUS_SUBMENU_SOLO_BLOCK_MEAN);
ui.add_sized([width, text], Label::new(solo_block_mean.to_string()));
} else {
ui.add_sized(
[width, text],
Label::new(
RichText::new("Your P2Pool Hashrate")
.underline()
.color(BONE),
),
)
.on_hover_text(STATUS_SUBMENU_YOUR_P2POOL_HASHRATE);
ui.add_sized(
[width, text],
Label::new(format!("{} H/s", api.hashrate_1h)),
);
ui.add_sized(
[width, text],
Label::new(
RichText::new("P2Pool Block Mean").underline().color(BONE),
),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_BLOCK_MEAN);
ui.add_sized(
[width, text],
Label::new(api.p2pool_block_mean.to_string()),
);
ui.add_sized(
[width, text],
Label::new(
RichText::new("Your P2Pool Share Mean")
.underline()
.color(BONE),
),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_SHARE_MEAN);
ui.add_sized(
[width, text],
Label::new(api.p2pool_share_mean.to_string()),
);
ui.add_sized(
[width, text],
Label::new(
RichText::new("Your Solo Block Mean")
.underline()
.color(BONE),
),
)
.on_hover_text(STATUS_SUBMENU_SOLO_BLOCK_MEAN);
ui.add_sized(
[width, text],
Label::new(api.solo_block_mean.to_string()),
);
}
})
});
ui.group(|ui| {
ui.vertical(|ui| {
ui.set_min_height(min_height);
if self.manual_hash {
let hashrate =
Hash::convert_to_hash(self.hashrate, self.hash_metric) as u64;
let user_p2pool_percent = PubP2poolApi::calculate_dominance(
hashrate,
api.p2pool_hashrate_u64,
);
let user_monero_percent = PubP2poolApi::calculate_dominance(
hashrate,
api.monero_hashrate_u64,
);
ui.add_sized(
[width, text],
Label::new(RichText::new("P2Pool Miners").underline().color(BONE)),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_MINERS);
ui.add_sized([width, text], Label::new(api.miners.as_str()));
ui.add_sized(
[width, text],
Label::new(
RichText::new("P2Pool Dominance").underline().color(BONE),
),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_DOMINANCE);
ui.add_sized([width, text], Label::new(api.p2pool_percent.as_str()));
ui.add_sized(
[width, text],
Label::new(
RichText::new("Your P2Pool Dominance")
.underline()
.color(BONE),
),
)
.on_hover_text(STATUS_SUBMENU_YOUR_P2POOL_DOMINANCE);
ui.add_sized([width, text], Label::new(user_p2pool_percent.as_str()));
ui.add_sized(
[width, text],
Label::new(
RichText::new("Your Monero Dominance")
.underline()
.color(BONE),
),
)
.on_hover_text(STATUS_SUBMENU_YOUR_MONERO_DOMINANCE);
ui.add_sized([width, text], Label::new(user_monero_percent.as_str()));
} else {
ui.add_sized(
[width, text],
Label::new(RichText::new("P2Pool Miners").underline().color(BONE)),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_MINERS);
ui.add_sized([width, text], Label::new(api.miners.as_str()));
ui.add_sized(
[width, text],
Label::new(
RichText::new("P2Pool Dominance").underline().color(BONE),
),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_DOMINANCE);
ui.add_sized([width, text], Label::new(api.p2pool_percent.as_str()));
ui.add_sized(
[width, text],
Label::new(
RichText::new("Your P2Pool Dominance")
.underline()
.color(BONE),
),
)
.on_hover_text(STATUS_SUBMENU_YOUR_P2POOL_DOMINANCE);
ui.add_sized(
[width, text],
Label::new(api.user_p2pool_percent.as_str()),
);
ui.add_sized(
[width, text],
Label::new(
RichText::new("Your Monero Dominance")
.underline()
.color(BONE),
),
)
.on_hover_text(STATUS_SUBMENU_YOUR_MONERO_DOMINANCE);
ui.add_sized(
[width, text],
Label::new(api.user_monero_percent.as_str()),
);
}
})
}); });
}); });
// Tick bar // });
ui.add_sized(
[ui.available_width(), text],
Label::new(api.calculate_tick_bar()),
)
.on_hover_text(STATUS_SUBMENU_PROGRESS_BAR);
drop(api); drop(api);
// Payout/Share Calculator
// let button = (width / 20.0) - (SPACE * 1.666);
ui.group(|ui| {
ui.set_width(ui.available_width());
ui.horizontal(|ui| {
// ui.set_min_width(width - SPACE);
let width = ui.available_width() / 10.0;
if ui
.add_sized(
[width, height],
SelectableLabel::new(!self.manual_hash, "Automatic"),
)
.on_hover_text(STATUS_SUBMENU_AUTOMATIC)
.clicked()
{
self.manual_hash = false;
}
ui.separator();
if ui
.add_sized(
[width, height],
SelectableLabel::new(self.manual_hash, "Manual"),
)
.on_hover_text(STATUS_SUBMENU_MANUAL)
.clicked()
{
self.manual_hash = true;
}
ui.separator();
ui.add_enabled_ui(self.manual_hash, |ui| {
if ui
.selectable_label(self.hash_metric == Hash::Hash, "Hash")
.on_hover_text(STATUS_SUBMENU_HASH)
.clicked()
{
self.hash_metric = Hash::Hash;
}
ui.separator();
if ui
.selectable_label(self.hash_metric == Hash::Kilo, "Kilo")
.on_hover_text(STATUS_SUBMENU_KILO)
.clicked()
{
self.hash_metric = Hash::Kilo;
}
ui.separator();
if ui
.selectable_label(self.hash_metric == Hash::Mega, "Mega")
.on_hover_text(STATUS_SUBMENU_MEGA)
.clicked()
{
self.hash_metric = Hash::Mega;
}
ui.separator();
if ui
.selectable_label(self.hash_metric == Hash::Giga, "Giga")
.on_hover_text(STATUS_SUBMENU_GIGA)
.clicked()
{
self.hash_metric = Hash::Giga;
}
ui.separator();
ui.spacing_mut().slider_width = (ui.available_width() / 1.2).max(0.0);
ui.add_sized(
[0.0, height],
Slider::new(&mut self.hashrate, 1.0..=1_000.0)
.suffix(format!(" {}", self.hash_metric)),
);
});
})
});
// Actual stats
ui.add_space(height / 2.0);
ui.add_enabled_ui(p2pool_alive, |ui| {
let min_height = ui.available_height() / 1.5;
let api = p2pool_api.lock().unwrap();
ui.horizontal(|ui| {
ui.columns_const(|[col1, col2, col3]| {
col1.group(|ui| {
ui.vertical_centered(|ui| {
ui.add_space(height * 2.0);
ui.set_min_height(min_height);
ui.label(
RichText::new("Monero Difficulty").underline().color(BONE),
)
.on_hover_text(STATUS_SUBMENU_MONERO_DIFFICULTY);
ui.label(api.monero_difficulty.as_str());
ui.label(RichText::new("Monero Hashrate").underline().color(BONE))
.on_hover_text(STATUS_SUBMENU_MONERO_HASHRATE);
ui.label(api.monero_hashrate.as_str());
ui.label(
RichText::new("P2Pool Difficulty").underline().color(BONE),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_DIFFICULTY);
ui.label(api.p2pool_difficulty.as_str());
ui.label(RichText::new("P2Pool Hashrate").underline().color(BONE))
.on_hover_text(STATUS_SUBMENU_P2POOL_HASHRATE);
ui.label(api.p2pool_hashrate.as_str());
})
});
col2.group(|ui| {
ui.vertical_centered(|ui| {
ui.add_space(height * 2.0);
ui.set_min_height(min_height);
if self.manual_hash {
let hashrate =
Hash::convert_to_hash(self.hashrate, self.hash_metric)
as u64;
let p2pool_share_mean =
PubP2poolApi::calculate_share_or_block_time(
hashrate,
api.p2pool_difficulty_u64,
);
let solo_block_mean =
PubP2poolApi::calculate_share_or_block_time(
hashrate,
api.monero_difficulty_u64,
);
ui.label(
RichText::new("Manually Inputted Hashrate")
.underline()
.color(BONE),
);
ui.label(format!("{} H/s", Unsigned::from(hashrate)));
ui.label(
RichText::new("P2Pool Block Mean").underline().color(BONE),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_BLOCK_MEAN);
ui.label(api.p2pool_block_mean.to_string());
ui.label(
RichText::new("Your P2Pool Share Mean")
.underline()
.color(BONE),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_SHARE_MEAN);
ui.label(p2pool_share_mean.to_string());
ui.label(
RichText::new("Your Solo Block Mean")
.underline()
.color(BONE),
)
.on_hover_text(STATUS_SUBMENU_SOLO_BLOCK_MEAN);
ui.label(solo_block_mean.to_string());
} else {
ui.label(
RichText::new("Your P2Pool Hashrate")
.underline()
.color(BONE),
)
.on_hover_text(STATUS_SUBMENU_YOUR_P2POOL_HASHRATE);
ui.label(format!("{} H/s", api.hashrate_1h));
ui.label(
RichText::new("P2Pool Block Mean").underline().color(BONE),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_BLOCK_MEAN);
ui.label(api.p2pool_block_mean.to_string());
ui.label(
RichText::new("Your P2Pool Share Mean")
.underline()
.color(BONE),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_SHARE_MEAN);
ui.label(api.p2pool_share_mean.to_string());
ui.label(
RichText::new("Your Solo Block Mean")
.underline()
.color(BONE),
)
.on_hover_text(STATUS_SUBMENU_SOLO_BLOCK_MEAN);
ui.label(api.solo_block_mean.to_string());
}
})
});
col3.group(|ui| {
ui.vertical_centered(|ui| {
ui.add_space(height * 2.0);
ui.set_min_height(min_height);
if self.manual_hash {
let hashrate =
Hash::convert_to_hash(self.hashrate, self.hash_metric)
as u64;
let user_p2pool_percent = PubP2poolApi::calculate_dominance(
hashrate,
api.p2pool_hashrate_u64,
);
let user_monero_percent = PubP2poolApi::calculate_dominance(
hashrate,
api.monero_hashrate_u64,
);
ui.label(
RichText::new("P2Pool Miners").underline().color(BONE),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_MINERS);
ui.label(api.miners.as_str());
ui.label(
RichText::new("P2Pool Dominance").underline().color(BONE),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_DOMINANCE);
ui.label(api.p2pool_percent.as_str());
ui.label(
RichText::new("Your P2Pool Dominance")
.underline()
.color(BONE),
)
.on_hover_text(STATUS_SUBMENU_YOUR_P2POOL_DOMINANCE);
ui.label(user_p2pool_percent.as_str());
ui.label(
RichText::new("Your Monero Dominance")
.underline()
.color(BONE),
)
.on_hover_text(STATUS_SUBMENU_YOUR_MONERO_DOMINANCE);
ui.label(user_monero_percent.as_str());
} else {
ui.label(
RichText::new("P2Pool Miners").underline().color(BONE),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_MINERS);
ui.label(api.miners.as_str());
ui.label(
RichText::new("P2Pool Dominance").underline().color(BONE),
)
.on_hover_text(STATUS_SUBMENU_P2POOL_DOMINANCE);
ui.label(api.p2pool_percent.as_str());
ui.label(
RichText::new("Your P2Pool Dominance")
.underline()
.color(BONE),
)
.on_hover_text(STATUS_SUBMENU_YOUR_P2POOL_DOMINANCE);
ui.label(api.user_p2pool_percent.as_str());
ui.label(
RichText::new("Your Monero Dominance")
.underline()
.color(BONE),
)
.on_hover_text(STATUS_SUBMENU_YOUR_MONERO_DOMINANCE);
ui.label(api.user_monero_percent.as_str());
}
})
});
});
});
// Tick bar
ui.vertical_centered(|ui| {
ui.label(api.calculate_tick_bar())
.on_hover_text(STATUS_SUBMENU_PROGRESS_BAR);
});
drop(api);
});
}); });
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -15,481 +15,195 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::app::panels::middle::common::console::{console, input_args_field, start_options_field};
use crate::app::panels::middle::common::list_poolnode::list_poolnode;
use crate::app::panels::middle::common::state_edit_field::{
monero_address_field, slider_state_field,
};
use crate::constants::*; use crate::constants::*;
use crate::disk::pool::Pool;
use crate::disk::state::Xmrig; use crate::disk::state::Xmrig;
use crate::helper::Process; use crate::helper::Process;
use crate::helper::xrig::xmrig::PubXmrigApi; use crate::helper::xrig::xmrig::PubXmrigApi;
use crate::regex::{REGEXES, num_lines}; use crate::miscs::height_txt_before_button;
use crate::utils::regex::Regexes; use crate::regex::REGEXES;
use egui::{ use egui::{Checkbox, Ui, vec2};
Button, Checkbox, ComboBox, Label, RichText, SelectableLabel, Slider, TextEdit, TextStyle,
Vec2, vec2,
};
use log::*; use log::*;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use super::common::list_poolnode::PoolNode;
use super::common::state_edit_field::StateTextEdit;
impl Xmrig { impl Xmrig {
#[inline(always)] // called once #[inline(always)] // called once
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn show( pub fn show(
&mut self, &mut self,
pool_vec: &mut Vec<(String, Pool)>, pool_vec: &mut Vec<(String, PoolNode)>,
process: &Arc<Mutex<Process>>, process: &Arc<Mutex<Process>>,
api: &Arc<Mutex<PubXmrigApi>>, api: &Arc<Mutex<PubXmrigApi>>,
buffer: &mut String, buffer: &mut String,
size: Vec2,
_ctx: &egui::Context, _ctx: &egui::Context,
ui: &mut egui::Ui, ui: &mut egui::Ui,
) { ) {
let text_edit = size.y / 25.0;
//---------------------------------------------------------------------------------------------------- [Simple] Console
debug!("XMRig Tab | Rendering [Console]"); debug!("XMRig Tab | Rendering [Console]");
egui::ScrollArea::vertical().show(ui, |ui| { egui::ScrollArea::vertical().show(ui, |ui| {
ui.group(|ui| {
let text = &api.lock().unwrap().output;
let nb_lines = num_lines(text);
let (height, width) = if self.simple {
(size.y / 1.5, size.x - SPACE)
} else {
(size.y / 2.8, size.x - SPACE)
};
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
ui.style_mut().override_text_style = Some(TextStyle::Small);
egui::ScrollArea::vertical()
.stick_to_bottom(true)
.max_width(width)
.max_height(height)
.auto_shrink([false; 2])
// .show_viewport(ui, |ui, _| {
.show_rows(
ui,
ui.text_style_height(&TextStyle::Small),
nb_lines,
|ui, row_range| {
for i in row_range {
if let Some(line) = text.lines().nth(i) {
ui.label(line);
}
}
},
);
});
//---------------------------------------------------------------------------------------------------- [Advanced] Console
if !self.simple {
ui.separator();
let response = ui
.add_sized(
[width, text_edit],
TextEdit::hint_text(
TextEdit::singleline(buffer),
r#"Commands: [h]ashrate, [p]ause, [r]esume, re[s]ults, [c]onnection"#,
),
)
.on_hover_text(XMRIG_INPUT);
// If the user pressed enter, dump buffer contents into the process STDIN
if response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
response.request_focus(); // Get focus back
let buffer = std::mem::take(buffer); // Take buffer
let mut process = process.lock().unwrap(); // Lock
if process.is_alive() {
process.input.push(buffer);
} // Push only if alive
}
}
});
//---------------------------------------------------------------------------------------------------- Arguments
if !self.simple {
debug!("XMRig Tab | Rendering [Arguments]");
ui.group(|ui| { ui.group(|ui| {
ui.horizontal(|ui| { let text = &api.lock().unwrap().output;
let width = (size.x / 10.0) - SPACE; console(ui, text);
ui.add_sized([width, text_edit], Label::new("Command arguments:")); if !self.simple {
ui.add_sized(
[ui.available_width(), text_edit],
TextEdit::hint_text(
TextEdit::singleline(&mut self.arguments),
r#"--url <...> --user <...> --config <...>"#,
),
)
.on_hover_text(XMRIG_ARGUMENTS);
self.arguments.truncate(1024);
})
});
ui.add_enabled_ui(self.arguments.is_empty(), |ui|{
//---------------------------------------------------------------------------------------------------- Address
debug!("XMRig Tab | Rendering [Address]");
ui.group(|ui| {
let width = size.x - SPACE;
ui.spacing_mut().text_edit_width = (width) - (SPACE * 3.0);
let text;
let color;
let len = format!("{:02}", self.address.len());
if self.address.is_empty() {
text = format!("Monero Address [{}/95] ", len);
color = LIGHT_GRAY;
} else if Regexes::addr_ok(&self.address) {
text = format!("Monero Address [{}/95] ✔", len);
color = GREEN;
} else {
text = format!("Monero Address [{}/95] ❌", len);
color = RED;
}
ui.add_sized(
[width, text_edit],
Label::new(RichText::new(text).color(color)),
);
ui.add_sized(
[width, text_edit],
TextEdit::hint_text(TextEdit::singleline(&mut self.address), "4..."),
)
.on_hover_text(XMRIG_ADDRESS);
self.address.truncate(95);
});
});
}
//---------------------------------------------------------------------------------------------------- Threads
if self.simple {
ui.add_space(SPACE);
}
debug!("XMRig Tab | Rendering [Threads]");
ui.vertical(|ui| {
let width = size.x / 10.0;
let text_width = width * 2.4;
ui.spacing_mut().slider_width = width * 6.5;
ui.spacing_mut().icon_width = width / 25.0;
ui.horizontal(|ui| {
ui.add_sized(
[text_width, text_edit],
Label::new(format!("Threads [1-{}]:", self.max_threads)),
);
ui.add_sized(
[width, text_edit],
Slider::new(&mut self.current_threads, 1..=self.max_threads),
)
.on_hover_text(XMRIG_THREADS);
});
#[cfg(not(target_os = "linux"))] // Pause on active isn't supported on Linux
ui.horizontal(|ui| {
ui.add_sized(
[text_width, text_edit],
Label::new("Pause on active [0-255]:".to_string()),
);
ui.add_sized([width, text_edit], Slider::new(&mut self.pause, 0..=255))
.on_hover_text(format!("{} [{}] seconds.", XMRIG_PAUSE, self.pause));
});
});
//---------------------------------------------------------------------------------------------------- Simple
if !self.simple {
debug!("XMRig Tab | Rendering [Pool List] elements");
let width = ui.available_width() - 10.0;
let mut incorrect_input = false; // This will disable [Add/Delete] on bad input
// [Pool IP/Port]
ui.horizontal(|ui| {
ui.group(|ui| {
let width = width/10.0;
ui.vertical(|ui| {
ui.spacing_mut().text_edit_width = width*3.32;
ui.horizontal(|ui| {
let text;
let color;
let len = format!("{:02}", self.name.len());
if self.name.is_empty() {
text = format!("Name [ {}/30 ]", len);
color = LIGHT_GRAY;
incorrect_input = true;
} else if REGEXES.name.is_match(&self.name) {
text = format!("Name [ {}/30 ]✔", len);
color = GREEN;
} else {
text = format!("Name [ {}/30 ]❌", len);
color = RED;
incorrect_input = true;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.name).on_hover_text(XMRIG_NAME);
self.name.truncate(30);
});
ui.horizontal(|ui| {
let text;
let color;
let len = format!("{:03}", self.ip.len());
if self.ip.is_empty() {
text = format!(" IP [{}/255]", len);
color = LIGHT_GRAY;
incorrect_input = true;
} else if self.ip == "localhost" || REGEXES.ipv4.is_match(&self.ip) || REGEXES.domain.is_match(&self.ip) {
text = format!(" IP [{}/255]✔", len);
color = GREEN;
} else {
text = format!(" IP [{}/255]❌", len);
color = RED;
incorrect_input = true;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.ip).on_hover_text(XMRIG_IP);
self.ip.truncate(255);
});
ui.horizontal(|ui| {
let text;
let color;
let len = self.port.len();
if self.port.is_empty() {
text = format!("Port [ {}/5 ]", len);
color = LIGHT_GRAY;
incorrect_input = true;
} else if REGEXES.port.is_match(&self.port) {
text = format!("Port [ {}/5 ]✔", len);
color = GREEN;
} else {
text = format!("Port [ {}/5 ]❌", len);
color = RED;
incorrect_input = true;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.port).on_hover_text(XMRIG_PORT);
self.port.truncate(5);
});
ui.horizontal(|ui| {
let text;
let color;
let len = format!("{:02}", self.rig.len());
if self.rig.is_empty() {
text = format!(" Rig [ {}/30 ]", len);
color = LIGHT_GRAY;
} else if REGEXES.name.is_match(&self.rig) {
text = format!(" Rig [ {}/30 ]✔", len);
color = GREEN;
} else {
text = format!(" Rig [ {}/30 ]❌", len);
color = RED;
incorrect_input = true;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.rig).on_hover_text(XMRIG_RIG);
self.rig.truncate(30);
});
});
ui.vertical(|ui| {
let width = ui.available_width();
ui.add_space(1.0);
// [Manual node selection]
ui.spacing_mut().slider_width = width - 8.0;
ui.spacing_mut().icon_width = width / 25.0;
// [Node List]
debug!("XMRig Tab | Rendering [Node List] ComboBox");
let text = RichText::new(format!("{}. {}", self.selected_index+1, self.selected_name));
ComboBox::from_id_salt("manual_pool").selected_text(text).width(width).show_ui(ui, |ui| {
for (n, (name, pool)) in pool_vec.iter().enumerate() {
let text = format!("{}. {}\n IP: {}\n Port: {}\n Rig: {}", n+1, name, pool.ip, pool.port, pool.rig);
if ui.add(SelectableLabel::new(self.selected_name == *name, text)).clicked() {
self.selected_index = n;
let pool = pool.clone();
self.selected_name.clone_from(name);
self.selected_rig.clone_from(&pool.rig);
self.selected_ip.clone_from(&pool.ip);
self.selected_port.clone_from(&pool.port);
self.name.clone_from(name);
self.rig = pool.rig;
self.ip = pool.ip;
self.port = pool.port;
}
}
});
// [Add/Save]
let pool_vec_len = pool_vec.len();
let mut exists = false;
let mut save_diff = true;
let mut existing_index = 0;
for (name, pool) in pool_vec.iter() {
if *name == self.name {
exists = true;
if self.rig == pool.rig && self.ip == pool.ip && self.port == pool.port {
save_diff = false;
}
break
}
existing_index += 1;
}
ui.horizontal(|ui| {
let text = if exists { LIST_SAVE } else { LIST_ADD };
let text = format!("{}\n Currently selected pool: {}. {}\n Current amount of pools: {}/1000", text, self.selected_index+1, self.selected_name, pool_vec_len);
// If the pool already exists, show [Save] and mutate the already existing pool
if exists {
ui.add_enabled_ui(!incorrect_input && save_diff, |ui|{
if ui.add_sized([width, text_edit], Button::new("Save")).on_hover_text(text).clicked() {
let pool = Pool {
rig: self.rig.clone(),
ip: self.ip.clone(),
port: self.port.clone(),
};
pool_vec[existing_index].1 = pool;
self.selected_name.clone_from(&self.name);
self.selected_rig.clone_from(&self.rig);
self.selected_ip.clone_from(&self.ip);
self.selected_port.clone_from(&self.port);
info!("Node | S | [index: {}, name: \"{}\", ip: \"{}\", port: {}, rig: \"{}\"]", existing_index+1, self.name, self.ip, self.port, self.rig);
}
});
// Else, add to the list
} else {
ui.add_enabled_ui(!incorrect_input && pool_vec_len < 1000, |ui|{
if ui.add_sized([width, text_edit], Button::new("Add")).on_hover_text(text).clicked() {
let pool = Pool {
rig: self.rig.clone(),
ip: self.ip.clone(),
port: self.port.clone(),
};
pool_vec.push((self.name.clone(), pool));
self.selected_index = pool_vec_len;
self.selected_name.clone_from(&self.name);
self.selected_rig.clone_from(&self.rig);
self.selected_ip.clone_from(&self.ip);
self.selected_port.clone_from(&self.port);
info!("Node | A | [index: {}, name: \"{}\", ip: \"{}\", port: {}, rig: \"{}\"]", pool_vec_len, self.name, self.ip, self.port, self.rig);
}
});
}
});
// [Delete]
ui.horizontal(|ui| {
ui.add_enabled_ui(pool_vec_len > 1, |ui|{
let text = format!("{}\n Currently selected pool: {}. {}\n Current amount of pools: {}/1000", LIST_DELETE, self.selected_index+1, self.selected_name, pool_vec_len);
if ui.add_sized([width, text_edit], Button::new("Delete")).on_hover_text(text).clicked() {
let new_name;
let new_pool;
match self.selected_index {
0 => {
new_name = pool_vec[1].0.clone();
new_pool = pool_vec[1].1.clone();
pool_vec.remove(0);
}
_ => {
pool_vec.remove(self.selected_index);
self.selected_index -= 1;
new_name = pool_vec[self.selected_index].0.clone();
new_pool = pool_vec[self.selected_index].1.clone();
}
};
self.selected_name.clone_from(&new_name);
self.selected_rig.clone_from(&new_pool.rig);
self.selected_ip.clone_from(&new_pool.ip);
self.selected_port.clone_from(&new_pool.port);
self.name = new_name;
self.rig = new_pool.rig;
self.ip = new_pool.ip;
self.port = new_pool.port;
info!("Node | D | [index: {}, name: \"{}\", ip: \"{}\", port: {}, rig\"{}\"]", self.selected_index, self.selected_name, self.selected_ip, self.selected_port, self.selected_rig);
}
});
});
ui.horizontal(|ui| {
ui.add_enabled_ui(!self.name.is_empty() || !self.ip.is_empty() || !self.port.is_empty(), |ui|{
if ui.add_sized([width, text_edit], Button::new("Clear")).on_hover_text(LIST_CLEAR).clicked() {
self.name.clear();
self.rig.clear();
self.ip.clear();
self.port.clear();
}
});
});
});
});
});
ui.add_space(5.0);
debug!("XMRig Tab | Rendering [API] TextEdits");
// [HTTP API IP/Port]
ui.group(|ui| {
ui.horizontal(|ui| {
ui.vertical(|ui| {
let width = width / 10.0;
ui.spacing_mut().text_edit_width = width * 2.39;
// HTTP API
ui.horizontal(|ui| {
let text;
let color;
let len = format!("{:03}", self.api_ip.len());
if self.api_ip.is_empty() {
text = format!("HTTP API IP [{}/255]", len);
color = LIGHT_GRAY;
incorrect_input = true;
} else if self.api_ip == "localhost"
|| REGEXES.ipv4.is_match(&self.api_ip)
|| REGEXES.domain.is_match(&self.api_ip)
{
text = format!("HTTP API IP [{}/255]✔", len);
color = GREEN;
} else {
text = format!("HTTP API IP [{}/255]❌", len);
color = RED;
incorrect_input = true;
}
ui.add_sized(
[width, text_edit],
Label::new(RichText::new(text).color(color)),
);
ui.text_edit_singleline(&mut self.api_ip)
.on_hover_text(XMRIG_API_IP);
self.api_ip.truncate(255);
});
ui.horizontal(|ui| {
let text;
let color;
let len = self.api_port.len();
if self.api_port.is_empty() {
text = format!("HTTP API Port [ {}/5 ]", len);
color = LIGHT_GRAY;
incorrect_input = true;
} else if REGEXES.port.is_match(&self.api_port) {
text = format!("HTTP API Port [ {}/5 ]✔", len);
color = GREEN;
} else {
text = format!("HTTP API Port [ {}/5 ]❌", len);
color = RED;
incorrect_input = true;
}
ui.add_sized(
[width, text_edit],
Label::new(RichText::new(text).color(color)),
);
ui.text_edit_singleline(&mut self.api_port)
.on_hover_text(XMRIG_API_PORT);
self.api_port.truncate(5);
});
});
ui.separator(); ui.separator();
input_args_field(
debug!("XMRig Tab | Rendering [TLS/Keepalive] buttons"); ui,
ui.vertical(|ui| { buffer,
// TLS/Keepalive process,
ui.horizontal(|ui| { r#"Commands: [h]ashrate, [p]ause, [r]esume, re[s]ults, [c]onnection"#,
let width = (ui.available_width() / 2.0) - 11.0; XMRIG_INPUT,
let height = text_edit * 2.0; );
let size = vec2(width, height); }
// let mut style = (*ctx.style()).clone(); });
// style.spacing.icon_width_inner = width / 8.0; if !self.simple {
// style.spacing.icon_width = width / 6.0; debug!("XMRig Tab | Rendering [Arguments]");
// style.spacing.icon_spacing = 20.0; ui.horizontal(|ui| {
// ctx.set_style(style); start_options_field(
ui.add_sized(size, Checkbox::new(&mut self.tls, "TLS Connection")) ui,
.on_hover_text(XMRIG_TLS); &mut self.arguments,
ui.separator(); r#"--url <...> --user <...> --config <...>"#,
ui.add_sized(size, Checkbox::new(&mut self.keepalive, "Keepalive")) XMRIG_ARGUMENTS,
.on_hover_text(XMRIG_KEEPALIVE); );
});
ui.add_enabled_ui(self.arguments.is_empty(), |ui| {
debug!("XMRig Tab | Rendering [Address]");
monero_address_field(&mut self.address, ui, XMRIG_ADDRESS);
});
}
if self.simple {
ui.add_space(SPACE);
}
debug!("XMRig Tab | Rendering [Threads]");
ui.vertical_centered(|ui| {
ui.set_max_width(ui.available_width() * 0.75);
slider_state_field(
ui,
&format!("Threads [1-{}]:", self.max_threads),
XMRIG_THREADS,
&mut self.current_threads,
1..=self.max_threads,
);
#[cfg(not(target_os = "linux"))] // Pause on active isn't supported on Linux
slider_state_field(
ui,
"Pause on active [0-255]:",
&format!("{} [{}] seconds.", XMRIG_PAUSE, self.pause),
&mut self.pause,
0..=255,
);
});
if !self.simple {
debug!("XMRig Tab | Rendering [Pool List] elements");
let mut incorrect_input = false; // This will disable [Add/Delete] on bad input
ui.horizontal(|ui| {
ui.group(|ui| {
ui.vertical(|ui| {
if !self.name_field(ui) {
incorrect_input = false;
}
if !self.ip_field(ui) {
incorrect_input = false;
}
if !self.rpc_port_field(ui) {
incorrect_input = false;
}
if !self.rig_field(ui) {
incorrect_input = false;
}
});
ui.vertical(|ui| {
list_poolnode(
ui,
&mut (&mut self.name, &mut self.ip, &mut self.port, &mut self.rig),
&mut self.selected_pool,
pool_vec,
incorrect_input,
);
}); });
}); });
}); });
}); ui.add_space(5.0);
} debug!("XMRig Tab | Rendering [API] TextEdits");
}); // [HTTP API IP/Port]
ui.group(|ui| {
ui.horizontal(|ui| {
ui.vertical(|ui| {
self.api_ip_field(ui);
self.api_port_field(ui);
});
ui.separator();
debug!("XMRig Tab | Rendering [TLS/Keepalive] buttons");
ui.vertical(|ui| {
// TLS/Keepalive
ui.horizontal(|ui| {
let width = (ui.available_width() / 2.0) - 11.0;
let height =
height_txt_before_button(ui, &egui::TextStyle::Button) * 2.0;
let size = vec2(width, height);
ui.add_sized(size, Checkbox::new(&mut self.tls, "TLS Connection"))
.on_hover_text(XMRIG_TLS);
ui.separator();
ui.add_sized(size, Checkbox::new(&mut self.keepalive, "Keepalive"))
.on_hover_text(XMRIG_KEEPALIVE);
});
});
});
});
}
});
}
fn name_field(&mut self, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" Name ")
.max_ch(30)
.help_msg(XMRIG_NAME)
.validations(&[|x| REGEXES.name.is_match(x)])
.build(ui, &mut self.name)
}
fn rpc_port_field(&mut self, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" RPC PORT ")
.max_ch(5)
.help_msg(XMRIG_PORT)
.validations(&[|x| REGEXES.port.is_match(x)])
.build(ui, &mut self.port)
}
fn ip_field(&mut self, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" IP ")
.max_ch(255)
.help_msg(XMRIG_IP)
.validations(&[|x| REGEXES.ipv4.is_match(x) || REGEXES.domain.is_match(x)])
.build(ui, &mut self.ip)
}
fn rig_field(&mut self, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" Name ")
.max_ch(30)
.help_msg(XMRIG_RIG)
.build(ui, &mut self.rig)
}
fn api_ip_field(&mut self, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" API IP ")
.max_ch(255)
.help_msg(XMRIG_API_IP)
.validations(&[|x| REGEXES.ipv4.is_match(x) || REGEXES.domain.is_match(x)])
.build(ui, &mut self.api_ip)
}
fn api_port_field(&mut self, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" API PORT ")
.max_ch(5)
.help_msg(XMRIG_API_PORT)
.validations(&[|x| REGEXES.port.is_match(x)])
.build(ui, &mut self.api_port)
} }
} }

View file

@ -1,439 +1,201 @@
use egui::{ use egui::{Checkbox, Label, TextStyle, Ui, vec2};
Button, Checkbox, ComboBox, Label, RichText, SelectableLabel, TextEdit, TextStyle, Vec2, vec2,
};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use log::{debug, info}; use log::debug;
use crate::disk::pool::Pool; use crate::app::panels::middle::common::console::{console, input_args_field, start_options_field};
use crate::app::panels::middle::common::list_poolnode::list_poolnode;
use crate::disk::state::XmrigProxy; use crate::disk::state::XmrigProxy;
use crate::helper::Process; use crate::helper::Process;
use crate::helper::xrig::xmrig_proxy::PubXmrigProxyApi; use crate::helper::xrig::xmrig_proxy::PubXmrigProxyApi;
use crate::regex::{REGEXES, num_lines}; use crate::miscs::height_txt_before_button;
use crate::utils::constants::DARK_GRAY; use crate::regex::REGEXES;
use crate::{ use crate::{
GREEN, LIGHT_GRAY, LIST_ADD, LIST_CLEAR, LIST_DELETE, LIST_SAVE, RED, SPACE, XMRIG_API_IP, SPACE, XMRIG_API_IP, XMRIG_API_PORT, XMRIG_IP, XMRIG_KEEPALIVE, XMRIG_NAME, XMRIG_PORT,
XMRIG_API_PORT, XMRIG_IP, XMRIG_KEEPALIVE, XMRIG_NAME, XMRIG_PORT, XMRIG_PROXY_ARGUMENTS, XMRIG_PROXY_ARGUMENTS, XMRIG_PROXY_INPUT, XMRIG_PROXY_REDIRECT, XMRIG_PROXY_URL, XMRIG_RIG,
XMRIG_PROXY_INPUT, XMRIG_PROXY_REDIRECT, XMRIG_PROXY_URL, XMRIG_RIG, XMRIG_TLS, XMRIG_TLS,
}; };
use super::common::list_poolnode::PoolNode;
use super::common::state_edit_field::StateTextEdit;
impl XmrigProxy { impl XmrigProxy {
#[inline(always)] // called once #[inline(always)] // called once
pub fn show( pub fn show(
&mut self, &mut self,
process: &Arc<Mutex<Process>>, process: &Arc<Mutex<Process>>,
pool_vec: &mut Vec<(String, Pool)>, pool_vec: &mut Vec<(String, PoolNode)>,
api: &Arc<Mutex<PubXmrigProxyApi>>, api: &Arc<Mutex<PubXmrigProxyApi>>,
buffer: &mut String, buffer: &mut String,
size: Vec2,
ui: &mut egui::Ui, ui: &mut egui::Ui,
) { ) {
let width = size.x;
let height = size.y;
let space_h = height / 48.0;
let text_edit = size.y / 25.0;
egui::ScrollArea::vertical().show(ui, |ui| {
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
ui.add_space(space_h); ui.add_space(SPACE);
ui.style_mut().override_text_style = Some(TextStyle::Heading); ui.style_mut().override_text_style = Some(TextStyle::Heading);
ui.hyperlink_to("XMRig-Proxy", XMRIG_PROXY_URL); ui.hyperlink_to("XMRig-Proxy", XMRIG_PROXY_URL);
ui.style_mut().override_text_style = Some(TextStyle::Body); ui.style_mut().override_text_style = Some(TextStyle::Body);
ui.add(Label::new("High performant proxy for your miners")); ui.add(Label::new("High performant proxy for your miners"));
ui.add_space(space_h); ui.add_space(SPACE);
}); });
// console output for log // console output for log
debug!("Xmrig-Proxy Tab | Rendering [Console]"); debug!("Xmrig-Proxy Tab | Rendering [Console]");
ui.group(|ui| { egui::ScrollArea::vertical().show(ui, |ui| {
let text = &api.lock().unwrap().output;
let nb_lines = num_lines(text);
let height = size.y / 2.8;
let width = size.x - (space_h / 2.0);
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Small
);
egui::ScrollArea::vertical()
.stick_to_bottom(true)
.max_width(width)
.max_height(height)
.auto_shrink([false; 2])
// .show_viewport(ui, |ui, _| {
.show_rows(
ui,
ui.text_style_height(&TextStyle::Small),
nb_lines,
|ui, row_range| {
for i in row_range {
if let Some(line) = text.lines().nth(i) {
ui.label(line);
}
}
},
);
});
});
//---------------------------------------------------------------------------------------------------- [Advanced] Console
if !self.simple {
ui.separator();
let response = ui
.add_sized(
[width, text_edit],
TextEdit::hint_text(
TextEdit::singleline(buffer),
r#"Commands: [h]ashrate, [c]onnections, [v]erbose, [w]orkers"#,
),
)
.on_hover_text(XMRIG_PROXY_INPUT);
// If the user pressed enter, dump buffer contents into the process STDIN
if response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
response.request_focus(); // Get focus back
let buffer = std::mem::take(buffer); // Take buffer
let mut process = process.lock().unwrap(); // Lock
if process.is_alive() {
process.input.push(buffer);
} // Push only if alive
}
//---------------------------------------------------------------------------------------------------- Arguments
debug!("XMRig-Proxy Tab | Rendering [Arguments]");
ui.group(|ui| { ui.group(|ui| {
ui.horizontal(|ui| { let text = &api.lock().unwrap().output;
let width = (size.x / 10.0) - SPACE; console(ui, text);
ui.add_sized([width, text_edit], Label::new("Command arguments:")); //---------------------------------------------------------------------------------------------------- [Advanced] Console
ui.add_sized( if !self.simple {
[ui.available_width(), text_edit],
TextEdit::hint_text(
TextEdit::singleline(&mut self.arguments),
r#"--url <...> --user <...> --config <...>"#,
),
)
.on_hover_text(XMRIG_PROXY_ARGUMENTS);
self.arguments.truncate(1024);
})
});
if !self.arguments.is_empty() {
ui.disable();
}
ui.add_space(space_h);
ui.style_mut().spacing.icon_width_inner = width / 45.0;
ui.style_mut().spacing.icon_width = width / 35.0;
ui.style_mut().spacing.icon_spacing = space_h;
ui.checkbox(
&mut self.redirect_local_xmrig,
"Auto Redirect local Xmrig to Xmrig-Proxy",
)
.on_hover_text(XMRIG_PROXY_REDIRECT);
// idea
// need to warn the user if local firewall is blocking port
// need to warn the user if NAT is blocking port
// need to show local ip address
// need to show public ip
debug!("XMRig-Proxy Tab | Rendering [Pool List] elements");
let width = ui.available_width() - 10.0;
let mut incorrect_input = false; // This will disable [Add/Delete] on bad input
// [Pool IP/Port]
ui.horizontal(|ui| {
ui.group(|ui| {
let width = width/10.0;
ui.vertical(|ui| {
ui.spacing_mut().text_edit_width = width*3.32;
ui.horizontal(|ui| {
let text;
let color;
let len = format!("{:02}", self.name.len());
if self.name.is_empty() {
text = format!("Name [ {}/30 ]", len);
color = LIGHT_GRAY;
incorrect_input = true;
} else if REGEXES.name.is_match(&self.name) {
text = format!("Name [ {}/30 ]✔", len);
color = GREEN;
} else {
text = format!("Name [ {}/30 ]❌", len);
color = RED;
incorrect_input = true;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.name).on_hover_text(XMRIG_NAME);
self.name.truncate(30);
});
ui.horizontal(|ui| {
let text;
let color;
let len = format!("{:03}", self.p2pool_ip.len());
if self.p2pool_ip.is_empty() {
text = format!(" IP [{}/255]", len);
color = LIGHT_GRAY;
incorrect_input = true;
} else if self.p2pool_ip == "localhost" || REGEXES.ipv4.is_match(&self.p2pool_ip) || REGEXES.domain.is_match(&self.p2pool_ip) {
text = format!(" IP [{}/255]✔", len);
color = GREEN;
} else {
text = format!(" IP [{}/255]❌", len);
color = RED;
incorrect_input = true;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.p2pool_ip).on_hover_text(XMRIG_IP);
self.p2pool_ip.truncate(255);
});
ui.horizontal(|ui| {
let text;
let color;
let len = self.p2pool_port.len();
if self.p2pool_port.is_empty() {
text = format!("Port [ {}/5 ]", len);
color = LIGHT_GRAY;
incorrect_input = true;
} else if REGEXES.port.is_match(&self.p2pool_port) {
text = format!("Port [ {}/5 ]✔", len);
color = GREEN;
} else {
text = format!("Port [ {}/5 ]❌", len);
color = RED;
incorrect_input = true;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.p2pool_port).on_hover_text(XMRIG_PORT);
self.p2pool_port.truncate(5);
});
ui.horizontal(|ui| {
let text;
let color;
let len = format!("{:02}", self.rig.len());
if self.rig.is_empty() {
text = format!(" Rig [ {}/30 ]", len);
color = LIGHT_GRAY;
} else if REGEXES.name.is_match(&self.rig) {
text = format!(" Rig [ {}/30 ]✔", len);
color = GREEN;
} else {
text = format!(" Rig [ {}/30 ]❌", len);
color = RED;
incorrect_input = true;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.rig).on_hover_text(XMRIG_RIG);
self.rig.truncate(30);
});
});
ui.vertical(|ui| {
let width = ui.available_width();
ui.add_space(1.0);
// [Manual node selection]
ui.spacing_mut().slider_width = width - 8.0;
ui.spacing_mut().icon_width = width / 25.0;
// [Node List]
debug!("XMRig-Proxy Tab | Rendering [Node List] ComboBox");
let text = RichText::new(format!("{}. {}", self.selected_index+1, self.selected_name));
ComboBox::from_id_salt("manual_pool").selected_text(text).width(width).show_ui(ui, |ui| {
for (n, (name, pool)) in pool_vec.iter().enumerate() {
let text = format!("{}. {}\n IP: {}\n Port: {}\n Rig: {}", n+1, name, pool.ip, pool.port, pool.rig);
if ui.add(SelectableLabel::new(self.selected_name == *name, text)).clicked() {
self.selected_index = n;
let pool = pool.clone();
self.selected_name.clone_from(name);
self.selected_rig.clone_from(&pool.rig);
self.selected_ip.clone_from(&pool.ip);
self.selected_port.clone_from(&pool.port);
self.name.clone_from(name);
self.rig = pool.rig;
self.p2pool_ip = pool.ip;
self.p2pool_port = pool.port;
}
}
});
// [Add/Save]
let pool_vec_len = pool_vec.len();
let mut exists = false;
let mut save_diff = true;
let mut existing_index = 0;
for (name, pool) in pool_vec.iter() {
if *name == self.name {
exists = true;
if self.rig == pool.rig && self.p2pool_ip == pool.ip && self.p2pool_port == pool.port {
save_diff = false;
}
break
}
existing_index += 1;
}
ui.horizontal(|ui| {
let text = if exists { LIST_SAVE } else { LIST_ADD };
let text = format!("{}\n Currently selected pool: {}. {}\n Current amount of pools: {}/1000", text, self.selected_index+1, self.selected_name, pool_vec_len);
// If the pool already exists, show [Save] and mutate the already existing pool
if exists {
ui.add_enabled_ui(!incorrect_input && save_diff, |ui|{
if ui.add_sized([width, text_edit], Button::new("Save")).on_hover_text(text).clicked() {
let pool = Pool {
rig: self.rig.clone(),
ip: self.p2pool_ip.clone(),
port: self.p2pool_port.clone(),
};
pool_vec[existing_index].1 = pool;
self.selected_name.clone_from(&self.name);
self.selected_rig.clone_from(&self.rig);
self.selected_ip.clone_from(&self.p2pool_ip);
self.selected_port.clone_from(&self.p2pool_port);
info!("Node | S | [index: {}, name: \"{}\", ip: \"{}\", port: {}, rig: \"{}\"]", existing_index+1, self.name, self.p2pool_ip, self.p2pool_port, self.rig);
}
});
// Else, add to the list
} else {
ui.add_enabled_ui(!incorrect_input && pool_vec_len < 1000, |ui|{
if ui.add_sized([width, text_edit], Button::new("Add")).on_hover_text(text).clicked() {
let pool = Pool {
rig: self.rig.clone(),
ip: self.p2pool_ip.clone(),
port: self.p2pool_port.clone(),
};
pool_vec.push((self.name.clone(), pool));
self.selected_index = pool_vec_len;
self.selected_name.clone_from(&self.name);
self.selected_rig.clone_from(&self.rig);
self.selected_ip.clone_from(&self.p2pool_ip);
self.selected_port.clone_from(&self.p2pool_port);
info!("Node | A | [index: {}, name: \"{}\", ip: \"{}\", port: {}, rig: \"{}\"]", pool_vec_len, self.name, self.p2pool_ip, self.p2pool_port, self.rig);
}
});
}
});
// [Delete]
ui.horizontal(|ui| {
ui.add_enabled_ui(pool_vec_len > 1, |ui|{
let text = format!("{}\n Currently selected pool: {}. {}\n Current amount of pools: {}/1000", LIST_DELETE, self.selected_index+1, self.selected_name, pool_vec_len);
if ui.add_sized([width, text_edit], Button::new("Delete")).on_hover_text(text).clicked() {
let new_name;
let new_pool;
match self.selected_index {
0 => {
new_name = pool_vec[1].0.clone();
new_pool = pool_vec[1].1.clone();
pool_vec.remove(0);
}
_ => {
pool_vec.remove(self.selected_index);
self.selected_index -= 1;
new_name = pool_vec[self.selected_index].0.clone();
new_pool = pool_vec[self.selected_index].1.clone();
}
};
self.selected_name.clone_from(&new_name);
self.selected_rig.clone_from(&new_pool.rig);
self.selected_ip.clone_from(&new_pool.ip);
self.selected_port.clone_from(&new_pool.port);
self.name = new_name;
self.rig = new_pool.rig;
self.p2pool_ip = new_pool.ip;
self.p2pool_port = new_pool.port;
info!("Node | D | [index: {}, name: \"{}\", ip: \"{}\", port: {}, rig\"{}\"]", self.selected_index, self.selected_name, self.selected_ip, self.selected_port, self.selected_rig);
}
});
});
ui.horizontal(|ui| {
ui.add_enabled_ui(!self.name.is_empty() || !self.p2pool_ip.is_empty() || !self.p2pool_port.is_empty(), |ui|{
if ui.add_sized([width, text_edit], Button::new("Clear")).on_hover_text(LIST_CLEAR).clicked() {
self.name.clear();
self.rig.clear();
self.p2pool_ip.clear();
self.p2pool_port.clear();
}
});
});
});
});
});
ui.add_space(5.0);
debug!("XMRig-Proxy Tab | Rendering [API] TextEdits");
// [HTTP API IP/Port]
ui.group(|ui| {
ui.horizontal(|ui| {
ui.vertical(|ui| {
let width = width / 10.0;
ui.spacing_mut().text_edit_width = width * 2.39;
// HTTP API
ui.horizontal(|ui| {
let text;
let color;
let len = format!("{:03}", self.api_ip.len());
if self.api_ip.is_empty() {
text = format!("HTTP API IP [{}/255]", len);
color = LIGHT_GRAY;
incorrect_input = true;
} else if self.api_ip == "localhost"
|| REGEXES.ipv4.is_match(&self.api_ip)
|| REGEXES.domain.is_match(&self.api_ip)
{
text = format!("HTTP API IP [{}/255]✔", len);
color = GREEN;
} else {
text = format!("HTTP API IP [{}/255]❌", len);
color = RED;
incorrect_input = true;
}
ui.add_sized(
[width, text_edit],
Label::new(RichText::new(text).color(color)),
);
ui.text_edit_singleline(&mut self.api_ip)
.on_hover_text(XMRIG_API_IP);
self.api_ip.truncate(255);
});
ui.horizontal(|ui| {
let text;
let color;
let len = self.api_port.len();
if self.api_port.is_empty() {
text = format!("HTTP API Port [ {}/5 ]", len);
color = LIGHT_GRAY;
incorrect_input = true;
} else if REGEXES.port.is_match(&self.api_port) {
text = format!("HTTP API Port [ {}/5 ]✔", len);
color = GREEN;
} else {
text = format!("HTTP API Port [ {}/5 ]❌", len);
color = RED;
incorrect_input = true;
}
ui.add_sized(
[width, text_edit],
Label::new(RichText::new(text).color(color)),
);
ui.text_edit_singleline(&mut self.api_port)
.on_hover_text(XMRIG_API_PORT);
self.api_port.truncate(5);
});
});
ui.separator(); ui.separator();
input_args_field(
ui,
buffer,
process,
r#"Commands: [h]ashrate, [c]onnections, [v]erbose, [w]orkers"#,
XMRIG_PROXY_INPUT,
);
}
});
if !self.simple {
//---------------------------------------------------------------------------------------------------- Arguments
debug!("XMRig-Proxy Tab | Rendering [Arguments]");
ui.horizontal(|ui| {
start_options_field(
ui,
&mut self.arguments,
r#"--url <...> --user <...> --config <...>"#,
XMRIG_PROXY_ARGUMENTS,
);
});
if !self.arguments.is_empty() {
ui.disable();
}
ui.add_space(SPACE);
// ui.style_mut().spacing.icon_width_inner = width / 45.0;
// ui.style_mut().spacing.icon_width = width / 35.0;
// ui.style_mut().spacing.icon_spacing = space_h;
ui.checkbox(
&mut self.redirect_local_xmrig,
"Auto Redirect local Xmrig to Xmrig-Proxy",
)
.on_hover_text(XMRIG_PROXY_REDIRECT);
debug!("XMRig-Proxy Tab | Rendering [TLS/Keepalive] buttons"); // idea
ui.vertical(|ui| { // need to warn the user if local firewall is blocking port
// TLS/Keepalive // need to warn the user if NAT is blocking port
ui.horizontal(|ui| { // need to show local ip address
let width = (ui.available_width() / 2.0) - 11.0; // need to show public ip
let height = text_edit * 2.0;
let size = vec2(width, height); debug!("XMRig-Proxy Tab | Rendering [Pool List] elements");
// let mut style = (*ctx.style()).clone(); // let width = ui.available_width() - 10.0;
// style.spacing.icon_width_inner = width / 8.0; let mut incorrect_input = false; // This will disable [Add/Delete] on bad input
// style.spacing.icon_width = width / 6.0; // [Pool IP/Port]
// style.spacing.icon_spacing = 20.0; ui.horizontal(|ui| {
// ctx.set_style(style); ui.group(|ui| {
ui.add_sized(size, Checkbox::new(&mut self.tls, "TLS Connection")) // let width = width / 10.0;
.on_hover_text(XMRIG_TLS); ui.vertical(|ui| {
ui.separator(); if !self.name_field(ui) {
ui.add_sized(size, Checkbox::new(&mut self.keepalive, "Keepalive")) incorrect_input = false;
.on_hover_text(XMRIG_KEEPALIVE); }
if !self.ip_field(ui) {
incorrect_input = false;
}
if !self.rpc_port_field(ui) {
incorrect_input = false;
}
if !self.rig_field(ui) {
incorrect_input = false;
}
});
ui.vertical(|ui| {
list_poolnode(
ui,
&mut (&mut self.name, &mut self.ip, &mut self.port, &mut self.rig),
&mut self.selected_pool,
pool_vec,
incorrect_input,
);
}); });
}); });
}); });
}); ui.add_space(5.0);
}
}); debug!("XMRig-Proxy Tab | Rendering [API] TextEdits");
// [HTTP API IP/Port]
ui.group(|ui| {
ui.horizontal(|ui| {
ui.vertical(|ui| {
// HTTP API
self.api_ip_field(ui);
self.api_port_field(ui);
});
ui.separator();
debug!("XMRig-Proxy Tab | Rendering [TLS/Keepalive] buttons");
ui.vertical(|ui| {
// TLS/Keepalive
ui.horizontal(|ui| {
let width = (ui.available_width() / 2.0) - 11.0;
let height = height_txt_before_button(ui, &TextStyle::Button) * 2.0;
let size = vec2(width, height);
ui.add_sized(size, Checkbox::new(&mut self.tls, "TLS Connection"))
.on_hover_text(XMRIG_TLS);
ui.separator();
ui.add_sized(size, Checkbox::new(&mut self.keepalive, "Keepalive"))
.on_hover_text(XMRIG_KEEPALIVE);
});
});
});
});
}
});
}
fn name_field(&mut self, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" Name ")
.max_ch(30)
.help_msg(XMRIG_NAME)
.validations(&[|x| REGEXES.name.is_match(x)])
.build(ui, &mut self.name)
}
fn rpc_port_field(&mut self, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" RPC PORT ")
.max_ch(5)
.help_msg(XMRIG_PORT)
.validations(&[|x| REGEXES.port.is_match(x)])
.build(ui, &mut self.port)
}
fn ip_field(&mut self, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" IP ")
.max_ch(255)
.help_msg(XMRIG_IP)
.validations(&[|x| REGEXES.ipv4.is_match(x) || REGEXES.domain.is_match(x)])
.build(ui, &mut self.ip)
}
fn rig_field(&mut self, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" Name ")
.max_ch(30)
.help_msg(XMRIG_RIG)
.build(ui, &mut self.rig)
}
fn api_ip_field(&mut self, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" API IP ")
.max_ch(255)
.help_msg(XMRIG_API_IP)
.validations(&[|x| REGEXES.ipv4.is_match(x) || REGEXES.domain.is_match(x)])
.build(ui, &mut self.api_ip)
}
fn api_port_field(&mut self, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" API PORT ")
.max_ch(5)
.help_msg(XMRIG_API_PORT)
.validations(&[|x| REGEXES.port.is_match(x)])
.build(ui, &mut self.api_port)
} }
} }

View file

@ -1,30 +1,33 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use egui::{Image, RichText, TextEdit, TextStyle, Ui, Vec2, vec2}; use egui::{Align, Image, RichText, ScrollArea, TextStyle, Ui};
use log::debug; use log::debug;
use readable::num::Float; use readable::num::Float;
use readable::up::Uptime; use readable::up::Uptime;
use strum::EnumCount;
use crate::XVB_MINING_ON_FIELD; use crate::XVB_MINING_ON_FIELD;
use crate::app::panels::middle::common::console::console;
use crate::app::panels::middle::common::header_tab::header_tab;
use crate::app::panels::middle::common::state_edit_field::StateTextEdit;
use crate::disk::state::{ManualDonationLevel, ManualDonationMetric, XvbMode}; use crate::disk::state::{ManualDonationLevel, ManualDonationMetric, XvbMode};
use crate::helper::xrig::xmrig::PubXmrigApi; use crate::helper::xrig::xmrig::PubXmrigApi;
use crate::helper::xrig::xmrig_proxy::PubXmrigProxyApi; use crate::helper::xrig::xmrig_proxy::PubXmrigProxyApi;
use crate::helper::xvb::PubXvbApi; use crate::helper::xvb::PubXvbApi;
use crate::helper::xvb::priv_stats::RuntimeMode; use crate::helper::xvb::priv_stats::RuntimeMode;
use crate::regex::num_lines; use crate::miscs::height_txt_before_button;
use crate::utils::constants::{ use crate::utils::constants::{
GREEN, LIGHT_GRAY, ORANGE, RED, XVB_DONATED_1H_FIELD, XVB_DONATED_24H_FIELD, ORANGE, XVB_DONATED_1H_FIELD, XVB_DONATED_24H_FIELD, XVB_DONATION_LEVEL_DONOR_HELP,
XVB_DONATION_LEVEL_DONOR_HELP, XVB_DONATION_LEVEL_MEGA_DONOR_HELP, XVB_DONATION_LEVEL_MEGA_DONOR_HELP, XVB_DONATION_LEVEL_VIP_DONOR_HELP,
XVB_DONATION_LEVEL_VIP_DONOR_HELP, XVB_DONATION_LEVEL_WHALE_DONOR_HELP, XVB_FAILURE_FIELD, XVB_DONATION_LEVEL_WHALE_DONOR_HELP, XVB_FAILURE_FIELD, XVB_HELP, XVB_HERO_SELECT,
XVB_HELP, XVB_HERO_SELECT, XVB_MANUAL_SLIDER_MANUAL_P2POOL_HELP, XVB_MANUAL_SLIDER_MANUAL_P2POOL_HELP, XVB_MANUAL_SLIDER_MANUAL_XVB_HELP,
XVB_MANUAL_SLIDER_MANUAL_XVB_HELP, XVB_MODE_MANUAL_DONATION_LEVEL_HELP, XVB_MODE_MANUAL_DONATION_LEVEL_HELP, XVB_MODE_MANUAL_P2POOL_HELP, XVB_MODE_MANUAL_XVB_HELP,
XVB_MODE_MANUAL_P2POOL_HELP, XVB_MODE_MANUAL_XVB_HELP, XVB_ROUND_TYPE_FIELD, XVB_TOKEN_FIELD, XVB_ROUND_TYPE_FIELD, XVB_TOKEN_LEN, XVB_URL_RULES, XVB_WINNER_FIELD,
XVB_TOKEN_LEN, XVB_URL_RULES, XVB_WINNER_FIELD,
}; };
use crate::utils::regex::Regexes; use crate::utils::regex::Regexes;
use crate::{ use crate::{
constants::{BYTES_XVB, SPACE}, constants::{BYTES_XVB, SPACE},
utils::constants::{DARK_GRAY, XVB_URL}, utils::constants::XVB_URL,
}; };
impl crate::disk::state::Xvb { impl crate::disk::state::Xvb {
@ -32,7 +35,6 @@ impl crate::disk::state::Xvb {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn show( pub fn show(
&mut self, &mut self,
size: Vec2,
address: &str, address: &str,
_ctx: &egui::Context, _ctx: &egui::Context,
ui: &mut egui::Ui, ui: &mut egui::Ui,
@ -41,96 +43,47 @@ impl crate::disk::state::Xvb {
gui_api_xp: &Arc<Mutex<PubXmrigProxyApi>>, gui_api_xp: &Arc<Mutex<PubXmrigProxyApi>>,
is_alive: bool, is_alive: bool,
) { ) {
// let text_edit = ui.available_height() / 25.0;
// let website_height = ui.available_height() / 10.0;
// logo and website link
let logo = Some(Image::from_bytes("bytes:/xvb.png", BYTES_XVB));
header_tab(
ui,
logo,
&[
("XMRvsBEAST", XVB_URL, ""),
(
"Rules",
XVB_URL_RULES,
"Click here to read the rules and understand how the raffle works.",
),
("FAQ", "https://xmrvsbeast.com/p2pool/faq.html", ""),
],
None,
true,
);
egui::ScrollArea::vertical().show(ui, |ui| { egui::ScrollArea::vertical().show(ui, |ui| {
let text_edit = size.y / 25.0;
let website_height = size.y / 10.0;
let width = size.x;
let height = size.y;
let space_h = height / 48.0;
// logo and website link
ui.vertical_centered(|ui| {
ui.add_sized(
[width, website_height],
Image::from_bytes("bytes:/xvb.png", BYTES_XVB),
);
ui.style_mut().override_text_style = Some(TextStyle::Heading);
ui.add_space(space_h);
ui.hyperlink_to("XMRvsBeast", XVB_URL);
ui.add_space(space_h);
});
// console output for log // console output for log
debug!("XvB Tab | Rendering [Console]"); debug!("XvB Tab | Rendering [Console]");
ui.group(|ui| { ui.group(|ui| {
let text = &api.lock().unwrap().output; let text = &api.lock().unwrap().output;
let nb_lines = num_lines(text); // let nb_lines = num_lines(text);
let height = size.y / 2.8; console(ui, text);
let width = size.x - (space_h / 2.0);
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
ui.style_mut().override_text_style = Some(TextStyle::Small);
egui::ScrollArea::vertical()
.stick_to_bottom(true)
.max_width(width)
.max_height(height)
.auto_shrink([false; 2])
// .show_viewport(ui, |ui, _| {
.show_rows(
ui,
ui.text_style_height(&TextStyle::Small),
nb_lines,
|ui, row_range| {
for i in row_range {
if let Some(line) = text.lines().nth(i) {
ui.label(line);
}
}
},
);
});
}); });
// input token // input token
let len_token = format!("{}", self.token.len()); ui.add_space(SPACE);
let (text, color) = if self.token.is_empty() {
(
format!("{} [{}/{}] ", XVB_TOKEN_FIELD, len_token, XVB_TOKEN_LEN),
LIGHT_GRAY,
)
} else if self.token.parse::<u32>().is_ok() && self.token.len() < XVB_TOKEN_LEN {
(
format!("{} [{}/{}]", XVB_TOKEN_FIELD, len_token, XVB_TOKEN_LEN),
GREEN,
)
} else if self.token.parse::<u32>().is_ok() && self.token.len() == XVB_TOKEN_LEN {
(format!("{}", XVB_TOKEN_FIELD), GREEN)
} else {
(
format!("{} [{}/{}] ❌", XVB_TOKEN_FIELD, len_token, XVB_TOKEN_LEN),
RED,
)
};
ui.add_space(space_h);
ui.horizontal(|ui| { ui.horizontal(|ui| {
// hovering text is difficult because egui doesn't hover over inner widget. But on disabled does. ui.group(|ui|{
ui.group(|ui| { ui.style_mut().override_text_valign = Some(Align::Center);
ui.colored_label(color, text) // ui.set_height(height_txt_before_button(ui, &TextStyle::Body));
.on_hover_text(XVB_HELP); self.field_token(ui);
ui.add(
TextEdit::singleline(&mut self.token)
.char_limit(9)
.desired_width(ui.text_style_height(&TextStyle::Body) * 9.0)
.vertical_align(egui::Align::Center),
).on_hover_text(XVB_HELP)
}); });
// .on_hover_text(XVB_HELP);
ui.add_space(height / 48.0);
ui.style_mut().spacing.icon_width_inner = width / 45.0;
ui.style_mut().spacing.icon_width = width / 35.0;
ui.style_mut().spacing.icon_spacing = space_h;
// --------------------------- XVB Simple ------------------------------------------- // --------------------------- XVB Simple -------------------------------------------
if self.simple { if self.simple {
ui.add_space(SPACE);
ui.checkbox(&mut self.simple_hero_mode, "Hero Mode").on_hover_text(XVB_HERO_SELECT); ui.checkbox(&mut self.simple_hero_mode, "Hero Mode").on_hover_text(XVB_HERO_SELECT);
// set runtime mode immediately if we are on simple mode. // set runtime mode immediately if we are on simple mode.
if self.simple_hero_mode { if self.simple_hero_mode {
@ -140,14 +93,19 @@ impl crate::disk::state::Xvb {
} }
} }
}); });
ui.add_space(space_h); ui.add_space(SPACE);
// --------------------------- XVB Advanced ----------------------------------------- // --------------------------- XVB Advanced -----------------------------------------
if !self.simple { if !self.simple {
ui.group(|ui| { ui.group(|ui| {
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
ui.horizontal(|ui| { ui.style_mut().override_text_valign = Some(Align::Center);
egui::ComboBox::from_label("") ui.set_height(0.0);
// ui.horizontal_centered(|ui| {
ui.set_height(0.0);
let text_height = height_txt_before_button(ui, &TextStyle::Heading) * 1.4;
// let text_height = 0.0;
egui::ComboBox::from_label("").height(XvbMode::COUNT as f32 * (ui.text_style_height(&TextStyle::Button) + (ui.spacing().button_padding.y * 2.0) + ui.spacing().item_spacing.y))
.selected_text(self.mode.to_string()) .selected_text(self.mode.to_string())
.show_ui(ui, |ui| { .show_ui(ui, |ui| {
ui.selectable_value(&mut self.mode, XvbMode::Auto, ui.selectable_value(&mut self.mode, XvbMode::Auto,
@ -166,7 +124,7 @@ impl crate::disk::state::Xvb {
}); });
if self.mode == XvbMode::ManualXvb || self.mode == XvbMode::ManualP2pool { if self.mode == XvbMode::ManualXvb || self.mode == XvbMode::ManualP2pool {
ui.add_space(space_h); ui.add_space(SPACE);
let default_xmrig_hashrate = match self.manual_donation_metric { let default_xmrig_hashrate = match self.manual_donation_metric {
ManualDonationMetric::Hash => 1_000.0, ManualDonationMetric::Hash => 1_000.0,
@ -203,22 +161,22 @@ impl crate::disk::state::Xvb {
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui.add(egui::SelectableLabel::new(self.manual_donation_metric == ManualDonationMetric::Hash, "Hash")).clicked() { if ui.add_sized([0.0, text_height],egui::SelectableLabel::new(self.manual_donation_metric == ManualDonationMetric::Hash, "Hash")).clicked() {
self.manual_donation_metric = ManualDonationMetric::Hash; self.manual_donation_metric = ManualDonationMetric::Hash;
self.manual_slider_amount = self.manual_amount_raw; self.manual_slider_amount = self.manual_amount_raw;
} }
if ui.add(egui::SelectableLabel::new(self.manual_donation_metric == ManualDonationMetric::Kilo, "Kilo")).clicked() { if ui.add_sized([0.0, text_height],egui::SelectableLabel::new(self.manual_donation_metric == ManualDonationMetric::Kilo, "Kilo")).clicked() {
self.manual_donation_metric = ManualDonationMetric::Kilo; self.manual_donation_metric = ManualDonationMetric::Kilo;
self.manual_slider_amount = self.manual_amount_raw / 1000.0; self.manual_slider_amount = self.manual_amount_raw / 1000.0;
}; };
if ui.add(egui::SelectableLabel::new(self.manual_donation_metric == ManualDonationMetric::Mega, "Mega")).clicked() { if ui.add_sized([0.0, text_height],egui::SelectableLabel::new(self.manual_donation_metric == ManualDonationMetric::Mega, "Mega")).clicked() {
self.manual_donation_metric = ManualDonationMetric::Mega; self.manual_donation_metric = ManualDonationMetric::Mega;
self.manual_slider_amount = self.manual_amount_raw / 1_000_000.0; self.manual_slider_amount = self.manual_amount_raw / 1_000_000.0;
}; };
// less menu, less metrics buttons,less space, less metrics.
ui.spacing_mut().slider_width = width * 0.5; ui.spacing_mut().slider_width = ui.available_width() * 0.3;
ui.add_sized( ui.add_sized(
[width, text_edit], [ui.available_width(), text_height],
egui::Slider::new(&mut self.manual_slider_amount, 0.0..=(hashrate_xmrig as f64)) egui::Slider::new(&mut self.manual_slider_amount, 0.0..=(hashrate_xmrig as f64))
.text(self.manual_donation_metric.to_string()) .text(self.manual_donation_metric.to_string())
.max_decimals(3) .max_decimals(3)
@ -228,6 +186,8 @@ impl crate::disk::state::Xvb {
} }
if self.mode == XvbMode::ManualDonationLevel { if self.mode == XvbMode::ManualDonationLevel {
ui.add_space(SPACE);
ui.horizontal(|ui| {
ui.radio_value(&mut self.manual_donation_level, ManualDonationLevel::Donor, ui.radio_value(&mut self.manual_donation_level, ManualDonationLevel::Donor,
ManualDonationLevel::Donor.to_string()) ManualDonationLevel::Donor.to_string())
.on_hover_text(XVB_DONATION_LEVEL_DONOR_HELP); .on_hover_text(XVB_DONATION_LEVEL_DONOR_HELP);
@ -242,10 +202,12 @@ impl crate::disk::state::Xvb {
.on_hover_text(XVB_DONATION_LEVEL_MEGA_DONOR_HELP); .on_hover_text(XVB_DONATION_LEVEL_MEGA_DONOR_HELP);
api.lock().unwrap().stats_priv.runtime_manual_donation_level = self.manual_donation_level.clone().into(); api.lock().unwrap().stats_priv.runtime_manual_donation_level = self.manual_donation_level.clone().into();
});
} }
ui.add_space(SPACE);
}); });
}); });
}); // });
// Update manual_amount_raw based on slider // Update manual_amount_raw based on slider
match self.manual_donation_metric { match self.manual_donation_metric {
@ -263,18 +225,18 @@ impl crate::disk::state::Xvb {
// Set runtime_mode & runtime_manual_amount // Set runtime_mode & runtime_manual_amount
api.lock().unwrap().stats_priv.runtime_mode = self.mode.clone().into(); api.lock().unwrap().stats_priv.runtime_mode = self.mode.clone().into();
api.lock().unwrap().stats_priv.runtime_manual_amount = self.manual_amount_raw; api.lock().unwrap().stats_priv.runtime_manual_amount = self.manual_amount_raw;
ui.add_space(space_h); ui.add_space(SPACE);
// allow user to modify the buffer for p2pool // allow user to modify the buffer for p2pool
// button // button
ui.add_sized( ui.add_sized(
[width, text_edit], [ui.available_width() * 0.8, height_txt_before_button(ui, &TextStyle::Button)],
egui::Slider::new(&mut self.p2pool_buffer, -100..=100) egui::Slider::new(&mut self.p2pool_buffer, -100..=100)
.text("% P2Pool Buffer" ) .text("% P2Pool Buffer" )
).on_hover_text("Set the % amount of additional HR to send to p2pool. Will reduce (if positive) or augment (if negative) the chances to miss the p2pool window"); ).on_hover_text("Set the % amount of additional HR to send to p2pool. Will reduce (if positive) or augment (if negative) the chances to miss the p2pool window");
} }
ui.add_space(space_h); ui.add_space(SPACE);
// need to warn the user if no address is set in p2pool tab // need to warn the user if no address is set in p2pool tab
if !Regexes::addr_ok(address) { if !Regexes::addr_ok(address) {
debug!("XvB Tab | Rendering warning text"); debug!("XvB Tab | Rendering warning text");
@ -284,100 +246,62 @@ impl crate::disk::state::Xvb {
}); });
} }
// private stats // private stats
ui.add_space(space_h); ui.add_space(SPACE);
// ui.add_enabled_ui(is_alive, |ui| { // ui.add_enabled_ui(is_alive, |ui| {
ui.add_enabled_ui(is_alive, |ui| { ui.add_enabled_ui(is_alive, |ui| {
let api = &api.lock().unwrap(); let api = &api.lock().unwrap();
let priv_stats = &api.stats_priv; let priv_stats = &api.stats_priv;
let current_node = &api.current_node; let current_node = &api.current_node;
let width_stat = (ui.available_width() - SPACE * 4.0) / 5.0; let style_height = ui.text_style_height(&TextStyle::Body);
let height_stat = 0.0; ui.spacing_mut().item_spacing = [style_height * 2.0, style_height * 2.0].into();
let size_stat = vec2(width_stat, height_stat);
ui.horizontal(|ui| { // let width_stat = (ui.available_width() - SPACE * 4.0) / 5.0;
let round = match &priv_stats.round_participate { let width_column = ui.text_style_height(&TextStyle::Body) * 16.0;
Some(r) => r.to_string(), let height_column = 0.0;
None => "None".to_string(), ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
}; ScrollArea::horizontal().id_salt("horizontal").show(ui, |ui| {
ui.add_sized(size_stat, |ui: &mut Ui| { ui.horizontal(|ui| {
ui.group(|ui| { // Failures
let size_stat = vec2( stat_box(ui, XVB_FAILURE_FIELD, &priv_stats.fails.to_string(), width_column, height_column, style_height);
ui.available_width(), stat_box(ui, XVB_DONATED_1H_FIELD,
0.0, // + ui.spacing().item_spacing.y, &[
);
ui.add_sized(size_stat, |ui: &mut Ui| {
ui.vertical_centered(|ui| {
ui.label(XVB_FAILURE_FIELD);
ui.label(priv_stats.fails.to_string());
})
.response
});
ui.separator();
ui.add_sized(size_stat, |ui: &mut Ui| {
ui.vertical_centered(|ui| {
ui.label(XVB_DONATED_1H_FIELD);
ui.label(
[
Float::from_3(priv_stats.donor_1hr_avg as f64).to_string(), Float::from_3(priv_stats.donor_1hr_avg as f64).to_string(),
" kH/s".to_string(), " kH/s".to_string(),
] ]
.concat(), .concat()
); , width_column, height_column, style_height);
}) stat_box(ui, XVB_DONATED_24H_FIELD,
.response &[
});
ui.separator();
ui.add_sized(size_stat, |ui: &mut Ui| {
ui.vertical_centered(|ui| {
ui.label(XVB_DONATED_24H_FIELD);
ui.label(
[
Float::from_3(priv_stats.donor_24hr_avg as f64).to_string(), Float::from_3(priv_stats.donor_24hr_avg as f64).to_string(),
" kH/s".to_string(), " kH/s".to_string(),
] ]
.concat(), .concat()
); , width_column, height_column, style_height);
})
.response
});
ui.separator();
ui.add_enabled_ui(priv_stats.round_participate.is_some(), |ui| { ui.add_enabled_ui(priv_stats.round_participate.is_some(), |ui| {
ui.add_sized(size_stat, |ui: &mut Ui| { let round = match &priv_stats.round_participate {
ui.vertical_centered(|ui| { Some(r) => r.to_string(),
ui.label(XVB_ROUND_TYPE_FIELD); None => "None".to_string(),
ui.label(round); };
}) stat_box(ui, XVB_ROUND_TYPE_FIELD, &round, width_column, height_column, style_height);
.response }).response
})
.on_disabled_hover_text( .on_disabled_hover_text(
"You do not yet have a share in the PPLNS Window.", "You do not yet have a share in the PPLNS Window.",
); );
}); stat_box(ui, XVB_WINNER_FIELD,
ui.separator(); if priv_stats.win_current {
ui.add_sized(size_stat, |ui: &mut Ui| {
ui.vertical_centered(|ui| {
ui.label(XVB_WINNER_FIELD);
ui.label(if priv_stats.win_current {
"You are Winning the round !" "You are Winning the round !"
} else { } else {
"You are not the winner" "You are not the winner"
}); }
}) , width_column, height_column, style_height);
.response
});
})
.response
});
}); });
// indicators });
ui.horizontal(|ui| { ui.vertical(|ui| {
ui.add_sized(size_stat, |ui: &mut Ui| {
ui.group(|ui| { ui.group(|ui| {
let size_stat = vec2( ui.set_width(width_column);
ui.available_width(), ui.set_height(height_column);
0.0, // + ui.spacing().item_spacing.y, ui.vertical_centered(|ui| {
); ui.add_space(SPACE);
ui.add_sized(size_stat, |ui: &mut Ui| {
ui.vertical_centered(|ui| {
ui.label(XVB_MINING_ON_FIELD) ui.label(XVB_MINING_ON_FIELD)
.on_hover_text_at_pointer(&priv_stats.msg_indicator); .on_hover_text_at_pointer(&priv_stats.msg_indicator);
ui.label( ui.label(
@ -389,24 +313,44 @@ impl crate::disk::state::Xvb {
ui.label(Uptime::from(priv_stats.time_switch_node).to_string()) ui.label(Uptime::from(priv_stats.time_switch_node).to_string())
.on_hover_text_at_pointer(&priv_stats.msg_indicator) .on_hover_text_at_pointer(&priv_stats.msg_indicator)
}) })
.response });
}) })
})
.response .response
.on_disabled_hover_text("Algorithm is not running.") .on_disabled_hover_text("Algorithm is not running.");
// indicators
}) })
// currently mining on // currently mining on
}); });
}); }
// Rules link help fn field_token(&mut self, ui: &mut Ui) {
ui.horizontal_centered(|ui| { StateTextEdit::new(ui)
// can't have horizontal and vertical centering work together so fix by this. .help_msg(XVB_HELP)
ui.add_space((width / 2.0) - (ui.text_style_height(&TextStyle::Heading) * 1.5)); .max_ch(XVB_TOKEN_LEN as u8)
ui.style_mut().override_text_style = Some(TextStyle::Heading); .text_edit_width_same_as_max_ch(ui)
ui.hyperlink_to("Rules", XVB_URL_RULES) .description(" Token ")
.on_hover_text("Click here to read the rules and understand how the raffle works."); .validations(&[|x| x.parse::<u32>().is_ok() && x.len() == XVB_TOKEN_LEN])
}); .build(ui, &mut self.token);
});
} }
} }
fn stat_box(
ui: &mut Ui,
title: &str,
value: &str,
column_width: f32,
column_height: f32,
style_height: f32,
) {
ui.vertical(|ui| {
ui.group(|ui| {
ui.set_width(column_width);
ui.set_height(column_height);
ui.vertical_centered(|ui| {
ui.spacing_mut().item_spacing = [style_height / 2.0, style_height / 2.0].into();
ui.add_space(SPACE * 3.0);
ui.label(title);
ui.label(value);
ui.add_space(SPACE);
});
});
});
}

View file

@ -189,7 +189,7 @@ impl crate::app::App {
.add_sized([width, height / 2.0], Button::new("Quit")) .add_sized([width, height / 2.0], Button::new("Quit"))
.clicked() .clicked()
{ {
if self.state.gupax.save_before_quit { if self.state.gupax.auto.save_before_quit {
self.save_before_quit(); self.save_before_quit();
} }
exit(0); exit(0);

View file

@ -12,8 +12,6 @@ impl crate::app::App {
ui.style_mut().spacing.item_spacing.x = 4.0; ui.style_mut().spacing.item_spacing.x = 4.0;
// spacing of separator, will reduce width size of the button. Low value so that tabs can be selected easily. // spacing of separator, will reduce width size of the button. Low value so that tabs can be selected easily.
let spacing_separator = 2.0; let spacing_separator = 2.0;
// TODO if screen smaller, go on two lines.
// TODO if screen really to small, go on tab per line.
ui.with_layout(egui::Layout::left_to_right(egui::Align::Min), |ui| { ui.with_layout(egui::Layout::left_to_right(egui::Align::Min), |ui| {
ui.style_mut().override_text_style = Some(TextStyle::Heading); ui.style_mut().override_text_style = Some(TextStyle::Heading);
let height = ui let height = ui

View file

@ -16,11 +16,11 @@ impl App {
return None; return None;
} }
info!("quit"); info!("quit");
if self.state.gupax.ask_before_quit { if self.state.gupax.auto.ask_before_quit {
// If we're already on the [ask_before_quit] screen and // If we're already on the [ask_before_quit] screen and
// the user tried to exit again, exit. // the user tried to exit again, exit.
if self.error_state.quit_twice { if self.error_state.quit_twice {
if self.state.gupax.save_before_quit { if self.state.gupax.auto.save_before_quit {
self.save_before_quit(); self.save_before_quit();
} }
return Some(ViewportCommand::Close); return Some(ViewportCommand::Close);
@ -32,7 +32,7 @@ impl App {
Some(ViewportCommand::CancelClose) Some(ViewportCommand::CancelClose)
// Else, just quit. // Else, just quit.
} else { } else {
if self.state.gupax.save_before_quit { if self.state.gupax.auto.save_before_quit {
self.save_before_quit(); self.save_before_quit();
} }
Some(ViewportCommand::Close) Some(ViewportCommand::Close)

View file

@ -241,7 +241,7 @@ impl Update {
#[cfg(feature = "distro")] #[cfg(feature = "distro")]
return; return;
// verify validity of absolute path for p2pool, xmrig and xmrig-proxy only if we want to update them. // verify validity of absolute path for p2pool, xmrig and xmrig-proxy only if we want to update them.
if og.lock().unwrap().gupax.bundled { if og.lock().unwrap().gupax.auto.bundled {
// Check P2Pool path for safety // Check P2Pool path for safety
// Attempt relative to absolute path // Attempt relative to absolute path
// it's ok if file doesn't exist. User could enable bundled version for the first time. // it's ok if file doesn't exist. User could enable bundled version for the first time.
@ -465,7 +465,7 @@ impl Update {
// arch // arch
// standalone or bundled // standalone or bundled
// archive extension // archive extension
let bundle = if og.lock().unwrap().gupax.bundled { let bundle = if og.lock().unwrap().gupax.auto.bundled {
"bundle" "bundle"
} else { } else {
"standalone" "standalone"
@ -577,7 +577,7 @@ impl Update {
path.display() path.display()
); );
// if bundled, create directory for p2pool, xmrig and xmrig-proxy if not present // if bundled, create directory for p2pool, xmrig and xmrig-proxy if not present
if og.lock().unwrap().gupax.bundled if og.lock().unwrap().gupax.auto.bundled
&& (name == P2POOL_BINARY && (name == P2POOL_BINARY
|| name == XMRIG_BINARY || name == XMRIG_BINARY
|| name == XMRIG_PROXY_BINARY || name == XMRIG_PROXY_BINARY

View file

@ -1,25 +1,25 @@
use crate::disk::*; use crate::{app::panels::middle::common::list_poolnode::PoolNode, disk::*};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
//---------------------------------------------------------------------------------------------------- [Node] Impl //---------------------------------------------------------------------------------------------------- [Node] Impl
impl Node { impl Node {
pub fn localhost() -> Self { pub fn localhost() -> PoolNode {
Self { PoolNode::Node(Self {
ip: "localhost".to_string(), ip: "localhost".to_string(),
rpc: "18081".to_string(), rpc: "18081".to_string(),
zmq: "18083".to_string(), zmq: "18083".to_string(),
} })
} }
pub fn new_vec() -> Vec<(String, Self)> { pub fn new_vec() -> Vec<(String, PoolNode)> {
vec![("Local Monero Node".to_string(), Self::localhost())] vec![("Local Monero Node".to_string(), Self::localhost())]
} }
pub fn new_tuple() -> (String, Self) { pub fn new_tuple() -> (String, PoolNode) {
("Local Monero Node".to_string(), Self::localhost()) ("Local Monero Node".to_string(), Self::localhost())
} }
// Convert [String] to [Node] Vec // Convert [String] to [Node] Vec
pub fn from_str_to_vec(string: &str) -> Result<Vec<(String, Self)>, TomlError> { pub fn from_str_to_vec(string: &str) -> Result<Vec<(String, PoolNode)>, TomlError> {
let nodes: toml::map::Map<String, toml::Value> = match toml::de::from_str(string) { let nodes: toml::map::Map<String, toml::Value> = match toml::de::from_str(string) {
Ok(map) => { Ok(map) => {
info!("Node | Parse ... OK"); info!("Node | Parse ... OK");
@ -73,20 +73,23 @@ impl Node {
} }
}; };
let node = Node { ip, rpc, zmq }; let node = Node { ip, rpc, zmq };
vec.push((key.clone(), node)); vec.push((key.clone(), PoolNode::Node(node)));
} }
Ok(vec) Ok(vec)
} }
// Convert [Vec<(String, Self)>] into [String] // Convert [Vec<(String, Self)>] into [String]
// that can be written as a proper TOML file // that can be written as a proper TOML file
pub fn to_string(vec: &[(String, Self)]) -> Result<String, TomlError> { pub fn to_string(vec: &[(String, PoolNode)]) -> Result<String, TomlError> {
let mut toml = String::new(); let mut toml = String::new();
for (key, value) in vec.iter() { for (key, value) in vec.iter() {
write!( write!(
toml, toml,
"[\'{}\']\nip = {:#?}\nrpc = {:#?}\nzmq = {:#?}\n\n", "[\'{}\']\nip = {:#?}\nrpc = {:#?}\nzmq = {:#?}\n\n",
key, value.ip, value.rpc, value.zmq, key,
value.ip(),
value.port(),
value.custom(),
)?; )?;
} }
Ok(toml) Ok(toml)
@ -97,7 +100,7 @@ impl Node {
// |_ Create a default file if not found // |_ Create a default file if not found
// 2. Deserialize [String] into a proper [Struct] // 2. Deserialize [String] into a proper [Struct]
// |_ Attempt to merge if deserialization fails // |_ Attempt to merge if deserialization fails
pub fn get(path: &PathBuf) -> Result<Vec<(String, Self)>, TomlError> { pub fn get(path: &PathBuf) -> Result<Vec<(String, PoolNode)>, TomlError> {
// Read // Read
let file = File::Node; let file = File::Node;
let string = match read_to_string(file, path) { let string = match read_to_string(file, path) {
@ -114,7 +117,7 @@ impl Node {
// Completely overwrite current [node.toml] // Completely overwrite current [node.toml]
// with a new default version, and return [Vec<String, Self>]. // with a new default version, and return [Vec<String, Self>].
pub fn create_new(path: &PathBuf) -> Result<Vec<(String, Self)>, TomlError> { pub fn create_new(path: &PathBuf) -> Result<Vec<(String, PoolNode)>, TomlError> {
info!("Node | Creating new default..."); info!("Node | Creating new default...");
let new = Self::new_vec(); let new = Self::new_vec();
let string = Self::to_string(&Self::new_vec())?; let string = Self::to_string(&Self::new_vec())?;
@ -124,7 +127,7 @@ impl Node {
} }
// Save [Node] onto disk file [node.toml] // Save [Node] onto disk file [node.toml]
pub fn save(vec: &[(String, Self)], path: &PathBuf) -> Result<(), TomlError> { pub fn save(vec: &[(String, PoolNode)], path: &PathBuf) -> Result<(), TomlError> {
info!("Node | Saving to disk ... [{}]", path.display()); info!("Node | Saving to disk ... [{}]", path.display());
let string = Self::to_string(vec)?; let string = Self::to_string(vec)?;
match fs::write(path, string) { match fs::write(path, string) {

View file

@ -1,23 +1,25 @@
use crate::app::panels::middle::common::list_poolnode::PoolNode;
use super::*; use super::*;
//---------------------------------------------------------------------------------------------------- [Pool] impl //---------------------------------------------------------------------------------------------------- [Pool] impl
impl Pool { impl Pool {
pub fn p2pool() -> Self { pub fn p2pool() -> PoolNode {
Self { PoolNode::Pool(Self {
rig: GUPAX_VERSION_UNDERSCORE.to_string(), rig: GUPAX_VERSION_UNDERSCORE.to_string(),
ip: "localhost".to_string(), ip: "localhost".to_string(),
port: "3333".to_string(), port: "3333".to_string(),
} })
} }
pub fn new_vec() -> Vec<(String, Self)> { pub fn new_vec() -> Vec<(String, PoolNode)> {
vec![("Local P2Pool".to_string(), Self::p2pool())] vec![("Local P2Pool".to_string(), Self::p2pool())]
} }
pub fn new_tuple() -> (String, Self) { pub fn new_tuple() -> (String, PoolNode) {
("Local P2Pool".to_string(), Self::p2pool()) ("Local P2Pool".to_string(), Self::p2pool())
} }
pub fn from_str_to_vec(string: &str) -> Result<Vec<(String, Self)>, TomlError> { pub fn from_str_to_vec(string: &str) -> Result<Vec<(String, PoolNode)>, TomlError> {
let pools: toml::map::Map<String, toml::Value> = match toml::de::from_str(string) { let pools: toml::map::Map<String, toml::Value> = match toml::de::from_str(string) {
Ok(map) => { Ok(map) => {
info!("Pool | Parse ... OK"); info!("Pool | Parse ... OK");
@ -72,24 +74,27 @@ impl Pool {
} }
}; };
let pool = Pool { rig, ip, port }; let pool = Pool { rig, ip, port };
vec.push((key.clone(), pool)); vec.push((key.clone(), PoolNode::Pool(pool)));
} }
Ok(vec) Ok(vec)
} }
pub fn to_string(vec: &[(String, Self)]) -> Result<String, TomlError> { pub fn to_string(vec: &[(String, PoolNode)]) -> Result<String, TomlError> {
let mut toml = String::new(); let mut toml = String::new();
for (key, value) in vec.iter() { for (key, value) in vec.iter() {
write!( write!(
toml, toml,
"[\'{}\']\nrig = {:#?}\nip = {:#?}\nport = {:#?}\n\n", "[\'{}\']\nrig = {:#?}\nip = {:#?}\nport = {:#?}\n\n",
key, value.rig, value.ip, value.port, key,
value.custom(),
value.ip(),
value.port(),
)?; )?;
} }
Ok(toml) Ok(toml)
} }
pub fn get(path: &PathBuf) -> Result<Vec<(String, Self)>, TomlError> { pub fn get(path: &PathBuf) -> Result<Vec<(String, PoolNode)>, TomlError> {
// Read // Read
let file = File::Pool; let file = File::Pool;
let string = match read_to_string(file, path) { let string = match read_to_string(file, path) {
@ -104,7 +109,7 @@ impl Pool {
Self::from_str_to_vec(&string) Self::from_str_to_vec(&string)
} }
pub fn create_new(path: &PathBuf) -> Result<Vec<(String, Self)>, TomlError> { pub fn create_new(path: &PathBuf) -> Result<Vec<(String, PoolNode)>, TomlError> {
info!("Pool | Creating new default..."); info!("Pool | Creating new default...");
let new = Self::new_vec(); let new = Self::new_vec();
let string = Self::to_string(&Self::new_vec())?; let string = Self::to_string(&Self::new_vec())?;
@ -113,7 +118,7 @@ impl Pool {
Ok(new) Ok(new)
} }
pub fn save(vec: &[(String, Self)], path: &PathBuf) -> Result<(), TomlError> { pub fn save(vec: &[(String, PoolNode)], path: &PathBuf) -> Result<(), TomlError> {
info!("Pool | Saving to disk ... [{}]", path.display()); info!("Pool | Saving to disk ... [{}]", path.display());
let string = Self::to_string(vec)?; let string = Self::to_string(vec)?;
match fs::write(path, string) { match fs::write(path, string) {

View file

@ -1,8 +1,9 @@
use anyhow::Result; use anyhow::Result;
use rand::{Rng, distributions::Alphanumeric, thread_rng}; use rand::{Rng, distributions::Alphanumeric, thread_rng};
use strum::{EnumCount, EnumIter};
use super::*; use super::*;
use crate::{components::node::RemoteNode, disk::status::*}; use crate::{components::node::RemoteNode, disk::status::*, helper::ProcessName};
//---------------------------------------------------------------------------------------------------- [State] Impl //---------------------------------------------------------------------------------------------------- [State] Impl
impl Default for State { impl Default for State {
fn default() -> Self { fn default() -> Self {
@ -12,7 +13,7 @@ impl Default for State {
impl State { impl State {
pub fn new() -> Self { pub fn new() -> Self {
let max_threads = benri::threads!(); let max_threads = benri::threads!() as u16;
let current_threads = if max_threads == 1 { 1 } else { max_threads / 2 }; let current_threads = if max_threads == 1 { 1 } else { max_threads / 2 };
Self { Self {
status: Status::default(), status: Status::default(),
@ -132,7 +133,6 @@ impl State {
} }
} }
} }
// Take [String] as input, merge it with whatever the current [default] is, // Take [String] as input, merge it with whatever the current [default] is,
// leaving behind old keys+values and updating [default] with old valid ones. // leaving behind old keys+values and updating [default] with old valid ones.
pub fn merge(old: &str) -> Result<Self, TomlError> { pub fn merge(old: &str) -> Result<Self, TomlError> {
@ -179,14 +179,7 @@ pub struct Status {
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct Gupax { pub struct Gupax {
pub simple: bool, pub simple: bool,
pub auto_update: bool, pub auto: AutoEnabled,
pub auto_p2pool: bool,
pub auto_node: bool,
pub auto_xmrig: bool,
pub auto_xp: bool,
pub auto_xvb: bool,
pub ask_before_quit: bool,
pub save_before_quit: bool,
pub p2pool_path: String, pub p2pool_path: String,
pub node_path: String, pub node_path: String,
pub xmrig_path: String, pub xmrig_path: String,
@ -200,9 +193,94 @@ pub struct Gupax {
pub selected_scale: f32, pub selected_scale: f32,
pub tab: Tab, pub tab: Tab,
pub ratio: Ratio, pub ratio: Ratio,
pub bundled: bool,
} }
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct AutoEnabled {
pub update: bool,
pub bundled: bool,
pub ask_before_quit: bool,
pub save_before_quit: bool,
pub processes: Vec<ProcessName>,
}
impl AutoEnabled {
pub fn enable(&mut self, auto: &AutoStart, enable: bool) {
match auto {
AutoStart::Update => self.update = enable,
AutoStart::Bundle => self.bundled = enable,
AutoStart::AskBeforeQuit => self.ask_before_quit = enable,
AutoStart::SaveBeforequit => self.save_before_quit = enable,
AutoStart::Process(p) => {
let processes = &mut self.processes;
if !processes.iter().any(|a| a == p) && enable {
self.processes.push(*p);
} else if let Some(i) = processes.iter().position(|a| a == p) {
if !enable {
processes.remove(i);
}
}
}
}
}
pub fn is_enabled(&self, auto: &AutoStart) -> bool {
match auto {
AutoStart::Update => self.update,
AutoStart::Bundle => self.bundled,
AutoStart::AskBeforeQuit => self.ask_before_quit,
AutoStart::SaveBeforequit => self.save_before_quit,
AutoStart::Process(p) => self.processes.iter().any(|a| a == p),
}
}
}
#[derive(PartialEq, strum::Display, EnumCount, EnumIter)]
pub enum AutoStart {
#[strum(to_string = "Auto-Update")]
Update,
Bundle,
#[strum(to_string = "Confirm quit")]
AskBeforeQuit,
#[strum(to_string = "Save on exit")]
SaveBeforequit,
#[strum(to_string = "Auto-{0}")]
Process(ProcessName),
}
impl AutoStart {
pub const fn help_msg(&self) -> &str {
match self {
AutoStart::Update => GUPAX_AUTO_UPDATE,
AutoStart::Bundle => GUPAX_BUNDLED_UPDATE,
AutoStart::AskBeforeQuit => GUPAX_ASK_BEFORE_QUIT,
AutoStart::SaveBeforequit => GUPAX_SAVE_BEFORE_QUIT,
AutoStart::Process(p) => p.msg_auto_help(),
}
}
// todo: generate as const with all process in middle ?
// Would necessities unstable feature https://github.com/rust-lang/rust/issues/87575
pub const ALL: &[AutoStart] = &[
AutoStart::Update,
AutoStart::Bundle,
AutoStart::Process(ProcessName::Node),
AutoStart::Process(ProcessName::P2pool),
AutoStart::Process(ProcessName::Xmrig),
AutoStart::Process(ProcessName::XmrigProxy),
AutoStart::Process(ProcessName::Xvb),
AutoStart::AskBeforeQuit,
AutoStart::SaveBeforequit,
];
// non const:
// let mut autos = AutoStart::iter().collect::<Vec<_>>();
// // remove ProcessName default
// autos.remove(AutoStart::COUNT - 1);
// // insert ProcessName before AskBeforeQuit
// let before_quit_index = autos
// .iter()
// .position(|a| *a == AutoStart::AskBeforeQuit)
// .expect("Before quit should be in iter");
// ProcessName::iter()
// .rev()
// .for_each(|p| autos.insert(before_quit_index, AutoStart::Process(p)));
// autos
}
#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] #[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)]
pub struct P2pool { pub struct P2pool {
pub simple: bool, pub simple: bool,
@ -213,7 +291,7 @@ pub struct P2pool {
pub backup_host: bool, pub backup_host: bool,
pub out_peers: u16, pub out_peers: u16,
pub in_peers: u16, pub in_peers: u16,
pub log_level: u8, pub log_level: u16,
pub node: String, pub node: String,
pub arguments: String, pub arguments: String,
pub address: String, pub address: String,
@ -221,11 +299,17 @@ pub struct P2pool {
pub ip: String, pub ip: String,
pub rpc: String, pub rpc: String,
pub zmq: String, pub zmq: String,
pub selected_index: usize, pub selected_node: SelectedPoolNode,
pub selected_name: String, }
pub selected_ip: String,
pub selected_rpc: String, // compatible for P2Pool and Xmrig/Proxy
pub selected_zmq: String, #[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)]
pub struct SelectedPoolNode {
pub index: usize,
pub name: String,
pub ip: String,
pub rpc: String,
pub zmq_rig: String,
} }
#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] #[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)]
@ -235,7 +319,7 @@ pub struct Node {
pub api_port: String, pub api_port: String,
pub out_peers: u16, pub out_peers: u16,
pub in_peers: u16, pub in_peers: u16,
pub log_level: u8, pub log_level: u16,
pub arguments: String, pub arguments: String,
pub zmq_ip: String, pub zmq_ip: String,
pub zmq_port: String, pub zmq_port: String,
@ -268,13 +352,13 @@ impl Default for Node {
#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] #[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)]
pub struct Xmrig { pub struct Xmrig {
pub simple: bool, pub simple: bool,
pub pause: u8, pub pause: u16,
pub simple_rig: String, pub simple_rig: String,
pub arguments: String, pub arguments: String,
pub tls: bool, pub tls: bool,
pub keepalive: bool, pub keepalive: bool,
pub max_threads: usize, pub max_threads: u16,
pub current_threads: usize, pub current_threads: u16,
pub address: String, pub address: String,
pub api_ip: String, pub api_ip: String,
pub api_port: String, pub api_port: String,
@ -282,11 +366,7 @@ pub struct Xmrig {
pub rig: String, pub rig: String,
pub ip: String, pub ip: String,
pub port: String, pub port: String,
pub selected_index: usize, pub selected_pool: SelectedPoolNode,
pub selected_name: String,
pub selected_rig: String,
pub selected_ip: String,
pub selected_port: String,
pub token: String, pub token: String,
} }
@ -307,15 +387,41 @@ pub struct XmrigProxy {
pub api_port: String, pub api_port: String,
pub p2pool_ip: String, pub p2pool_ip: String,
pub p2pool_port: String, pub p2pool_port: String,
pub selected_index: usize, pub selected_pool: SelectedPoolNode,
pub selected_name: String,
pub selected_rig: String,
pub selected_ip: String,
pub selected_port: String,
pub token: String, pub token: String,
pub redirect_local_xmrig: bool, pub redirect_local_xmrig: bool,
} }
impl Gupax {
pub fn path_binary(&mut self, process: &BundledProcess) -> &mut String {
match process {
BundledProcess::Node => &mut self.node_path,
BundledProcess::P2Pool => &mut self.p2pool_path,
BundledProcess::Xmrig => &mut self.xmrig_path,
BundledProcess::XmrigProxy => &mut self.xmrig_proxy_path,
}
}
}
// do not include process that are from Gupaxx
#[derive(EnumIter)]
pub enum BundledProcess {
Node,
P2Pool,
Xmrig,
XmrigProxy,
}
impl BundledProcess {
pub fn process_name(&self) -> ProcessName {
match self {
BundledProcess::Node => ProcessName::Node,
BundledProcess::P2Pool => ProcessName::P2pool,
BundledProcess::Xmrig => ProcessName::Xmrig,
BundledProcess::XmrigProxy => ProcessName::XmrigProxy,
}
}
}
impl Default for XmrigProxy { impl Default for XmrigProxy {
fn default() -> Self { fn default() -> Self {
XmrigProxy { XmrigProxy {
@ -335,11 +441,13 @@ impl Default for XmrigProxy {
port: "3355".to_string(), port: "3355".to_string(),
p2pool_ip: "localhost".to_string(), p2pool_ip: "localhost".to_string(),
p2pool_port: "3333".to_string(), p2pool_port: "3333".to_string(),
selected_index: 0, selected_pool: SelectedPoolNode {
selected_name: "Local P2Pool".to_string(), index: 0,
selected_ip: "localhost".to_string(), name: "Local P2Pool".to_string(),
selected_rig: GUPAX_VERSION_UNDERSCORE.to_string(), ip: "localhost".to_string(),
selected_port: "3333".to_string(), rpc: "3333".to_string(),
zmq_rig: GUPAX_VERSION_UNDERSCORE.to_string(),
},
api_ip: "localhost".to_string(), api_ip: "localhost".to_string(),
api_port: "18089".to_string(), api_port: "18089".to_string(),
tls: false, tls: false,
@ -361,7 +469,7 @@ pub struct Xvb {
pub p2pool_buffer: i8, pub p2pool_buffer: i8,
} }
#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize, Default)] #[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize, Default, EnumCount, EnumIter)]
pub enum XvbMode { pub enum XvbMode {
#[default] #[default]
Auto, Auto,
@ -435,6 +543,20 @@ pub struct Version {
} }
//---------------------------------------------------------------------------------------------------- [State] Defaults //---------------------------------------------------------------------------------------------------- [State] Defaults
impl Default for AutoEnabled {
fn default() -> Self {
Self {
update: false,
#[cfg(feature = "bundle")]
bundled: true,
#[cfg(not(feature = "bundle"))]
bundled: false,
ask_before_quit: true,
save_before_quit: true,
processes: Vec::new(),
}
}
}
impl Default for Status { impl Default for Status {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -452,14 +574,7 @@ impl Default for Gupax {
fn default() -> Self { fn default() -> Self {
Self { Self {
simple: true, simple: true,
auto_update: false, auto: AutoEnabled::default(),
auto_p2pool: false,
auto_node: false,
auto_xmrig: false,
auto_xp: false,
auto_xvb: false,
ask_before_quit: true,
save_before_quit: true,
p2pool_path: DEFAULT_P2POOL_PATH.to_string(), p2pool_path: DEFAULT_P2POOL_PATH.to_string(),
xmrig_path: DEFAULT_XMRIG_PATH.to_string(), xmrig_path: DEFAULT_XMRIG_PATH.to_string(),
node_path: DEFAULT_NODE_PATH.to_string(), node_path: DEFAULT_NODE_PATH.to_string(),
@ -473,10 +588,6 @@ impl Default for Gupax {
selected_scale: APP_DEFAULT_SCALE, selected_scale: APP_DEFAULT_SCALE,
ratio: Ratio::Width, ratio: Ratio::Width,
tab: Tab::Xvb, tab: Tab::Xvb,
#[cfg(feature = "bundle")]
bundled: true,
#[cfg(not(feature = "bundle"))]
bundled: false,
} }
} }
} }
@ -500,17 +611,19 @@ impl Default for P2pool {
ip: "localhost".to_string(), ip: "localhost".to_string(),
rpc: "18081".to_string(), rpc: "18081".to_string(),
zmq: "18083".to_string(), zmq: "18083".to_string(),
selected_index: 0, selected_node: SelectedPoolNode {
selected_name: "Local Monero Node".to_string(), index: 0,
selected_ip: "localhost".to_string(), name: "Local Monero Node".to_string(),
selected_rpc: "18081".to_string(), ip: "localhost".to_string(),
selected_zmq: "18083".to_string(), rpc: "18081".to_string(),
zmq_rig: "18083".to_string(),
},
} }
} }
} }
impl Xmrig { impl Xmrig {
fn with_threads(max_threads: usize, current_threads: usize) -> Self { fn with_threads(max_threads: u16, current_threads: u16) -> Self {
let xmrig = Self::default(); let xmrig = Self::default();
Self { Self {
max_threads, max_threads,
@ -531,17 +644,19 @@ impl Default for Xmrig {
rig: GUPAX_VERSION_UNDERSCORE.to_string(), rig: GUPAX_VERSION_UNDERSCORE.to_string(),
ip: "localhost".to_string(), ip: "localhost".to_string(),
port: "3333".to_string(), port: "3333".to_string(),
selected_index: 0,
selected_name: "Local P2Pool".to_string(),
selected_ip: "localhost".to_string(),
selected_rig: GUPAX_VERSION_UNDERSCORE.to_string(),
selected_port: "3333".to_string(),
api_ip: "localhost".to_string(), api_ip: "localhost".to_string(),
api_port: "18088".to_string(), api_port: "18088".to_string(),
tls: false, tls: false,
keepalive: false, keepalive: false,
current_threads: 1, current_threads: 1,
max_threads: 1, max_threads: 1,
selected_pool: SelectedPoolNode {
index: 0,
name: "Local Monero Node".to_string(),
ip: "localhost".to_string(),
rpc: "18081".to_string(),
zmq_rig: "18083".to_string(),
},
token: thread_rng() token: thread_rng()
.sample_iter(Alphanumeric) .sample_iter(Alphanumeric)
.take(16) .take(16)

View file

@ -1,3 +1,6 @@
use derive_more::derive::Display;
use strum::{EnumCount, EnumIter};
use super::*; use super::*;
//---------------------------------------------------------------------------------------------------- [Submenu] enum for [Status] tab //---------------------------------------------------------------------------------------------------- [Submenu] enum for [Status] tab
#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] #[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)]
@ -25,7 +28,9 @@ impl Display for Submenu {
//---------------------------------------------------------------------------------------------------- [PayoutView] enum for [Status/P2Pool] tab //---------------------------------------------------------------------------------------------------- [PayoutView] enum for [Status/P2Pool] tab
// The enum buttons for selecting which "view" to sort the payout log in. // The enum buttons for selecting which "view" to sort the payout log in.
#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] #[derive(
Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize, Display, EnumIter, EnumCount,
)]
pub enum PayoutView { pub enum PayoutView {
Latest, // Shows the most recent logs first Latest, // Shows the most recent logs first
Oldest, // Shows the oldest logs first Oldest, // Shows the oldest logs first
@ -33,6 +38,17 @@ pub enum PayoutView {
Smallest, // Shows lowest to highest payouts Smallest, // Shows lowest to highest payouts
} }
impl PayoutView {
pub const fn msg_help(&self) -> &str {
match self {
Self::Latest => STATUS_SUBMENU_LATEST,
Self::Oldest => STATUS_SUBMENU_OLDEST,
Self::Biggest => STATUS_SUBMENU_SMALLEST,
Self::Smallest => STATUS_SUBMENU_BIGGEST,
}
}
}
impl PayoutView { impl PayoutView {
fn new() -> Self { fn new() -> Self {
Self::Latest Self::Latest
@ -45,12 +61,6 @@ impl Default for PayoutView {
} }
} }
impl Display for PayoutView {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
//---------------------------------------------------------------------------------------------------- [Hash] enum for [Status/P2Pool] //---------------------------------------------------------------------------------------------------- [Hash] enum for [Status/P2Pool]
#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] #[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)]
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
@ -110,8 +120,10 @@ impl Hash {
impl Display for Hash { impl Display for Hash {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self { match self {
Hash::Hash => write!(f, "Hash"), Hash::Hash => write!(f, "H/s"),
_ => write!(f, "{:?}hash", self), Hash::Kilo => write!(f, "KH/s"),
Hash::Mega => write!(f, "MH/s"),
Hash::Giga => write!(f, "GH/s"),
} }
} }
} }

View file

@ -51,6 +51,13 @@ mod test {
ratio = "Width" ratio = "Width"
bundled = false bundled = false
[gupax.auto]
update = false
bundled = false
ask_before_quit = false
save_before_quit = true
processes = []
[status] [status]
submenu = "P2pool" submenu = "P2pool"
payout_view = "Oldest" payout_view = "Oldest"
@ -76,11 +83,13 @@ mod test {
ip = "192.168.1.123" ip = "192.168.1.123"
rpc = "18089" rpc = "18089"
zmq = "18083" zmq = "18083"
selected_index = 0
selected_name = "Local Monero Node" [p2pool.selected_node]
selected_ip = "192.168.1.123" index = 0
selected_rpc = "18089" name = "Local Monero Node"
selected_zmq = "18083" ip = "localhost"
rpc = "18081"
zmq_rig = "18083"
[xmrig] [xmrig]
simple = true simple = true
@ -98,13 +107,17 @@ mod test {
rig = "Gupaxx" rig = "Gupaxx"
ip = "192.168.1.122" ip = "192.168.1.122"
port = "3333" port = "3333"
selected_index = 1
selected_name = "linux"
selected_rig = "Gupaxx"
selected_ip = "192.168.1.122"
selected_port = "3333"
token = "testtoken" token = "testtoken"
[xmrig.selected_pool]
index = 0
name = "Local Monero Node"
ip = "localhost"
rpc = "18081"
zmq_rig = "18083"
[xmrig_proxy] [xmrig_proxy]
simple = true simple = true
arguments = "" arguments = ""
@ -121,13 +134,15 @@ mod test {
p2pool_ip = "localhost" p2pool_ip = "localhost"
p2pool_port = "18088" p2pool_port = "18088"
token = "testtoken" token = "testtoken"
selected_index = 1
selected_name = "linux"
selected_rig = "Gupaxx"
selected_ip = "192.168.1.122"
selected_port = "3333"
redirect_local_xmrig = true redirect_local_xmrig = true
[xmrig_proxy.selected_pool]
index = 0
name = "Local Monero Node"
ip = "localhost"
rpc = "18081"
zmq_rig = "18083"
[xvb] [xvb]
simple = true simple = true
simple_hero_mode = true simple_hero_mode = true

View file

@ -33,6 +33,7 @@
// This also includes all things related to handling the child processes (P2Pool/XMRig): // This also includes all things related to handling the child processes (P2Pool/XMRig):
// piping their stdout/stderr/stdin, accessing their APIs (HTTP + disk files), etc. // piping their stdout/stderr/stdin, accessing their APIs (HTTP + disk files), etc.
use crate::components::gupax::FileType;
use crate::components::update::{NODE_BINARY, P2POOL_BINARY, XMRIG_BINARY, XMRIG_PROXY_BINARY}; use crate::components::update::{NODE_BINARY, P2POOL_BINARY, XMRIG_BINARY, XMRIG_PROXY_BINARY};
//---------------------------------------------------------------------------------------------------- Import //---------------------------------------------------------------------------------------------------- Import
use crate::helper::xrig::xmrig_proxy::PubXmrigProxyApi; use crate::helper::xrig::xmrig_proxy::PubXmrigProxyApi;
@ -46,6 +47,7 @@ use log::*;
use node::PubNodeApi; use node::PubNodeApi;
use portable_pty::Child; use portable_pty::Child;
use readable::up::Uptime; use readable::up::Uptime;
use serde::{Deserialize, Serialize};
use std::fmt::Write; use std::fmt::Write;
use std::path::Path; use std::path::Path;
use std::{ use std::{
@ -54,7 +56,7 @@ use std::{
thread, thread,
time::*, time::*,
}; };
use strum::EnumIter; use strum::{EnumCount, EnumIter};
use self::xvb::{PubXvbApi, nodes::XvbNode}; use self::xvb::{PubXvbApi, nodes::XvbNode};
pub mod node; pub mod node;
@ -250,18 +252,21 @@ impl Default for ProcessSignal {
} }
} }
#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, EnumIter)] #[derive(
Copy, Clone, Eq, PartialEq, Debug, Display, EnumIter, EnumCount, Serialize, Deserialize, Default,
)]
pub enum ProcessName { pub enum ProcessName {
Node, Node,
P2pool, P2pool,
Xmrig, Xmrig,
#[display("Proxy")] #[display("Proxy")]
XmrigProxy, XmrigProxy,
#[default]
Xvb, Xvb,
} }
impl ProcessName { impl ProcessName {
pub fn binary_name(&self) -> &str { pub const fn binary_name(&self) -> &str {
match self { match self {
ProcessName::Node => NODE_BINARY, ProcessName::Node => NODE_BINARY,
ProcessName::P2pool => P2POOL_BINARY, ProcessName::P2pool => P2POOL_BINARY,
@ -270,6 +275,69 @@ impl ProcessName {
ProcessName::Xvb => "", ProcessName::Xvb => "",
} }
} }
pub const fn msg_binary_path_empty(&self) -> &str {
match self {
ProcessName::Node => NODE_PATH_EMPTY,
ProcessName::P2pool => P2POOL_PATH_EMPTY,
ProcessName::Xmrig => XMRIG_PATH_EMPTY,
ProcessName::XmrigProxy => XMRIG_PROXY_PATH_EMPTY,
ProcessName::Xvb => "",
}
}
pub const fn msg_binary_path_not_file(&self) -> &str {
match self {
ProcessName::Node => NODE_PATH_NOT_FILE,
ProcessName::P2pool => P2POOL_PATH_NOT_FILE,
ProcessName::Xmrig => XMRIG_PATH_NOT_FILE,
ProcessName::XmrigProxy => XMRIG_PROXY_PATH_NOT_FILE,
ProcessName::Xvb => "",
}
}
pub const fn msg_binary_path_invalid(&self) -> &str {
match self {
ProcessName::Node => NODE_PATH_NOT_VALID,
ProcessName::P2pool => P2POOL_PATH_NOT_VALID,
ProcessName::Xmrig => XMRIG_PATH_NOT_VALID,
ProcessName::XmrigProxy => XMRIG_PROXY_PATH_NOT_VALID,
ProcessName::Xvb => "",
}
}
pub const fn msg_binary_path_ok(&self) -> &str {
match self {
ProcessName::Node => NODE_PATH_OK,
ProcessName::P2pool => P2POOL_PATH_OK,
ProcessName::Xmrig => XMRIG_PATH_OK,
ProcessName::XmrigProxy => XMRIG_PROXY_PATH_OK,
ProcessName::Xvb => "",
}
}
pub const fn msg_path_edit(&self) -> &str {
match self {
ProcessName::Node => GUPAX_PATH_NODE,
ProcessName::P2pool => GUPAX_PATH_P2POOL,
ProcessName::Xmrig => GUPAX_PATH_XMRIG,
ProcessName::XmrigProxy => GUPAX_PATH_XMRIG_PROXY,
ProcessName::Xvb => "",
}
}
pub const fn msg_auto_help(&self) -> &str {
match self {
ProcessName::Node => GUPAX_AUTO_NODE,
ProcessName::P2pool => GUPAX_AUTO_P2POOL,
ProcessName::Xmrig => GUPAX_AUTO_XMRIG,
ProcessName::XmrigProxy => GUPAX_AUTO_XMRIG_PROXY,
ProcessName::Xvb => GUPAX_AUTO_XVB,
}
}
pub const fn file_type(&self) -> Option<FileType> {
match self {
ProcessName::Node => Some(FileType::Node),
ProcessName::P2pool => Some(FileType::P2pool),
ProcessName::Xmrig => Some(FileType::Xmrig),
ProcessName::XmrigProxy => Some(FileType::XmrigProxy),
ProcessName::Xvb => None,
}
}
} }
impl std::fmt::Display for ProcessState { impl std::fmt::Display for ProcessState {
@ -374,7 +442,7 @@ impl Helper {
pub_sys: &mut Sys, pub_sys: &mut Sys,
pid: &sysinfo::Pid, pid: &sysinfo::Pid,
helper: &Helper, helper: &Helper,
max_threads: usize, max_threads: u16,
) { ) {
let gupax_uptime = helper.uptime.to_string(); let gupax_uptime = helper.uptime.to_string();
let cpu = &sysinfo.cpus()[0]; let cpu = &sysinfo.cpus()[0];
@ -416,7 +484,7 @@ impl Helper {
helper: &Arc<Mutex<Self>>, helper: &Arc<Mutex<Self>>,
mut sysinfo: sysinfo::System, mut sysinfo: sysinfo::System,
pid: sysinfo::Pid, pid: sysinfo::Pid,
max_threads: usize, max_threads: u16,
) { ) {
// The ordering of these locks is _very_ important. They MUST be in sync with how the main GUI thread locks stuff // The ordering of these locks is _very_ important. They MUST be in sync with how the main GUI thread locks stuff
// or a deadlock will occur given enough time. They will eventually both want to lock the [Arc<Mutex>] the other // or a deadlock will occur given enough time. They will eventually both want to lock the [Arc<Mutex>] the other

View file

@ -1,5 +1,6 @@
use super::Helper; use super::Helper;
use super::Process; use super::Process;
use crate::app::panels::middle::common::list_poolnode::PoolNode;
use crate::components::node::RemoteNode; use crate::components::node::RemoteNode;
use crate::disk::state::P2pool; use crate::disk::state::P2pool;
use crate::helper::ProcessName; use crate::helper::ProcessName;
@ -18,7 +19,7 @@ use crate::regex::estimated_hr;
use crate::regex::nb_current_shares; use crate::regex::nb_current_shares;
use crate::{ use crate::{
constants::*, constants::*,
disk::{gupax_p2pool_api::GupaxP2poolApi, node::Node}, disk::gupax_p2pool_api::GupaxP2poolApi,
helper::{MONERO_BLOCK_TIME_IN_SECONDS, P2POOL_BLOCK_TIME_IN_SECONDS}, helper::{MONERO_BLOCK_TIME_IN_SECONDS, P2POOL_BLOCK_TIME_IN_SECONDS},
human::*, human::*,
macros::*, macros::*,
@ -171,7 +172,7 @@ impl Helper {
helper: &Arc<Mutex<Self>>, helper: &Arc<Mutex<Self>>,
state: &P2pool, state: &P2pool,
path: &Path, path: &Path,
backup_hosts: Option<Vec<Node>>, backup_hosts: Option<Vec<PoolNode>>,
) { ) {
info!("P2Pool | Attempting to restart..."); info!("P2Pool | Attempting to restart...");
helper.lock().unwrap().p2pool.lock().unwrap().signal = ProcessSignal::Restart; helper.lock().unwrap().p2pool.lock().unwrap().signal = ProcessSignal::Restart;
@ -200,7 +201,7 @@ impl Helper {
helper: &Arc<Mutex<Self>>, helper: &Arc<Mutex<Self>>,
state: &P2pool, state: &P2pool,
path: &Path, path: &Path,
backup_hosts: Option<Vec<Node>>, backup_hosts: Option<Vec<PoolNode>>,
) { ) {
helper.lock().unwrap().p2pool.lock().unwrap().state = ProcessState::Middle; helper.lock().unwrap().p2pool.lock().unwrap().state = ProcessState::Middle;
@ -253,7 +254,7 @@ impl Helper {
helper: &Arc<Mutex<Self>>, helper: &Arc<Mutex<Self>>,
state: &P2pool, state: &P2pool,
path: &Path, path: &Path,
backup_hosts: Option<Vec<Node>>, backup_hosts: Option<Vec<PoolNode>>,
) -> (Vec<String>, PathBuf, PathBuf, PathBuf) { ) -> (Vec<String>, PathBuf, PathBuf, PathBuf) {
let mut args = Vec::with_capacity(500); let mut args = Vec::with_capacity(500);
let path = path.to_path_buf(); let path = path.to_path_buf();
@ -282,13 +283,13 @@ impl Helper {
// Push other nodes if `backup_host`. // Push other nodes if `backup_host`.
if let Some(nodes) = backup_hosts { if let Some(nodes) = backup_hosts {
for node in nodes { for node in nodes {
if (node.ip.as_str(), node.rpc.as_str(), node.zmq.as_str()) != (ip, rpc, zmq) { if (node.ip(), node.port(), node.custom()) != (ip, rpc, zmq) {
args.push("--host".to_string()); args.push("--host".to_string());
args.push(node.ip.to_string()); args.push(node.ip().to_string());
args.push("--rpc-port".to_string()); args.push("--rpc-port".to_string());
args.push(node.rpc.to_string()); args.push(node.port().to_string());
args.push("--zmq-port".to_string()); args.push("--zmq-port".to_string());
args.push(node.zmq.to_string()); args.push(node.custom().to_string());
} }
} }
} }
@ -395,20 +396,18 @@ impl Helper {
// Push other nodes if `backup_host`. // Push other nodes if `backup_host`.
if let Some(nodes) = backup_hosts { if let Some(nodes) = backup_hosts {
for node in nodes { for node in nodes {
let ip = if node.ip == "localhost" { let ip = if node.ip() == "localhost" {
"127.0.0.1" "127.0.0.1"
} else { } else {
&node.ip node.ip()
}; };
if (node.ip.as_str(), node.rpc.as_str(), node.zmq.as_str()) if (node.ip(), node.port(), node.custom()) != (ip, &state.rpc, &state.zmq) {
!= (ip, &state.rpc, &state.zmq)
{
args.push("--host".to_string()); args.push("--host".to_string());
args.push(node.ip.to_string()); args.push(node.ip().to_string());
args.push("--rpc-port".to_string()); args.push("--rpc-port".to_string());
args.push(node.rpc.to_string()); args.push(node.port().to_string());
args.push("--zmq-port".to_string()); args.push("--zmq-port".to_string());
args.push(node.zmq.to_string()); args.push(node.custom().to_string());
} }
} }
} }
@ -420,9 +419,9 @@ impl Helper {
"P2Pool Main".to_string() "P2Pool Main".to_string()
}, },
address: Self::head_tail_of_monero_address(&state.address), address: Self::head_tail_of_monero_address(&state.address),
host: state.selected_ip.to_string(), host: state.selected_node.ip.to_string(),
rpc: state.selected_rpc.to_string(), rpc: state.selected_node.rpc.to_string(),
zmq: state.selected_zmq.to_string(), zmq: state.selected_node.zmq_rig.to_string(),
out_peers: state.out_peers.to_string(), out_peers: state.out_peers.to_string(),
in_peers: state.in_peers.to_string(), in_peers: state.in_peers.to_string(),
}; };

View file

@ -298,13 +298,13 @@ Uptime = 0h 2m 4s
assert_eq!(p.miners.to_string(), "1,000"); assert_eq!(p.miners.to_string(), "1,000");
assert_eq!( assert_eq!(
p.solo_block_mean.to_string(), p.solo_block_mean.to_string(),
"5 months, 21 days, 9 hours, 52 minutes" "5 months\n21 days\n9 hours\n52 minutes"
); );
assert_eq!( assert_eq!(
p.p2pool_block_mean.to_string(), p.p2pool_block_mean.to_string(),
"3 days, 11 hours, 20 minutes" "3 days\n11 hours\n20 minutes"
); );
assert_eq!(p.p2pool_share_mean.to_string(), "8 minutes, 20 seconds"); assert_eq!(p.p2pool_share_mean.to_string(), "8 minutes\n20 seconds");
assert_eq!(p.p2pool_percent.to_string(), "0.040000%"); assert_eq!(p.p2pool_percent.to_string(), "0.040000%");
assert_eq!(p.user_p2pool_percent.to_string(), "2.000000%"); assert_eq!(p.user_p2pool_percent.to_string(), "2.000000%");
assert_eq!(p.user_monero_percent.to_string(), "0.000800%"); assert_eq!(p.user_monero_percent.to_string(), "0.000800%");

View file

@ -24,6 +24,7 @@ use std::time::Instant;
#[cold] #[cold]
#[inline(never)] #[inline(never)]
// everything is resized from here with the scale.
pub fn init_text_styles(ctx: &egui::Context, pixels_per_point: f32) { pub fn init_text_styles(ctx: &egui::Context, pixels_per_point: f32) {
ctx.all_styles_mut(|style| { ctx.all_styles_mut(|style| {
style.text_styles = [ style.text_styles = [
@ -34,13 +35,15 @@ pub fn init_text_styles(ctx: &egui::Context, pixels_per_point: f32) {
(Heading, FontId::new(22.0, egui::FontFamily::Monospace)), (Heading, FontId::new(22.0, egui::FontFamily::Monospace)),
] ]
.into(); .into();
style.spacing.icon_width_inner = 32.0; style.spacing.icon_width_inner = 24.0;
style.spacing.icon_width = 64.0; style.spacing.icon_width = 48.0;
style.spacing.icon_spacing = 20.0; style.spacing.icon_spacing = 16.0;
style.spacing.scroll = egui::style::ScrollStyle { style.spacing.button_padding = [8.0, 8.0].into();
bar_width: 8.0, style.spacing.item_spacing = [8.0, 8.0].into();
..egui::style::ScrollStyle::solid() // style.spacing.scroll = egui::style::ScrollStyle {
}; // bar_width: 8.0,
// ..egui::style::ScrollStyle::solid()
// };
}); });
// Make sure scale f32 is a regular number. // Make sure scale f32 is a regular number.
let pixels_per_point = clamp_scale(pixels_per_point); let pixels_per_point = clamp_scale(pixels_per_point);
@ -134,7 +137,7 @@ pub fn init_auto(app: &mut App) {
// [Auto-Update] // [Auto-Update]
#[cfg(not(feature = "distro"))] #[cfg(not(feature = "distro"))]
if app.state.gupax.auto_update { if app.state.gupax.auto.is_enabled(&AutoStart::Update) {
Update::spawn_thread( Update::spawn_thread(
&app.og, &app.og,
&app.state.gupax, &app.state.gupax,
@ -155,7 +158,12 @@ pub fn init_auto(app: &mut App) {
} }
// [Auto-Node] // [Auto-Node]
if app.state.gupax.auto_node { if app
.state
.gupax
.auto
.is_enabled(&AutoStart::Process(ProcessName::Node))
{
if !Gupax::path_is_file(&app.state.gupax.node_path) { if !Gupax::path_is_file(&app.state.gupax.node_path) {
warn!("Gupaxx | Node path is not a file! Skipping auto-node..."); warn!("Gupaxx | Node path is not a file! Skipping auto-node...");
} else if !check_binary_path(&app.state.gupax.node_path, ProcessName::Node) { } else if !check_binary_path(&app.state.gupax.node_path, ProcessName::Node) {
@ -177,7 +185,12 @@ pub fn init_auto(app: &mut App) {
info!("Skipping auto-node..."); info!("Skipping auto-node...");
} }
// [Auto-P2Pool] // [Auto-P2Pool]
if app.state.gupax.auto_p2pool { if app
.state
.gupax
.auto
.is_enabled(&AutoStart::Process(ProcessName::P2pool))
{
if !Regexes::addr_ok(&app.state.p2pool.address) { if !Regexes::addr_ok(&app.state.p2pool.address) {
warn!("Gupaxx | P2Pool address is not valid! Skipping auto-p2pool..."); warn!("Gupaxx | P2Pool address is not valid! Skipping auto-p2pool...");
} else if !Gupax::path_is_file(&app.state.gupax.p2pool_path) { } else if !Gupax::path_is_file(&app.state.gupax.p2pool_path) {
@ -202,7 +215,12 @@ pub fn init_auto(app: &mut App) {
} }
// [Auto-XMRig] // [Auto-XMRig]
if app.state.gupax.auto_xmrig { if app
.state
.gupax
.auto
.is_enabled(&AutoStart::Process(ProcessName::Xmrig))
{
if !Gupax::path_is_file(&app.state.gupax.xmrig_path) { if !Gupax::path_is_file(&app.state.gupax.xmrig_path) {
warn!("Gupaxx | XMRig path is not an executable! Skipping auto-xmrig..."); warn!("Gupaxx | XMRig path is not an executable! Skipping auto-xmrig...");
} else if !check_binary_path(&app.state.gupax.xmrig_path, ProcessName::Xmrig) { } else if !check_binary_path(&app.state.gupax.xmrig_path, ProcessName::Xmrig) {
@ -226,7 +244,12 @@ pub fn init_auto(app: &mut App) {
info!("Skipping auto-xmrig..."); info!("Skipping auto-xmrig...");
} }
// [Auto-XMRig-Proxy] // [Auto-XMRig-Proxy]
if app.state.gupax.auto_xp { if app
.state
.gupax
.auto
.is_enabled(&AutoStart::Process(ProcessName::XmrigProxy))
{
if !Gupax::path_is_file(&app.state.gupax.xmrig_proxy_path) { if !Gupax::path_is_file(&app.state.gupax.xmrig_proxy_path) {
warn!("Gupaxx | Xmrig-Proxy path is not a file! Skipping auto-xmrig_proxy..."); warn!("Gupaxx | Xmrig-Proxy path is not a file! Skipping auto-xmrig_proxy...");
} else if !check_binary_path(&app.state.gupax.xmrig_proxy_path, ProcessName::XmrigProxy) { } else if !check_binary_path(&app.state.gupax.xmrig_proxy_path, ProcessName::XmrigProxy) {
@ -247,7 +270,12 @@ pub fn init_auto(app: &mut App) {
info!("Skipping auto-XMRig-Proxy..."); info!("Skipping auto-XMRig-Proxy...");
} }
// [Auto-XvB] // [Auto-XvB]
if app.state.gupax.auto_xvb { if app
.state
.gupax
.auto
.is_enabled(&AutoStart::Process(ProcessName::Xvb))
{
Helper::start_xvb( Helper::start_xvb(
&app.helper, &app.helper,
&app.state.xvb, &app.state.xvb,

View file

@ -132,6 +132,8 @@ pub fn cmp_f64(a: f64, b: f64) -> std::cmp::Ordering {
use crate::disk::gupax_p2pool_api::GupaxP2poolApi; use crate::disk::gupax_p2pool_api::GupaxP2poolApi;
use crate::helper::ProcessName; use crate::helper::ProcessName;
use chrono::Local; use chrono::Local;
use egui::TextStyle;
use egui::Ui;
use log::error; use log::error;
use log::warn; use log::warn;
use regex::Regex; use regex::Regex;
@ -182,3 +184,7 @@ pub fn client() -> ClientWithMiddleware {
)) ))
.build() .build()
} }
/// to get the right height that a text must take before a button to be aligned in the center correctly.
pub fn height_txt_before_button(ui: &Ui, style: &TextStyle) -> f32 {
ui.style().spacing.button_padding.y * 2.0 + ui.text_style_height(style)
}

View file

@ -348,6 +348,7 @@ pub const GUPAX_TAB_STATUS: &str = "Set the tab Gupaxx starts on to: Status";
pub const GUPAX_TAB_GUPAX: &str = "Set the tab Gupaxx starts on to: Gupaxx"; pub const GUPAX_TAB_GUPAX: &str = "Set the tab Gupaxx starts on to: Gupaxx";
pub const GUPAX_TAB_P2POOL: &str = "Set the tab Gupaxx starts on to: P2Pool"; pub const GUPAX_TAB_P2POOL: &str = "Set the tab Gupaxx starts on to: P2Pool";
pub const GUPAX_TAB_XMRIG: &str = "Set the tab Gupaxx starts on to: XMRig"; pub const GUPAX_TAB_XMRIG: &str = "Set the tab Gupaxx starts on to: XMRig";
pub const GUPAX_TAB_XMRIG_PROXY: &str = "Set the tab Gupaxx starts on to: Proxy";
pub const GUPAX_TAB_XVB: &str = "Set the tab Gupaxx starts on to: XvB"; pub const GUPAX_TAB_XVB: &str = "Set the tab Gupaxx starts on to: XvB";
pub const GUPAX_TAB_NODE: &str = "Set the default tab Gupaxx starts on to: Node"; pub const GUPAX_TAB_NODE: &str = "Set the default tab Gupaxx starts on to: Node";
@ -526,7 +527,6 @@ pub const XVB_TIME_ALGO: u32 = 600;
pub const XVB_MIN_TIME_SEND: u32 = (XVB_TIME_ALGO as f32 * 0.01) as u32; pub const XVB_MIN_TIME_SEND: u32 = (XVB_TIME_ALGO as f32 * 0.01) as u32;
pub const XVB_TOKEN_LEN: usize = 9; pub const XVB_TOKEN_LEN: usize = 9;
pub const XVB_HERO_SELECT: &str = "Donate as much as possible while keeping a share on p2pool, increases the odds of your round winning\nWhen modified, the algorithm will use the new choice at the next decision."; pub const XVB_HERO_SELECT: &str = "Donate as much as possible while keeping a share on p2pool, increases the odds of your round winning\nWhen modified, the algorithm will use the new choice at the next decision.";
pub const XVB_TOKEN_FIELD: &str = "Token";
pub const XVB_FAILURE_FIELD: &str = "Failures"; pub const XVB_FAILURE_FIELD: &str = "Failures";
pub const XVB_DONATED_1H_FIELD: &str = "Donated last hour"; pub const XVB_DONATED_1H_FIELD: &str = "Donated last hour";
pub const XVB_DONATED_24H_FIELD: &str = "Donated last 24 hours"; pub const XVB_DONATED_24H_FIELD: &str = "Donated last 24 hours";

View file

@ -59,7 +59,7 @@ impl HumanTime {
) -> std::fmt::Result { ) -> std::fmt::Result {
if value > 0 { if value > 0 {
if *started { if *started {
f.write_str(", ")?; f.write_str("\n")?;
} }
write!(f, "{} {}", value, name)?; write!(f, "{} {}", value, name)?;
if value > 1 { if value > 1 {
@ -332,64 +332,64 @@ mod test {
assert!(HumanTime::into_human(Duration::from_secs(2)).to_string() == "2 seconds"); assert!(HumanTime::into_human(Duration::from_secs(2)).to_string() == "2 seconds");
assert!(HumanTime::into_human(Duration::from_secs(59)).to_string() == "59 seconds"); assert!(HumanTime::into_human(Duration::from_secs(59)).to_string() == "59 seconds");
assert!(HumanTime::into_human(Duration::from_secs(60)).to_string() == "1 minute"); assert!(HumanTime::into_human(Duration::from_secs(60)).to_string() == "1 minute");
assert!(HumanTime::into_human(Duration::from_secs(61)).to_string() == "1 minute, 1 second"); assert!(HumanTime::into_human(Duration::from_secs(61)).to_string() == "1 minute\n1 second");
assert!( assert!(
HumanTime::into_human(Duration::from_secs(62)).to_string() == "1 minute, 2 seconds" HumanTime::into_human(Duration::from_secs(62)).to_string() == "1 minute\n2 seconds"
); );
assert!(HumanTime::into_human(Duration::from_secs(120)).to_string() == "2 minutes"); assert!(HumanTime::into_human(Duration::from_secs(120)).to_string() == "2 minutes");
assert!( assert!(
HumanTime::into_human(Duration::from_secs(121)).to_string() == "2 minutes, 1 second" HumanTime::into_human(Duration::from_secs(121)).to_string() == "2 minutes\n1 second"
); );
assert!( assert!(
HumanTime::into_human(Duration::from_secs(122)).to_string() == "2 minutes, 2 seconds" HumanTime::into_human(Duration::from_secs(122)).to_string() == "2 minutes\n2 seconds"
); );
assert!( assert!(
HumanTime::into_human(Duration::from_secs(179)).to_string() == "2 minutes, 59 seconds" HumanTime::into_human(Duration::from_secs(179)).to_string() == "2 minutes\n59 seconds"
); );
assert!( assert!(
HumanTime::into_human(Duration::from_secs(3599)).to_string() HumanTime::into_human(Duration::from_secs(3599)).to_string()
== "59 minutes, 59 seconds" == "59 minutes\n59 seconds"
); );
assert!(HumanTime::into_human(Duration::from_secs(3600)).to_string() == "1 hour"); assert!(HumanTime::into_human(Duration::from_secs(3600)).to_string() == "1 hour");
assert!(HumanTime::into_human(Duration::from_secs(3601)).to_string() == "1 hour, 1 second"); assert!(HumanTime::into_human(Duration::from_secs(3601)).to_string() == "1 hour\n1 second");
assert!( assert!(
HumanTime::into_human(Duration::from_secs(3602)).to_string() == "1 hour, 2 seconds" HumanTime::into_human(Duration::from_secs(3602)).to_string() == "1 hour\n2 seconds"
); );
assert!(HumanTime::into_human(Duration::from_secs(3660)).to_string() == "1 hour, 1 minute"); assert!(HumanTime::into_human(Duration::from_secs(3660)).to_string() == "1 hour\n1 minute");
assert!( assert!(
HumanTime::into_human(Duration::from_secs(3720)).to_string() == "1 hour, 2 minutes" HumanTime::into_human(Duration::from_secs(3720)).to_string() == "1 hour\n2 minutes"
); );
assert!( assert!(
HumanTime::into_human(Duration::from_secs(86399)).to_string() HumanTime::into_human(Duration::from_secs(86399)).to_string()
== "23 hours, 59 minutes, 59 seconds" == "23 hours\n59 minutes\n59 seconds"
); );
assert!(HumanTime::into_human(Duration::from_secs(86400)).to_string() == "1 day"); assert!(HumanTime::into_human(Duration::from_secs(86400)).to_string() == "1 day");
assert!(HumanTime::into_human(Duration::from_secs(86401)).to_string() == "1 day, 1 second"); assert!(HumanTime::into_human(Duration::from_secs(86401)).to_string() == "1 day\n1 second");
assert!( assert!(
HumanTime::into_human(Duration::from_secs(86402)).to_string() == "1 day, 2 seconds" HumanTime::into_human(Duration::from_secs(86402)).to_string() == "1 day\n2 seconds"
); );
assert!(HumanTime::into_human(Duration::from_secs(86460)).to_string() == "1 day, 1 minute"); assert!(HumanTime::into_human(Duration::from_secs(86460)).to_string() == "1 day\n1 minute");
assert!( assert!(
HumanTime::into_human(Duration::from_secs(86520)).to_string() == "1 day, 2 minutes" HumanTime::into_human(Duration::from_secs(86520)).to_string() == "1 day\n2 minutes"
); );
assert!(HumanTime::into_human(Duration::from_secs(90000)).to_string() == "1 day, 1 hour"); assert!(HumanTime::into_human(Duration::from_secs(90000)).to_string() == "1 day\n1 hour");
assert!(HumanTime::into_human(Duration::from_secs(93600)).to_string() == "1 day, 2 hours"); assert!(HumanTime::into_human(Duration::from_secs(93600)).to_string() == "1 day\n2 hours");
assert!( assert!(
HumanTime::into_human(Duration::from_secs(604799)).to_string() HumanTime::into_human(Duration::from_secs(604799)).to_string()
== "6 days, 23 hours, 59 minutes, 59 seconds" == "6 days\n23 hours\n59 minutes\n59 seconds"
); );
assert!(HumanTime::into_human(Duration::from_secs(604800)).to_string() == "7 days"); assert!(HumanTime::into_human(Duration::from_secs(604800)).to_string() == "7 days");
assert!(HumanTime::into_human(Duration::from_secs(2630016)).to_string() == "1 month"); assert!(HumanTime::into_human(Duration::from_secs(2630016)).to_string() == "1 month");
assert!( assert!(
HumanTime::into_human(Duration::from_secs(3234815)).to_string() HumanTime::into_human(Duration::from_secs(3234815)).to_string()
== "1 month, 6 days, 23 hours, 59 minutes, 59 seconds" == "1 month\n6 days\n23 hours\n59 minutes\n59 seconds"
); );
assert!(HumanTime::into_human(Duration::from_secs(5260032)).to_string() == "2 months"); assert!(HumanTime::into_human(Duration::from_secs(5260032)).to_string() == "2 months");
assert!(HumanTime::into_human(Duration::from_secs(31557600)).to_string() == "1 year"); assert!(HumanTime::into_human(Duration::from_secs(31557600)).to_string() == "1 year");
assert!(HumanTime::into_human(Duration::from_secs(63115200)).to_string() == "2 years"); assert!(HumanTime::into_human(Duration::from_secs(63115200)).to_string() == "2 years");
assert_eq!( assert_eq!(
HumanTime::into_human(Duration::from_secs(18446744073709551615)).to_string(), HumanTime::into_human(Duration::from_secs(18446744073709551615)).to_string(),
"584542046090 years, 7 months, 15 days, 17 hours, 5 minutes, 3 seconds", "584542046090 years\n7 months\n15 days\n17 hours\n5 minutes\n3 seconds",
); );
} }
} }