// Copyright 2023 The Grim Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use chrono::{DateTime, NaiveDateTime, Utc}; use egui::{RichText, Rounding, ScrollArea}; use grin_chain::SyncStatus; use grin_servers::WorkerStats; use crate::gui::Colors; use crate::gui::icons::{BARBELL, CLOCK_AFTERNOON, CPU, CUBE, FADERS, FOLDER_DASHED, FOLDER_SIMPLE_MINUS, FOLDER_SIMPLE_PLUS, HARD_DRIVES, PLUGS, PLUGS_CONNECTED, POLYGON}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{NetworkContent, View}; use crate::gui::views::network::setup::StratumSetup; use crate::gui::views::network::types::{NetworkTab, NetworkTabType}; use crate::node::{Node, NodeConfig}; /// Mining tab content. pub struct NetworkMining { /// Stratum server setup content. stratum_server_setup: StratumSetup, } impl Default for NetworkMining { fn default() -> Self { Self { stratum_server_setup: StratumSetup::default() } } } impl NetworkTab for NetworkMining { fn get_type(&self) -> NetworkTabType { NetworkTabType::Mining } fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) { let server_stats = Node::get_stats(); // Show message to enable node when it's not running. if !Node::is_running() { NetworkContent::disabled_node_ui(ui); return; } // Show loading spinner when node is stopping or stratum server is starting. if Node::is_stopping() || Node::is_stratum_starting() { ui.centered_and_justified(|ui| { View::big_loading_spinner(ui); }); return; } // Show message when mining is not available. if server_stats.is_none() || Node::is_restarting() || Node::get_sync_status().unwrap() != SyncStatus::NoSync { View::center_content(ui, 162.0, |ui| { View::big_loading_spinner(ui); ui.add_space(18.0); ui.label(RichText::new(t!("network_mining.loading")) .size(16.0) .color(Colors::INACTIVE_TEXT) ); }); return; } // Show stratum server setup when mining server is not running. let stratum_stats = Node::get_stratum_stats(); if !stratum_stats.is_running { ScrollArea::vertical() .id_source("stratum_setup_scroll") .auto_shrink([false; 2]) .show(ui, |ui| { self.stratum_server_setup.ui(ui, frame, cb); }); return; } // Show stratum mining server info. View::sub_title(ui, format!("{} {}", HARD_DRIVES, t!("network_mining.server"))); ui.columns(2, |columns| { columns[0].vertical_centered(|ui| { let (stratum_addr, stratum_port) = NodeConfig::get_stratum_address(); View::rounded_box(ui, format!("{}:{}", stratum_addr, stratum_port), t!("network_mining.address"), [true, false, true, false]); }); columns[1].vertical_centered(|ui| { //TODO: Stratum mining wallet listening address. Replace with local wallet name. let wallet_addr = NodeConfig::get_stratum_wallet_addr().replace("http://", ""); View::rounded_box(ui, wallet_addr, t!("network_mining.rewards_wallet"), [false, true, false, true]); }); }); ui.add_space(4.0); // Show network info. View::sub_title(ui, format!("{} {}", POLYGON, t!("network.self"))); ui.columns(3, |columns| { columns[0].vertical_centered(|ui| { let difficulty = if stratum_stats.network_difficulty > 0 { stratum_stats.network_difficulty.to_string() } else { "-".into() }; View::rounded_box(ui, difficulty, t!("network_node.difficulty"), [true, false, true, false]); }); columns[1].vertical_centered(|ui| { let block_height = if stratum_stats.block_height > 0 { stratum_stats.block_height.to_string() } else { "-".into() }; View::rounded_box(ui, block_height, t!("network_node.header"), [false, false, false, false]); }); columns[2].vertical_centered(|ui| { let hashrate = if stratum_stats.network_hashrate > 0.0 { format!("{:.*}", 2, stratum_stats.network_hashrate) } else { "-".into() }; View::rounded_box(ui, hashrate, t!("network_mining.hashrate", "bits" => stratum_stats.edge_bits), [false, true, false, true]); }); }); ui.add_space(4.0); // Show mining info. View::sub_title(ui, format!("{} {}", CPU, t!("network_mining.miners"))); ui.columns(2, |columns| { columns[0].vertical_centered(|ui| { View::rounded_box(ui, stratum_stats.num_workers.to_string(), t!("network_mining.devices"), [true, false, true, false]); }); columns[1].vertical_centered(|ui| { View::rounded_box(ui, stratum_stats.blocks_found.to_string(), t!("network_mining.blocks_found"), [false, true, false, true]); }); }); ui.add_space(4.0); // Show workers stats or info text when possible. let workers_size = stratum_stats.worker_stats.len(); if workers_size != 0 && stratum_stats.num_workers > 0 { ui.add_space(4.0); View::horizontal_line(ui, Colors::ITEM_STROKE); ui.add_space(4.0); ScrollArea::vertical() .auto_shrink([false; 2]) .id_source("stratum_workers_scroll") .show_rows( ui, WORKER_ITEM_HEIGHT, workers_size, |ui, row_range| { for index in row_range { // Add space before the first item. if index == 0 { ui.add_space(4.0); } let worker = stratum_stats.worker_stats.get(index).unwrap(); worker_item_ui(ui, worker, View::item_rounding(index, workers_size)); } }, ); } else if ui.available_height() > 142.0 { View::center_content(ui, 142.0, |ui| { ui.label(RichText::new(t!("network_mining.info", "settings" => FADERS)) .size(16.0) .color(Colors::INACTIVE_TEXT) ); }); } } } /// Height of Stratum server worker list item. const WORKER_ITEM_HEIGHT: f32 = 76.0; /// Draw worker statistics item. fn worker_item_ui(ui: &mut egui::Ui, ws: &WorkerStats, rounding: Rounding) { ui.horizontal_wrapped(|ui| { ui.vertical_centered_justified(|ui| { // Draw round background. let mut rect = ui.available_rect_before_wrap(); rect.set_height(WORKER_ITEM_HEIGHT); ui.painter().rect(rect, rounding, Colors::WHITE, View::ITEM_STROKE); ui.add_space(2.0); ui.horizontal(|ui| { ui.add_space(5.0); // Draw worker connection status. let (status_text, status_icon, status_color) = match ws.is_connected { true => (t!("network_mining.connected"), PLUGS_CONNECTED, Colors::BLACK), false => (t!("network_mining.disconnected"), PLUGS, Colors::INACTIVE_TEXT) }; let status_line_text = format!("{} {} {}", status_icon, ws.id, status_text); ui.heading(RichText::new(status_line_text) .color(status_color) .size(17.0)); ui.add_space(2.0); }); ui.horizontal(|ui| { ui.add_space(6.0); // Draw difficulty. let diff_text = format!("{} {}", BARBELL, ws.pow_difficulty); ui.heading(RichText::new(diff_text) .color(Colors::TITLE) .size(16.0)); ui.add_space(6.0); // Draw accepted shares. let accepted_text = format!("{} {}", FOLDER_SIMPLE_PLUS, ws.num_accepted); ui.heading(RichText::new(accepted_text) .color(Colors::GREEN) .size(16.0)); ui.add_space(6.0); // Draw rejected shares. let rejected_text = format!("{} {}", FOLDER_SIMPLE_MINUS, ws.num_rejected); ui.heading(RichText::new(rejected_text) .color(Colors::RED) .size(16.0)); ui.add_space(6.0); // Draw stale shares. let stale_text = format!("{} {}", FOLDER_DASHED, ws.num_stale); ui.heading(RichText::new(stale_text) .color(Colors::GRAY) .size(16.0)); ui.add_space(6.0); // Draw blocks found. let blocks_found_text = format!("{} {}", CUBE, ws.num_blocks_found); ui.heading(RichText::new(blocks_found_text) .color(Colors::TITLE) .size(16.0)); }); ui.horizontal(|ui| { ui.add_space(6.0); // Draw block time let seen = ws.last_seen.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(); let naive_datetime = NaiveDateTime::from_timestamp_opt(seen as i64, 0).unwrap(); let datetime: DateTime = DateTime::from_utc(naive_datetime, Utc); let date_text = format!("{} {}", CLOCK_AFTERNOON, datetime.format("%d/%m/%Y %H:%M:%S UTC")); ui.heading(RichText::new(date_text) .color(Colors::GRAY) .size(16.0)); }); }); }); }