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

View file

@ -1,11 +1,14 @@
use std::sync::{Arc, Mutex};
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 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::regex::Regexes;
use crate::{
@ -22,12 +25,13 @@ impl crate::disk::state::Xvb {
_ctx: &egui::Context,
ui: &mut egui::Ui,
api: &Arc<Mutex<PubXvbApi>>,
xvb_is_alive: bool,
) {
ui.reset_style();
let website_height = size.y / 10.0;
// let width = size.x - SPACE;
// let height = size.y - SPACE;
let width = size.x;
let text_edit = size.y / 25.0;
// logo and website link
ui.vertical_centered(|ui| {
ui.add_sized(
@ -61,43 +65,103 @@ impl crate::disk::state::Xvb {
});
// input token
let len_token = format!("{}", self.token.len());
let text_check;
let color;
if self.token.is_empty() {
text_check = format!("[{}/{}] ", len_token, XVB_TOKEN_LEN);
color = LIGHT_GRAY;
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 {
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 {
text_check = "".to_string();
color = GREEN;
(format!("{}", XVB_TOKEN_FIELD), GREEN)
} 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| {
let width = width - SPACE;
ui.spacing_mut().text_edit_width = (width) - (SPACE * 3.0);
ui.label("Your Token:");
ui.horizontal(|ui| {
ui.add_sized(
[width / 8.0, text_edit],
TextEdit::singleline(&mut self.token),
);
ui.add(Label::new(RichText::new(text_check).color(color)))
});
})
.response
.on_hover_text_at_pointer(XVB_HELP);
// why does this group is not centered into the parent group ?
ui.with_layout(Layout::left_to_right(egui::Align::Center), |ui| {
ui.group(|ui| {
ui.colored_label(color, text);
// ui.add_sized(
// [width / 8.0, text_edit],
// Label::new(RichText::new(text).color(color)),
// );
ui.add(
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
if !Regexes::addr_ok(address) {
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.")
.color(ORANGE));
}
// hero option
// 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 hyper::StatusCode;
use hyper_tls::HttpsConnector;
use anyhow::{Result};
use super::*;
use crate::{components::node::RemoteNode, disk::status::*};
@ -246,6 +247,7 @@ pub struct Xmrig {
#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize, Default)]
pub struct Xvb {
pub token: String,
pub hero: bool,
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 {
fn with_threads(max_threads: usize, current_threads: usize) -> Self {
let xmrig = Self::default();

View file

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

View file

@ -1,5 +1,8 @@
use anyhow::{bail, Result};
use bytes::Bytes;
use derive_more::Display;
use hyper::client::HttpConnector;
use hyper::StatusCode;
use hyper_tls::HttpsConnector;
use log::{debug, error, info, warn};
use serde::Deserialize;
@ -10,8 +13,8 @@ use std::{
time::Instant,
};
use crate::utils::constants::XVB_URL;
use crate::{
disk::state::Xvb,
helper::{ProcessSignal, ProcessState},
utils::{
constants::{HORI_CONSOLE, XVB_URL_PUBLIC_API},
@ -69,8 +72,8 @@ impl Helper {
let gui_api = Arc::clone(&lock!(helper).gui_api_xvb);
let pub_api = Arc::clone(&lock!(helper).pub_api_xvb);
let process = Arc::clone(&lock!(helper).xvb);
let state_xvb = state_xvb.clone();
let state_p2pool = state_p2pool.clone();
let state_xvb_check = state_xvb.clone();
let state_p2pool_check = state_p2pool.clone();
// 2. Set process state
debug!("XvB | Setting process state...");
@ -83,7 +86,7 @@ impl Helper {
// verify if token and address are existent on XvB server
let rt = tokio::runtime::Runtime::new().unwrap();
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(())
});
match resp {
@ -105,9 +108,17 @@ impl Helper {
lock2!(helper, xvb).state = ProcessState::NotMining;
}
}
let state_xvb_thread = state_xvb.clone();
let state_p2pool_thread = state_p2pool.clone();
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]
@ -116,6 +127,8 @@ impl Helper {
gui_api: Arc<Mutex<PubXvbApi>>,
pub_api: Arc<Mutex<PubXvbApi>>,
process: Arc<Mutex<Process>>,
state_xvb: &crate::disk::state::Xvb,
state_p2pool: &crate::disk::state::P2pool,
) {
info!("XvB started");
@ -142,14 +155,11 @@ impl Helper {
// if since is 0, send request because it's the first time.
let since = lock!(gui_api).tick;
if since >= 60 || since == 0 {
// *lock!(pub_api) = PubXvbApi::new();
// *lock!(gui_api) = PubXvbApi::new();
debug!("XvB Watchdog | Attempting HTTP API request...");
match PubXvbApi::request_xvb_public_api(client.clone(), XVB_URL_PUBLIC_API).await {
debug!("XvB Watchdog | Attempting HTTP public API request...");
match XvbPubStats::request_api(client.clone()).await {
Ok(new_data) => {
debug!("XvB Watchdog | HTTP API request OK");
*lock!(&pub_api) = new_data;
lock!(gui_api).tick += 0;
lock!(&pub_api).stats_pub = new_data;
}
Err(err) => {
warn!(
@ -168,6 +178,45 @@ impl Helper {
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;
@ -188,14 +237,17 @@ impl Helper {
}
//---------------------------------------------------------------------------------------------------- Public XvB API
use serde_this_or_that::as_u64;
#[derive(Debug, Clone, Default, Deserialize)]
#[derive(Debug, Clone, Default)]
pub struct PubXvbApi {
#[serde(skip)]
pub output: String,
#[serde(skip)]
pub uptime: HumanTime,
#[serde(skip)]
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 bonus_hr: f64,
pub donate_hr: f64, // donated hr from all donors
@ -217,6 +269,62 @@ pub struct PubXvbApi {
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)]
pub enum XvbRound {
#[default]
@ -257,24 +365,6 @@ impl PubXvbApi {
..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(
@ -323,8 +413,7 @@ fn signal_interrupt(
mod test {
use std::thread;
use super::PubXvbApi;
use crate::utils::constants::XVB_URL_PUBLIC_API;
use super::XvbPubStats;
use hyper::Client;
use hyper_tls::HttpsConnector;
@ -337,9 +426,7 @@ mod test {
dbg!(new_data);
}
#[tokio::main]
async fn corr(client: Client<HttpsConnector<hyper::client::HttpConnector>>) -> PubXvbApi {
PubXvbApi::request_xvb_public_api(client, XVB_URL_PUBLIC_API)
.await
.unwrap()
async fn corr(client: Client<HttpsConnector<hyper::client::HttpConnector>>) -> XvbPubStats {
XvbPubStats::request_api(client).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_PUBLIC_API: &str = "https://xmrvsbeast.com/p2pool/stats";
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
pub const ARG_HELP: &str = r#"USAGE: ./gupax [--flag]