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"
dirs = "5.0.1"
#--------------------------------------------------------------------------------
egui = "0.29.1"
egui_extras = {version="0.29.1", features = ["image"] }
# egui = "0.29.1"
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
##
## 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}
cfg-if = "1.0"
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"]}
# Unix dependencies
[target.'cfg(unix)'.dependencies]
@ -97,7 +101,8 @@ sudo = "0.6.0"
# linked as well which causes problems, so statically link it.
lzma-sys = { version = "0.1", features = ["static"] }
[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]
# 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
# need the same version that eframe is using with egui_wgpu
# 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"
is_elevated = "0.1.2"

View file

@ -1,5 +1,13 @@
use crate::APP_DEFAULT_HEIGHT;
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::OS;
use crate::cli::Cli;
@ -47,6 +55,7 @@ use log::debug;
use log::error;
use log::info;
use log::warn;
use panels::middle::common::list_poolnode::PoolNode;
use serde::Deserialize;
use serde::Serialize;
use std::path::PathBuf;
@ -87,10 +96,10 @@ pub struct App {
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 ping: Arc<Mutex<Ping>>, // Ping data found in [node.rs]
pub og_node_vec: Vec<(String, Node)>, // Manual Node database
pub node_vec: Vec<(String, Node)>, // Manual Node database
pub og_pool_vec: Vec<(String, Pool)>, // Manual Pool database
pub pool_vec: Vec<(String, Pool)>, // Manual Pool database
pub og_node_vec: Vec<(String, PoolNode)>, // Manual Node database
pub node_vec: Vec<(String, PoolNode)>, // Manual Node database
pub og_pool_vec: Vec<(String, PoolNode)>, // Manual Pool database
pub pool_vec: Vec<(String, PoolNode)>, // Manual Pool database
pub diff: bool, // This bool indicates state changes
// Restart state:
// If Gupax updated itself, this represents that the
@ -136,7 +145,7 @@ pub struct App {
// Static stuff
pub benchmarks: Vec<Benchmark>, // XMRig CPU benchmarks
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 exe: String, // Path for [Gupax] binary
pub dir: String, // Directory [Gupax] binary is in
@ -311,7 +320,7 @@ impl App {
pub_sys,
benchmarks,
pid,
max_threads: benri::threads!(),
max_threads: benri::threads!() as u16,
now,
admin: false,
exe: String::new(),
@ -511,65 +520,83 @@ impl App {
}
// Handle [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!(
"App | Overflowing manual node index [{} > {}]",
og.p2pool.selected_index,
og.p2pool.selected_node.index,
app.og_node_vec.len()
);
let (name, node) = match app.og_node_vec.first() {
Some(zero) => zero.clone(),
None => Node::new_tuple(),
};
og.p2pool.selected_index = 0;
og.p2pool.selected_name.clone_from(&name);
og.p2pool.selected_ip.clone_from(&node.ip);
og.p2pool.selected_rpc.clone_from(&node.rpc);
og.p2pool.selected_zmq.clone_from(&node.zmq);
app.state.p2pool.selected_index = 0;
app.state.p2pool.selected_name = name;
app.state.p2pool.selected_ip = node.ip;
app.state.p2pool.selected_rpc = node.rpc;
app.state.p2pool.selected_zmq = node.zmq;
og.p2pool.selected_node.index = 0;
og.p2pool.selected_node.name.clone_from(&name);
og.p2pool
.selected_node
.ip
.clone_from(&node.ip().to_string());
og.p2pool
.selected_node
.rpc
.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
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!(
"App | Overflowing manual pool index [{} > {}], resetting to 1",
og.xmrig.selected_index,
og.xmrig.selected_pool.index,
app.og_pool_vec.len()
);
let (name, pool) = match app.og_pool_vec.first() {
Some(zero) => zero.clone(),
None => Pool::new_tuple(),
};
og.xmrig.selected_index = 0;
og.xmrig.selected_name.clone_from(&name);
og.xmrig.selected_ip.clone_from(&pool.ip);
og.xmrig.selected_port.clone_from(&pool.port);
app.state.xmrig.selected_index = 0;
app.state.xmrig.selected_name = name;
app.state.xmrig.selected_ip = pool.ip;
app.state.xmrig.selected_port = pool.port;
if og.xmrig_proxy.selected_index > app.og_pool_vec.len() {
og.xmrig.selected_pool.index = 0;
og.xmrig.selected_pool.name.clone_from(&name);
og.xmrig.selected_pool.ip.clone_from(&pool.ip().to_string());
og.xmrig
.selected_pool
.rpc
.clone_from(&pool.port().to_string());
app.state.xmrig.selected_pool.index = 0;
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!(
"App | Overflowing manual pool index [{} > {}], resetting to 1",
og.xmrig_proxy.selected_index,
og.xmrig_proxy.selected_pool.index,
app.og_pool_vec.len()
);
let (name, pool) = match app.og_pool_vec.first() {
Some(zero) => zero.clone(),
None => Pool::new_tuple(),
};
og.xmrig_proxy.selected_index = 0;
og.xmrig_proxy.selected_name.clone_from(&name);
og.xmrig_proxy.selected_ip.clone_from(&pool.ip);
og.xmrig_proxy.selected_port.clone_from(&pool.port);
app.state.xmrig_proxy.selected_index = 0;
app.state.xmrig_proxy.selected_name = name;
app.state.xmrig_proxy.selected_ip = pool.ip;
app.state.xmrig_proxy.selected_port = pool.port;
og.xmrig_proxy.selected_pool.index = 0;
og.xmrig_proxy.selected_pool.name.clone_from(&name);
og.xmrig_proxy
.selected_pool
.ip
.clone_from(&pool.ip().to_string());
og.xmrig_proxy
.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]
#[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 {
return None;
}
@ -695,7 +722,7 @@ impl App {
zmq: zmq.into(),
};
vec.push(node);
vec.push(PoolNode::Node(node));
}
if vec.is_empty() {
@ -747,6 +774,18 @@ impl Tab {
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
#[derive(Clone, Copy, Debug, PartialEq, Eq)]

View file

@ -207,16 +207,30 @@ impl crate::app::App {
let restart_msg = format!("Restart {}", name);
if process.waiting {
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());
ui.add(Separator::default().grow(0.0));
ui.add_sized(size, Button::new(""))
.on_disabled_hover_text(process.run_middle_msg());
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());
});
} 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
|| ui
.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 {
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 ui_enabled = text_err.is_empty();
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::check_binary_path;
use crate::disk::state::*;
use crate::miscs::height_txt_before_button;
use log::debug;
use std::path::Path;
use std::sync::Arc;
use std::sync::Mutex;
use strum::EnumCount;
use strum::IntoEnumIterator;
impl Gupax {
#[inline(always)] // called once
#[allow(clippy::too_many_arguments)]
@ -20,7 +23,6 @@ impl Gupax {
file_window: &Arc<Mutex<FileWindow>>,
error_state: &mut ErrorState,
restart: &Arc<Mutex<Restart>>,
size: Vec2,
_frame: &mut eframe::Frame,
_ctx: &egui::Context,
ui: &mut egui::Ui,
@ -28,33 +30,30 @@ impl Gupax {
) {
// 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| {
ui.style_mut().spacing.item_spacing = [height_font, height_font].into();
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();
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,
// disable built-in updating completely.
#[cfg(feature = "distro")]
ui.disable();
#[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);
#[cfg(not(feature = "distro"))]
ui.add_enabled_ui(!updating && *restart.lock().unwrap() == Restart::No, |ui| {
#[cfg(not(feature = "distro"))]
// if ui
// .add_sized([width, button], Button::new("Check for updates"))
if ui
.add_sized([width, button], Button::new("Check for updates"))
.button("Check for updates")
.on_hover_text(GUPAX_UPDATE)
.clicked()
{
@ -68,8 +67,6 @@ impl Gupax {
);
}
});
});
ui.vertical(|ui| {
ui.add_enabled_ui(updating, |ui| {
let prog = *update.lock().unwrap().prog.lock().unwrap();
let msg = format!(
@ -78,249 +75,61 @@ impl Gupax {
prog,
"%"
);
ui.add_sized([width, height * 1.4], Label::new(RichText::new(msg)));
let height = height / 2.0;
let size = vec2(width, height);
ui.label(msg);
if updating {
ui.add_sized(size, Spinner::new().size(height));
ui.spinner();
} else {
ui.add_sized(size, Label::new("..."));
ui.label("...");
}
ui.add_sized(
size,
ProgressBar::new(
ui.add(ProgressBar::new(
update.lock().unwrap().prog.lock().unwrap().round() / 100.0,
),
);
));
});
});
});
debug!("Gupaxx Tab | Rendering bool buttons");
ui.horizontal(|ui| {
// debug!("Gupaxx Tab | Rendering bool buttons");
ui.group(|ui| {
egui::ScrollArea::horizontal().show(ui, |ui| {
let width = (size.x - SPACE * 17.0) / 8.0;
let height = if self.simple {
size.y / 10.0
} 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.vertical_centered(|ui| {
ui.add(Label::new(
RichText::new("Default Behaviour")
.underline()
.color(LIGHT_GRAY),
))
});
ui.separator();
self.horizontal_flex_auto_start(ui, AutoStart::ALL);
});
});
if self.simple {
return;
}
debug!("Gupaxx Tab | Rendering P2Pool/XMRig path selection");
// P2Pool/XMRig binary path selection
debug!("Gupaxx Tab | Rendering Node/P2Pool/XMRig/XMRig-Proxy path selection");
// need to clone bool so file_window is not locked across a thread
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.add_sized(
[ui.available_width(), height / 2.0],
Label::new(
ui.push_id(2, |ui| {
ui.vertical_centered(|ui| {
ui.add(Label::new(
RichText::new("Node/P2Pool/XMRig/XMRig-Proxy PATHs")
.underline()
.color(LIGHT_GRAY),
),
)
))
.on_hover_text("Gupaxx is online");
});
ui.separator();
ui.horizontal(|ui| {
if self.node_path.is_empty() {
ui.add_sized(
[text_edit, height],
Label::new(RichText::new("Node Binary Path ").color(LIGHT_GRAY)),
ScrollArea::horizontal().show(ui, |ui| {
ui.vertical(|ui| {
BundledProcess::iter().for_each(|name| {
path_binary(
self.path_binary(&name),
name.process_name(),
ui,
window_busy,
file_window,
)
.on_hover_text(NODE_PATH_EMPTY);
} else if !Self::path_is_file(&self.node_path) {
ui.add_sized(
[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();
@ -341,101 +150,60 @@ impl Gupax {
guard.picked_node = false;
}
drop(guard);
let height = ui.available_height() / 6.0;
});
// Saved [Tab]
debug!("Gupaxx Tab | Rendering [Tab] selector");
ui.group(|ui| {
let width = (size.x / 7.0) - (SPACE * 1.93);
let size = vec2(width, height);
ui.add_sized(
[ui.available_width(), height / 2.0],
Label::new(RichText::new("Default Tab").underline().color(LIGHT_GRAY)),
)
ui.vertical_centered(|ui| {
ui.add(Label::new(
RichText::new("Default Tab").underline().color(LIGHT_GRAY),
))
.on_hover_text(GUPAX_TAB);
});
ui.separator();
ui.push_id(1, |ui| {
ScrollArea::horizontal().show(ui, |ui| {
ui.horizontal(|ui| {
if ui
.add_sized(size, SelectableLabel::new(self.tab == Tab::About, "About"))
.on_hover_text(GUPAX_TAB_ABOUT)
.clicked()
{
self.tab = Tab::About;
}
ui.separator();
let width = (ui.available_width() / Tab::COUNT as f32)
- (ui.spacing().button_padding.y * 2.0
+ ui.spacing().item_spacing.x)
- SPACE;
Tab::iter().enumerate().for_each(|(count, tab)| {
if ui
.add_sized(
size,
SelectableLabel::new(self.tab == Tab::Status, "Status"),
[width, height_txt_before_button(ui, &TextStyle::Button)],
SelectableLabel::new(self.tab == tab, tab.to_string()),
)
.on_hover_text(GUPAX_TAB_STATUS)
.on_hover_text(tab.msg_default_tab())
.clicked()
{
self.tab = Tab::Status;
self.tab = tab;
}
if count + 1 != Tab::COUNT {
ui.separator();
if ui
.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
debug!("Gupaxx Tab | Rendering resolution sliders");
ui.group(|ui| {
ui.add_sized(
[ui.available_width(), height / 2.0],
Label::new(
RichText::new("Width/Height Adjust")
ui.vertical_centered(|ui| {
ui.add(Label::new(
RichText::new("Width/Height/Scaling Adjustment")
.underline()
.color(LIGHT_GRAY),
),
)
))
.on_hover_text(GUPAX_ADJUST);
ui.separator();
});
ui.horizontal(|ui| {
ScrollArea::horizontal().show(ui, |ui| {
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 => {
@ -449,54 +217,41 @@ impl Gupax {
self.selected_width = width as u16;
}
}
let height = height / 3.5;
let size = vec2(width, height);
// 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!(
ui.label(format!(
" Width [{}-{}]:",
APP_MIN_WIDTH as u16, APP_MAX_WIDTH as u16
)),
);
ui.add_sized(
size,
Slider::new(
));
ui.add(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!(
ui.label(format!(
" Height [{}-{}]:",
APP_MIN_HEIGHT as u16, APP_MAX_HEIGHT as u16
)),
);
ui.add_sized(
size,
Slider::new(
));
ui.add(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)
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);
@ -505,15 +260,11 @@ impl Gupax {
ui.style_mut().override_text_style = Some(egui::TextStyle::Button);
ui.separator();
// Width/Height locks
ui.horizontal(|ui| {
ui.vertical(|ui| {
use Ratio::*;
let width = (size.x / 4.0) - (SPACE * 1.5);
let size = vec2(width, height);
ui.horizontal(|ui| {
if ui
.add_sized(
size,
SelectableLabel::new(self.ratio == Width, "Lock to width"),
)
.selectable_label(self.ratio == Width, "Lock to width")
.on_hover_text(GUPAX_LOCK_WIDTH)
.clicked()
{
@ -521,10 +272,7 @@ impl Gupax {
}
ui.separator();
if ui
.add_sized(
size,
SelectableLabel::new(self.ratio == Height, "Lock to height"),
)
.selectable_label(self.ratio == Height, "Lock to height")
.on_hover_text(GUPAX_LOCK_HEIGHT)
.clicked()
{
@ -532,25 +280,141 @@ impl Gupax {
}
ui.separator();
if ui
.add_sized(size, SelectableLabel::new(self.ratio == None, "No lock"))
.selectable_label(self.ratio == None, "No lock")
.on_hover_text(GUPAX_NO_LOCK)
.clicked()
{
self.ratio = None;
}
if ui
.add_sized(size, Button::new("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));
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::eframe_impl::ProcessStatesGui;
use crate::app::keys::KeyPressed;
use crate::components::gupax::FileWindow;
use crate::helper::ProcessName;
use crate::regex::REGEXES;
use crate::utils::constants::*;
use common::state_edit_field::StateTextEdit;
use egui::*;
use log::debug;
mod about;
pub mod common;
mod gupax;
mod node;
mod p2pool;
@ -47,7 +51,6 @@ impl crate::app::App {
self.max_threads,
&self.gupax_p2pool_api,
&self.benchmarks,
self.size,
ctx,
ui,
);
@ -62,7 +65,6 @@ impl crate::app::App {
&self.file_window,
&mut self.error_state,
&self.restart,
self.size,
frame,
ctx,
ui,
@ -76,7 +78,6 @@ impl crate::app::App {
&self.node,
&self.node_api,
&mut self.node_stdin,
self.size,
&self.file_window,
ui,
);
@ -91,7 +92,6 @@ impl crate::app::App {
&self.p2pool,
&self.p2pool_api,
&mut self.p2pool_stdin,
self.size,
ctx,
ui,
);
@ -104,7 +104,6 @@ impl crate::app::App {
&self.xmrig,
&self.xmrig_api,
&mut self.xmrig_stdin,
self.size,
ctx,
ui,
);
@ -117,7 +116,6 @@ impl crate::app::App {
&mut self.pool_vec,
&self.xmrig_proxy_api,
&mut self.xmrig_proxy_stdin,
self.size,
ui,
);
}
@ -125,7 +123,6 @@ impl crate::app::App {
debug!("App | Entering [XvB] Tab");
crate::disk::state::Xvb::show(
&mut self.state.xvb,
self.size,
&self.state.p2pool.address,
ctx,
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::{
GUPAX_SELECT, NODE_API_BIND, NODE_API_PORT, NODE_ARGUMENTS, NODE_DB_DIR, NODE_DB_PATH_EMPTY,
NODE_DNS_BLOCKLIST, NODE_DNS_CHECKPOINT, NODE_INPUT, NODE_PATH_OK, NODE_PRUNNING, NODE_URL,
NODE_ZMQ_BIND, NODE_ZMQ_PORT,
NODE_ARGUMENTS, NODE_DNS_BLOCKLIST, NODE_DNS_CHECKPOINT, NODE_INPUT, NODE_PRUNNING, NODE_URL,
};
use egui::{Color32, Label, RichText, Slider, TextEdit, TextStyle, Ui, Vec2};
use regex::Regex;
use egui::{Label, TextStyle};
use std::sync::{Arc, Mutex};
use log::debug;
use crate::components::gupax::{FileType, FileWindow};
use crate::disk::state::{Gupax, Node};
use crate::components::gupax::FileWindow;
use crate::disk::state::Node;
use crate::helper::Process;
use crate::helper::node::PubNodeApi;
use crate::regex::{REGEXES, num_lines};
use crate::utils::constants::DARK_GRAY;
use crate::{GREEN, LIGHT_GRAY, P2POOL_IN, P2POOL_LOG, P2POOL_OUT, RED, SPACE};
use crate::{P2POOL_IN, P2POOL_LOG, P2POOL_OUT, SPACE};
impl Node {
#[inline(always)] // called once
@ -24,102 +22,47 @@ impl Node {
process: &Arc<Mutex<Process>>,
api: &Arc<Mutex<PubNodeApi>>,
buffer: &mut String,
size: Vec2,
file_window: &Arc<Mutex<FileWindow>>,
ui: &mut egui::Ui,
) {
let width = size.x;
let height = size.y;
let space_h = height / 48.0;
let text_height = size.y / 25.0;
let txt_description_width = size.x * 0.1;
egui::ScrollArea::vertical().show(ui, |ui| {
ui.style_mut().override_text_style = Some(TextStyle::Body);
ui.vertical_centered(|ui| {
ui.add_space(space_h);
ui.add_space(SPACE);
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.style_mut().override_text_style = None;
ui.add(Label::new("C++ Monero Node"));
ui.add_space(space_h);
ui.add_space(SPACE);
});
// console output for log
debug!("Node 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)).max(0.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.group(|ui| {
console(ui, text);
if !self.simple {
ui.separator();
input_args_field(
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);
}
}
},
buffer,
process,
r#"Commands: help, status, set_log <level>, diff"#,
NODE_INPUT,
);
});
}
});
//---------------------------------------------------------------------------------------------------- [Advanced] Console
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
debug!("Node Tab | Rendering [Arguments]");
ui.group(|ui| {
ui.horizontal(|ui| {
ui.add_sized(
[txt_description_width, text_height],
Label::new("Command arguments:"),
);
ui.add_sized(
[ui.available_width(), text_height],
TextEdit::hint_text(
TextEdit::singleline(&mut self.arguments),
start_options_field(
ui,
&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();
}
NODE_ARGUMENTS,
);
//---------------------------------------------------------------------------------------------------- Prunned checkbox
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.add_space(SPACE);
debug!("Node Tab | Rendering DNS and Prunning buttons");
ui.horizontal(|ui| {
ui.group(|ui| {
@ -134,24 +77,21 @@ impl Node {
});
});
ui.add_space(space_h);
// 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
// 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.add_space(SPACE);
// // 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
ui.horizontal(|ui| {
egui::ScrollArea::horizontal().show(ui, |ui| {
ui.group(|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);
rpc_bind_field(&mut self.api_ip, ui);
rpc_port_field(&mut self.api_port, ui);
ui.add_space(SPACE);
zmq_bind_field(&mut self.zmq_ip, ui);
zmq_port_field(&mut self.zmq_port, ui);
});
});
@ -159,251 +99,47 @@ impl Node {
debug!("Node Tab | Rendering sliders elements");
ui.vertical(|ui| {
ui.group(|ui| {
ui.style_mut().override_text_style = Some(TextStyle::Small);
ui.horizontal(|ui| {
// ui.label("Out peers [10-450]:");
ui.add_sized(
[txt_description_width, text_height],
Label::new("Out peers [2-450]:"),
ui.add_space(SPACE);
slider_state_field(
ui,
"Out peers [2-450]:",
P2POOL_OUT,
&mut self.out_peers,
2..=450,
);
// 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(
[txt_description_width, text_height],
Label::new("In peers [2-450]:"),
ui.add_space(SPACE);
slider_state_field(
ui,
"In peers [2-450]:",
P2POOL_IN,
&mut self.in_peers,
2..=450,
);
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.in_peers, 2..=450))
.on_hover_text(P2POOL_IN);
});
ui.horizontal(|ui| {
// ui.label("Log level [ 0-4 ]:");
ui.add_sized(
[txt_description_width, text_height],
Label::new("Log level [ 0-4 ] :"),
ui.add_space(SPACE);
slider_state_field(
ui,
"Log level [ 0-4 ]:",
P2POOL_LOG,
&mut self.log_level,
0..=6,
);
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.log_level, 0..=4))
.on_hover_text(P2POOL_LOG);
ui.add_space(SPACE);
});
});
});
});
//---------------------------------------------------------------------------------------------------- DB path
ui.add_space(space_h);
ui.add_space(SPACE);
ui.group(|ui| {
path_db_field(self, ui, txt_description_width, text_height, file_window);
});
ui.add_space(space_h);
}
});
}
}
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);
});
});
path_db_field(ui, &mut self.path_db, file_window);
let mut guard = file_window.lock().unwrap();
if guard.picked_nodedb {
state.path_db.clone_from(&guard.nodedb_path);
self.path_db.clone_from(&guard.nodedb_path);
guard.picked_nodedb = false;
}
});
ui.add_space(SPACE);
}
#[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,238 +1,43 @@
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 egui::Checkbox;
use egui::Slider;
use egui::{Button, Vec2};
use crate::constants::*;
use egui::{Color32, ComboBox, Label, RichText, SelectableLabel, Ui};
use egui::{Checkbox, SelectableLabel, Ui};
use log::*;
impl P2pool {
pub(super) fn advanced(
&mut self,
ui: &mut Ui,
size: Vec2,
text_edit: f32,
node_vec: &mut Vec<(String, Node)>,
) {
let height = size.y / 16.0;
let space_h = size.y / 128.0;
pub(super) fn advanced(&mut self, ui: &mut Ui, node_vec: &mut Vec<(String, PoolNode)>) {
// let height = size.y / 16.0;
// let space_h = size.y / 128.0;
debug!("P2Pool Tab | Rendering [Node List] elements");
let mut incorrect_input = false; // This will disable [Add/Delete] on bad input
// [Monero node IP/RPC/ZMQ]
ui.horizontal(|ui| {
ui.group(|ui| {
let width = size.x/10.0;
// let width = size.x/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 = Color32::LIGHT_GRAY;
incorrect_input = true;
} else if REGEXES.name.is_match(&self.name) {
text = format!("Name [ {}/30 ]✔", len);
color = Color32::from_rgb(100, 230, 100);
} else {
text = format!("Name [ {}/30 ]❌", len);
color = Color32::from_rgb(230, 50, 50);
incorrect_input = true;
if !self.name_field(ui) {
incorrect_input = false;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.name).on_hover_text(P2POOL_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 = 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;
if !self.ip_field(ui) {
incorrect_input = false;
}
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;
if !self.rpc_port_field(ui) {
incorrect_input = false;
}
if !self.zmq_port_field(ui) {
incorrect_input = false;
}
});
// [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();
}
});
});
});
list_poolnode(
ui,
&mut (&mut self.name, &mut self.ip, &mut self.rpc, &mut self.zmq),
&mut self.selected_node,
node_vec,
incorrect_input,
);
});
});
// ui.add_space(space_h);
@ -240,12 +45,15 @@ impl P2pool {
debug!("P2Pool Tab | Rendering [Main/Mini/Peers/Log] elements");
// [Main/Mini]
ui.horizontal(|ui| {
let height = height / 4.0;
// let height = height / 4.0;
ui.group(|ui| {
ui.vertical(|ui| {
let height = height_txt_before_button(ui, &egui::TextStyle::Button) * 1.9;
ui.horizontal(|ui| {
let width = (size.x / 4.0) - SPACE;
let height = height + space_h;
let width = (ui.available_width() / 4.0) - SPACE;
if ui
// if ui.add_sized(, )
// .selectable_label(!self.mini, "P2Pool Main")
.add_sized(
[width, height],
SelectableLabel::new(!self.mini, "P2Pool Main"),
@ -256,6 +64,8 @@ impl P2pool {
self.mini = false;
}
if ui
// .selectable_label(!self.mini, "P2Pool Mini")
// if ui
.add_sized(
[width, height],
SelectableLabel::new(self.mini, "P2Pool Mini"),
@ -265,49 +75,80 @@ impl P2pool {
{
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"),
)
// ui.checkbox(&mut self.backup_host, "Backup host")
.on_hover_text(P2POOL_BACKUP_HOST_ADVANCED);
});
});
});
// [Out/In Peers] + [Log Level]
ui.group(|ui| {
ui.vertical(|ui| {
let text = (ui.available_width() / 10.0) - SPACE;
let width = (text * 8.0) - SPACE;
let height = height / 3.0;
ui.style_mut().spacing.slider_width = width / 1.1;
ui.style_mut().spacing.interact_size.y = height;
ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
ui.horizontal(|ui| {
ui.add_sized([text, height], Label::new("Out peers [10-450]:"));
ui.add_sized([width, height], Slider::new(&mut self.out_peers, 10..=450))
.on_hover_text(P2POOL_OUT);
ui.add_space(ui.available_width() - 4.0);
});
ui.horizontal(|ui| {
ui.add_sized([text, height], Label::new(" In peers [10-450]:"));
ui.add_sized([width, height], Slider::new(&mut self.in_peers, 10..=450))
.on_hover_text(P2POOL_IN);
});
ui.horizontal(|ui| {
ui.add_sized([text, height], Label::new(" Log level [0-6]:"));
ui.add_sized([width, height], Slider::new(&mut self.log_level, 0..=6))
.on_hover_text(P2POOL_LOG);
});
ui.add_space(SPACE);
slider_state_field(
ui,
"Out peers [2-450]:",
P2POOL_OUT,
&mut self.out_peers,
2..=450,
);
ui.add_space(SPACE);
slider_state_field(
ui,
"In peers [2-450]:",
P2POOL_IN,
&mut self.in_peers,
2..=450,
);
ui.add_space(SPACE);
slider_state_field(
ui,
"Log level [ 0-6 ]:",
P2POOL_LOG,
&mut self.log_level,
0..=6,
);
})
});
});
debug!("P2Pool Tab | Rendering Backup host button");
ui.group(|ui| {
let width = size.x - SPACE;
let height = ui.available_height();
ui.style_mut().spacing.icon_width = height;
ui.style_mut().spacing.icon_width_inner = height * 0.9;
// [Backup host]
ui.add_sized(
[width, height],
Checkbox::new(&mut self.backup_host, "Backup host"),
)
.on_hover_text(P2POOL_BACKUP_HOST_ADVANCED);
});
}
fn name_field(&mut self, ui: &mut Ui) -> bool {
StateTextEdit::new(ui)
.description(" Name ")
.max_ch(30)
.help_msg(P2POOL_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(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::helper::p2pool::PubP2poolApi;
use crate::regex::num_lines;
// Gupax - GUI Uniting P2Pool And XMRig
//
// 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
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{components::node::*, constants::*, helper::*, utils::regex::Regexes};
use egui::{Color32, Label, RichText, TextEdit, TextStyle, Vec2, vec2};
use crate::{components::node::*, constants::*, helper::*};
use log::*;
use std::sync::{Arc, Mutex};
use super::common::list_poolnode::PoolNode;
mod advanced;
mod simple;
@ -32,143 +32,51 @@ impl P2pool {
#[allow(clippy::too_many_arguments)]
pub fn show(
&mut self,
node_vec: &mut Vec<(String, Node)>,
node_vec: &mut Vec<(String, PoolNode)>,
_og: &Arc<Mutex<State>>,
ping: &Arc<Mutex<Ping>>,
process: &Arc<Mutex<Process>>,
api: &Arc<Mutex<PubP2poolApi>>,
buffer: &mut String,
size: Vec2,
_ctx: &egui::Context,
ui: &mut egui::Ui,
) {
let height = size.y;
let width = size.x;
let text_edit = size.y / 25.0;
//---------------------------------------------------------------------------------------------------- [Simple] Console
// debug!("P2Pool Tab | Rendering [Console]");
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 * 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);
}
}
},
);
});
ui.group(|ui| {
console(ui, text);
if !self.simple {
//---------------------------------------------------------------------------------------------------- [Advanced] Console
ui.separator();
let response = ui
.add_sized(
[width, text_edit],
TextEdit::hint_text(
TextEdit::singleline(buffer),
input_args_field(
ui,
buffer,
process,
r#"Type a command (e.g "help" or "status") and press Enter"#,
),
)
.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 {
debug!("P2Pool Tab | Rendering [Arguments]");
ui.group(|ui| {
ui.horizontal(|ui| {
let width = (width / 10.0) - SPACE;
ui.add_sized([width, text_edit], Label::new("Command 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)),
P2POOL_INPUT,
);
ui.add_sized(
[width, text_edit],
TextEdit::hint_text(TextEdit::singleline(&mut self.address), "4..."),
)
.on_hover_text(P2POOL_ADDRESS);
self.address.truncate(95);
}
});
if !self.simple {
start_options_field(
ui,
&mut self.arguments,
r#"--wallet <...> --host <...>"#,
P2POOL_ARGUMENTS,
);
}
debug!("P2Pool Tab | Rendering [Address]");
crate::app::panels::middle::common::state_edit_field::monero_address_field(
&mut self.address,
ui,
P2POOL_ADDRESS,
);
// let height = ui.available_height();
let size = vec2(width, height);
if self.simple {
//---------------------------------------------------------------------------------------------------- Simple
self.simple(ui, size, ping);
//---------------------------------------------------------------------------------------------------- Advanced
self.simple(ui, ping);
} 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::Mutex;
use crate::app::panels::middle::Hyperlink;
use crate::app::panels::middle::ProgressBar;
use crate::app::panels::middle::Spinner;
use crate::components::node::Ping;
use crate::components::node::RemoteNode;
use crate::components::node::format_ip_location;
use crate::components::node::format_ms;
use crate::disk::state::P2pool;
use crate::miscs::height_txt_before_button;
use egui::Button;
use egui::Checkbox;
use egui::Vec2;
use egui::ScrollArea;
use egui::TextStyle;
use egui::TextWrapMode;
use egui::vec2;
use crate::constants::*;
use egui::{Color32, ComboBox, Label, RichText, Ui};
use egui::{Color32, ComboBox, RichText, Ui};
use log::*;
impl P2pool {
pub(super) fn simple(&mut self, ui: &mut Ui, size: Vec2, 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
pub(super) fn simple(&mut self, ui: &mut Ui, ping: &Arc<Mutex<Ping>>) {
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.add_space(space_h * 2.0);
ui.add_space(SPACE * 2.0);
// if checked, use only local node
// if unchecked, show remote nodes.
// disable remote if local is checked.
let visible = !self.local_node;
debug!("P2Pool Tab | Running [auto-select] check");
@ -54,7 +39,6 @@ impl P2pool {
}
drop(ping);
}
ui.add_enabled_ui(visible, |ui| {
ui.vertical(|ui| {
ui.horizontal(|ui| {
@ -74,9 +58,11 @@ impl P2pool {
debug!("P2Pool Tab | Rendering [ComboBox] of Remote Nodes");
let ip_location = format_ip_location(&self.node, false);
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")
.selected_text(text)
.width(size.x)
.width(ui.available_width())
.show_ui(ui, |ui| {
for data in ping.lock().unwrap().nodes.iter() {
let ms = format_ms(data.ms);
@ -87,16 +73,24 @@ impl P2pool {
}
});
});
ui.add_space(space_h);
ui.add_space(SPACE);
debug!("P2Pool Tab | Rendering [Select fastest ... Ping] buttons");
ScrollArea::horizontal()
.scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden)
.id_salt("horizontal")
.show(ui, |ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Button);
ui.horizontal(|ui| {
let width = ((size.x / 5.0) - 6.0).max(0.0);
let size = vec2(width, height);
ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
// ui.columns_const(|[col1, col2, col3, col4, col5]| {
let width = ((ui.available_width() / 5.0)
- (ui.spacing().item_spacing.x * (4.0 / 5.0)))
.max(20.0);
let height = height_txt_before_button(ui, &TextStyle::Button) * 2.0;
// [Select random node]
ui.style_mut().override_text_valign = Some(egui::Align::Center);
if ui
.add_sized(size, Button::new("Select random node"))
.add_sized([width, height], Button::new("Select random node"))
.on_hover_text(P2POOL_SELECT_RANDOM)
.clicked()
{
@ -104,7 +98,7 @@ impl P2pool {
}
// [Select fastest node]
if ui
.add_sized(size, Button::new("Select fastest node"))
.add_sized([width, height], Button::new("Select fastest node"))
.on_hover_text(P2POOL_SELECT_FASTEST)
.clicked()
&& ping.lock().unwrap().pinged
@ -114,7 +108,7 @@ impl P2pool {
// [Ping Button]
ui.add_enabled_ui(!ping.lock().unwrap().pinging, |ui| {
if ui
.add_sized(size, Button::new("Ping remote nodes"))
.add_sized([width, height], Button::new("Ping remote nodes"))
.on_hover_text(P2POOL_PING)
.clicked()
{
@ -123,14 +117,15 @@ impl P2pool {
});
// [Last <-]
if ui
.add_sized(size, Button::new("⬅ Last"))
.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)
self.node =
RemoteNode::get_last_from_ping(&self.node, &ping.nodes)
}
false => self.node = RemoteNode::get_last(&self.node),
}
@ -138,14 +133,15 @@ impl P2pool {
}
// [Next ->]
if ui
.add_sized(size, Button::new("Next ➡"))
.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)
self.node =
RemoteNode::get_next_from_ping(&self.node, &ping.nodes)
}
false => self.node = RemoteNode::get_next(&self.node),
}
@ -153,57 +149,75 @@ impl P2pool {
}
});
ui.vertical(|ui| {
let height = height / 2.0;
ui.vertical_centered(|ui| {
// let height = height / 2.0;
let pinging = ping.lock().unwrap().pinging;
ui.add_enabled_ui(pinging, |ui| {
let prog = ping.lock().unwrap().prog.round();
let msg =
RichText::new(format!("{} ... {}%", ping.lock().unwrap().msg, prog));
let height = height / 1.25;
let size = vec2(size.x, height);
ui.add_space(space_h);
ui.add_sized(size, Label::new(msg));
ui.add_space(space_h);
let msg = RichText::new(format!(
"{} ... {}%",
ping.lock().unwrap().msg,
prog
));
// let height = height / 1.25;
// let size = vec2(size.x, height);
ui.add_space(SPACE);
ui.label(msg);
ui.add_space(SPACE);
if pinging {
ui.add_sized(size, Spinner::new().size(height));
ui.spinner();
} else {
ui.add_sized(size, Label::new("..."));
ui.label("...");
}
ui.add_sized(size, ProgressBar::new(prog.round() / 100.0));
ui.add_space(space_h);
});
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 = ((size.x / 3.0) - (SPACE * 1.75)).max(0.0);
let size = vec2(width, height);
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.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.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 warning text");
ui.add_sized(
[size.x, height / 2.0],
Hyperlink::from_label_and_url(
ui.add_space(SPACE);
ui.vertical_centered(|ui| {
ui.hyperlink_to(
"WARNING: It is recommended to run/use your own Monero Node (hover for details)",
"https://github.com/Cyrix126/gupaxx#running-a-local-monero-node",
),
)
.on_hover_text(P2POOL_COMMUNITY_NODE_WARNING);
});
});
}
}

View file

@ -1,7 +1,7 @@
use std::sync::{Arc, Mutex};
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 readable::num::{Float, Percent, Unsigned};
@ -11,85 +11,56 @@ use log::*;
impl Status {
pub(super) fn benchmarks(
&mut self,
size: Vec2,
ui: &mut egui::Ui,
benchmarks: &[Benchmark],
xmrig_alive: bool,
xmrig_api: &Arc<Mutex<PubXmrigApi>>,
) {
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 log = size.y / 3.0;
let width = size.x;
let width = ui.available_width();
// [0], The user's CPU (most likely).
let cpu = &benchmarks[0];
ui.horizontal(|ui| {
let width = (width / 2.0) - (SPACE * 1.666);
let min_height = log;
ui.group(|ui| {
ui.vertical(|ui| {
ui.set_min_height(min_height);
ui.add_sized(
[width, text],
Label::new(RichText::new("Your CPU").underline().color(BONE)),
)
ui.set_max_width(ui.available_width() / 2.0);
ui.vertical_centered(|ui| {
ui.label(RichText::new("Your CPU").underline().color(BONE))
.on_hover_text(STATUS_SUBMENU_YOUR_CPU);
ui.add_sized([width, text], Label::new(cpu.cpu.as_str()));
ui.add_sized(
[width, text],
Label::new(RichText::new("Total Benchmarks").underline().color(BONE)),
)
ui.label(cpu.cpu.as_str());
ui.label(RichText::new("Total Banchmarks").underline().color(BONE))
.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)),
)
ui.label(format!("{}", cpu.benchmarks));
// ui.add_sized([width, text], Label::new(format!("{}", cpu.benchmarks)));
ui.label(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.label(format!("{}/{}", cpu.rank, &benchmarks.len()));
})
});
ui.group(|ui| {
ui.vertical(|ui| {
ui.set_min_height(min_height);
ui.add_sized(
[width, text],
Label::new(RichText::new("High Hashrate").underline().color(BONE)),
)
ui.vertical_centered(|ui| {
ui.label(RichText::new("High Hashrate").underline().color(BONE))
.on_hover_text(STATUS_SUBMENU_YOUR_HIGH);
ui.add_sized(
[width, text],
Label::new(format!("{} H/s", Float::from_0(cpu.high.into()))),
);
ui.add_sized(
[width, text],
Label::new(RichText::new("Average Hashrate").underline().color(BONE)),
)
ui.label(format!("{} H/s", Float::from_0(cpu.high.into())));
ui.label(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)),
)
ui.label(format!("{} H/s", Float::from_0(cpu.average.into())));
ui.label(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()))),
);
ui.label(RichText::new(format!(
"{} H/s",
Float::from_0(cpu.low.into())
)));
})
})
});
// 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 {
let api = xmrig_api.lock().unwrap();
let percent = (api.hashrate_raw / cpu.high) * 100.0;
@ -102,11 +73,11 @@ impl Status {
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 {
ui.add_sized([width, text], Label::new("Measuring hashrate..."));
ui.add_sized([width, text], Spinner::new().size(text));
ui.add_sized([width, text], ProgressBar::new(0.0));
ui.label("Measuring hashrate...");
ui.spinner();
ui.add(ProgressBar::new(0.0));
} else {
ui.add_sized(
[width, double],
@ -115,7 +86,7 @@ impl Status {
human, api.hashrate
)),
);
ui.add_sized([width, text], ProgressBar::new(percent / 100.0));
ui.add(ProgressBar::new(percent / 100.0));
}
} else {
ui.add_enabled_ui(xmrig_alive, |ui| {
@ -123,22 +94,21 @@ impl Status {
[width, double],
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
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 (cpu, bar, high, average, low, rank, bench) = (
width_column * 10.0,
width_column * 6.0,
width_column * 3.0,
width_column * 2.0,
width_column * 2.0,
@ -146,6 +116,7 @@ impl Status {
width_column,
width_column * 2.0,
);
ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
ScrollArea::horizontal().show(ui, |ui| {
TableBuilder::new(ui)
.columns(Column::auto(), 7)

View file

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

View file

@ -1,7 +1,8 @@
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 strum::{EnumCount, IntoEnumIterator};
use crate::{
disk::{
@ -16,23 +17,31 @@ use crate::{
impl Status {
pub fn p2pool(
&mut self,
size: Vec2,
ui: &mut egui::Ui,
gupax_p2pool_api: &Arc<Mutex<GupaxP2poolApi>>,
p2pool_alive: bool,
p2pool_api: &Arc<Mutex<PubP2poolApi>>,
) {
let api = gupax_p2pool_api.lock().unwrap();
let height = size.y;
let width = size.x;
let text = height / 25.0;
let log = height / 2.8;
// let height = size.y;
// let width = size.x;
// let text = height / 25.0;
// let log = height / 2.8;
// Payout Text + PayoutView buttons
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
ui.style_mut().override_text_style = Some(TextStyle::Body);
let size_text = ui.text_style_height(&TextStyle::Body);
let height = (ui.style().spacing.button_padding.y * 2.0) + size_text;
ScrollArea::vertical().show(ui, |ui| {
ui.group(|ui| {
ScrollArea::horizontal().show(ui, |ui| {
ui.horizontal(|ui| {
let width = (width / 3.0) - (SPACE * 4.0);
let width =
((ui.available_width() / 3.0) - (SPACE * 4.0)).max(size_text * 9.0);
let width_button =
((ui.available_width() / 3.0 / 4.0) - SPACE).max(size_text * 4.0);
ui.add_sized(
[width, text],
[width, height],
Label::new(
RichText::new(format!("Total Payouts: {}", api.payout))
.underline()
@ -40,9 +49,9 @@ impl Status {
),
)
.on_hover_text(STATUS_SUBMENU_PAYOUT);
ui.separator();
ui.add(Separator::default().vertical());
ui.add_sized(
[width, text],
[width, height],
Label::new(
RichText::new(format!("Total XMR: {}", api.xmr))
.underline()
@ -50,92 +59,63 @@ impl Status {
),
)
.on_hover_text(STATUS_SUBMENU_XMR);
let width = width / 4.0;
ui.separator();
// });
ui.add(Separator::default().vertical());
PayoutView::iter().enumerate().for_each(|(count, p)| {
if ui
.add_sized(
[width, text],
SelectableLabel::new(self.payout_view == PayoutView::Latest, "Latest"),
[width_button, height],
SelectableLabel::new(self.payout_view == p, p.to_string()),
)
.on_hover_text(STATUS_SUBMENU_LATEST)
.on_hover_text(p.msg_help())
.clicked()
{
self.payout_view = PayoutView::Latest;
self.payout_view = p;
}
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;
if count + 1 < PayoutView::COUNT {
ui.add(Separator::default().vertical());
}
});
ui.separator();
});
});
// 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)
.max_width(ui.available_width())
.max_height(ui.available_height() / 2.8)
.auto_shrink([false; 2])
.show_viewport(ui, |ui, _| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Body);
ui.style_mut().spacing.text_edit_width = ui.available_width();
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],
TextEdit::multiline(&mut api.log.as_str()),
),
PayoutView::Biggest => ui.add_sized(
[width, log],
TextEdit::multiline(&mut api.payout_high.as_str()),
),
PayoutView::Smallest => ui.add_sized(
[width, log],
TextEdit::multiline(&mut api.payout_low.as_str()),
),
PayoutView::Latest => {
ui.text_edit_multiline(&mut api.log_rev.as_str())
}
PayoutView::Oldest => ui.text_edit_multiline(&mut api.log.as_str()),
PayoutView::Biggest => {
ui.text_edit_multiline(&mut api.payout_high.as_str())
}
PayoutView::Smallest => {
ui.text_edit_multiline(&mut api.payout_low.as_str())
}
};
});
});
});
// });
drop(api);
// Payout/Share Calculator
let button = (width / 20.0) - (SPACE * 1.666);
// 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);
// ui.set_min_width(width - SPACE);
let width = ui.available_width() / 10.0;
if ui
.add_sized(
[button * 2.0, text],
[width, height],
SelectableLabel::new(!self.manual_hash, "Automatic"),
)
.on_hover_text(STATUS_SUBMENU_AUTOMATIC)
@ -146,7 +126,7 @@ impl Status {
ui.separator();
if ui
.add_sized(
[button * 2.0, text],
[width, height],
SelectableLabel::new(self.manual_hash, "Manual"),
)
.on_hover_text(STATUS_SUBMENU_MANUAL)
@ -157,10 +137,7 @@ impl Status {
ui.separator();
ui.add_enabled_ui(self.manual_hash, |ui| {
if ui
.add_sized(
[button, text],
SelectableLabel::new(self.hash_metric == Hash::Hash, "Hash"),
)
.selectable_label(self.hash_metric == Hash::Hash, "Hash")
.on_hover_text(STATUS_SUBMENU_HASH)
.clicked()
{
@ -168,10 +145,7 @@ impl Status {
}
ui.separator();
if ui
.add_sized(
[button, text],
SelectableLabel::new(self.hash_metric == Hash::Kilo, "Kilo"),
)
.selectable_label(self.hash_metric == Hash::Kilo, "Kilo")
.on_hover_text(STATUS_SUBMENU_KILO)
.clicked()
{
@ -179,10 +153,7 @@ impl Status {
}
ui.separator();
if ui
.add_sized(
[button, text],
SelectableLabel::new(self.hash_metric == Hash::Mega, "Mega"),
)
.selectable_label(self.hash_metric == Hash::Mega, "Mega")
.on_hover_text(STATUS_SUBMENU_MEGA)
.clicked()
{
@ -190,177 +161,132 @@ impl Status {
}
ui.separator();
if ui
.add_sized(
[button, text],
SelectableLabel::new(self.hash_metric == Hash::Giga, "Giga"),
)
.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 = button * 11.5;
ui.spacing_mut().slider_width = (ui.available_width() / 1.2).max(0.0);
ui.add_sized(
[button * 14.0, text],
Slider::new(&mut self.hashrate, 1.0..=1_000.0),
[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 text = height / 25.0;
let width = (width / 3.0) - (SPACE * 1.666);
let min_height = ui.available_height() / 1.3;
let min_height = ui.available_height() / 1.5;
let api = p2pool_api.lock().unwrap();
ui.horizontal(|ui| {
ui.group(|ui| {
ui.vertical(|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.add_sized(
[width, text],
Label::new(RichText::new("Monero Difficulty").underline().color(BONE)),
ui.label(
RichText::new("Monero Difficulty").underline().color(BONE),
)
.on_hover_text(STATUS_SUBMENU_MONERO_DIFFICULTY);
ui.add_sized([width, text], Label::new(api.monero_difficulty.as_str()));
ui.add_sized(
[width, text],
Label::new(RichText::new("Monero Hashrate").underline().color(BONE)),
)
ui.label(api.monero_difficulty.as_str());
ui.label(RichText::new("Monero Hashrate").underline().color(BONE))
.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)),
ui.label(api.monero_hashrate.as_str());
ui.label(
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)),
)
ui.label(api.p2pool_difficulty.as_str());
ui.label(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.label(api.p2pool_hashrate.as_str());
})
});
ui.group(|ui| {
ui.vertical(|ui| {
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(
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(
let solo_block_mean =
PubP2poolApi::calculate_share_or_block_time(
hashrate,
api.monero_difficulty_u64,
);
ui.add_sized(
[width, text],
Label::new(
ui.label(
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(
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.add_sized(
[width, text],
Label::new(api.p2pool_block_mean.to_string()),
);
ui.add_sized(
[width, text],
Label::new(
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.add_sized([width, text], Label::new(p2pool_share_mean.to_string()));
ui.add_sized(
[width, text],
Label::new(
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.add_sized([width, text], Label::new(solo_block_mean.to_string()));
ui.label(solo_block_mean.to_string());
} else {
ui.add_sized(
[width, text],
Label::new(
ui.label(
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(
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.add_sized(
[width, text],
Label::new(api.p2pool_block_mean.to_string()),
);
ui.add_sized(
[width, text],
Label::new(
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.add_sized(
[width, text],
Label::new(api.p2pool_share_mean.to_string()),
);
ui.add_sized(
[width, text],
Label::new(
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.add_sized(
[width, text],
Label::new(api.solo_block_mean.to_string()),
);
ui.label(api.solo_block_mean.to_string());
}
})
});
ui.group(|ui| {
ui.vertical(|ui| {
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;
Hash::convert_to_hash(self.hashrate, self.hash_metric)
as u64;
let user_p2pool_percent = PubP2poolApi::calculate_dominance(
hashrate,
api.p2pool_hashrate_u64,
@ -369,92 +295,67 @@ impl Status {
hashrate,
api.monero_hashrate_u64,
);
ui.add_sized(
[width, text],
Label::new(RichText::new("P2Pool Miners").underline().color(BONE)),
ui.label(
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(
ui.label(api.miners.as_str());
ui.label(
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(
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.add_sized([width, text], Label::new(user_p2pool_percent.as_str()));
ui.add_sized(
[width, text],
Label::new(
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.add_sized([width, text], Label::new(user_monero_percent.as_str()));
ui.label(user_monero_percent.as_str());
} else {
ui.add_sized(
[width, text],
Label::new(RichText::new("P2Pool Miners").underline().color(BONE)),
ui.label(
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(
ui.label(api.miners.as_str());
ui.label(
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(
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.add_sized(
[width, text],
Label::new(api.user_p2pool_percent.as_str()),
);
ui.add_sized(
[width, text],
Label::new(
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.add_sized(
[width, text],
Label::new(api.user_monero_percent.as_str()),
);
ui.label(api.user_monero_percent.as_str());
}
})
});
});
});
// Tick bar
ui.add_sized(
[ui.available_width(), text],
Label::new(api.calculate_tick_bar()),
)
ui.vertical_centered(|ui| {
ui.label(api.calculate_tick_bar())
.on_hover_text(STATUS_SUBMENU_PROGRESS_BAR);
});
drop(api);
});
});
}
}

View file

@ -1,4 +1,4 @@
use egui::{ScrollArea, Ui, Vec2};
use egui::{ScrollArea, Ui};
use readable::up::UptimeFull;
use std::sync::{Arc, Mutex};
@ -12,14 +12,13 @@ use crate::helper::xvb::{PubXvbApi, rounds::XvbRound};
use crate::helper::{ProcessName, Sys};
use crate::constants::*;
use egui::{Label, RichText, TextStyle};
use egui::{RichText, TextStyle};
use log::*;
impl Status {
#[allow(clippy::too_many_arguments)]
pub(super) fn processes(
&mut self,
sys: &Arc<Mutex<Sys>>,
size: Vec2,
ui: &mut egui::Ui,
node_api: &Arc<Mutex<PubNodeApi>>,
p2pool_api: &Arc<Mutex<PubP2poolApi>>,
@ -28,534 +27,359 @@ impl Status {
xmrig_proxy_api: &Arc<Mutex<PubXmrigProxyApi>>,
xmrig_img: &Arc<Mutex<ImgXmrig>>,
xvb_api: &Arc<Mutex<PubXvbApi>>,
max_threads: usize,
max_threads: u16,
states: &ProcessStatesGui,
) {
// set fixed text size, temporary solution before refactoring text/widget size.
let width = ((size.x / 5.0) - (SPACE * 1.7500)).max(0.0);
let height = size.y / 25.0;
// height must be height - top - bottom - space * 2 - space of text
let size: Vec2 = [width, height].into();
let min_height = (size.y - SPACE).max(0.0);
// min width must allow to display text without wrapping.
let size_text = ui.text_style_height(&TextStyle::Body);
// ui.spacing_mut().item_spacing = Vec2::new(2.0, 2.0);
let min_width = size_text * 14.0;
let min_size: Vec2 = [min_width, min_height].into();
let width_column = ui.text_style_height(&TextStyle::Body) * 16.0;
let height_column = width_column * 2.4;
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
// let width = ((ui.available_width() / 5.0) - (SPACE * 1.7500)).max(0.0);
ScrollArea::vertical().show(ui, |ui| {
ui.horizontal(|ui| {
ScrollArea::horizontal().show(ui, |ui| {
ui.set_min_height(min_height * 34.2);
gupax(ui, min_size, size, sys);
node(
ui,
min_size,
size,
states.is_alive(ProcessName::Node),
node_api,
);
ui.vertical(|ui| {
ui.group(|ui| {
ui.set_width(width_column);
ui.set_height(height_column);
ui.vertical_centered(|ui| {
// ui.set_min_width(ui.text_style_height(&TextStyle::Body) * 2.0);
gupax(ui, sys);
});
});
});
ui.vertical(|ui| {
ui.group(|ui| {
ui.set_width(width_column);
ui.set_height(height_column);
ui.vertical_centered(|ui| {
node(ui, states.is_alive(ProcessName::Node), node_api);
});
});
});
ui.vertical(|ui| {
ui.group(|ui| {
ui.set_width(width_column);
ui.set_height(height_column);
ui.vertical_centered(|ui| {
p2pool(
ui,
min_size,
size,
states.is_alive(ProcessName::P2pool),
p2pool_api,
p2pool_img,
);
});
});
});
ui.vertical(|ui| {
ui.group(|ui| {
ui.set_width(width_column);
ui.set_height(height_column);
ui.vertical_centered(|ui| {
xmrig(
ui,
min_size,
size,
states.is_alive(ProcessName::Xmrig),
xmrig_api,
xmrig_img,
max_threads,
);
});
});
});
ui.vertical(|ui| {
ui.group(|ui| {
ui.set_width(width_column);
ui.set_height(height_column);
ui.vertical_centered(|ui| {
xmrig_proxy(
ui,
min_size,
size,
states.is_alive(ProcessName::XmrigProxy),
xmrig_proxy_api,
);
xvb(
ui,
min_size,
size,
states.is_alive(ProcessName::Xvb),
xvb_api,
);
})
});
});
});
ui.vertical(|ui| {
ui.group(|ui| {
ui.set_width(width_column);
ui.set_height(height_column);
ui.vertical_centered(|ui| {
xvb(ui, states.is_alive(ProcessName::Xvb), xvb_api);
});
});
});
});
});
});
}
}
fn gupax(ui: &mut Ui, min_size: Vec2, size: Vec2, sys: &Arc<Mutex<Sys>>) {
ui.group(|ui| {
ui.vertical(|ui| {
ui.set_min_size(min_size);
ui.set_min_height(min_size.y * 34.0);
debug!("Status Tab | Rendering [Gupaxx]");
// ui.set_min_size([min_size.x, min_size.y / 2.0].into());
ui.add_sized(
size,
Label::new(
fn gupax(ui: &mut Ui, sys: &Arc<Mutex<Sys>>) {
ui.label(
RichText::new("[Gupaxx]")
.color(LIGHT_GRAY)
.text_style(TextStyle::Heading),
),
)
.on_hover_text("Gupaxx is online");
let sys = sys.lock().unwrap();
ui.add_sized(
size,
Label::new(RichText::new("Uptime").underline().color(BONE)),
)
ui.label(RichText::new("Uptime").underline().color(BONE))
.on_hover_text(STATUS_GUPAX_UPTIME);
ui.add_sized(size, Label::new(sys.gupax_uptime.to_string()));
ui.add_sized(
size,
Label::new(RichText::new("Gupaxx CPU").underline().color(BONE)),
)
ui.label(sys.gupax_uptime.to_string());
ui.label(RichText::new("Gupaxx CPU").underline().color(BONE))
.on_hover_text(STATUS_GUPAX_CPU_USAGE);
ui.add_sized(size, Label::new(sys.gupax_cpu_usage.to_string()));
ui.add_sized(
size,
Label::new(RichText::new("Gupaxx Memory").underline().color(BONE)),
)
ui.label(sys.gupax_cpu_usage.to_string());
ui.label(RichText::new("Gupaxx Memory").underline().color(BONE))
.on_hover_text(STATUS_GUPAX_MEMORY_USAGE);
ui.add_sized(size, Label::new(sys.gupax_memory_used_mb.to_string()));
ui.add_sized(
size,
Label::new(RichText::new("System CPU").underline().color(BONE)),
)
ui.label(sys.gupax_memory_used_mb.to_string());
ui.label(RichText::new("System CPU").underline().color(BONE))
.on_hover_text(STATUS_GUPAX_SYSTEM_CPU_USAGE);
ui.add_sized(size, Label::new(sys.system_cpu_usage.to_string()));
ui.add_sized(
size,
Label::new(RichText::new("System Memory").underline().color(BONE)),
)
ui.label(sys.system_cpu_usage.to_string());
ui.label(RichText::new("System Memory").underline().color(BONE))
.on_hover_text(STATUS_GUPAX_SYSTEM_MEMORY);
ui.add_sized(size, Label::new(sys.system_memory.to_string()));
ui.add_sized(
size,
Label::new(RichText::new("System CPU Model").underline().color(BONE)),
)
ui.label(sys.system_memory.to_string());
ui.label(RichText::new("System CPU Model").underline().color(BONE))
.on_hover_text(STATUS_GUPAX_SYSTEM_CPU_MODEL);
ui.add_sized(size, Label::new(sys.system_cpu_model.to_string()));
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Wrap);
ui.label(sys.system_cpu_model.to_string());
drop(sys);
})
});
}
fn p2pool(
ui: &mut Ui,
min_size: Vec2,
size: Vec2,
p2pool_alive: bool,
p2pool_api: &Arc<Mutex<PubP2poolApi>>,
p2pool_img: &Arc<Mutex<ImgP2pool>>,
) {
ui.group(|ui| {
ui.vertical(|ui| {
ScrollArea::vertical().show(ui, |ui| {
ui.set_min_height(min_size.y * 34.0);
ui.set_min_size(min_size);
debug!("Status Tab | Rendering [P2Pool]");
ui.add_enabled_ui(p2pool_alive, |ui| {
ui.add_sized(
size,
Label::new(
ui.label(
RichText::new("[P2Pool]")
.color(LIGHT_GRAY)
.text_style(TextStyle::Heading),
),
)
.on_hover_text("P2Pool is online")
.on_disabled_hover_text("P2Pool is offline");
ui.style_mut().override_text_style = Some(TextStyle::Small);
let size = [size.x, size.y / 1.4];
let api = p2pool_api.lock().unwrap();
ui.add_sized(
size,
Label::new(RichText::new("Uptime").underline().color(BONE)),
)
ui.label(RichText::new("Uptime").underline().color(BONE))
.on_hover_text(STATUS_P2POOL_UPTIME);
ui.add_sized(size, Label::new(format!("{}", api.uptime)));
ui.add_sized(
size,
Label::new(RichText::new("Current Shares").underline().color(BONE)),
)
ui.label(format!("{}", api.uptime));
ui.label(RichText::new("Current Shares").underline().color(BONE))
.on_hover_text(STATUS_P2POOL_CURRENT_SHARES);
ui.add_sized(size, Label::new(api.sidechain_shares.to_string()));
ui.add_sized(
size,
Label::new(RichText::new("Shares Found").underline().color(BONE)),
)
ui.label(api.sidechain_shares.to_string());
ui.label(RichText::new("Shares Found").underline().color(BONE))
.on_hover_text(STATUS_P2POOL_SHARES);
ui.add_sized(
size,
Label::new(
ui.label(
(if let Some(s) = api.shares_found {
s.to_string()
} else {
UNKNOWN_DATA.to_string()
})
.to_string(),
),
);
ui.add_sized(
size,
Label::new(RichText::new("Payouts").underline().color(BONE)),
)
ui.label(RichText::new("Payouts").underline().color(BONE))
.on_hover_text(STATUS_P2POOL_PAYOUTS);
ui.add_sized(size, Label::new(format!("Total: {}", api.payouts)));
ui.add_sized(
size,
Label::new(format!(
ui.label(format!("Total: {}", api.payouts));
ui.label(format!(
"[{:.7}/hour]\n[{:.7}/day]\n[{:.7}/month]",
api.payouts_hour, api.payouts_day, api.payouts_month
)),
);
ui.add_sized(
size,
Label::new(RichText::new("XMR Mined").underline().color(BONE)),
)
));
ui.label(RichText::new("XMR Mined").underline().color(BONE))
.on_hover_text(STATUS_P2POOL_XMR);
ui.add_sized(size, Label::new(format!("Total: {:.13} XMR", api.xmr)));
ui.add_sized(
size,
Label::new(format!(
ui.label(format!("Total: {:.13} XMR", api.xmr));
ui.label(format!(
"[{:.7}/hour]\n[{:.7}/day]\n[{:.7}/month]",
api.xmr_hour, api.xmr_day, api.xmr_month
)),
);
ui.add_sized(
size,
Label::new(
));
ui.label(
RichText::new("Hashrate (15m/1h/24h)")
.underline()
.color(BONE),
),
)
.on_hover_text(STATUS_P2POOL_HASHRATE);
ui.add_sized(
size,
Label::new(format!(
"[{} H/s] [{} H/s] [{} H/s]",
ui.label(format!(
"[{} H/s]\n[{} H/s]\n[{} H/s]",
api.hashrate_15m, api.hashrate_1h, api.hashrate_24h
)),
);
ui.add_sized(
size,
Label::new(RichText::new("Miners Connected").underline().color(BONE)),
)
));
ui.label(RichText::new("Miners Connected").underline().color(BONE))
.on_hover_text(STATUS_P2POOL_CONNECTIONS);
ui.add_sized(size, Label::new(format!("{}", api.connections)));
ui.add_sized(
size,
Label::new(RichText::new("Effort").underline().color(BONE)),
)
ui.label(format!("{}", api.connections));
ui.label(RichText::new("Effort").underline().color(BONE))
.on_hover_text(STATUS_P2POOL_EFFORT);
ui.add_sized(
size,
Label::new(format!(
ui.label(format!(
"[Average: {}] [Current: {}]",
api.average_effort, api.current_effort
)),
);
));
let img = p2pool_img.lock().unwrap();
ui.add_sized(
size,
Label::new(RichText::new("Monero Node").underline().color(BONE)),
)
ui.label(RichText::new("Monero Node").underline().color(BONE))
.on_hover_text(STATUS_P2POOL_MONERO_NODE);
ui.add_sized(
size,
Label::new(format!(
"[IP: {}]\n[RPC: {}] [ZMQ: {}]",
ui.label(format!(
"IP: {}]\n[RPC: {}] [ZMQ: {}]",
&img.host, &img.rpc, &img.zmq
)),
);
ui.add_sized(
size,
Label::new(RichText::new("Sidechain").underline().color(BONE)),
)
));
ui.label(RichText::new("Sidechain").underline().color(BONE))
.on_hover_text(STATUS_P2POOL_POOL);
ui.add_sized(size, Label::new(&img.mini));
ui.add_sized(
size,
Label::new(RichText::new("Address").underline().color(BONE)),
)
ui.label(&img.mini);
ui.label(RichText::new("Address").underline().color(BONE))
.on_hover_text(STATUS_P2POOL_ADDRESS);
ui.add_sized(size, Label::new(&img.address));
ui.label(&img.address);
drop(img);
drop(api);
});
})
})
});
}
#[allow(clippy::too_many_arguments)]
fn xmrig_proxy(
ui: &mut Ui,
min_size: Vec2,
size: Vec2,
xmrig_proxy_alive: bool,
xmrig_proxy_api: &Arc<Mutex<PubXmrigProxyApi>>,
) {
ui.group(|ui| {
ui.vertical(|ui| {
ui.set_min_height(min_size.y * 34.0);
debug!("Status Tab | Rendering [XMRig-Proxy]");
ui.add_enabled_ui(xmrig_proxy_alive, |ui| {
ui.set_min_size(min_size);
ui.add_sized(
size,
Label::new(
ui.label(
RichText::new("[XMRig-Proxy]")
.color(LIGHT_GRAY)
.text_style(TextStyle::Heading),
),
)
.on_hover_text("XMRig-Proxy is online")
.on_disabled_hover_text("XMRig-Proxy is offline");
let api = xmrig_proxy_api.lock().unwrap();
ui.add_sized(
size,
Label::new(RichText::new("Uptime").underline().color(BONE)),
)
ui.label(RichText::new("Uptime").underline().color(BONE))
.on_hover_text(STATUS_XMRIG_PROXY_UPTIME);
ui.add_sized(size, Label::new(UptimeFull::from(api.uptime).as_str()));
ui.add_sized(
size,
Label::new(
ui.label(UptimeFull::from(api.uptime).as_str());
ui.label(
RichText::new("Hashrate\n(1m/10m/1h/12h/24h)")
.underline()
.color(BONE),
),
)
.on_hover_text(STATUS_XMRIG_PROXY_HASHRATE);
ui.add_sized(
size,
Label::new(format!(
"[{} H/s] [{} H/s]\n[{} H/s] [{} H/s] [{} H/s]",
api.hashrate_1m,
api.hashrate_10m,
api.hashrate_1h,
api.hashrate_12h,
api.hashrate_24h
)),
);
ui.add_sized(
size,
Label::new(format!(
ui.label(format!(
"[{} H/s]\n[{} H/s]\n[{} H/s]\n[{} H/s]\n[{} H/s]",
api.hashrate_1m, api.hashrate_10m, api.hashrate_1h, api.hashrate_12h, api.hashrate_24h
));
ui.label(format!(
"[Accepted: {}]\n[Rejected: {}]",
api.accepted, api.rejected
)),
);
ui.add_sized(
size,
Label::new(RichText::new("Pool").underline().color(BONE)),
)
));
ui.label(RichText::new("Pool").underline().color(BONE))
.on_hover_text(STATUS_XMRIG_PROXY_POOL);
ui.add_sized(size, Label::new(api.node.to_string()));
ui.label(api.node.to_string());
drop(api);
});
})
});
}
#[allow(clippy::too_many_arguments)]
fn xmrig(
ui: &mut Ui,
min_size: Vec2,
size: Vec2,
xmrig_alive: bool,
xmrig_api: &Arc<Mutex<PubXmrigApi>>,
xmrig_img: &Arc<Mutex<ImgXmrig>>,
max_threads: usize,
max_threads: u16,
) {
ui.group(|ui| {
// ScrollArea::vertical().show(ui, |ui| {
ui.vertical(|ui| {
ui.set_min_height(min_size.y * 34.0);
ui.spacing_mut().item_spacing = Vec2::new(2.0, 2.0);
debug!("Status Tab | Rendering [XMRig]");
ui.add_enabled_ui(xmrig_alive, |ui| {
// ui.set_min_size(min_size);
ui.add_sized(
size,
Label::new(
ui.label(
RichText::new("[XMRig]")
.color(LIGHT_GRAY)
.text_style(TextStyle::Heading),
),
)
.on_hover_text("XMRig is online")
.on_disabled_hover_text("XMRig is offline");
let api = xmrig_api.lock().unwrap();
ui.add_sized(
size,
Label::new(RichText::new("Uptime").underline().color(BONE)),
)
ui.label(RichText::new("Uptime").underline().color(BONE))
.on_hover_text(STATUS_XMRIG_UPTIME);
ui.add_sized(size, Label::new(UptimeFull::from(api.uptime).as_str()));
ui.add_sized(size, Label::new(api.resources.to_string()));
ui.add_sized(
size,
Label::new(
ui.label(UptimeFull::from(api.uptime).as_str());
ui.label(api.resources.to_string());
ui.label(
RichText::new("Hashrate\n(10s/1m/15m)")
.underline()
.color(BONE),
),
)
.on_hover_text(STATUS_XMRIG_HASHRATE);
ui.add_sized(size, Label::new(api.hashrate.to_string()));
ui.add_sized(
size,
Label::new(RichText::new("Difficulty").underline().color(BONE)),
)
ui.label(api.hashrate.to_string());
ui.label(RichText::new("Difficulty").underline().color(BONE))
.on_hover_text(STATUS_XMRIG_DIFFICULTY);
ui.add_sized(size, Label::new(api.diff.to_string()));
ui.add_sized(
size,
Label::new(RichText::new("Shares").underline().color(BONE)),
)
ui.label(api.diff.to_string());
ui.label(RichText::new("Shares").underline().color(BONE))
.on_hover_text(STATUS_XMRIG_SHARES);
ui.add_sized(
size,
Label::new(format!(
ui.label(format!(
"[Accepted: {}]\n[Rejected: {}]",
api.accepted, api.rejected
)),
);
ui.add_sized(
size,
Label::new(RichText::new("Pool").underline().color(BONE)),
)
));
ui.label(RichText::new("Pool").underline().color(BONE))
.on_hover_text(STATUS_XMRIG_POOL);
ui.add_sized(size, Label::new(api.node.to_string()));
ui.add_sized(
size,
Label::new(RichText::new("Threads").underline().color(BONE)),
)
ui.label(api.node.to_string());
ui.label(RichText::new("Threads").underline().color(BONE))
.on_hover_text(STATUS_XMRIG_THREADS);
ui.add_sized(
size,
Label::new(format!(
ui.label(format!(
"{}/{}",
&xmrig_img.lock().unwrap().threads,
max_threads
)),
);
));
drop(api);
});
})
// })
});
}
fn xvb(ui: &mut Ui, min_size: Vec2, size: Vec2, xvb_alive: bool, xvb_api: &Arc<Mutex<PubXvbApi>>) {
fn xvb(ui: &mut Ui, xvb_alive: bool, xvb_api: &Arc<Mutex<PubXvbApi>>) {
//
let api = &xvb_api.lock().unwrap().stats_pub;
let enabled = xvb_alive;
ui.group(|ui| {
ScrollArea::vertical().show(ui, |ui| {
ui.set_min_height(min_size.y * 34.0);
ui.vertical(|ui| {
debug!("Status Tab | Rendering [XvB]");
ui.add_enabled_ui(enabled, |ui| {
// for now there is no API ping or /health, so we verify if the field reward_yearly is empty or not.
// ui.set_min_size(min_size);
ui.add_sized(
size,
Label::new(
ui.label(
RichText::new("[XvB Raffle]")
.color(LIGHT_GRAY)
.text_style(TextStyle::Heading),
),
)
.on_hover_text("XvB API stats")
.on_disabled_hover_text("No data received from XvB API");
// [Round Type]
ui.add_sized(
size,
Label::new(RichText::new("Round Type").underline().color(BONE)),
)
ui.label(RichText::new("Round Type").underline().color(BONE))
.on_hover_text(STATUS_XVB_ROUND_TYPE);
ui.add_sized(size, Label::new(api.round_type.to_string()));
ui.label(api.round_type.to_string());
// [Time Remaining]
ui.add_sized(
size,
Label::new(
ui.label(
RichText::new("Round Time Remaining")
.underline()
.color(BONE),
),
)
.on_hover_text(STATUS_XVB_TIME_REMAIN);
ui.add_sized(size, Label::new(format!("{} minutes", api.time_remain)));
ui.label(format!("{} minutes", api.time_remain));
// Donated Hashrate
ui.add_sized(
size,
Label::new(RichText::new("Bonus Hashrate").underline().color(BONE)),
)
ui.label(RichText::new("Bonus Hashrate").underline().color(BONE))
.on_hover_text(STATUS_XVB_DONATED_HR);
ui.add_sized(
size,
Label::new(format!(
ui.label(format!(
"{}kH/s\n+\n{}kH/s\ndonated by\n{} donors\n with\n{} miners",
api.bonus_hr, api.donate_hr, api.donate_miners, api.donate_workers
)),
);
));
// Players
ui.add_sized(
size,
Label::new(RichText::new("Players").underline().color(BONE)),
)
ui.label(RichText::new("Players").underline().color(BONE))
.on_hover_text(STATUS_XVB_PLAYERS);
ui.add_sized(
size,
Label::new(format!(
ui.label(format!(
"[Registered: {}]\n[Playing: {}]",
api.players, api.players_round
)),
);
));
// Winner
ui.add_sized(
size,
Label::new(RichText::new("Winner").underline().color(BONE)),
)
ui.label(RichText::new("Winner").underline().color(BONE))
.on_hover_text(STATUS_XVB_WINNER);
ui.add_sized(size, Label::new(&api.winner));
ui.label(&api.winner);
// Share effort
ui.add_sized(
size,
Label::new(RichText::new("Share Effort").underline().color(BONE)),
)
ui.label(RichText::new("Share Effort").underline().color(BONE))
.on_hover_text(STATUS_XVB_SHARE);
ui.add_sized(size, Label::new(api.share_effort.to_string()));
ui.label(api.share_effort.to_string());
// Block reward
ui.add_sized(
size,
Label::new(RichText::new("Block Reward").underline().color(BONE)),
)
ui.label(RichText::new("Block Reward").underline().color(BONE))
.on_hover_text(STATUS_XVB_BLOCK_REWARD);
ui.add_sized(size, Label::new(api.block_reward.to_string()));
ui.label(api.block_reward.to_string());
// reward yearly
ui.add_sized(
size,
Label::new(
ui.label(
RichText::new("Est. Reward (Yearly)")
.underline()
.color(BONE),
),
)
.on_hover_text(STATUS_XVB_YEARLY);
if api.reward_yearly.is_empty() {
ui.add_sized(size, Label::new("No information".to_string()));
ui.label("No information".to_string());
} else {
ui.add_sized(
size,
Label::new(format!(
ui.label(format!(
"{}: {} XMR\n{}: {} XMR\n{}: {} XMR\n{}: {} XMR\n{}: {} XMR",
XvbRound::Vip,
api.reward_yearly[0],
@ -567,103 +391,53 @@ fn xvb(ui: &mut Ui, min_size: Vec2, size: Vec2, xvb_alive: bool, xvb_api: &Arc<M
api.reward_yearly[3],
XvbRound::DonorMega,
api.reward_yearly[4]
)),
);
));
}
});
})
// by round
});
});
}
#[allow(clippy::too_many_arguments)]
fn node(
ui: &mut Ui,
min_size: Vec2,
size: Vec2,
node_alive: bool,
node_api: &Arc<Mutex<PubNodeApi>>,
) {
ui.group(|ui| {
ui.vertical(|ui| {
ui.set_min_height(min_size.y * 34.0);
fn node(ui: &mut Ui, node_alive: bool, node_api: &Arc<Mutex<PubNodeApi>>) {
debug!("Status Tab | Rendering [Node]");
ui.add_enabled_ui(node_alive, |ui| {
ui.set_min_size(min_size);
ui.add_sized(
size,
Label::new(
ui.label(
RichText::new("[Node]")
.color(LIGHT_GRAY)
.text_style(TextStyle::Heading),
),
)
.on_hover_text("Node is online")
.on_disabled_hover_text("Node is offline");
let api = node_api.lock().unwrap();
ui.add_sized(
size,
Label::new(RichText::new("Uptime").underline().color(BONE)),
)
ui.label(RichText::new("Uptime").underline().color(BONE))
.on_hover_text(STATUS_NODE_UPTIME);
ui.add_sized(size, Label::new(api.uptime.to_string()));
ui.label(api.uptime.to_string());
ui.add_sized(
size,
Label::new(RichText::new("Block Height").underline().color(BONE)),
)
ui.label(RichText::new("Block Height").underline().color(BONE))
.on_hover_text(STATUS_NODE_BLOCK_HEIGHT);
ui.add_sized(size, Label::new(api.blockheight.to_string()));
ui.add_sized(
size,
Label::new(RichText::new("Network Difficulty").underline().color(BONE)),
)
ui.label(api.blockheight.to_string());
ui.label(RichText::new("Network Difficulty").underline().color(BONE))
.on_hover_text(STATUS_NODE_DIFFICULTY);
ui.add_sized(size, Label::new(api.difficulty.to_string()));
ui.add_sized(
size,
Label::new(RichText::new("Database size").underline().color(BONE)),
)
ui.label(api.difficulty.to_string());
ui.label(RichText::new("Database size").underline().color(BONE))
.on_hover_text(STATUS_NODE_DB_SIZE);
ui.add_sized(size, Label::new(api.database_size.to_owned()));
ui.add_sized(
size,
Label::new(RichText::new("Free space").underline().color(BONE)),
)
ui.label(api.database_size.to_owned());
ui.label(RichText::new("Free space").underline().color(BONE))
.on_hover_text(STATUS_NODE_FREESPACE);
ui.add_sized(size, Label::new(api.free_space.to_owned()));
ui.add_sized(
size,
Label::new(RichText::new("Network Type").underline().color(BONE)),
)
ui.label(api.free_space.to_owned());
ui.label(RichText::new("Network Type").underline().color(BONE))
.on_hover_text(STATUS_NODE_NETTYPE);
ui.add_sized(size, Label::new(api.nettype.to_string()));
ui.add_sized(
size,
Label::new(RichText::new("Outgoing peers").underline().color(BONE)),
)
ui.label(api.nettype.to_string());
ui.label(RichText::new("Outgoing peers").underline().color(BONE))
.on_hover_text(STATUS_NODE_OUT);
ui.add_sized(size, Label::new(api.outgoing_connections.to_string()));
ui.add_sized(
size,
Label::new(RichText::new("Incoming peers").underline().color(BONE)),
)
ui.label(api.outgoing_connections.to_string());
ui.label(RichText::new("Incoming peers").underline().color(BONE))
.on_hover_text(STATUS_NODE_IN);
ui.add_sized(size, Label::new(api.incoming_connections.to_string()));
ui.add_sized(
size,
Label::new(RichText::new("Synchronized").underline().color(BONE)),
)
ui.label(api.incoming_connections.to_string());
ui.label(RichText::new("Synchronized").underline().color(BONE))
.on_hover_text(STATUS_NODE_SYNC);
ui.add_sized(size, Label::new(api.synchronized.to_string()));
ui.add_sized(
size,
Label::new(RichText::new("Status").underline().color(BONE)),
)
ui.label(api.synchronized.to_string());
ui.label(RichText::new("Status").underline().color(BONE))
.on_hover_text(STATUS_NODE_STATUS);
ui.add_sized(size, Label::new(api.status.to_string()));
ui.label(api.status.to_string());
drop(api);
});
})
});
}

View file

@ -15,471 +15,138 @@
// You should have received a copy of the GNU General Public License
// 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::disk::pool::Pool;
use crate::disk::state::Xmrig;
use crate::helper::Process;
use crate::helper::xrig::xmrig::PubXmrigApi;
use crate::regex::{REGEXES, num_lines};
use crate::utils::regex::Regexes;
use egui::{
Button, Checkbox, ComboBox, Label, RichText, SelectableLabel, Slider, TextEdit, TextStyle,
Vec2, vec2,
};
use crate::miscs::height_txt_before_button;
use crate::regex::REGEXES;
use egui::{Checkbox, Ui, vec2};
use log::*;
use std::sync::{Arc, Mutex};
use super::common::list_poolnode::PoolNode;
use super::common::state_edit_field::StateTextEdit;
impl Xmrig {
#[inline(always)] // called once
#[allow(clippy::too_many_arguments)]
pub fn show(
&mut self,
pool_vec: &mut Vec<(String, Pool)>,
pool_vec: &mut Vec<(String, PoolNode)>,
process: &Arc<Mutex<Process>>,
api: &Arc<Mutex<PubXmrigApi>>,
buffer: &mut String,
size: Vec2,
_ctx: &egui::Context,
ui: &mut egui::Ui,
) {
let text_edit = size.y / 25.0;
//---------------------------------------------------------------------------------------------------- [Simple] Console
debug!("XMRig Tab | Rendering [Console]");
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
console(ui, text);
if !self.simple {
ui.separator();
let response = ui
.add_sized(
[width, text_edit],
TextEdit::hint_text(
TextEdit::singleline(buffer),
input_args_field(
ui,
buffer,
process,
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
}
XMRIG_INPUT,
);
}
});
//---------------------------------------------------------------------------------------------------- Arguments
if !self.simple {
debug!("XMRig Tab | Rendering [Arguments]");
ui.group(|ui| {
ui.horizontal(|ui| {
let width = (size.x / 10.0) - SPACE;
ui.add_sized([width, text_edit], Label::new("Command arguments:"));
ui.add_sized(
[ui.available_width(), text_edit],
TextEdit::hint_text(
TextEdit::singleline(&mut self.arguments),
start_options_field(
ui,
&mut self.arguments,
r#"--url <...> --user <...> --config <...>"#,
),
)
.on_hover_text(XMRIG_ARGUMENTS);
self.arguments.truncate(1024);
})
XMRIG_ARGUMENTS,
);
});
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);
});
monero_address_field(&mut self.address, ui, XMRIG_ADDRESS);
});
}
//---------------------------------------------------------------------------------------------------- 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.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,
);
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()),
slider_state_field(
ui,
"Pause on active [0-255]:",
&format!("{} [{}] seconds.", XMRIG_PAUSE, self.pause),
&mut self.pause,
0..=255,
);
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;
if !self.name_field(ui) {
incorrect_input = false;
}
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;
if !self.ip_field(ui) {
incorrect_input = false;
}
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;
if !self.rpc_port_field(ui) {
incorrect_input = false;
}
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;
if !self.rig_field(ui) {
incorrect_input = false;
}
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();
}
});
});
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| {
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);
self.api_ip_field(ui);
self.api_port_field(ui);
});
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();
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 = text_edit * 2.0;
let height =
height_txt_before_button(ui, &egui::TextStyle::Button) * 2.0;
let size = vec2(width, height);
// let mut style = (*ctx.style()).clone();
// style.spacing.icon_width_inner = width / 8.0;
// style.spacing.icon_width = width / 6.0;
// style.spacing.icon_spacing = 20.0;
// ctx.set_style(style);
ui.add_sized(size, Checkbox::new(&mut self.tls, "TLS Connection"))
.on_hover_text(XMRIG_TLS);
ui.separator();
@ -492,4 +159,51 @@ impl Xmrig {
}
});
}
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,122 +1,78 @@
use egui::{
Button, Checkbox, ComboBox, Label, RichText, SelectableLabel, TextEdit, TextStyle, Vec2, vec2,
};
use egui::{Checkbox, Label, TextStyle, Ui, vec2};
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::helper::Process;
use crate::helper::xrig::xmrig_proxy::PubXmrigProxyApi;
use crate::regex::{REGEXES, num_lines};
use crate::utils::constants::DARK_GRAY;
use crate::miscs::height_txt_before_button;
use crate::regex::REGEXES;
use crate::{
GREEN, LIGHT_GRAY, LIST_ADD, LIST_CLEAR, LIST_DELETE, LIST_SAVE, RED, SPACE, XMRIG_API_IP,
XMRIG_API_PORT, XMRIG_IP, XMRIG_KEEPALIVE, XMRIG_NAME, XMRIG_PORT, XMRIG_PROXY_ARGUMENTS,
XMRIG_PROXY_INPUT, XMRIG_PROXY_REDIRECT, XMRIG_PROXY_URL, XMRIG_RIG, XMRIG_TLS,
SPACE, XMRIG_API_IP, XMRIG_API_PORT, XMRIG_IP, XMRIG_KEEPALIVE, XMRIG_NAME, XMRIG_PORT,
XMRIG_PROXY_ARGUMENTS, XMRIG_PROXY_INPUT, XMRIG_PROXY_REDIRECT, XMRIG_PROXY_URL, XMRIG_RIG,
XMRIG_TLS,
};
use super::common::list_poolnode::PoolNode;
use super::common::state_edit_field::StateTextEdit;
impl XmrigProxy {
#[inline(always)] // called once
pub fn show(
&mut self,
process: &Arc<Mutex<Process>>,
pool_vec: &mut Vec<(String, Pool)>,
pool_vec: &mut Vec<(String, PoolNode)>,
api: &Arc<Mutex<PubXmrigProxyApi>>,
buffer: &mut String,
size: Vec2,
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.add_space(space_h);
ui.add_space(SPACE);
ui.style_mut().override_text_style = Some(TextStyle::Heading);
ui.hyperlink_to("XMRig-Proxy", XMRIG_PROXY_URL);
ui.style_mut().override_text_style = Some(TextStyle::Body);
ui.add(Label::new("High performant proxy for your miners"));
ui.add_space(space_h);
ui.add_space(SPACE);
});
// console output for log
debug!("Xmrig-Proxy Tab | Rendering [Console]");
egui::ScrollArea::vertical().show(ui, |ui| {
ui.group(|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);
}
}
},
);
});
});
console(ui, text);
//---------------------------------------------------------------------------------------------------- [Advanced] Console
if !self.simple {
ui.separator();
let response = ui
.add_sized(
[width, text_edit],
TextEdit::hint_text(
TextEdit::singleline(buffer),
input_args_field(
ui,
buffer,
process,
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
XMRIG_PROXY_INPUT,
);
}
});
if !self.simple {
//---------------------------------------------------------------------------------------------------- Arguments
debug!("XMRig-Proxy Tab | Rendering [Arguments]");
ui.group(|ui| {
ui.horizontal(|ui| {
let width = (size.x / 10.0) - SPACE;
ui.add_sized([width, text_edit], Label::new("Command arguments:"));
ui.add_sized(
[ui.available_width(), text_edit],
TextEdit::hint_text(
TextEdit::singleline(&mut self.arguments),
start_options_field(
ui,
&mut self.arguments,
r#"--url <...> --user <...> --config <...>"#,
),
)
.on_hover_text(XMRIG_PROXY_ARGUMENTS);
self.arguments.truncate(1024);
})
XMRIG_PROXY_ARGUMENTS,
);
});
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.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",
@ -130,220 +86,35 @@ impl XmrigProxy {
// need to show public ip
debug!("XMRig-Proxy Tab | Rendering [Pool List] elements");
let width = ui.available_width() - 10.0;
// 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;
// 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;
if !self.name_field(ui) {
incorrect_input = false;
}
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;
if !self.ip_field(ui) {
incorrect_input = false;
}
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;
if !self.rpc_port_field(ui) {
incorrect_input = false;
}
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;
if !self.rig_field(ui) {
incorrect_input = false;
}
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();
}
});
});
list_poolnode(
ui,
&mut (&mut self.name, &mut self.ip, &mut self.port, &mut self.rig),
&mut self.selected_pool,
pool_vec,
incorrect_input,
);
});
});
});
@ -354,60 +125,9 @@ impl XmrigProxy {
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);
});
self.api_ip_field(ui);
self.api_port_field(ui);
});
ui.separator();
@ -417,13 +137,8 @@ impl XmrigProxy {
// TLS/Keepalive
ui.horizontal(|ui| {
let width = (ui.available_width() / 2.0) - 11.0;
let height = text_edit * 2.0;
let height = height_txt_before_button(ui, &TextStyle::Button) * 2.0;
let size = vec2(width, height);
// let mut style = (*ctx.style()).clone();
// style.spacing.icon_width_inner = width / 8.0;
// style.spacing.icon_width = width / 6.0;
// style.spacing.icon_spacing = 20.0;
// ctx.set_style(style);
ui.add_sized(size, Checkbox::new(&mut self.tls, "TLS Connection"))
.on_hover_text(XMRIG_TLS);
ui.separator();
@ -436,4 +151,51 @@ impl XmrigProxy {
}
});
}
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 egui::{Image, RichText, TextEdit, TextStyle, Ui, Vec2, vec2};
use egui::{Align, Image, RichText, ScrollArea, TextStyle, Ui};
use log::debug;
use readable::num::Float;
use readable::up::Uptime;
use strum::EnumCount;
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::helper::xrig::xmrig::PubXmrigApi;
use crate::helper::xrig::xmrig_proxy::PubXmrigProxyApi;
use crate::helper::xvb::PubXvbApi;
use crate::helper::xvb::priv_stats::RuntimeMode;
use crate::regex::num_lines;
use crate::miscs::height_txt_before_button;
use crate::utils::constants::{
GREEN, LIGHT_GRAY, ORANGE, RED, XVB_DONATED_1H_FIELD, XVB_DONATED_24H_FIELD,
XVB_DONATION_LEVEL_DONOR_HELP, XVB_DONATION_LEVEL_MEGA_DONOR_HELP,
XVB_DONATION_LEVEL_VIP_DONOR_HELP, XVB_DONATION_LEVEL_WHALE_DONOR_HELP, XVB_FAILURE_FIELD,
XVB_HELP, XVB_HERO_SELECT, XVB_MANUAL_SLIDER_MANUAL_P2POOL_HELP,
XVB_MANUAL_SLIDER_MANUAL_XVB_HELP, XVB_MODE_MANUAL_DONATION_LEVEL_HELP,
XVB_MODE_MANUAL_P2POOL_HELP, XVB_MODE_MANUAL_XVB_HELP, XVB_ROUND_TYPE_FIELD, XVB_TOKEN_FIELD,
XVB_TOKEN_LEN, XVB_URL_RULES, XVB_WINNER_FIELD,
ORANGE, XVB_DONATED_1H_FIELD, XVB_DONATED_24H_FIELD, XVB_DONATION_LEVEL_DONOR_HELP,
XVB_DONATION_LEVEL_MEGA_DONOR_HELP, XVB_DONATION_LEVEL_VIP_DONOR_HELP,
XVB_DONATION_LEVEL_WHALE_DONOR_HELP, XVB_FAILURE_FIELD, XVB_HELP, XVB_HERO_SELECT,
XVB_MANUAL_SLIDER_MANUAL_P2POOL_HELP, XVB_MANUAL_SLIDER_MANUAL_XVB_HELP,
XVB_MODE_MANUAL_DONATION_LEVEL_HELP, XVB_MODE_MANUAL_P2POOL_HELP, XVB_MODE_MANUAL_XVB_HELP,
XVB_ROUND_TYPE_FIELD, XVB_TOKEN_LEN, XVB_URL_RULES, XVB_WINNER_FIELD,
};
use crate::utils::regex::Regexes;
use crate::{
constants::{BYTES_XVB, SPACE},
utils::constants::{DARK_GRAY, XVB_URL},
utils::constants::XVB_URL,
};
impl crate::disk::state::Xvb {
@ -32,7 +35,6 @@ impl crate::disk::state::Xvb {
#[allow(clippy::too_many_arguments)]
pub fn show(
&mut self,
size: Vec2,
address: &str,
_ctx: &egui::Context,
ui: &mut egui::Ui,
@ -41,96 +43,47 @@ impl crate::disk::state::Xvb {
gui_api_xp: &Arc<Mutex<PubXmrigProxyApi>>,
is_alive: bool,
) {
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;
// let text_edit = ui.available_height() / 25.0;
// let website_height = ui.available_height() / 10.0;
// logo and website link
ui.vertical_centered(|ui| {
ui.add_sized(
[width, website_height],
Image::from_bytes("bytes:/xvb.png", BYTES_XVB),
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,
);
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);
});
egui::ScrollArea::vertical().show(ui, |ui| {
// console output for log
debug!("XvB Tab | Rendering [Console]");
ui.group(|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(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);
}
}
},
);
});
// let nb_lines = num_lines(text);
console(ui, text);
});
// input token
let len_token = format!("{}", self.token.len());
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.add_space(SPACE);
ui.horizontal(|ui| {
// hovering text is difficult because egui doesn't hover over inner widget. But on disabled does.
ui.group(|ui|{
ui.colored_label(color, text)
.on_hover_text(XVB_HELP);
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)
ui.style_mut().override_text_valign = Some(Align::Center);
// ui.set_height(height_txt_before_button(ui, &TextStyle::Body));
self.field_token(ui);
});
// .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 -------------------------------------------
if self.simple {
ui.add_space(SPACE);
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.
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 -----------------------------------------
if !self.simple {
ui.group(|ui| {
ui.vertical_centered(|ui| {
ui.horizontal(|ui| {
egui::ComboBox::from_label("")
ui.style_mut().override_text_valign = Some(Align::Center);
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())
.show_ui(ui, |ui| {
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 {
ui.add_space(space_h);
ui.add_space(SPACE);
let default_xmrig_hashrate = match self.manual_donation_metric {
ManualDonationMetric::Hash => 1_000.0,
@ -203,22 +161,22 @@ impl crate::disk::state::Xvb {
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_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_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_slider_amount = self.manual_amount_raw / 1_000_000.0;
};
ui.spacing_mut().slider_width = width * 0.5;
// less menu, less metrics buttons,less space, less metrics.
ui.spacing_mut().slider_width = ui.available_width() * 0.3;
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))
.text(self.manual_donation_metric.to_string())
.max_decimals(3)
@ -228,6 +186,8 @@ impl crate::disk::state::Xvb {
}
if self.mode == XvbMode::ManualDonationLevel {
ui.add_space(SPACE);
ui.horizontal(|ui| {
ui.radio_value(&mut self.manual_donation_level, ManualDonationLevel::Donor,
ManualDonationLevel::Donor.to_string())
.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);
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
match self.manual_donation_metric {
@ -263,18 +225,18 @@ impl crate::disk::state::Xvb {
// Set runtime_mode & runtime_manual_amount
api.lock().unwrap().stats_priv.runtime_mode = self.mode.clone().into();
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
// button
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)
.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");
}
ui.add_space(space_h);
ui.add_space(SPACE);
// need to warn the user if no address is set in p2pool tab
if !Regexes::addr_ok(address) {
debug!("XvB Tab | Rendering warning text");
@ -284,100 +246,62 @@ impl crate::disk::state::Xvb {
});
}
// 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| {
let api = &api.lock().unwrap();
let priv_stats = &api.stats_priv;
let current_node = &api.current_node;
let width_stat = (ui.available_width() - SPACE * 4.0) / 5.0;
let height_stat = 0.0;
let size_stat = vec2(width_stat, height_stat);
let style_height = ui.text_style_height(&TextStyle::Body);
ui.spacing_mut().item_spacing = [style_height * 2.0, style_height * 2.0].into();
// let width_stat = (ui.available_width() - SPACE * 4.0) / 5.0;
let width_column = ui.text_style_height(&TextStyle::Body) * 16.0;
let height_column = 0.0;
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
ScrollArea::horizontal().id_salt("horizontal").show(ui, |ui| {
ui.horizontal(|ui| {
// Failures
stat_box(ui, XVB_FAILURE_FIELD, &priv_stats.fails.to_string(), width_column, height_column, style_height);
stat_box(ui, XVB_DONATED_1H_FIELD,
&[
Float::from_3(priv_stats.donor_1hr_avg as f64).to_string(),
" kH/s".to_string(),
]
.concat()
, width_column, height_column, style_height);
stat_box(ui, XVB_DONATED_24H_FIELD,
&[
Float::from_3(priv_stats.donor_24hr_avg as f64).to_string(),
" kH/s".to_string(),
]
.concat()
, width_column, height_column, style_height);
ui.add_enabled_ui(priv_stats.round_participate.is_some(), |ui| {
let round = match &priv_stats.round_participate {
Some(r) => r.to_string(),
None => "None".to_string(),
};
ui.add_sized(size_stat, |ui: &mut Ui| {
ui.group(|ui| {
let size_stat = vec2(
ui.available_width(),
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(),
" kH/s".to_string(),
]
.concat(),
);
})
.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(),
" kH/s".to_string(),
]
.concat(),
);
})
.response
});
ui.separator();
ui.add_enabled_ui(priv_stats.round_participate.is_some(), |ui| {
ui.add_sized(size_stat, |ui: &mut Ui| {
ui.vertical_centered(|ui| {
ui.label(XVB_ROUND_TYPE_FIELD);
ui.label(round);
})
.response
})
stat_box(ui, XVB_ROUND_TYPE_FIELD, &round, width_column, height_column, style_height);
}).response
.on_disabled_hover_text(
"You do not yet have a share in the PPLNS Window.",
);
});
ui.separator();
ui.add_sized(size_stat, |ui: &mut Ui| {
ui.vertical_centered(|ui| {
ui.label(XVB_WINNER_FIELD);
ui.label(if priv_stats.win_current {
stat_box(ui, XVB_WINNER_FIELD,
if priv_stats.win_current {
"You are Winning the round !"
} else {
"You are not the winner"
});
})
.response
});
})
.response
}
, width_column, height_column, style_height);
});
});
// indicators
ui.horizontal(|ui| {
ui.add_sized(size_stat, |ui: &mut Ui| {
ui.vertical(|ui| {
ui.group(|ui| {
let size_stat = vec2(
ui.available_width(),
0.0, // + ui.spacing().item_spacing.y,
);
ui.add_sized(size_stat, |ui: &mut Ui| {
ui.set_width(width_column);
ui.set_height(height_column);
ui.vertical_centered(|ui| {
ui.add_space(SPACE);
ui.label(XVB_MINING_ON_FIELD)
.on_hover_text_at_pointer(&priv_stats.msg_indicator);
ui.label(
@ -389,24 +313,44 @@ impl crate::disk::state::Xvb {
ui.label(Uptime::from(priv_stats.time_switch_node).to_string())
.on_hover_text_at_pointer(&priv_stats.msg_indicator)
})
.response
})
});
})
.response
.on_disabled_hover_text("Algorithm is not running.")
.on_disabled_hover_text("Algorithm is not running.");
// indicators
})
// currently mining on
});
});
// Rules link help
ui.horizontal_centered(|ui| {
// can't have horizontal and vertical centering work together so fix by this.
ui.add_space((width / 2.0) - (ui.text_style_height(&TextStyle::Heading) * 1.5));
ui.style_mut().override_text_style = Some(TextStyle::Heading);
ui.hyperlink_to("Rules", XVB_URL_RULES)
.on_hover_text("Click here to read the rules and understand how the raffle works.");
});
});
}
fn field_token(&mut self, ui: &mut Ui) {
StateTextEdit::new(ui)
.help_msg(XVB_HELP)
.max_ch(XVB_TOKEN_LEN as u8)
.text_edit_width_same_as_max_ch(ui)
.description(" Token ")
.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"))
.clicked()
{
if self.state.gupax.save_before_quit {
if self.state.gupax.auto.save_before_quit {
self.save_before_quit();
}
exit(0);

View file

@ -12,8 +12,6 @@ impl crate::app::App {
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.
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.style_mut().override_text_style = Some(TextStyle::Heading);
let height = ui

View file

@ -16,11 +16,11 @@ impl App {
return None;
}
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
// the user tried to exit again, exit.
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();
}
return Some(ViewportCommand::Close);
@ -32,7 +32,7 @@ impl App {
Some(ViewportCommand::CancelClose)
// Else, just quit.
} else {
if self.state.gupax.save_before_quit {
if self.state.gupax.auto.save_before_quit {
self.save_before_quit();
}
Some(ViewportCommand::Close)

View file

@ -241,7 +241,7 @@ impl Update {
#[cfg(feature = "distro")]
return;
// 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
// Attempt relative to absolute path
// it's ok if file doesn't exist. User could enable bundled version for the first time.
@ -465,7 +465,7 @@ impl Update {
// arch
// standalone or bundled
// archive extension
let bundle = if og.lock().unwrap().gupax.bundled {
let bundle = if og.lock().unwrap().gupax.auto.bundled {
"bundle"
} else {
"standalone"
@ -577,7 +577,7 @@ impl Update {
path.display()
);
// 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 == XMRIG_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};
//---------------------------------------------------------------------------------------------------- [Node] Impl
impl Node {
pub fn localhost() -> Self {
Self {
pub fn localhost() -> PoolNode {
PoolNode::Node(Self {
ip: "localhost".to_string(),
rpc: "18081".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())]
}
pub fn new_tuple() -> (String, Self) {
pub fn new_tuple() -> (String, PoolNode) {
("Local Monero Node".to_string(), Self::localhost())
}
// 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) {
Ok(map) => {
info!("Node | Parse ... OK");
@ -73,20 +73,23 @@ impl Node {
}
};
let node = Node { ip, rpc, zmq };
vec.push((key.clone(), node));
vec.push((key.clone(), PoolNode::Node(node)));
}
Ok(vec)
}
// Convert [Vec<(String, Self)>] into [String]
// 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();
for (key, value) in vec.iter() {
write!(
toml,
"[\'{}\']\nip = {:#?}\nrpc = {:#?}\nzmq = {:#?}\n\n",
key, value.ip, value.rpc, value.zmq,
key,
value.ip(),
value.port(),
value.custom(),
)?;
}
Ok(toml)
@ -97,7 +100,7 @@ impl Node {
// |_ Create a default file if not found
// 2. Deserialize [String] into a proper [Struct]
// |_ 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
let file = File::Node;
let string = match read_to_string(file, path) {
@ -114,7 +117,7 @@ impl Node {
// Completely overwrite current [node.toml]
// 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...");
let new = Self::new_vec();
let string = Self::to_string(&Self::new_vec())?;
@ -124,7 +127,7 @@ impl Node {
}
// 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());
let string = Self::to_string(vec)?;
match fs::write(path, string) {

View file

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

View file

@ -1,8 +1,9 @@
use anyhow::Result;
use rand::{Rng, distributions::Alphanumeric, thread_rng};
use strum::{EnumCount, EnumIter};
use super::*;
use crate::{components::node::RemoteNode, disk::status::*};
use crate::{components::node::RemoteNode, disk::status::*, helper::ProcessName};
//---------------------------------------------------------------------------------------------------- [State] Impl
impl Default for State {
fn default() -> Self {
@ -12,7 +13,7 @@ impl Default for State {
impl State {
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 };
Self {
status: Status::default(),
@ -132,7 +133,6 @@ impl State {
}
}
}
// Take [String] as input, merge it with whatever the current [default] is,
// leaving behind old keys+values and updating [default] with old valid ones.
pub fn merge(old: &str) -> Result<Self, TomlError> {
@ -179,14 +179,7 @@ pub struct Status {
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct Gupax {
pub simple: bool,
pub auto_update: bool,
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 auto: AutoEnabled,
pub p2pool_path: String,
pub node_path: String,
pub xmrig_path: String,
@ -200,9 +193,94 @@ pub struct Gupax {
pub selected_scale: f32,
pub tab: Tab,
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)]
pub struct P2pool {
pub simple: bool,
@ -213,7 +291,7 @@ pub struct P2pool {
pub backup_host: bool,
pub out_peers: u16,
pub in_peers: u16,
pub log_level: u8,
pub log_level: u16,
pub node: String,
pub arguments: String,
pub address: String,
@ -221,11 +299,17 @@ pub struct P2pool {
pub ip: String,
pub rpc: String,
pub zmq: String,
pub selected_index: usize,
pub selected_name: String,
pub selected_ip: String,
pub selected_rpc: String,
pub selected_zmq: String,
pub selected_node: SelectedPoolNode,
}
// compatible for P2Pool and Xmrig/Proxy
#[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)]
@ -235,7 +319,7 @@ pub struct Node {
pub api_port: String,
pub out_peers: u16,
pub in_peers: u16,
pub log_level: u8,
pub log_level: u16,
pub arguments: String,
pub zmq_ip: String,
pub zmq_port: String,
@ -268,13 +352,13 @@ impl Default for Node {
#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)]
pub struct Xmrig {
pub simple: bool,
pub pause: u8,
pub pause: u16,
pub simple_rig: String,
pub arguments: String,
pub tls: bool,
pub keepalive: bool,
pub max_threads: usize,
pub current_threads: usize,
pub max_threads: u16,
pub current_threads: u16,
pub address: String,
pub api_ip: String,
pub api_port: String,
@ -282,11 +366,7 @@ pub struct Xmrig {
pub rig: String,
pub ip: String,
pub port: String,
pub selected_index: usize,
pub selected_name: String,
pub selected_rig: String,
pub selected_ip: String,
pub selected_port: String,
pub selected_pool: SelectedPoolNode,
pub token: String,
}
@ -307,15 +387,41 @@ pub struct XmrigProxy {
pub api_port: String,
pub p2pool_ip: String,
pub p2pool_port: String,
pub selected_index: usize,
pub selected_name: String,
pub selected_rig: String,
pub selected_ip: String,
pub selected_port: String,
pub selected_pool: SelectedPoolNode,
pub token: String,
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 {
fn default() -> Self {
XmrigProxy {
@ -335,11 +441,13 @@ impl Default for XmrigProxy {
port: "3355".to_string(),
p2pool_ip: "localhost".to_string(),
p2pool_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(),
selected_pool: SelectedPoolNode {
index: 0,
name: "Local P2Pool".to_string(),
ip: "localhost".to_string(),
rpc: "3333".to_string(),
zmq_rig: GUPAX_VERSION_UNDERSCORE.to_string(),
},
api_ip: "localhost".to_string(),
api_port: "18089".to_string(),
tls: false,
@ -361,7 +469,7 @@ pub struct Xvb {
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 {
#[default]
Auto,
@ -435,6 +543,20 @@ pub struct Version {
}
//---------------------------------------------------------------------------------------------------- [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 {
fn default() -> Self {
Self {
@ -452,14 +574,7 @@ impl Default for Gupax {
fn default() -> Self {
Self {
simple: true,
auto_update: false,
auto_p2pool: false,
auto_node: false,
auto_xmrig: false,
auto_xp: false,
auto_xvb: false,
ask_before_quit: true,
save_before_quit: true,
auto: AutoEnabled::default(),
p2pool_path: DEFAULT_P2POOL_PATH.to_string(),
xmrig_path: DEFAULT_XMRIG_PATH.to_string(),
node_path: DEFAULT_NODE_PATH.to_string(),
@ -473,10 +588,6 @@ impl Default for Gupax {
selected_scale: APP_DEFAULT_SCALE,
ratio: Ratio::Width,
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(),
rpc: "18081".to_string(),
zmq: "18083".to_string(),
selected_index: 0,
selected_name: "Local Monero Node".to_string(),
selected_ip: "localhost".to_string(),
selected_rpc: "18081".to_string(),
selected_zmq: "18083".to_string(),
selected_node: SelectedPoolNode {
index: 0,
name: "Local Monero Node".to_string(),
ip: "localhost".to_string(),
rpc: "18081".to_string(),
zmq_rig: "18083".to_string(),
},
}
}
}
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();
Self {
max_threads,
@ -531,17 +644,19 @@ impl Default for Xmrig {
rig: GUPAX_VERSION_UNDERSCORE.to_string(),
ip: "localhost".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_port: "18088".to_string(),
tls: false,
keepalive: false,
current_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()
.sample_iter(Alphanumeric)
.take(16)

View file

@ -1,3 +1,6 @@
use derive_more::derive::Display;
use strum::{EnumCount, EnumIter};
use super::*;
//---------------------------------------------------------------------------------------------------- [Submenu] enum for [Status] tab
#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)]
@ -25,7 +28,9 @@ impl Display for Submenu {
//---------------------------------------------------------------------------------------------------- [PayoutView] enum for [Status/P2Pool] tab
// 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 {
Latest, // Shows the most recent logs first
Oldest, // Shows the oldest logs first
@ -33,6 +38,17 @@ pub enum PayoutView {
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 {
fn new() -> Self {
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]
#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)]
#[allow(clippy::enum_variant_names)]
@ -110,8 +120,10 @@ impl Hash {
impl Display for Hash {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Hash::Hash => write!(f, "Hash"),
_ => write!(f, "{:?}hash", self),
Hash::Hash => write!(f, "H/s"),
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"
bundled = false
[gupax.auto]
update = false
bundled = false
ask_before_quit = false
save_before_quit = true
processes = []
[status]
submenu = "P2pool"
payout_view = "Oldest"
@ -76,11 +83,13 @@ mod test {
ip = "192.168.1.123"
rpc = "18089"
zmq = "18083"
selected_index = 0
selected_name = "Local Monero Node"
selected_ip = "192.168.1.123"
selected_rpc = "18089"
selected_zmq = "18083"
[p2pool.selected_node]
index = 0
name = "Local Monero Node"
ip = "localhost"
rpc = "18081"
zmq_rig = "18083"
[xmrig]
simple = true
@ -98,13 +107,17 @@ mod test {
rig = "Gupaxx"
ip = "192.168.1.122"
port = "3333"
selected_index = 1
selected_name = "linux"
selected_rig = "Gupaxx"
selected_ip = "192.168.1.122"
selected_port = "3333"
token = "testtoken"
[xmrig.selected_pool]
index = 0
name = "Local Monero Node"
ip = "localhost"
rpc = "18081"
zmq_rig = "18083"
[xmrig_proxy]
simple = true
arguments = ""
@ -121,13 +134,15 @@ mod test {
p2pool_ip = "localhost"
p2pool_port = "18088"
token = "testtoken"
selected_index = 1
selected_name = "linux"
selected_rig = "Gupaxx"
selected_ip = "192.168.1.122"
selected_port = "3333"
redirect_local_xmrig = true
[xmrig_proxy.selected_pool]
index = 0
name = "Local Monero Node"
ip = "localhost"
rpc = "18081"
zmq_rig = "18083"
[xvb]
simple = true
simple_hero_mode = true

View file

@ -33,6 +33,7 @@
// 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.
use crate::components::gupax::FileType;
use crate::components::update::{NODE_BINARY, P2POOL_BINARY, XMRIG_BINARY, XMRIG_PROXY_BINARY};
//---------------------------------------------------------------------------------------------------- Import
use crate::helper::xrig::xmrig_proxy::PubXmrigProxyApi;
@ -46,6 +47,7 @@ use log::*;
use node::PubNodeApi;
use portable_pty::Child;
use readable::up::Uptime;
use serde::{Deserialize, Serialize};
use std::fmt::Write;
use std::path::Path;
use std::{
@ -54,7 +56,7 @@ use std::{
thread,
time::*,
};
use strum::EnumIter;
use strum::{EnumCount, EnumIter};
use self::xvb::{PubXvbApi, nodes::XvbNode};
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 {
Node,
P2pool,
Xmrig,
#[display("Proxy")]
XmrigProxy,
#[default]
Xvb,
}
impl ProcessName {
pub fn binary_name(&self) -> &str {
pub const fn binary_name(&self) -> &str {
match self {
ProcessName::Node => NODE_BINARY,
ProcessName::P2pool => P2POOL_BINARY,
@ -270,6 +275,69 @@ impl ProcessName {
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 {
@ -374,7 +442,7 @@ impl Helper {
pub_sys: &mut Sys,
pid: &sysinfo::Pid,
helper: &Helper,
max_threads: usize,
max_threads: u16,
) {
let gupax_uptime = helper.uptime.to_string();
let cpu = &sysinfo.cpus()[0];
@ -416,7 +484,7 @@ impl Helper {
helper: &Arc<Mutex<Self>>,
mut sysinfo: sysinfo::System,
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
// 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::Process;
use crate::app::panels::middle::common::list_poolnode::PoolNode;
use crate::components::node::RemoteNode;
use crate::disk::state::P2pool;
use crate::helper::ProcessName;
@ -18,7 +19,7 @@ use crate::regex::estimated_hr;
use crate::regex::nb_current_shares;
use crate::{
constants::*,
disk::{gupax_p2pool_api::GupaxP2poolApi, node::Node},
disk::gupax_p2pool_api::GupaxP2poolApi,
helper::{MONERO_BLOCK_TIME_IN_SECONDS, P2POOL_BLOCK_TIME_IN_SECONDS},
human::*,
macros::*,
@ -171,7 +172,7 @@ impl Helper {
helper: &Arc<Mutex<Self>>,
state: &P2pool,
path: &Path,
backup_hosts: Option<Vec<Node>>,
backup_hosts: Option<Vec<PoolNode>>,
) {
info!("P2Pool | Attempting to restart...");
helper.lock().unwrap().p2pool.lock().unwrap().signal = ProcessSignal::Restart;
@ -200,7 +201,7 @@ impl Helper {
helper: &Arc<Mutex<Self>>,
state: &P2pool,
path: &Path,
backup_hosts: Option<Vec<Node>>,
backup_hosts: Option<Vec<PoolNode>>,
) {
helper.lock().unwrap().p2pool.lock().unwrap().state = ProcessState::Middle;
@ -253,7 +254,7 @@ impl Helper {
helper: &Arc<Mutex<Self>>,
state: &P2pool,
path: &Path,
backup_hosts: Option<Vec<Node>>,
backup_hosts: Option<Vec<PoolNode>>,
) -> (Vec<String>, PathBuf, PathBuf, PathBuf) {
let mut args = Vec::with_capacity(500);
let path = path.to_path_buf();
@ -282,13 +283,13 @@ impl Helper {
// Push other nodes if `backup_host`.
if let Some(nodes) = backup_hosts {
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(node.ip.to_string());
args.push(node.ip().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(node.zmq.to_string());
args.push(node.custom().to_string());
}
}
}
@ -395,20 +396,18 @@ impl Helper {
// Push other nodes if `backup_host`.
if let Some(nodes) = backup_hosts {
for node in nodes {
let ip = if node.ip == "localhost" {
let ip = if node.ip() == "localhost" {
"127.0.0.1"
} else {
&node.ip
node.ip()
};
if (node.ip.as_str(), node.rpc.as_str(), node.zmq.as_str())
!= (ip, &state.rpc, &state.zmq)
{
if (node.ip(), node.port(), node.custom()) != (ip, &state.rpc, &state.zmq) {
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(node.rpc.to_string());
args.push(node.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()
},
address: Self::head_tail_of_monero_address(&state.address),
host: state.selected_ip.to_string(),
rpc: state.selected_rpc.to_string(),
zmq: state.selected_zmq.to_string(),
host: state.selected_node.ip.to_string(),
rpc: state.selected_node.rpc.to_string(),
zmq: state.selected_node.zmq_rig.to_string(),
out_peers: state.out_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.solo_block_mean.to_string(),
"5 months, 21 days, 9 hours, 52 minutes"
"5 months\n21 days\n9 hours\n52 minutes"
);
assert_eq!(
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.user_p2pool_percent.to_string(), "2.000000%");
assert_eq!(p.user_monero_percent.to_string(), "0.000800%");

View file

@ -24,6 +24,7 @@ use std::time::Instant;
#[cold]
#[inline(never)]
// everything is resized from here with the scale.
pub fn init_text_styles(ctx: &egui::Context, pixels_per_point: f32) {
ctx.all_styles_mut(|style| {
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)),
]
.into();
style.spacing.icon_width_inner = 32.0;
style.spacing.icon_width = 64.0;
style.spacing.icon_spacing = 20.0;
style.spacing.scroll = egui::style::ScrollStyle {
bar_width: 8.0,
..egui::style::ScrollStyle::solid()
};
style.spacing.icon_width_inner = 24.0;
style.spacing.icon_width = 48.0;
style.spacing.icon_spacing = 16.0;
style.spacing.button_padding = [8.0, 8.0].into();
style.spacing.item_spacing = [8.0, 8.0].into();
// style.spacing.scroll = egui::style::ScrollStyle {
// bar_width: 8.0,
// ..egui::style::ScrollStyle::solid()
// };
});
// Make sure scale f32 is a regular number.
let pixels_per_point = clamp_scale(pixels_per_point);
@ -134,7 +137,7 @@ pub fn init_auto(app: &mut App) {
// [Auto-Update]
#[cfg(not(feature = "distro"))]
if app.state.gupax.auto_update {
if app.state.gupax.auto.is_enabled(&AutoStart::Update) {
Update::spawn_thread(
&app.og,
&app.state.gupax,
@ -155,7 +158,12 @@ pub fn init_auto(app: &mut App) {
}
// [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) {
warn!("Gupaxx | Node path is not a file! Skipping auto-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...");
}
// [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) {
warn!("Gupaxx | P2Pool address is not valid! Skipping auto-p2pool...");
} else if !Gupax::path_is_file(&app.state.gupax.p2pool_path) {
@ -202,7 +215,12 @@ pub fn init_auto(app: &mut App) {
}
// [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) {
warn!("Gupaxx | XMRig path is not an executable! Skipping auto-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...");
}
// [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) {
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) {
@ -247,7 +270,12 @@ pub fn init_auto(app: &mut App) {
info!("Skipping auto-XMRig-Proxy...");
}
// [Auto-XvB]
if app.state.gupax.auto_xvb {
if app
.state
.gupax
.auto
.is_enabled(&AutoStart::Process(ProcessName::Xvb))
{
Helper::start_xvb(
&app.helper,
&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::helper::ProcessName;
use chrono::Local;
use egui::TextStyle;
use egui::Ui;
use log::error;
use log::warn;
use regex::Regex;
@ -182,3 +184,7 @@ pub fn client() -> ClientWithMiddleware {
))
.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_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_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_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_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_TOKEN_FIELD: &str = "Token";
pub const XVB_FAILURE_FIELD: &str = "Failures";
pub const XVB_DONATED_1H_FIELD: &str = "Donated last hour";
pub const XVB_DONATED_24H_FIELD: &str = "Donated last 24 hours";

View file

@ -59,7 +59,7 @@ impl HumanTime {
) -> std::fmt::Result {
if value > 0 {
if *started {
f.write_str(", ")?;
f.write_str("\n")?;
}
write!(f, "{} {}", value, name)?;
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(59)).to_string() == "59 seconds");
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!(
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(121)).to_string() == "2 minutes, 1 second"
HumanTime::into_human(Duration::from_secs(121)).to_string() == "2 minutes\n1 second"
);
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!(
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!(
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(3601)).to_string() == "1 hour, 1 second");
assert!(HumanTime::into_human(Duration::from_secs(3601)).to_string() == "1 hour\n1 second");
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!(
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!(
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(86401)).to_string() == "1 day, 1 second");
assert!(HumanTime::into_human(Duration::from_secs(86401)).to_string() == "1 day\n1 second");
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!(
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(93600)).to_string() == "1 day, 2 hours");
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\n2 hours");
assert!(
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(2630016)).to_string() == "1 month");
assert!(
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(31557600)).to_string() == "1 year");
assert!(HumanTime::into_human(Duration::from_secs(63115200)).to_string() == "2 years");
assert_eq!(
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",
);
}
}