node: handle statuses, added base ui

This commit is contained in:
ardocrat
2023-05-04 20:09:26 +03:00
parent b24a204d59
commit ba5cd82f4b
19 changed files with 751 additions and 454 deletions
+8
View File
@@ -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
View File
@@ -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 = "";
+6 -13
View File
@@ -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();
+5 -5
View File
@@ -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
View File
@@ -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
}
+9 -9
View File
@@ -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);
}
+5 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+288
View File
@@ -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"),
}
}
+13
View File
@@ -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.
+76
View File
@@ -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
}
}
+13
View File
@@ -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.
+32 -37
View File
@@ -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
View File
@@ -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
View File
@@ -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());
}
}
})
}