feat: construct XvB tab

feat: add Hero checkbox ui and hovering help
feat: add private stats ui and thread
feat: debug error of API connection in console
This commit is contained in:
Louis-Marie Baer 2024-03-14 23:40:47 +01:00
parent f2d0c9a288
commit 0a9375130f
7 changed files with 234 additions and 105 deletions

View file

@ -161,7 +161,7 @@ path_xmr: {:#?}\n
} }
Tab::Xvb => { Tab::Xvb => {
debug!("App | Entering [XvB] Tab"); debug!("App | Entering [XvB] Tab");
crate::disk::state::Xvb::show(&mut self.state.xvb, self.size, &self.state.p2pool.address, ctx, ui, &self.xvb_api); crate::disk::state::Xvb::show(&mut self.state.xvb, self.size, &self.state.p2pool.address, ctx, ui, &self.xvb_api, lock!(self.xvb).is_alive());
} }
} }
}); });

View file

@ -361,7 +361,7 @@ fn xvb(
xvb_api: &Arc<Mutex<PubXvbApi>>, xvb_api: &Arc<Mutex<PubXvbApi>>,
) { ) {
// //
let api = lock!(xvb_api); let api = &lock!(xvb_api).stats_pub;
let enabled = xvb_alive; let enabled = xvb_alive;
ScrollArea::vertical().show(ui, |ui| { ScrollArea::vertical().show(ui, |ui| {
ui.group(|ui| { ui.group(|ui| {
@ -477,7 +477,6 @@ fn xvb(
)), )),
); );
} }
drop(api);
}); });
// by round // by round
}); });

View file

@ -1,11 +1,14 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use egui::TextStyle::Name; use egui::TextStyle::Name;
use egui::{Hyperlink, Image, Label, RichText, TextEdit, Vec2}; use egui::{vec2, Hyperlink, Image, Layout, RichText, TextEdit, Ui, Vec2};
use log::debug; use log::debug;
use crate::helper::xvb::PubXvbApi; use crate::helper::xvb::PubXvbApi;
use crate::utils::constants::{GREEN, LIGHT_GRAY, ORANGE, RED, XVB_HELP, XVB_TOKEN_LEN}; use crate::utils::constants::{
GREEN, LIGHT_GRAY, ORANGE, RED, XVB_DONATED_1H_FIELD, XVB_DONATED_24H_FIELD, XVB_FAILURE_FIELD,
XVB_HELP, XVB_HERO_SELECT, XVB_TOKEN_FIELD, XVB_TOKEN_LEN,
};
use crate::utils::macros::lock; use crate::utils::macros::lock;
use crate::utils::regex::Regexes; use crate::utils::regex::Regexes;
use crate::{ use crate::{
@ -22,12 +25,13 @@ impl crate::disk::state::Xvb {
_ctx: &egui::Context, _ctx: &egui::Context,
ui: &mut egui::Ui, ui: &mut egui::Ui,
api: &Arc<Mutex<PubXvbApi>>, api: &Arc<Mutex<PubXvbApi>>,
xvb_is_alive: bool,
) { ) {
ui.reset_style();
let website_height = size.y / 10.0; let website_height = size.y / 10.0;
// let width = size.x - SPACE; // let width = size.x - SPACE;
// let height = size.y - SPACE; // let height = size.y - SPACE;
let width = size.x; let width = size.x;
let text_edit = size.y / 25.0;
// logo and website link // logo and website link
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
ui.add_sized( ui.add_sized(
@ -61,43 +65,103 @@ impl crate::disk::state::Xvb {
}); });
// input token // input token
let len_token = format!("{}", self.token.len()); let len_token = format!("{}", self.token.len());
let text_check; let (text, color) = if self.token.is_empty() {
let color; (
if self.token.is_empty() { format!("{} [{}/{}] ", XVB_TOKEN_FIELD, len_token, XVB_TOKEN_LEN),
text_check = format!("[{}/{}] ", len_token, XVB_TOKEN_LEN); LIGHT_GRAY,
color = LIGHT_GRAY; )
} else if self.token.parse::<u32>().is_ok() && self.token.len() < XVB_TOKEN_LEN { } else if self.token.parse::<u32>().is_ok() && self.token.len() < XVB_TOKEN_LEN {
text_check = format!("[{}/{}] ", len_token, XVB_TOKEN_LEN); (
color = GREEN; format!("{} [{}/{}]", XVB_TOKEN_FIELD, len_token, XVB_TOKEN_LEN),
GREEN,
)
} else if self.token.parse::<u32>().is_ok() && self.token.len() == XVB_TOKEN_LEN { } else if self.token.parse::<u32>().is_ok() && self.token.len() == XVB_TOKEN_LEN {
text_check = "".to_string(); (format!("{}", XVB_TOKEN_FIELD), GREEN)
color = GREEN;
} else { } else {
text_check = format!("[{}/{}] ❌", len_token, XVB_TOKEN_LEN); (
color = RED; format!("{} [{}/{}] ❌", XVB_TOKEN_FIELD, len_token, XVB_TOKEN_LEN),
} RED,
)
};
// let width = width - SPACE;
// ui.spacing_mut().text_edit_width = (width) - (SPACE * 3.0);
ui.group(|ui| { ui.group(|ui| {
let width = width - SPACE;
ui.spacing_mut().text_edit_width = (width) - (SPACE * 3.0);
ui.label("Your Token:");
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.add_sized( // why does this group is not centered into the parent group ?
[width / 8.0, text_edit], ui.with_layout(Layout::left_to_right(egui::Align::Center), |ui| {
TextEdit::singleline(&mut self.token), ui.group(|ui| {
); ui.colored_label(color, text);
// ui.add_sized(
ui.add(Label::new(RichText::new(text_check).color(color))) // [width / 8.0, text_edit],
}); // Label::new(RichText::new(text).color(color)),
}) // );
.response ui.add(
.on_hover_text_at_pointer(XVB_HELP); TextEdit::singleline(&mut self.token)
.char_limit(XVB_TOKEN_LEN)
.desired_width(width / 8.0)
.vertical_align(egui::Align::Center),
);
})
.response
.on_hover_text_at_pointer(XVB_HELP);
// hero option
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.add_space(width / 24.0);
ui.checkbox(&mut self.hero, "Hero")
.on_hover_text(XVB_HERO_SELECT);
})
});
})
});
// need to warn the user if no address is set in p2pool tab // need to warn the user if no address is set in p2pool tab
if !Regexes::addr_ok(address) { if !Regexes::addr_ok(address) {
debug!("XvB Tab | Rendering warning text"); debug!("XvB Tab | Rendering warning text");
ui.label(RichText::new("You don't have any payout address set in the P2pool Tab !\nXvB process needs one to function properly.") ui.label(RichText::new("You don't have any payout address set in the P2pool Tab !\nXvB process needs one to function properly.")
.color(ORANGE)); .color(ORANGE));
} }
// hero option
// private stats // private stats
let priv_stats = &lock!(api).stats_priv;
ui.set_enabled(xvb_is_alive);
// ui.vertical_centered(|ui| {
ui.horizontal(|ui| {
// widget takes a third less space for two separator.
let width_stat =
(ui.available_width() / 3.0) - (12.0 + ui.style().spacing.item_spacing.x) / 3.0;
// 0.0 means minimum
let height_stat = 0.0;
let size_stat = vec2(width_stat, height_stat);
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(priv_stats.donor_1hr_avg.to_string());
})
.response
});
ui.separator();
ui.add_sized(size_stat, |ui: &mut Ui| {
ui.vertical_centered(|ui| {
ui.label(XVB_DONATED_24H_FIELD);
ui.label(priv_stats.donor_24hr_avg.to_string());
})
.response
});
})
.response
});
});
} }
} }

View file

@ -1,6 +1,7 @@
use anyhow::{bail, Result}; use anyhow::{Result};
use hyper::StatusCode;
use hyper_tls::HttpsConnector;
use super::*; use super::*;
use crate::{components::node::RemoteNode, disk::status::*}; use crate::{components::node::RemoteNode, disk::status::*};
@ -246,6 +247,7 @@ pub struct Xmrig {
#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize, Default)] #[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize, Default)]
pub struct Xvb { pub struct Xvb {
pub token: String, pub token: String,
pub hero: bool,
pub node: XvbNode, pub node: XvbNode,
} }
@ -328,36 +330,6 @@ impl Default for P2pool {
} }
} }
impl Xvb {
pub async fn is_token_exist(address: String, token: String) -> Result<()> {
let https = HttpsConnector::new();
let client = hyper::Client::builder().build(https);
if let Ok(request) = hyper::Request::builder()
.method("GET")
.uri(format!(
"{}/cgi-bin/p2pool_bonus_history_api.cgi?address={}&token={}",
XVB_URL, address, token
))
.body(hyper::Body::empty())
{
match client.request(request).await {
Ok(resp) => match resp.status() {
StatusCode::OK => Ok(()),
StatusCode::UNPROCESSABLE_ENTITY => {
bail!("the token is invalid for this xmr address.")
}
_ => bail!("The status of the response is not expected"),
},
Err(err) => {
bail!("error from response: {}", err)
}
}
} else {
bail!("request could not be build")
}
}
}
impl Xmrig { impl Xmrig {
fn with_threads(max_threads: usize, current_threads: usize) -> Self { fn with_threads(max_threads: usize, current_threads: usize) -> Self {
let xmrig = Self::default(); let xmrig = Self::default();

View file

@ -99,6 +99,7 @@ mod test {
[xvb] [xvb]
token = "" token = ""
hero = false
node = "Europe" node = "Europe"
[version] [version]
gupax = "v1.3.0" gupax = "v1.3.0"

View file

@ -1,5 +1,8 @@
use anyhow::{bail, Result};
use bytes::Bytes;
use derive_more::Display; use derive_more::Display;
use hyper::client::HttpConnector; use hyper::client::HttpConnector;
use hyper::StatusCode;
use hyper_tls::HttpsConnector; use hyper_tls::HttpsConnector;
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use serde::Deserialize; use serde::Deserialize;
@ -10,8 +13,8 @@ use std::{
time::Instant, time::Instant,
}; };
use crate::utils::constants::XVB_URL;
use crate::{ use crate::{
disk::state::Xvb,
helper::{ProcessSignal, ProcessState}, helper::{ProcessSignal, ProcessState},
utils::{ utils::{
constants::{HORI_CONSOLE, XVB_URL_PUBLIC_API}, constants::{HORI_CONSOLE, XVB_URL_PUBLIC_API},
@ -69,8 +72,8 @@ impl Helper {
let gui_api = Arc::clone(&lock!(helper).gui_api_xvb); let gui_api = Arc::clone(&lock!(helper).gui_api_xvb);
let pub_api = Arc::clone(&lock!(helper).pub_api_xvb); let pub_api = Arc::clone(&lock!(helper).pub_api_xvb);
let process = Arc::clone(&lock!(helper).xvb); let process = Arc::clone(&lock!(helper).xvb);
let state_xvb = state_xvb.clone(); let state_xvb_check = state_xvb.clone();
let state_p2pool = state_p2pool.clone(); let state_p2pool_check = state_p2pool.clone();
// 2. Set process state // 2. Set process state
debug!("XvB | Setting process state..."); debug!("XvB | Setting process state...");
@ -83,7 +86,7 @@ impl Helper {
// verify if token and address are existent on XvB server // verify if token and address are existent on XvB server
let rt = tokio::runtime::Runtime::new().unwrap(); let rt = tokio::runtime::Runtime::new().unwrap();
let resp: anyhow::Result<()> = rt.block_on(async move { let resp: anyhow::Result<()> = rt.block_on(async move {
Xvb::is_token_exist(state_p2pool.address, state_xvb.token).await?; XvbPrivStats::request_api(&state_p2pool_check.address, &state_xvb_check.token).await?;
Ok(()) Ok(())
}); });
match resp { match resp {
@ -105,9 +108,17 @@ impl Helper {
lock2!(helper, xvb).state = ProcessState::NotMining; lock2!(helper, xvb).state = ProcessState::NotMining;
} }
} }
let state_xvb_thread = state_xvb.clone();
let state_p2pool_thread = state_p2pool.clone();
thread::spawn(move || { thread::spawn(move || {
Self::spawn_xvb_watchdog(client, gui_api, pub_api, process); Self::spawn_xvb_watchdog(
client,
gui_api,
pub_api,
process,
&state_xvb_thread,
&state_p2pool_thread,
);
}); });
} }
#[tokio::main] #[tokio::main]
@ -116,6 +127,8 @@ impl Helper {
gui_api: Arc<Mutex<PubXvbApi>>, gui_api: Arc<Mutex<PubXvbApi>>,
pub_api: Arc<Mutex<PubXvbApi>>, pub_api: Arc<Mutex<PubXvbApi>>,
process: Arc<Mutex<Process>>, process: Arc<Mutex<Process>>,
state_xvb: &crate::disk::state::Xvb,
state_p2pool: &crate::disk::state::P2pool,
) { ) {
info!("XvB started"); info!("XvB started");
@ -142,14 +155,11 @@ impl Helper {
// if since is 0, send request because it's the first time. // if since is 0, send request because it's the first time.
let since = lock!(gui_api).tick; let since = lock!(gui_api).tick;
if since >= 60 || since == 0 { if since >= 60 || since == 0 {
// *lock!(pub_api) = PubXvbApi::new(); debug!("XvB Watchdog | Attempting HTTP public API request...");
// *lock!(gui_api) = PubXvbApi::new(); match XvbPubStats::request_api(client.clone()).await {
debug!("XvB Watchdog | Attempting HTTP API request...");
match PubXvbApi::request_xvb_public_api(client.clone(), XVB_URL_PUBLIC_API).await {
Ok(new_data) => { Ok(new_data) => {
debug!("XvB Watchdog | HTTP API request OK"); debug!("XvB Watchdog | HTTP API request OK");
*lock!(&pub_api) = new_data; lock!(&pub_api).stats_pub = new_data;
lock!(gui_api).tick += 0;
} }
Err(err) => { Err(err) => {
warn!( warn!(
@ -168,6 +178,45 @@ impl Helper {
break; break;
} }
} }
debug!("XvB Watchdog | Attempting HTTP private API request...");
match XvbPrivStats::request_api(&state_p2pool.address, &state_xvb.token).await {
Ok(b) => {
debug!("XvB Watchdog | HTTP API request OK");
let new_data = match serde_json::from_slice::<XvbPrivStats>(&b) {
Ok(data) => data,
Err(e) => {
warn!("XvB Watchdog | Data provided from private API is not deserializ-able.Error: {}", e);
// output the error to console
if let Err(e) = writeln!(
lock!(gui_api).output,
"XvB Watchdog | Data provided from private API is not deserializ-able.Error: {}", e
) {
error!("XvB Watchdog | GUI status write failed: {}", e);
}
break;
}
};
lock!(&pub_api).stats_priv = new_data;
}
Err(err) => {
warn!(
"XvB Watchdog | Could not send HTTP private API request to: {}\n:{}",
XVB_URL, err
);
// output the error to console
if let Err(e) = writeln!(
lock!(gui_api).output,
"Failure to retrieve private stats from {}",
XVB_URL
) {
error!("XvB Watchdog | GUI status write failed: {}", e);
}
lock!(process).state = ProcessState::Failed;
break;
}
}
lock!(gui_api).tick += 0;
} }
lock!(gui_api).tick += 1; lock!(gui_api).tick += 1;
@ -188,14 +237,17 @@ impl Helper {
} }
//---------------------------------------------------------------------------------------------------- Public XvB API //---------------------------------------------------------------------------------------------------- Public XvB API
use serde_this_or_that::as_u64; use serde_this_or_that::as_u64;
#[derive(Debug, Clone, Default, Deserialize)] #[derive(Debug, Clone, Default)]
pub struct PubXvbApi { pub struct PubXvbApi {
#[serde(skip)]
pub output: String, pub output: String,
#[serde(skip)]
pub uptime: HumanTime, pub uptime: HumanTime,
#[serde(skip)]
pub tick: u8, pub tick: u8,
pub stats_pub: XvbPubStats,
pub stats_priv: XvbPrivStats,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct XvbPubStats {
pub time_remain: u32, // remaining time of round in minutes pub time_remain: u32, // remaining time of round in minutes
pub bonus_hr: f64, pub bonus_hr: f64,
pub donate_hr: f64, // donated hr from all donors pub donate_hr: f64, // donated hr from all donors
@ -217,6 +269,62 @@ pub struct PubXvbApi {
pub reward_yearly: Vec<f64>, pub reward_yearly: Vec<f64>,
} }
impl XvbPubStats {
#[inline]
// Send an HTTP request to XvB's API, serialize it into [Self] and return it
async fn request_api(
client: hyper::Client<HttpsConnector<HttpConnector>>,
) -> std::result::Result<Self, anyhow::Error> {
let request = hyper::Request::builder()
.method("GET")
.uri(XVB_URL_PUBLIC_API)
.body(hyper::Body::empty())?;
let response =
tokio::time::timeout(std::time::Duration::from_secs(8), client.request(request))
.await?;
// let response = client.request(request).await;
let body = hyper::body::to_bytes(response?.body_mut()).await?;
Ok(serde_json::from_slice::<Self>(&body)?)
}
}
impl XvbPrivStats {
pub async fn request_api(address: &str, token: &str) -> Result<Bytes> {
let https = HttpsConnector::new();
let client = hyper::Client::builder().build(https);
if let Ok(request) = hyper::Request::builder()
.method("GET")
.uri(format!(
"{}/cgi-bin/p2pool_bonus_history_api.cgi?address={}&token={}",
XVB_URL, address, token
))
.body(hyper::Body::empty())
{
match client.request(request).await {
Ok(mut resp) => match resp.status() {
StatusCode::OK => Ok(hyper::body::to_bytes(resp.body_mut()).await?),
StatusCode::UNPROCESSABLE_ENTITY => {
bail!("the token is invalid for this xmr address.")
}
_ => bail!("The status of the response is not expected"),
},
Err(err) => {
bail!("error from response: {}", err)
}
}
} else {
bail!("request could not be build")
}
}
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct XvbPrivStats {
pub fails: u8,
pub donor_1hr_avg: f32,
pub donor_24hr_avg: f32,
}
#[derive(Debug, Clone, Default, Display, Deserialize)] #[derive(Debug, Clone, Default, Display, Deserialize)]
pub enum XvbRound { pub enum XvbRound {
#[default] #[default]
@ -257,24 +365,6 @@ impl PubXvbApi {
..pub_api.clone() ..pub_api.clone()
}; };
} }
#[inline]
// Send an HTTP request to XvB's API, serialize it into [Self] and return it
async fn request_xvb_public_api(
client: hyper::Client<HttpsConnector<HttpConnector>>,
api_uri: &str,
) -> std::result::Result<Self, anyhow::Error> {
let request = hyper::Request::builder()
.method("GET")
.uri(api_uri)
.body(hyper::Body::empty())?;
let response =
tokio::time::timeout(std::time::Duration::from_secs(8), client.request(request))
.await?;
// let response = client.request(request).await;
let body = hyper::body::to_bytes(response?.body_mut()).await?;
Ok(serde_json::from_slice::<Self>(&body)?)
}
} }
fn signal_interrupt( fn signal_interrupt(
@ -323,8 +413,7 @@ fn signal_interrupt(
mod test { mod test {
use std::thread; use std::thread;
use super::PubXvbApi; use super::XvbPubStats;
use crate::utils::constants::XVB_URL_PUBLIC_API;
use hyper::Client; use hyper::Client;
use hyper_tls::HttpsConnector; use hyper_tls::HttpsConnector;
@ -337,9 +426,7 @@ mod test {
dbg!(new_data); dbg!(new_data);
} }
#[tokio::main] #[tokio::main]
async fn corr(client: Client<HttpsConnector<hyper::client::HttpConnector>>) -> PubXvbApi { async fn corr(client: Client<HttpsConnector<hyper::client::HttpConnector>>) -> XvbPubStats {
PubXvbApi::request_xvb_public_api(client, XVB_URL_PUBLIC_API) XvbPubStats::request_api(client).await.unwrap()
.await
.unwrap()
} }
} }

View file

@ -411,6 +411,12 @@ pub const XVB_HELP: &str = "You need to register an account by clicking on the l
pub const XVB_URL: &str = "https://xmrvsbeast.com"; pub const XVB_URL: &str = "https://xmrvsbeast.com";
pub const XVB_URL_PUBLIC_API: &str = "https://xmrvsbeast.com/p2pool/stats"; pub const XVB_URL_PUBLIC_API: &str = "https://xmrvsbeast.com/p2pool/stats";
pub const XVB_TOKEN_LEN: usize = 9; pub const XVB_TOKEN_LEN: usize = 9;
pub const XVB_HERO_SELECT: &str =
"Donate all spared hashrate to the raffle, even if there is more than enough to be in the most highest round type possible";
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";
// CLI argument messages // CLI argument messages
pub const ARG_HELP: &str = r#"USAGE: ./gupax [--flag] pub const ARG_HELP: &str = r#"USAGE: ./gupax [--flag]