add basic [--flags]

This commit is contained in:
hinto-janaiyo 2022-10-16 17:29:24 -04:00
parent ed7ddeeda1
commit 773bc65593
No known key found for this signature in database
GPG key ID: D7483F6CA27D1B1D
4 changed files with 236 additions and 150 deletions

View file

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// These are the versions bundled with Gupax.
pub const GUPAX_VERSION: &'static str = "v0.1.0";
pub const P2POOL_VERSION: &'static str = "v2.4";
pub const XMRIG_VERSION: &'static str = "v6.18.0";
@ -23,6 +23,7 @@ pub const BYTES_ICON: &[u8] = include_bytes!("../images/png/icon.png");
pub const BYTES_BANNER: &[u8] = include_bytes!("../images/png/banner.png");
pub const P2POOL_BASE_ARGS: &'static str = "";
pub const XMRIG_BASE_ARGS: &'static str = "--http-host=127.0.0.1 --http-port=18088 --algo=rx/0 --coin=Monero --randomx-cache-qos";
pub const HORIZONTAL: &'static str = "--------------------------------------------";
// OS specific
#[cfg(target_os = "windows")]
@ -66,3 +67,19 @@ pub const XMRIG_NICEHASH: &'static str = "Enable nicehash.com support";
pub const XMRIG_KEEPALIVE: &'static str = "Send keepalived packet to prevent timeout (needs pool support)";
pub const XMRIG_THREADS: &'static str = "Number of CPU threads to use for mining";
pub const XMRIG_PRIORITY: &'static str = "Set process priority (0 idle, 2 normal to 5 highest)";
// CLI argument messages
pub const ARG_HELP: &'static str =
r#"USAGE: gupax [--flags]
-h | --help Print this help message
-v | --version Print versions
-n | --no-startup Disable auto-update/node connections at startup
-r | --reset Reset all Gupax configuration/state"#;
pub const ARG_COPYRIGHT: &'static str =
r#"For more information:
https://github.com/hinto-janaiyo/gupax
https://github.com/SChernykh/p2pool
https://github.com/xmrig/xmrig
Gupax, P2Pool, and XMRig are licensed under GPLv3."#;

View file

@ -34,8 +34,9 @@ use env_logger::{Builder,WriteStyle};
use std::io::Write;
use std::process::exit;
use std::sync::{Arc,Mutex};
use std::thread;
use std::{thread,env};
use std::time::Instant;
use std::path::PathBuf;
// Modules
mod constants;
@ -55,10 +56,10 @@ use {constants::*,node::*,state::*,about::*,status::*,gupax::*,p2pool::*,xmrig::
pub struct App {
// Misc state
tab: Tab, // What tab are we on?
quit: bool, // Did user click quit button?
quit_confirm: bool, // Did user confirm to quit?
ping: bool, // Did user click the ping button?
ping_prog: Arc<Mutex<bool>>, // Are we in the progress of pinging?
quit: bool, // Was the quit button clicked?
quit_confirm: bool, // Was the quit confirmed?
ping: bool, // Was the ping button clicked?
pinging: Arc<Mutex<bool>>, // Is a ping in progress?
node: Arc<Mutex<NodeStruct>>, // Data on community nodes
// State:
// og = Old state to compare against
@ -68,42 +69,58 @@ pub struct App {
og: State,
state: State,
diff: bool,
// Process/update state:
// Doesn't make sense to save this on disk
// so it's represented as a bool here.
p2pool: bool, // Is p2pool online?
xmrig: bool, // Is xmrig online?
updating: bool, // Is an update in progress?
// State from [--flags]
startup: bool,
reset: bool,
// Static stuff
now: Instant,
resolution: Vec2,
os: &'static str,
version: String,
name_version: String,
banner: RetainedImage,
// TEMPORARY FIXME
p2pool: bool,
xmrig: bool,
now: Instant, // Internal timer
resolution: Vec2, // Frame resolution
os: &'static str, // OS
version: String, // Gupax version
name_version: String, // [Gupax vX.X.X]
banner: RetainedImage, // Gupax banner image
}
impl App {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
fn cc(cc: &eframe::CreationContext<'_>, app: Self) -> Self {
let resolution = cc.integration_info.window_info.size;
init_text_styles(&cc.egui_ctx, resolution[0] as f32);
Self {
resolution,
..app
}
}
fn default() -> Self {
let app = Self {
tab: Tab::default(),
quit: false,
quit_confirm: false,
ping: false,
ping_prog: Arc::new(Mutex::new(false)),
pinging: Arc::new(Mutex::new(false)),
node: Arc::new(Mutex::new(NodeStruct::default())),
og: State::default(),
state: State::default(),
diff: false,
p2pool: false,
xmrig: false,
updating: false,
startup: true,
reset: false,
now: Instant::now(),
resolution: cc.integration_info.window_info.size,
resolution: Vec2::new(1280.0, 720.0),
os: OS,
version: "v0.0.1".to_string(),
name_version: "Gupax v0.0.1".to_string(),
banner: RetainedImage::from_image_bytes("banner.png", BYTES_BANNER).expect("oops"),
// TEMPORARY FIXME
p2pool: false,
xmrig: false,
}
};
parse_args(app)
}
}
@ -153,6 +170,10 @@ fn init_text_styles(ctx: &egui::Context, width: f32) {
}
fn init_logger() {
#[cfg(debug_assertions)]
let filter = LevelFilter::Info;
#[cfg(not(debug_assertions))]
let filter = LevelFilter::Warn;
use env_logger::fmt::Color;
Builder::new().format(|buf, record| {
let level;
@ -173,7 +194,7 @@ fn init_logger() {
buf.style().set_dimmed(true).value(record.line().unwrap_or(0)),
record.args(),
)
}).filter_level(LevelFilter::Info).write_style(WriteStyle::Always).parse_default_env().format_timestamp_millis().init();
}).filter_level(filter).write_style(WriteStyle::Always).parse_default_env().format_timestamp_millis().init();
info!("init_logger() ... OK");
}
@ -195,6 +216,45 @@ fn init_options() -> NativeOptions {
options
}
//---------------------------------------------------------------------------------------------------- Misc functions
fn into_absolute_path(path: String) -> Result<PathBuf, std::io::Error> {
let path = PathBuf::from(path);
if path.is_relative() {
let mut dir = std::env::current_exe()?;
dir.pop();
dir.push(path);
Ok(dir)
} else {
Ok(path)
}
}
fn parse_args(mut app: App) -> App {
info!("Parsing CLI arguments...");
let mut args: Vec<String> = env::args().collect();
if args.len() == 1 { info!("No args ... OK"); return app } else { args.remove(0); info!("Args ... {:?}", args); }
// [help/version], exit early
for arg in &args {
match arg.as_str() {
"-h"|"--help" => { println!("{}", ARG_HELP); exit(0); },
"-v"|"--version" => {
println!("Gupax | {}\nP2Pool | {}\nXMRig | {}\n\n{}", GUPAX_VERSION, P2POOL_VERSION, XMRIG_VERSION, ARG_COPYRIGHT);
exit(0);
},
_ => (),
}
}
// Everything else
for arg in args {
match arg.as_str() {
"-n"|"--no-startup" => { info!("Disabling startup..."); app.startup = false; }
"-r"|"--reset" => { info!("Resetting state..."); app.reset = true; }
_ => { eprintln!("[Gupax error] Invalid option: [{}]\nFor help, use: [--help]", arg); exit(1); },
}
}
app
}
//---------------------------------------------------------------------------------------------------- [App] frame for [Panic] situations
struct Panic { error_msg: String, }
impl Panic {
@ -240,19 +300,19 @@ impl eframe::App for Panic {
//---------------------------------------------------------------------------------------------------- Main [App] frame
fn main() {
init_logger();
// let toml = match State::get() {
// Ok(toml) => toml,
// Err(err) => {
// error!("{}", err);
// let error_msg = err.to_string();
// let options = Panic::options();
// eframe::run_native("Gupax", options, Box::new(|cc| Box::new(Panic::new(cc, error_msg))),);
// exit(1);
// },
// };
let state = State::default();
let app = App::default();
let options = init_options();
eframe::run_native("Gupax", options, Box::new(|cc| Box::new(App::new(cc))),);
let toml = match State::get() {
Ok(toml) => toml,
Err(err) => {
error!("{}", err);
let error_msg = err.to_string();
let options = Panic::options();
eframe::run_native("Gupax", options, Box::new(|cc| Box::new(Panic::new(cc, error_msg))),);
exit(1);
},
};
eframe::run_native("Gupax", options, Box::new(|cc| Box::new(App::cc(cc, app))),);
}
impl eframe::App for App {
@ -262,49 +322,75 @@ impl eframe::App for App {
}
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
init_text_styles(ctx, 1280.0);
// init_text_styles(ctx, 1280.0);
// Close confirmation.
if self.quit {
egui::CentralPanel::default().show(ctx, |ui| {
init_text_styles(ctx, ui.available_width());
let width = ui.available_width();
let width = width - 10.0;
let height = ui.available_height();
init_text_styles(ctx, width);
ui.add_sized([width, height/2.0], Label::new("Are you sure you want to quit?"));
// Detect processes or update
if self.p2pool || self.xmrig {
ui.add_sized([width, height/6.0], Label::new("Are you sure you want to quit?"));
if self.p2pool { ui.add_sized([width, height/6.0], Label::new("P2Pool is online...!")); }
if self.xmrig { ui.add_sized([width, height/6.0], Label::new("XMRig is online...!")); }
// Else, just quit
} else {
if self.state.gupax.save_before_quit {
info!("Saving before quit...");
match self.state.save() {
Err(err) => { error!("{}", err); exit(1); },
_ => (),
};
}
info!("No processes or update in progress ... goodbye!");
exit(0);
}
egui::TopBottomPanel::bottom("quit").show(ctx, |ui| {
ui.group(|ui| {
if ui.add_sized([width, height/10.0], egui::Button::new("Yes")).clicked() {
if ui.add_sized([width, height/8.0], egui::Button::new("Yes")).clicked() {
if self.state.gupax.save_before_quit {
info!("Saving before quit...");
match self.state.save() {
Err(err) => { error!("{}", err); exit(1); },
_ => (),
};
}
info!("Quit confirmation = yes ... goodbye!");
self.quit_confirm = true;
frame.close();
} else if ui.add_sized([width, height/10.0], egui::Button::new("No")).clicked() {
exit(0);
} else if ui.add_sized([width, height/8.0], egui::Button::new("No")).clicked() {
self.quit = false;
}
});
});
});
return
}
//
// If ping was pressed, start thread
if self.ping {
self.ping = false;
self.ping_prog = Arc::new(Mutex::new(true));
self.pinging = Arc::new(Mutex::new(true));
let node_clone = Arc::clone(&self.node);
let prog_clone = Arc::clone(&self.ping_prog);
let pinging_clone = Arc::clone(&self.pinging);
thread::spawn(move|| {
let result = NodeStruct::ping();
*node_clone.lock().unwrap() = result.node;
*prog_clone.lock().unwrap() = false;
*node_clone.lock().unwrap() = result.nodes;
*pinging_clone.lock().unwrap() = false;
});
}
// if *self.ping_prog.lock().unwrap() {
// If ping-ING, display stats
// if *self.pinging.lock().unwrap() {
// egui::CentralPanel::default().show(ctx, |ui| {
// let width = ui.available_width();
// let width = width - 10.0;
// let height = ui.available_height();
// init_text_styles(ctx, width);
// ui.add_sized([width, height/2.0], Label::new(format!("In progress: {}", *self.ping_prog.lock().unwrap())));
// ui.add_sized([width, height/2.0], Label::new(format!("In progress: {}", *self.pinging.lock().unwrap())));
// ui.group(|ui| {
// if ui.add_sized([width, height/10.0], egui::Button::new("Yes")).clicked() {
// info!("Quit confirmation = yes ... goodbye!");
@ -324,8 +410,8 @@ impl eframe::App for App {
let width = (ui.available_width() - 90.0) / 5.0;
let height = ui.available_height() / 10.0;
ui.add_space(4.0);
ui.style_mut().override_text_style = Some(Name("Tab".into()));
ui.horizontal(|ui| {
ui.style_mut().override_text_style = Some(Name("Tab".into()));
ui.style_mut().visuals.widgets.inactive.fg_stroke.color = Color32::from_rgb(100, 100, 100);
ui.style_mut().visuals.selection.bg_fill = Color32::from_rgb(255, 120, 120);
ui.style_mut().visuals.selection.stroke = Stroke {
@ -346,7 +432,6 @@ impl eframe::App for App {
ui.separator();
// });
let height = height / 2.0;
// Bottom: app info + state/process buttons
egui::TopBottomPanel::bottom("bottom").show(ctx, |ui| {
@ -359,9 +444,6 @@ impl eframe::App for App {
ui.add_sized([width, height], Label::new(self.os));
ui.separator();
ui.add_sized([width/1.5, height], Label::new("P2Pool"));
// TODO
// self.p2pool + self.xmrig
// This is for process online/offline status
if self.p2pool == true {
ui.add_sized([width/4.0, height], Label::new(RichText::new("").color(Color32::from_rgb(100, 230, 100))));
} else {
@ -428,10 +510,6 @@ impl eframe::App for App {
});
});
// Central: tab contents
// Docs say to add central last, don't think it matters here but whatever:
// [https://docs.rs/egui/latest/egui/containers/panel/struct.TopBottomPanel.html]
// egui::TopBottomPanel::bottom("2").show(ctx, |ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Body);
match self.tab {
Tab::About => {
@ -451,8 +529,6 @@ impl eframe::App for App {
Xmrig::show(&mut self.state.xmrig, ctx, ui);
}
}
// });
});
}
}

View file

@ -41,6 +41,11 @@ pub const SUPPORTXMR: &'static str = "node.supportxmr.com:18081";
pub const SUPPORTXMR_IR: &'static str = "node.supportxmr.ir:18081";
pub const XMRVSBEAST: &'static str = "p2pmd.xmrvsbeast.com:18081";
pub const NODE_IPS: [&'static str; 12] = [
C3POOL,CAKE,CAKE_EU,CAKE_UK,CAKE_US,MONERUJO,RINO,
SELSTA,SETH,SUPPORTXMR,SUPPORTXMR_IR,XMRVSBEAST,
];
#[derive(Debug)]
pub struct NodeStruct {
c3pool: Data, cake: Data, cake_eu: Data, cake_uk: Data, cake_us: Data, monerujo: Data,
@ -52,7 +57,7 @@ pub struct Data {
pub ms: u128,
pub color: Color32,
pub id: NodeEnum,
pub ip: String,
pub ip: &'static str,
}
#[derive(Copy,Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
@ -69,39 +74,31 @@ impl std::fmt::Display for NodeEnum {
#[derive(Debug)]
pub struct PingResult {
pub node: NodeStruct,
pub nodes: NodeStruct,
pub fastest: NodeEnum,
}
use crate::NodeEnum::*;
impl NodeStruct {
pub fn default() -> Self {
use crate::NodeEnum::*;
let ms = 0;
let color = Color32::GRAY;
Self {
c3pool: Data { ms, color, id: C3pool, ip: C3POOL.to_string(), },
cake: Data { ms, color, id: Cake, ip: CAKE.to_string(), },
cake_eu: Data { ms, color, id: CakeEu, ip: CAKE_EU.to_string(), },
cake_uk: Data { ms, color, id: CakeUk, ip: CAKE_UK.to_string(), },
cake_us: Data { ms, color, id: CakeUs, ip: CAKE_US.to_string(), },
monerujo: Data { ms, color, id: Monerujo, ip: MONERUJO.to_string(), },
rino: Data { ms, color, id: Rino, ip: RINO.to_string(), },
selsta: Data { ms, color, id: Selsta, ip: SELSTA.to_string(), },
seth: Data { ms, color, id: Seth, ip: SETH.to_string(), },
supportxmr: Data { ms, color, id: SupportXmr, ip: SUPPORTXMR.to_string(), },
supportxmr_ir: Data { ms, color, id: SupportXmrIr, ip: SUPPORTXMR_IR.to_string(), },
xmrvsbeast: Data { ms, color, id: XmrVsBeast, ip: XMRVSBEAST.to_string(), },
c3pool: Data { ms, color, id: C3pool, ip: C3POOL, },
cake: Data { ms, color, id: Cake, ip: CAKE, },
cake_eu: Data { ms, color, id: CakeEu, ip: CAKE_EU, },
cake_uk: Data { ms, color, id: CakeUk, ip: CAKE_UK, },
cake_us: Data { ms, color, id: CakeUs, ip: CAKE_US, },
monerujo: Data { ms, color, id: Monerujo, ip: MONERUJO, },
rino: Data { ms, color, id: Rino, ip: RINO, },
selsta: Data { ms, color, id: Selsta, ip: SELSTA, },
seth: Data { ms, color, id: Seth, ip: SETH, },
supportxmr: Data { ms, color, id: SupportXmr, ip: SUPPORTXMR, },
supportxmr_ir: Data { ms, color, id: SupportXmrIr, ip: SUPPORTXMR_IR, },
xmrvsbeast: Data { ms, color, id: XmrVsBeast, ip: XMRVSBEAST, },
}
}
// Return array of all IPs
fn array() -> [&'static str; 12] {
[
C3POOL,CAKE,CAKE_EU,CAKE_UK,CAKE_US,MONERUJO,RINO,
SELSTA,SETH,SUPPORTXMR,SUPPORTXMR_IR,XMRVSBEAST,
]
}
// This is for pinging the community nodes to
// find the fastest/slowest one for the user.
// The process:
@ -123,9 +120,8 @@ impl NodeStruct {
// timeout = BLACK
// default = GRAY
pub fn ping() -> PingResult {
use crate::NodeEnum::*;
info!("Starting community node pings...");
let mut node = NodeStruct::default();
let mut nodes = NodeStruct::default();
let mut get_info = HashMap::new();
get_info.insert("jsonrpc", "2.0");
get_info.insert("id", "0");
@ -134,7 +130,7 @@ impl NodeStruct {
let fastest = false;
let timeout_sec = Duration::from_millis(5000);
for ip in Self::array().iter() {
for ip in NODE_IPS.iter() {
let id = match *ip {
C3POOL => C3pool,
CAKE => Cake,
@ -182,18 +178,18 @@ impl NodeStruct {
color = Color32::LIGHT_GREEN
}
match id {
C3pool => { node.c3pool.ms = ms; node.c3pool.color = color; },
Cake => { node.cake.ms = ms; node.cake.color = color; },
CakeEu => { node.cake_eu.ms = ms; node.cake_eu.color = color; },
CakeUk => { node.cake_uk.ms = ms; node.cake_uk.color = color; },
CakeUs => { node.cake_us.ms = ms; node.cake_us.color = color; },
Monerujo => { node.monerujo.ms = ms; node.monerujo.color = color; },
Rino => { node.rino.ms = ms; node.rino.color = color; },
Selsta => { node.selsta.ms = ms; node.selsta.color = color; },
Seth => { node.seth.ms = ms; node.seth.color = color; },
SupportXmr => { node.supportxmr.ms = ms; node.supportxmr.color = color; },
SupportXmrIr => { node.supportxmr_ir.ms = ms; node.supportxmr_ir.color = color; },
XmrVsBeast => { node.xmrvsbeast.ms = ms; node.xmrvsbeast.color = color; },
C3pool => { nodes.c3pool.ms = ms; nodes.c3pool.color = color; },
Cake => { nodes.cake.ms = ms; nodes.cake.color = color; },
CakeEu => { nodes.cake_eu.ms = ms; nodes.cake_eu.color = color; },
CakeUk => { nodes.cake_uk.ms = ms; nodes.cake_uk.color = color; },
CakeUs => { nodes.cake_us.ms = ms; nodes.cake_us.color = color; },
Monerujo => { nodes.monerujo.ms = ms; nodes.monerujo.color = color; },
Rino => { nodes.rino.ms = ms; nodes.rino.color = color; },
Selsta => { nodes.selsta.ms = ms; nodes.selsta.color = color; },
Seth => { nodes.seth.ms = ms; nodes.seth.color = color; },
SupportXmr => { nodes.supportxmr.ms = ms; nodes.supportxmr.color = color; },
SupportXmrIr => { nodes.supportxmr_ir.ms = ms; nodes.supportxmr_ir.color = color; },
XmrVsBeast => { nodes.xmrvsbeast.ms = ms; nodes.xmrvsbeast.color = color; },
}
}
let mut best_ms: u128 = vec[0].0;
@ -208,22 +204,7 @@ impl NodeStruct {
// The values don't update if not printed beforehand,
// so the match below on [fastest] gets funky.
info!("Fastest node ... {:#?} @ {:#?}ms", fastest, best_ms);
let ip = match fastest {
C3pool => C3POOL,
Cake => CAKE,
CakeEu => CAKE_EU,
CakeUk => CAKE_UK,
CakeUs => CAKE_US,
Monerujo => MONERUJO,
Rino => RINO,
Selsta => SELSTA,
Seth => SETH,
SupportXmr => SUPPORTXMR,
SupportXmrIr => SUPPORTXMR_IR,
XmrVsBeast => XMRVSBEAST,
};
info!("Using IP ... {}", ip);
info!("Community node ping ... OK");
PingResult { node, fastest, }
PingResult { nodes, fastest, }
}
}

View file

@ -33,6 +33,7 @@ use std::fmt::Display;
use std::path::{Path,PathBuf};
use std::result::Result;
use serde_derive::{Serialize,Deserialize};
use crate::constants::HORIZONTAL;
use log::*;
//---------------------------------------------------------------------------------------------------- Impl
@ -43,6 +44,7 @@ impl State {
gupax: Gupax {
auto_update: true,
ask_before_quit: true,
save_before_quit: true,
p2pool_path: DEFAULT_P2POOL_PATH.to_string(),
xmrig_path: DEFAULT_XMRIG_PATH.to_string(),
},
@ -107,14 +109,14 @@ impl State {
Ok(string)
},
Err(err) => {
error!("TOML not found, attempting to create default");
warn!("TOML not found, attempting to create default");
let default = match toml::ser::to_string(&State::default()) {
Ok(o) => { info!("TOML serialization ... OK"); o },
Err(e) => { error!("Couldn't serialize default TOML file: {}", e); return Err(TomlError::Serialize(e)) },
};
fs::write(&path, &default)?;
info!("TOML write ... OK");
Ok(fs::read_to_string(default)?)
Ok(default)
},
}
}
@ -124,7 +126,9 @@ impl State {
match toml::de::from_str(&string) {
Ok(toml) => {
info!("TOML parse ... OK");
eprint!("{}", string);
info!("{}", HORIZONTAL);
for i in string.lines() { info!("{}", i); }
info!("{}", HORIZONTAL);
Ok(toml)
},
Err(err) => { error!("Couldn't parse TOML from string"); Err(TomlError::Deserialize(err)) },
@ -133,22 +137,25 @@ impl State {
// Last three functions combined
// get_path() -> read_or_create() -> parse()
// pub fn get() -> Result<State, TomlError> {
// let path = Self::path();
// }
pub fn get() -> Result<State, TomlError> {
Self::parse(Self::read_or_create(Self::get_path()?)?)
}
// Overwrite disk Toml with memory State (save state)
pub fn overwrite(state: State, path: PathBuf) -> Result<(), TomlError> {
// Save [State] onto disk file [gupax.toml]
pub fn save(&self) -> Result<(), TomlError> {
let path = Self::get_path()?;
info!("Starting TOML overwrite...");
let string = match toml::ser::to_string(&state) {
let string = match toml::ser::to_string(&self) {
Ok(string) => {
info!("TOML parse ... OK");
eprint!("{}", string);
info!("{}", HORIZONTAL);
for i in string.lines() { info!("{}", i); }
info!("{}", HORIZONTAL);
string
},
Err(err) => { error!("Couldn't parse TOML into string"); return Err(TomlError::Serialize(err)) },
};
match fs::write(&path, string) {
match fs::write(path, string) {
Ok(_) => { info!("TOML overwrite ... OK"); Ok(()) },
Err(err) => { error!("Couldn't overwrite TOML file"); return Err(TomlError::Io(err)) },
}
@ -218,6 +225,7 @@ pub struct State {
pub struct Gupax {
pub auto_update: bool,
pub ask_before_quit: bool,
pub save_before_quit: bool,
pub p2pool_path: String,
pub xmrig_path: String,
}
@ -234,6 +242,8 @@ pub struct P2pool {
pub rpc: u16,
pub zmq: u16,
pub address: String,
// pub config: String,
// pub args: String,
}
#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
@ -248,6 +258,8 @@ pub struct Xmrig {
pub priority: u8,
pub pool: String,
pub address: String,
// pub config: String,
// pub args: String,
}
#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]