mirror of
https://code.gri.mw/GUI/grim.git
synced 2026-07-13 18:28:55 +00:00
node: handle statuses, added base ui
This commit is contained in:
@@ -56,3 +56,11 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_dual_panel_mode(frame: &mut Frame) -> bool {
|
||||
is_landscape(frame) && frame.info().window_info.size.x > 400.0
|
||||
}
|
||||
|
||||
pub fn is_landscape(frame: &mut Frame) -> bool {
|
||||
return frame.info().window_info.size.x > frame.info().window_info.size.y
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -27,9 +27,10 @@ pub const COLOR_LIGHT: egui::Color32 = egui::Color32::from_gray(240);
|
||||
pub const COLOR_DARK: egui::Color32 = egui::Color32::from_gray(60);
|
||||
|
||||
// Material icons chars
|
||||
pub const SYM_ARROW_BACK: &str = "";//"";
|
||||
pub const SYM_ADD: &str = "+";
|
||||
pub const SYM_ARROW_BACK: &str = "";
|
||||
pub const SYM_ACCOUNTS: &str = "";
|
||||
pub const SYM_NETWORK: &str = "";
|
||||
pub const SYM_SETTINGS: &str = "";//"";
|
||||
pub const SYM_SETTINGS: &str = "";
|
||||
pub const SYM_TUNING: &str = "";
|
||||
pub const SYM_METRICS: &str = "";
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::Context;
|
||||
use eframe::epaint::Stroke;
|
||||
use winit::platform::android::activity::AndroidApp;
|
||||
|
||||
use crate::gui::{App, PlatformApp};
|
||||
@@ -85,7 +85,10 @@ impl PlatformApp<Android> {
|
||||
}
|
||||
|
||||
fn setup_visuals(ctx: &egui::Context) {
|
||||
ctx.set_visuals(egui::Visuals::light());
|
||||
let mut visuals = egui::Visuals::light();
|
||||
// Disable stroke around panels by default
|
||||
visuals.widgets.noninteractive.bg_stroke = Stroke::NONE;
|
||||
ctx.set_visuals(visuals);
|
||||
}
|
||||
|
||||
fn setup_fonts(ctx: &egui::Context) {
|
||||
@@ -106,16 +109,6 @@ impl PlatformApp<Android> {
|
||||
// },
|
||||
// });
|
||||
|
||||
// fonts.font_data.insert(
|
||||
// "material".to_owned(),
|
||||
// egui::FontData::from_static(include_bytes!(
|
||||
// "../../../../fonts/material-light.ttf"
|
||||
// )).tweak(egui::FontTweak {
|
||||
// scale: 1.0,
|
||||
// y_offset_factor: 0.06,
|
||||
// y_offset: 0.0
|
||||
// }),
|
||||
// );
|
||||
fonts.font_data.insert(
|
||||
"material".to_owned(),
|
||||
egui::FontData::from_static(include_bytes!(
|
||||
@@ -160,7 +153,7 @@ impl PlatformApp<Android> {
|
||||
(Heading, FontId::new(20.0, Proportional)),
|
||||
(Name("icon".into()), FontId::new(24.0, Proportional)),
|
||||
(Body, FontId::new(16.0, Proportional)),
|
||||
(Button, FontId::new(20.0, Proportional)),
|
||||
(Button, FontId::new(18.0, Proportional)),
|
||||
(Small, FontId::new(12.0, Proportional)),
|
||||
(Monospace, FontId::new(16.0, Proportional)),
|
||||
].into();
|
||||
|
||||
@@ -32,11 +32,11 @@ impl super::Screen for Account {
|
||||
ScreenId::Account
|
||||
}
|
||||
|
||||
fn show(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut eframe::Frame,
|
||||
nav: &mut Navigator,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut eframe::Frame,
|
||||
nav: &mut Navigator,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
|
||||
}
|
||||
}
|
||||
+33
-30
@@ -13,13 +13,15 @@
|
||||
// limitations under the License.
|
||||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use egui::Widget;
|
||||
use eframe::epaint::{Color32, Stroke};
|
||||
|
||||
use egui::{Frame, Widget};
|
||||
|
||||
use crate::gui::{SYM_ARROW_BACK, SYM_NETWORK, SYM_SETTINGS};
|
||||
use crate::gui::app::is_dual_panel_mode;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::screens::{Navigator, Screen, ScreenId};
|
||||
use crate::gui::{SYM_ACCOUNTS, SYM_ARROW_BACK, SYM_NETWORK, SYM_SETTINGS};
|
||||
use crate::gui::screens::root::dual_panel_mode;
|
||||
use crate::gui::views::title_panel::{PanelAction, TitlePanel};
|
||||
use crate::gui::views::View;
|
||||
use crate::gui::views::{TitlePanel, TitlePanelAction};
|
||||
|
||||
pub struct Accounts {
|
||||
title: String,
|
||||
@@ -38,42 +40,43 @@ impl Screen for Accounts {
|
||||
ScreenId::Accounts
|
||||
}
|
||||
|
||||
fn show(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut eframe::Frame,
|
||||
nav: &mut Navigator,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut eframe::Frame,
|
||||
nav: &mut Navigator,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
let Self { title } = self;
|
||||
|
||||
let mut panel: TitlePanel = TitlePanel::default()
|
||||
.title(title)
|
||||
.right_action(PanelAction {
|
||||
.right_action(TitlePanelAction {
|
||||
icon: SYM_SETTINGS.into(),
|
||||
on_click: Box::new(on_settings_click),
|
||||
on_click: Box::new(|nav| {
|
||||
//TODO: open settings
|
||||
}),
|
||||
})
|
||||
.with_navigator(nav);
|
||||
if !dual_panel_mode(frame) {
|
||||
panel = panel.left_action(PanelAction {
|
||||
if !is_dual_panel_mode(frame) {
|
||||
panel = panel.left_action(TitlePanelAction {
|
||||
icon: SYM_NETWORK.into(),
|
||||
on_click: Box::new(on_network_click),
|
||||
on_click: Box::new(|nav|{
|
||||
nav.as_mut().unwrap().toggle_left_panel();
|
||||
}),
|
||||
});
|
||||
}
|
||||
panel.ui(ui);
|
||||
|
||||
ui.label(format!("{}Here we go 10000 ツ", SYM_ARROW_BACK));
|
||||
if ui.button("TEST").clicked() {
|
||||
nav.to(ScreenId::Account)
|
||||
};
|
||||
if ui.button(format!("{}BACK ", SYM_ARROW_BACK)).clicked() {
|
||||
nav.to(ScreenId::Account)
|
||||
};
|
||||
egui::CentralPanel::default().frame(Frame {
|
||||
stroke: Stroke::new(1.0, Color32::from_gray(190)),
|
||||
.. Default::default()
|
||||
}).show_inside(ui, |ui| {
|
||||
ui.label(format!("{}Here we go 10000 ツ", SYM_ARROW_BACK));
|
||||
if ui.button("TEST").clicked() {
|
||||
nav.to(ScreenId::Account)
|
||||
};
|
||||
if ui.button(format!("{}BACK ", SYM_ARROW_BACK)).clicked() {
|
||||
nav.to(ScreenId::Account)
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn on_network_click(nav: &mut Option<&mut Navigator>) {
|
||||
nav.as_mut().unwrap().toggle_left_panel();
|
||||
}
|
||||
|
||||
fn on_settings_click(nav: &mut Option<&mut Navigator>) {
|
||||
//TODO: Open settings
|
||||
}
|
||||
@@ -12,14 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub use account::Account;
|
||||
pub use accounts::Accounts;
|
||||
pub use navigator::Navigator;
|
||||
pub use root::Root;
|
||||
pub use accounts::Accounts;
|
||||
pub use account::Account;
|
||||
|
||||
use crate::gui::App;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::title_panel::PanelAction;
|
||||
use crate::gui::views::TitlePanelAction;
|
||||
|
||||
mod navigator;
|
||||
mod root;
|
||||
@@ -30,14 +30,14 @@ mod account;
|
||||
pub enum ScreenId {
|
||||
Root,
|
||||
Accounts,
|
||||
Account
|
||||
Account,
|
||||
}
|
||||
|
||||
pub trait Screen {
|
||||
fn id(&self) -> ScreenId;
|
||||
fn show(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut eframe::Frame,
|
||||
navigator: &mut Navigator,
|
||||
cb: &dyn PlatformCallbacks);
|
||||
fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut eframe::Frame,
|
||||
navigator: &mut Navigator,
|
||||
cb: &dyn PlatformCallbacks);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use std::collections::BTreeSet;
|
||||
use crate::gui::screens::ScreenId;
|
||||
|
||||
pub struct Navigator {
|
||||
pub(crate) stack: BTreeSet<ScreenId>,
|
||||
stack: BTreeSet<ScreenId>,
|
||||
pub(crate) left_panel_open: bool,
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ impl Default for Navigator {
|
||||
}
|
||||
|
||||
impl Navigator {
|
||||
pub fn current(&mut self) -> &ScreenId {
|
||||
self.stack.last().unwrap()
|
||||
}
|
||||
|
||||
pub fn to(&mut self, id: ScreenId) {
|
||||
self.stack.insert(id);
|
||||
}
|
||||
|
||||
+18
-27
@@ -13,17 +13,20 @@
|
||||
// limitations under the License.
|
||||
|
||||
use std::cmp::min;
|
||||
use eframe::epaint::{Shadow, Stroke};
|
||||
use eframe::Frame;
|
||||
|
||||
use eframe::epaint::{Color32, Shadow, Stroke};
|
||||
use egui::style::Margin;
|
||||
use egui::Ui;
|
||||
|
||||
use crate::gui::{App, COLOR_YELLOW};
|
||||
use crate::gui::app::is_dual_panel_mode;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::screens::{Account, Accounts, Navigator, Screen, ScreenId};
|
||||
use crate::gui::views::Network;
|
||||
|
||||
pub struct Root {
|
||||
navigator: Navigator,
|
||||
screens: Vec<Box<dyn Screen>>,
|
||||
network: Network
|
||||
}
|
||||
|
||||
impl Default for Root {
|
||||
@@ -34,6 +37,7 @@ impl Default for Root {
|
||||
Box::new(Accounts::default()),
|
||||
Box::new(Account::default())
|
||||
]),
|
||||
network: Network::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,32 +47,24 @@ impl Root {
|
||||
ScreenId::Root
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut Ui, frame: &mut Frame, cb: &dyn PlatformCallbacks) {
|
||||
let is_network_panel_open = self.navigator.left_panel_open || dual_panel_mode(frame);
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
|
||||
let is_network_panel_open = self.navigator.left_panel_open || is_dual_panel_mode(frame);
|
||||
|
||||
egui::SidePanel::left("network_panel")
|
||||
.resizable(false)
|
||||
.exact_width(if dual_panel_mode(frame) {
|
||||
.exact_width(if is_dual_panel_mode(frame) {
|
||||
min(frame.info().window_info.size.x as i64, 400) as f32
|
||||
} else {
|
||||
frame.info().window_info.size.x
|
||||
})
|
||||
.frame(egui::Frame {
|
||||
inner_margin: Margin::same(0.0),
|
||||
outer_margin: Margin::same(0.0),
|
||||
fill: COLOR_YELLOW,
|
||||
.. Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, is_network_panel_open, |ui| {
|
||||
//TODO: Network content
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.heading("🖧 Node");
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
self.network.ui(ui, frame, &mut self.navigator, cb);
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().frame(egui::containers::Frame {
|
||||
egui::CentralPanel::default().frame(egui::Frame {
|
||||
..Default::default()
|
||||
}).show_inside(ui, |ui| {
|
||||
self.show_current_screen(ui, frame, cb);
|
||||
@@ -76,22 +72,17 @@ impl Root {
|
||||
|
||||
}
|
||||
|
||||
pub fn show_current_screen(&mut self, ui: &mut Ui, frame: &mut Frame, cb: &dyn PlatformCallbacks) {
|
||||
pub fn show_current_screen(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut eframe::Frame,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
let Self { navigator, screens, .. } = self;
|
||||
let current = navigator.stack.last().unwrap();
|
||||
let current = navigator.current();
|
||||
for screen in screens.iter_mut() {
|
||||
if screen.id() == *current {
|
||||
screen.show(ui, frame, navigator, cb);
|
||||
screen.ui(ui, frame, navigator, cb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dual_panel_mode(frame: &mut Frame) -> bool {
|
||||
is_landscape(frame) && frame.info().window_info.size.x > 400.0
|
||||
}
|
||||
|
||||
pub fn is_landscape(frame: &mut Frame) -> bool {
|
||||
return frame.info().window_info.size.x > frame.info().window_info.size.y
|
||||
}
|
||||
+14
-4
@@ -13,8 +13,18 @@
|
||||
// limitations under the License.
|
||||
|
||||
pub mod buttons;
|
||||
pub mod title_panel;
|
||||
|
||||
pub trait View {
|
||||
fn ui(&mut self, ui: &mut egui::Ui);
|
||||
}
|
||||
mod title_panel;
|
||||
pub use crate::gui::views::title_panel::{TitlePanel, TitlePanelAction, TitlePanelActions};
|
||||
|
||||
mod network;
|
||||
pub use crate::gui::views::network::Network;
|
||||
|
||||
mod network_node;
|
||||
mod network_tuning;
|
||||
mod network_metrics;
|
||||
|
||||
pub trait NetworkTab {
|
||||
fn ui(&mut self, ui: &mut egui::Ui, node: &mut crate::node::Node);
|
||||
fn title(&self) -> &String;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
// 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 std::borrow::Cow;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::time::Duration;
|
||||
use eframe::epaint::{Color32, FontId, Stroke};
|
||||
use eframe::epaint::text::{LayoutJob, TextFormat, TextWrapping};
|
||||
use egui::{Response, RichText, Sense, Spinner, Widget};
|
||||
use egui::style::Margin;
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
use grin_chain::SyncStatus;
|
||||
|
||||
use grin_core::global::ChainTypes;
|
||||
use grin_servers::ServerStats;
|
||||
use crate::gui::app::is_dual_panel_mode;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::screens::Navigator;
|
||||
use crate::gui::{COLOR_DARK, COLOR_LIGHT, COLOR_YELLOW, SYM_ACCOUNTS, SYM_METRICS, SYM_NETWORK};
|
||||
|
||||
use crate::gui::views::{NetworkTab, TitlePanel, TitlePanelAction};
|
||||
use crate::gui::views::network_node::NetworkNode;
|
||||
use crate::node;
|
||||
use crate::node::Node;
|
||||
|
||||
enum Mode {
|
||||
Node,
|
||||
// Miner,
|
||||
Metrics,
|
||||
Tuning
|
||||
}
|
||||
|
||||
pub struct Network {
|
||||
current_mode: Mode,
|
||||
node: Node,
|
||||
node_view: NetworkNode,
|
||||
}
|
||||
|
||||
impl Default for Network {
|
||||
fn default() -> Self {
|
||||
let node = Node::new(ChainTypes::Mainnet, true);
|
||||
Self {
|
||||
node,
|
||||
current_mode: Mode::Node,
|
||||
node_view: NetworkNode::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Network {
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut eframe::Frame,
|
||||
nav: &mut Navigator,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
|
||||
egui::TopBottomPanel::top("network_title")
|
||||
.resizable(false)
|
||||
.frame(egui::Frame {
|
||||
fill: COLOR_YELLOW,
|
||||
inner_margin: Margin::same(0.0),
|
||||
outer_margin: Margin::same(0.0),
|
||||
stroke: Stroke::NONE,
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
self.draw_title(ui, frame, nav);
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().frame(egui::Frame {
|
||||
stroke: Stroke::new(1.0, Color32::from_gray(190)),
|
||||
fill: Color32::WHITE,
|
||||
.. Default::default()
|
||||
}).show_inside(ui, |ui| {
|
||||
self.draw_tab_content(ui);
|
||||
});
|
||||
|
||||
egui::TopBottomPanel::bottom("network_tabs")
|
||||
.frame(egui::Frame {
|
||||
stroke: Stroke::new(1.0, Color32::from_gray(190)),
|
||||
.. Default::default()
|
||||
})
|
||||
.resizable(false)
|
||||
.show_inside(ui, |ui| {
|
||||
self.draw_tabs(ui);
|
||||
});
|
||||
|
||||
ui.ctx().request_repaint_after(Duration::from_millis(500));
|
||||
}
|
||||
|
||||
fn draw_tabs(&self, ui: &mut egui::Ui) {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.columns(3, |columns| {
|
||||
columns[0].horizontal_wrapped(|ui| {
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
});
|
||||
columns[2].horizontal_wrapped(|ui| {
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn draw_tab_content(&mut self, ui: &mut egui::Ui) {
|
||||
match self.current_mode {
|
||||
Mode::Node => {
|
||||
self.node_view.ui(ui, &mut self.node);
|
||||
}
|
||||
Mode::Metrics => {}
|
||||
Mode::Tuning => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_title(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, nav: &mut Navigator) {
|
||||
// Disable stroke around title buttons on hover
|
||||
ui.style_mut().visuals.widgets.active.bg_stroke = Stroke::NONE;
|
||||
|
||||
StripBuilder::new(ui)
|
||||
.size(Size::exact(52.0))
|
||||
.vertical(|mut strip| {
|
||||
strip.strip(|builder| {
|
||||
builder
|
||||
.size(Size::exact(52.0))
|
||||
.size(Size::remainder())
|
||||
.size(Size::exact(52.0))
|
||||
.horizontal(|mut strip| {
|
||||
strip.empty();
|
||||
strip.strip(|builder| {
|
||||
self.draw_title_text(builder);
|
||||
});
|
||||
strip.cell(|ui| {
|
||||
if !is_dual_panel_mode(frame) {
|
||||
ui.centered_and_justified(|ui| {
|
||||
let b = egui::widgets::Button::new(
|
||||
RichText::new(SYM_ACCOUNTS)
|
||||
.size(24.0)
|
||||
.color(COLOR_DARK)
|
||||
).fill(Color32::TRANSPARENT)
|
||||
.ui(ui).interact(Sense::click_and_drag());
|
||||
if b.drag_released() || b.clicked() {
|
||||
nav.toggle_left_panel();
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn draw_title_text(&self, mut builder: StripBuilder) {
|
||||
let title_text = match &self.current_mode {
|
||||
Mode::Node => {
|
||||
self.node_view.title()
|
||||
}
|
||||
Mode::Metrics => {
|
||||
self.node_view.title()
|
||||
}
|
||||
Mode::Tuning => {
|
||||
self.node_view.title()
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let state = self.node.acquire_state();
|
||||
let syncing = state.stats.is_some() &&
|
||||
state.stats.as_ref().unwrap().sync_status != SyncStatus::NoSync;
|
||||
|
||||
let mut b = builder.size(Size::remainder());
|
||||
if syncing {
|
||||
b = b.size(Size::remainder());
|
||||
}
|
||||
b.vertical(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
ui.centered_and_justified(|ui| {
|
||||
ui.heading(title_text.to_uppercase());
|
||||
});
|
||||
});
|
||||
if syncing {
|
||||
let stats = state.stats.as_ref().unwrap();
|
||||
strip.cell(|ui| {
|
||||
ui.centered_and_justified(|ui| {
|
||||
let status_text = if state.is_stopping() {
|
||||
get_sync_status(SyncStatus::Shutdown).to_string()
|
||||
} else if state.is_restarting() {
|
||||
"Restarting".to_string()
|
||||
} else {
|
||||
get_sync_status(stats.sync_status).to_string()
|
||||
};
|
||||
let mut job = LayoutJob::single_section(status_text, TextFormat {
|
||||
font_id: FontId::proportional(15.0),
|
||||
color: COLOR_DARK,
|
||||
.. Default::default()
|
||||
});
|
||||
job.wrap = TextWrapping {
|
||||
max_rows: 1,
|
||||
break_anywhere: false,
|
||||
overflow_character: Option::from('…'),
|
||||
..Default::default()
|
||||
};
|
||||
ui.label(job);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sync_status(sync_status: SyncStatus) -> Cow<'static, str> {
|
||||
match sync_status {
|
||||
SyncStatus::Initial => Cow::Borrowed("Initializing"),
|
||||
SyncStatus::NoSync => Cow::Borrowed("Running"),
|
||||
SyncStatus::AwaitingPeers(_) => Cow::Borrowed("Waiting for peers"),
|
||||
SyncStatus::HeaderSync {
|
||||
sync_head,
|
||||
highest_height,
|
||||
..
|
||||
} => {
|
||||
if highest_height == 0 {
|
||||
Cow::Borrowed("Downloading headers data")
|
||||
} else {
|
||||
let percent = sync_head.height * 100 / highest_height;
|
||||
Cow::Owned(format!("Downloading headers: {}%", percent))
|
||||
}
|
||||
}
|
||||
SyncStatus::TxHashsetDownload(stat) => {
|
||||
Cow::Borrowed("Downloading chain state")
|
||||
}
|
||||
SyncStatus::TxHashsetSetup => {
|
||||
Cow::Borrowed("Preparing chain state for validation")
|
||||
}
|
||||
SyncStatus::TxHashsetRangeProofsValidation {
|
||||
rproofs,
|
||||
rproofs_total,
|
||||
} => {
|
||||
let r_percent = if rproofs_total > 0 {
|
||||
(rproofs * 100) / rproofs_total
|
||||
} else {
|
||||
0
|
||||
};
|
||||
Cow::Owned(format!("Validating state - range proofs: {}%", r_percent))
|
||||
}
|
||||
SyncStatus::TxHashsetKernelsValidation {
|
||||
kernels,
|
||||
kernels_total,
|
||||
} => {
|
||||
let k_percent = if kernels_total > 0 {
|
||||
(kernels * 100) / kernels_total
|
||||
} else {
|
||||
0
|
||||
};
|
||||
Cow::Owned(format!("Validating state - kernels: {}%", k_percent))
|
||||
}
|
||||
SyncStatus::TxHashsetSave => {
|
||||
Cow::Borrowed("Finalizing chain state")
|
||||
}
|
||||
SyncStatus::TxHashsetDone => {
|
||||
Cow::Borrowed("Finalized chain state")
|
||||
}
|
||||
SyncStatus::BodySync {
|
||||
current_height,
|
||||
highest_height,
|
||||
} => {
|
||||
if highest_height == 0 {
|
||||
Cow::Borrowed("Downloading blocks data")
|
||||
} else {
|
||||
Cow::Owned(format!(
|
||||
"Downloading blocks: {}%",
|
||||
current_height * 100 / highest_height
|
||||
))
|
||||
}
|
||||
}
|
||||
SyncStatus::Shutdown => Cow::Borrowed("Shutting down"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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.
|
||||
@@ -0,0 +1,76 @@
|
||||
// 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 std::borrow::Cow;
|
||||
use std::ptr::null;
|
||||
use std::sync::mpsc;
|
||||
|
||||
use chrono::Utc;
|
||||
use egui::{Ui, Widget};
|
||||
use grin_chain::SyncStatus;
|
||||
use grin_core::global::ChainTypes;
|
||||
use grin_servers::ServerStats;
|
||||
|
||||
use crate::gui::views::NetworkTab;
|
||||
use crate::node::Node;
|
||||
|
||||
|
||||
pub struct NetworkNode {
|
||||
title: String
|
||||
}
|
||||
|
||||
impl Default for NetworkNode {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
title: t!("node"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkTab for NetworkNode {
|
||||
fn ui(&mut self, ui: &mut Ui, node: &mut Node) {
|
||||
// ui.vertical_centered_justified(|ui| {
|
||||
// let node_state = node.acquire_state();
|
||||
// let stats = &node_state.stats;
|
||||
// if stats.is_some() {
|
||||
// ui.horizontal_wrapped(|ui| {
|
||||
// let sync_status = stats.as_ref().unwrap().sync_status;
|
||||
// ui.label(get_sync_progress_status(sync_status));
|
||||
// ui.spinner();
|
||||
// });
|
||||
// } else {
|
||||
// if node.stop_state.is_stopped() {
|
||||
// ui.label("Stopped");
|
||||
// } else {
|
||||
// ui.label(get_sync_progress_status(SyncStatus::Initial));
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
if ui.button("stop").clicked() {
|
||||
node.stop();
|
||||
}
|
||||
|
||||
if ui.button("re-start").clicked() {
|
||||
node.restart(ChainTypes::Mainnet);
|
||||
}
|
||||
|
||||
if ui.button("start").clicked() {
|
||||
node.start(ChainTypes::Mainnet);
|
||||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> &String {
|
||||
&self.title
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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.
|
||||
@@ -12,61 +12,55 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
use eframe::epaint::text::{LayoutJob, TextFormat, TextWrapping};
|
||||
use egui::{Color32, FontId, RichText, Sense, Stroke, Widget};
|
||||
use egui::style::Margin;
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
|
||||
use crate::gui::{COLOR_DARK, COLOR_YELLOW};
|
||||
use crate::gui::screens::Navigator;
|
||||
use crate::gui::views::View;
|
||||
|
||||
pub struct PanelAction {
|
||||
pub struct TitlePanelAction {
|
||||
pub(crate) icon: Box<str>,
|
||||
pub(crate) on_click: Box<dyn Fn(&mut Option<&mut Navigator>)>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PanelActions {
|
||||
left: Option<PanelAction>,
|
||||
right: Option<PanelAction>
|
||||
pub struct TitlePanelActions {
|
||||
left: Option<TitlePanelAction>,
|
||||
right: Option<TitlePanelAction>
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TitlePanel<'screen> {
|
||||
title: Option<&'screen String>,
|
||||
actions: PanelActions,
|
||||
navigator: Option<&'screen mut Navigator>
|
||||
pub struct TitlePanel<'nav> {
|
||||
title: Option<&'nav str>,
|
||||
actions: TitlePanelActions,
|
||||
navigator: Option<&'nav mut Navigator>
|
||||
}
|
||||
|
||||
impl<'screen> TitlePanel<'screen> {
|
||||
pub fn title(mut self, title: &'screen String) -> Self {
|
||||
impl<'nav> TitlePanel<'nav> {
|
||||
pub fn title(mut self, title: &'nav str) -> Self {
|
||||
self.title = Some(title);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn left_action(mut self, action: PanelAction) -> Self {
|
||||
pub fn left_action(mut self, action: TitlePanelAction) -> Self {
|
||||
self.actions.left = Some(action);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn right_action(mut self, action: PanelAction) -> Self {
|
||||
pub fn right_action(mut self, action: TitlePanelAction) -> Self {
|
||||
self.actions.right = Some(action);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_navigator(mut self, nav: &'screen mut Navigator) -> Self {
|
||||
pub fn with_navigator(mut self, nav: &'nav mut Navigator) -> Self {
|
||||
self.navigator = Some(nav);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl View for TitlePanel<'_> {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
// Disable stroke around panel
|
||||
ui.style_mut().visuals.widgets.noninteractive.bg_stroke = Stroke::NONE;
|
||||
|
||||
// Disable stroke around buttons on hover
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
// Disable stroke around panel buttons on hover
|
||||
ui.style_mut().visuals.widgets.active.bg_stroke = Stroke::NONE;
|
||||
|
||||
let Self { actions, title, navigator } = self;
|
||||
@@ -114,7 +108,7 @@ impl View for TitlePanel<'_> {
|
||||
strip.cell(|ui| {
|
||||
if title.is_some() {
|
||||
ui.centered_and_justified(|ui| {
|
||||
show_title(title.as_ref().unwrap(), ui);
|
||||
Self::show_title(title.unwrap(), ui);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -141,19 +135,20 @@ impl View for TitlePanel<'_> {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn show_title(title: &str, ui: &mut egui::Ui) {
|
||||
let mut job = LayoutJob::single_section(title.to_uppercase(), TextFormat {
|
||||
font_id: FontId::proportional(20.0),
|
||||
color: COLOR_DARK,
|
||||
.. Default::default()
|
||||
});
|
||||
job.wrap = TextWrapping {
|
||||
max_rows: 1,
|
||||
break_anywhere: false,
|
||||
overflow_character: Option::from('…'),
|
||||
..Default::default()
|
||||
};
|
||||
ui.label(job);
|
||||
}
|
||||
}
|
||||
|
||||
fn show_title(title: &String, ui: &mut egui::Ui) {
|
||||
let mut job = LayoutJob::single_section(title.to_uppercase(), TextFormat {
|
||||
font_id: FontId::proportional(20.0),
|
||||
color: COLOR_DARK,
|
||||
.. Default::default()
|
||||
});
|
||||
job.wrap = TextWrapping {
|
||||
max_rows: 1,
|
||||
break_anywhere: false,
|
||||
overflow_character: Option::from('…'),
|
||||
..Default::default()
|
||||
};
|
||||
ui.label(job);
|
||||
}
|
||||
+3
-2
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod node;
|
||||
mod node;
|
||||
|
||||
pub use self::node::start;
|
||||
pub use self::node::Node;
|
||||
pub use self::node::NodeState;
|
||||
+211
-43
@@ -12,63 +12,231 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::mpsc;
|
||||
use grin_config::{config, GlobalConfig};
|
||||
use std::{fs, thread};
|
||||
use std::fmt::format;
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, LockResult, mpsc, Mutex, MutexGuard};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::channel::oneshot;
|
||||
use grin_chain::SyncStatus;
|
||||
use grin_config::config;
|
||||
use grin_core::global;
|
||||
use grin_core::global::ChainTypes;
|
||||
use grin_servers::{Server, ServerStats};
|
||||
use grin_servers::common::types::Error;
|
||||
use grin_util::logger::LogEntry;
|
||||
use grin_util::StopState;
|
||||
use log::info;
|
||||
use futures::channel::oneshot;
|
||||
|
||||
pub fn start(chain_type: &ChainTypes) {
|
||||
let node_config = Some(
|
||||
config::initial_setup_server(&ChainTypes::Mainnet).unwrap_or_else(|e| {
|
||||
//TODO: Error handling
|
||||
panic!("Error loading server configuration: {}", e);
|
||||
}),
|
||||
);
|
||||
pub struct Node {
|
||||
/// Node state updated from the separate thread
|
||||
node_state: Arc<Mutex<NodeState>>,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
/// Instantiate new node with provided chain type, start server if needed
|
||||
pub fn new(chain_type: ChainTypes, start: bool) -> Self {
|
||||
let stop_state = Arc::new(StopState::new());
|
||||
let mut state = NodeState::new(chain_type, stop_state.clone());
|
||||
let node_state = Arc::new(Mutex::new(state));
|
||||
if start {
|
||||
let server = start_server(&chain_type, stop_state);
|
||||
start_server_thread(node_state.clone(), server);
|
||||
} else {
|
||||
stop_state.stop();
|
||||
}
|
||||
Self { node_state }
|
||||
}
|
||||
|
||||
/// Acquire node state to be used by a current thread
|
||||
pub fn acquire_state(&self) -> MutexGuard<'_, NodeState> {
|
||||
self.node_state.lock().unwrap()
|
||||
}
|
||||
|
||||
/// Stop server
|
||||
pub fn stop(&self) {
|
||||
self.acquire_state().stop_needed = true;
|
||||
}
|
||||
|
||||
/// Start server with provided chain type
|
||||
pub fn start(&self, chain_type: ChainTypes) {
|
||||
let mut state = self.node_state.lock().unwrap();
|
||||
if state.stop_state.is_stopped() {
|
||||
self.start_with_acquired_state(state, chain_type);
|
||||
}
|
||||
}
|
||||
|
||||
/// Restart server with provided chain type
|
||||
pub fn restart(&mut self, chain_type: ChainTypes) {
|
||||
let mut state = self.acquire_state();
|
||||
if !state.stop_state.is_stopped() {
|
||||
state.chain_type = chain_type;
|
||||
state.restart_needed = true;
|
||||
} else {
|
||||
self.start_with_acquired_state(state, chain_type);
|
||||
}
|
||||
}
|
||||
|
||||
/// Start server with provided acquired state
|
||||
fn start_with_acquired_state(&self, mut state: MutexGuard<NodeState>, chain_type: ChainTypes) {
|
||||
state.chain_type = chain_type;
|
||||
state.stop_state = Arc::new(StopState::new());
|
||||
|
||||
let server = start_server(&chain_type, state.stop_state.clone());
|
||||
start_server_thread(self.node_state.clone(), server);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NodeState {
|
||||
/// To check server state
|
||||
stop_state: Arc<StopState>,
|
||||
/// Data for UI, None means server is not started
|
||||
pub(crate) stats: Option<ServerStats>,
|
||||
/// Chain type of launched server
|
||||
chain_type: ChainTypes,
|
||||
/// Thread flag to stop the server and start it again
|
||||
restart_needed: bool,
|
||||
/// Thread flag to stop the server
|
||||
stop_needed: bool,
|
||||
}
|
||||
|
||||
impl NodeState {
|
||||
/// Instantiate new node state with provided chain type and server state
|
||||
pub fn new(chain_type: ChainTypes, stop_state: Arc<StopState>) -> Self {
|
||||
Self {
|
||||
stop_state,
|
||||
stats: None,
|
||||
chain_type,
|
||||
restart_needed: false,
|
||||
stop_needed: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if server is stopping at separate thread
|
||||
pub fn is_stopping(&self) -> bool {
|
||||
return self.stop_needed
|
||||
}
|
||||
|
||||
/// Check if server is restarting at separate thread
|
||||
pub fn is_restarting(&self) -> bool {
|
||||
return self.restart_needed
|
||||
}
|
||||
}
|
||||
|
||||
/// Start server with provided chain type and node state
|
||||
fn start_server(chain_type: &ChainTypes, stop_state: Arc<StopState>) -> Server {
|
||||
let mut node_config_result = config::initial_setup_server(chain_type);
|
||||
if node_config_result.is_err() {
|
||||
// Remove config file on init error
|
||||
let mut grin_path = dirs::home_dir().unwrap();
|
||||
grin_path.push(".grin");
|
||||
grin_path.push(chain_type.shortname());
|
||||
grin_path.push(config::SERVER_CONFIG_FILE_NAME);
|
||||
fs::remove_file(grin_path).unwrap();
|
||||
|
||||
// Reinit config
|
||||
node_config_result = config::initial_setup_server(chain_type);
|
||||
}
|
||||
|
||||
let node_config = node_config_result.ok();
|
||||
let config = node_config.clone().unwrap();
|
||||
let server_config = config.members.as_ref().unwrap().server.clone();
|
||||
|
||||
// Initialize our global chain_type, feature flags (NRD kernel support currently), accept_fee_base, and future_time_limit.
|
||||
let mut db_path = PathBuf::from(&server_config.db_root);
|
||||
db_path.push("grin.lock");
|
||||
fs::remove_file(db_path).unwrap();
|
||||
|
||||
// Initialize our global chain_type, feature flags (NRD kernel support currently),
|
||||
// accept_fee_base, and future_time_limit.
|
||||
// These are read via global and not read from config beyond this point.
|
||||
global::init_global_chain_type(config.members.as_ref().unwrap().server.chain_type);
|
||||
if !global::GLOBAL_CHAIN_TYPE.is_init() {
|
||||
global::init_global_chain_type(config.members.as_ref().unwrap().server.chain_type);
|
||||
}
|
||||
info!("Chain: {:?}", global::get_chain_type());
|
||||
match global::get_chain_type() {
|
||||
ChainTypes::Mainnet => {
|
||||
// Set various mainnet specific feature flags.
|
||||
global::init_global_nrd_enabled(false);
|
||||
}
|
||||
_ => {
|
||||
// Set various non-mainnet feature flags.
|
||||
global::init_global_nrd_enabled(true);
|
||||
|
||||
if !global::GLOBAL_NRD_FEATURE_ENABLED.is_init() {
|
||||
match global::get_chain_type() {
|
||||
ChainTypes::Mainnet => {
|
||||
// Set various mainnet specific feature flags.
|
||||
global::init_global_nrd_enabled(false);
|
||||
}
|
||||
_ => {
|
||||
// Set various non-mainnet feature flags.
|
||||
global::init_global_nrd_enabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
let afb = config
|
||||
.members
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.server
|
||||
.pool_config
|
||||
.accept_fee_base;
|
||||
global::init_global_accept_fee_base(afb);
|
||||
info!("Accept Fee Base: {:?}", global::get_accept_fee_base());
|
||||
global::init_global_future_time_limit(config.members.unwrap().server.future_time_limit);
|
||||
info!("Future Time Limit: {:?}", global::get_future_time_limit());
|
||||
if !global::GLOBAL_ACCEPT_FEE_BASE.is_init() {
|
||||
let afb = config
|
||||
.members
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.server
|
||||
.pool_config
|
||||
.accept_fee_base;
|
||||
global::init_global_accept_fee_base(afb);
|
||||
info!("Accept Fee Base: {:?}", global::get_accept_fee_base());
|
||||
}
|
||||
if !global::GLOBAL_FUTURE_TIME_LIMIT.is_init() {
|
||||
global::init_global_future_time_limit(config.members.unwrap().server.future_time_limit);
|
||||
info!("Future Time Limit: {:?}", global::get_future_time_limit());
|
||||
}
|
||||
|
||||
let api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>) =
|
||||
Box::leak(Box::new(oneshot::channel::<()>()));
|
||||
grin_servers::Server::start(
|
||||
server_config,
|
||||
None,
|
||||
|serv: grin_servers::Server, info: Option<mpsc::Receiver<LogEntry>>| {
|
||||
serv.get_server_stats();
|
||||
info!("Info callback")
|
||||
//serv.stop();
|
||||
},
|
||||
None,
|
||||
api_chan
|
||||
)
|
||||
.unwrap();
|
||||
let mut server_result = Server::new(server_config.clone(), Some(stop_state.clone()), api_chan);
|
||||
if server_result.is_err() {
|
||||
let mut db_path = PathBuf::from(&server_config.db_root);
|
||||
db_path.push("grin.lock");
|
||||
fs::remove_file(db_path).unwrap();
|
||||
|
||||
// Remove chain data on server start error
|
||||
let dirs_to_remove: Vec<&str> = vec!["header", "lmdb", "txhashset", "peer"];
|
||||
for dir in dirs_to_remove {
|
||||
let mut path = PathBuf::from(&server_config.db_root);
|
||||
path.push(dir);
|
||||
fs::remove_dir_all(path).unwrap();
|
||||
}
|
||||
|
||||
// Recreate server
|
||||
let config = node_config.clone().unwrap();
|
||||
let server_config = config.members.as_ref().unwrap().server.clone();
|
||||
let api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>) =
|
||||
Box::leak(Box::new(oneshot::channel::<()>()));
|
||||
server_result = Server::new(server_config.clone(), Some(stop_state.clone()), api_chan);
|
||||
}
|
||||
|
||||
server_result.unwrap()
|
||||
}
|
||||
|
||||
/// Start a thread to launch server and update node state with server stats
|
||||
fn start_server_thread(node_state: Arc<Mutex<NodeState>>, mut server: Server) -> JoinHandle<()> {
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
let mut state = node_state.lock().unwrap();
|
||||
if state.restart_needed {
|
||||
server.stop();
|
||||
|
||||
// Create new server with new stop state
|
||||
state.stop_state = Arc::new(StopState::new());
|
||||
server = start_server(&state.chain_type, state.stop_state.clone());
|
||||
|
||||
state.restart_needed = false;
|
||||
} else if state.stop_needed {
|
||||
server.stop();
|
||||
state.stats = None;
|
||||
state.stop_needed = false;
|
||||
break;
|
||||
}
|
||||
if !state.stop_state.is_stopped() {
|
||||
let stats = server.get_server_stats();
|
||||
if stats.is_ok() {
|
||||
state.stats = Some(stats.as_ref().ok().unwrap().clone());
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user