From 6e6bb24e7d2364b6570a509a7a8a918aa51da606 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Sat, 1 Jul 2023 03:29:05 +0300 Subject: [PATCH] config + ui: fix network change, optimize api secrets, add api secrets and ftl ui, refactor settings ui, fix exit modal message font size, update translations --- locales/en.yml | 16 +- locales/ru.yml | 14 +- src/gui/navigator.rs | 6 +- src/gui/screens/root.rs | 9 +- src/gui/views/network.rs | 114 ++------- src/gui/views/network_mining.rs | 9 +- src/gui/views/network_node.rs | 16 +- src/gui/views/network_settings.rs | 131 +++++++++- src/gui/views/settings_node.rs | 413 +++++++++++++++++++++++++----- src/gui/views/settings_stratum.rs | 71 +++-- src/node/config.rs | 149 +++++++---- src/settings.rs | 47 +++- 12 files changed, 715 insertions(+), 280 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 4b1ebbe..753ac07 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -70,23 +70,31 @@ network_mining: connected: Connected disconnected: Disconnected network_settings: - port: Port ip: IP Address + port: Port change_port: Change port - enter_value: Enter value + change_value: Change value + stratum_port: Stratum server port port_unavailable: Specified port is unavailable restart_app_required: App restart is required to apply changes. - restart_node_required: Node server restart is required to apply changes. + restart_node_required: Node restart is required to apply changes. enable: Enable disable: Disable restart: Restart server: Server api_ip: API IP Address api_port: API Port + api_secret: Rest API and V2 Owner API token + foreign_api_secret: Foreign API token + disabled: Disabled + copy: Copy + paste: Paste + ftl: The Future Time Limit (FTL) + ftl_description: Limit on how far into the future, relative to a node's local time in seconds, the timestamp on a new block can be, in order for the block to be accepted. + not_valid_value: Entered value is not valid modal: cancel: Cancel save: Save - confirmation: Confirmation modal_exit: description: Are you sure you want to quit the application? exit: Exit \ No newline at end of file diff --git a/locales/ru.yml b/locales/ru.yml index 4662c91..d25661e 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -65,7 +65,7 @@ network_mining: address: Адрес miners: Майнеры devices: Устройства - found: Найдено + blocks_found: Найдено блоков hashrate: 'Хешрэйт (C%{bits})' connected: Подключен disconnected: Отключен @@ -73,7 +73,8 @@ network_settings: ip: IP Адрес port: Порт change_port: Изменить порт - enter_value: Введите значение + change_value: Изменить значение + stratum_port: Порт Stratum сервера port_unavailable: Указанный порт недоступен restart_app_required: Для применения изменений требуется перезапуск приложения. restart_node_required: Для применения изменений требуется перезапуск узла. @@ -83,10 +84,17 @@ network_settings: server: Сервер api_ip: API IP Адрес api_port: API Порт + api_secret: Rest и V2 Owner API токен + foreign_api_secret: Foreign API токен + disabled: Отключен + copy: Копировать + paste: Вставить + ftl: Предел Будущего Времени (FTL) + ftl_description: Ограничение на то, насколько далеко в будущем, относительно локального времени узла в секундах, находится временная метка на новом блоке для его принятия. + not_valid_value: Введено недопустимое значение modal: cancel: Отмена save: Сохранить - confirmation: Подтверждение modal_exit: description: Вы уверены, что хотите выйти из приложения? exit: Выход \ No newline at end of file diff --git a/src/gui/navigator.rs b/src/gui/navigator.rs index b0b0689..06d6ef1 100644 --- a/src/gui/navigator.rs +++ b/src/gui/navigator.rs @@ -26,7 +26,7 @@ lazy_static! { static ref NAVIGATOR_STATE: RwLock = RwLock::new(Navigator::default()); } -/// Logic of navigation at ui, stores screen identifiers stack, showing modal and side panel state. +/// Logic of common navigation at ui for screens and modals. pub struct Navigator { /// Screen identifiers in navigation stack. screen_stack: BTreeSet, @@ -81,7 +81,7 @@ impl Navigator { return; } - // Go back at screen stack or set exit confirmation Modal. + // Go back at screen stack or show exit confirmation Modal. if w_nav.screen_stack.len() > 1 { w_nav.screen_stack.pop_last(); } else { @@ -97,7 +97,7 @@ impl Navigator { /// Set exit confirmation [`Modal`] with provided [NAVIGATOR_STATE] lock. fn show_exit_modal_nav(mut w_nav: RwLockWriteGuard) { - let m = Modal::new(Self::EXIT_MODAL).title(t!("modal.confirmation")); + let m = Modal::new(Self::EXIT_MODAL).title(t!("modal_exit.exit")); w_nav.modal = Some(m); } diff --git a/src/gui/screens/root.rs b/src/gui/screens/root.rs index 5a4d045..918cdbe 100644 --- a/src/gui/screens/root.rs +++ b/src/gui/screens/root.rs @@ -13,6 +13,7 @@ // limitations under the License. use std::cmp::min; +use egui::RichText; use crate::gui::{App, Colors, Navigator}; use crate::gui::platform::PlatformCallbacks; use crate::gui::screens::{Account, Accounts, Screen, ScreenId}; @@ -88,13 +89,17 @@ impl Root { ui.vertical_centered(|ui| { View::small_loading_spinner(ui); ui.add_space(12.0); - ui.label(t!("sync_status.shutdown")); + ui.label(RichText::new(t!("sync_status.shutdown")) + .size(18.0) + .color(Colors::TEXT)); }); ui.add_space(10.0); } else { ui.add_space(8.0); ui.vertical_centered(|ui| { - ui.label(t!("modal_exit.description")); + ui.label(RichText::new(t!("modal_exit.description")) + .size(18.0) + .color(Colors::TEXT)); }); ui.add_space(10.0); diff --git a/src/gui/views/network.rs b/src/gui/views/network.rs index 7005907..044a593 100644 --- a/src/gui/views/network.rs +++ b/src/gui/views/network.rs @@ -12,14 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::net::{IpAddr, Ipv4Addr, SocketAddrV4, TcpListener}; -use std::str::FromStr; use std::time::Duration; use egui::{Color32, lerp, Rgba, RichText}; use egui::style::Margin; use egui_extras::{Size, StripBuilder}; use grin_chain::SyncStatus; +use crate::AppConfig; use crate::gui::{Colors, Navigator}; use crate::gui::icons::{CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE}; @@ -32,7 +31,6 @@ use crate::gui::views::network_settings::NetworkSettings; use crate::gui::views::settings_node::NodeSetup; use crate::gui::views::settings_stratum::StratumServerSetup; use crate::node::Node; -use crate::Settings; pub trait NetworkTab { fn get_type(&self) -> NetworkTabType; @@ -69,8 +67,12 @@ impl Default for Network { Self { current_tab: Box::new(NetworkNode::default()), modal_ids: vec![ + NetworkSettings::NODE_RESTART_REQUIRED_MODAL, StratumServerSetup::STRATUM_PORT_MODAL, - NodeSetup::API_PORT_MODAL + NodeSetup::API_PORT_MODAL, + NodeSetup::API_SECRET_MODAL, + NodeSetup::FOREIGN_API_SECRET_MODAL, + NodeSetup::FTL_MODAL ] } } @@ -101,7 +103,7 @@ impl Network { ..Default::default() }) .show_inside(ui, |ui| { - self.draw_title(ui, frame); + self.title_ui(ui, frame); }); egui::TopBottomPanel::bottom("network_tabs") @@ -110,7 +112,7 @@ impl Network { ..Default::default() }) .show_inside(ui, |ui| { - self.draw_tabs(ui); + self.tabs_ui(ui); }); egui::CentralPanel::default() @@ -125,7 +127,8 @@ impl Network { }); } - fn draw_tabs(&mut self, ui: &mut egui::Ui) { + /// Draw tab buttons in the bottom of the screen. + fn tabs_ui(&mut self, ui: &mut egui::Ui) { ui.scope(|ui| { // Setup spacing between tabs. ui.style_mut().spacing.item_spacing = egui::vec2(5.0, 0.0); @@ -157,11 +160,13 @@ impl Network { }); } + /// Check if current tab equals providing [`NetworkTabType`]. fn is_current_tab(&self, tab_type: NetworkTabType) -> bool { self.current_tab.get_type() == tab_type } - fn draw_title(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { + /// Draw title content. + fn title_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { StripBuilder::new(ui) .size(Size::exact(52.0)) .vertical(|mut strip| { @@ -179,7 +184,7 @@ impl Network { }); }); strip.strip(|builder| { - self.draw_title_text(builder); + self.title_text_ui(builder); }); strip.cell(|ui| { if !View::is_dual_panel_mode(frame) { @@ -195,7 +200,8 @@ impl Network { }); } - fn draw_title_text(&self, builder: StripBuilder) { + /// Draw title text. + fn title_text_ui(&self, builder: StripBuilder) { builder .size(Size::remainder()) .size(Size::exact(32.0)) @@ -214,8 +220,8 @@ impl Network { // Setup text color animation based on sync status let idle = match sync_status { - None => { !Node::is_starting() } - Some(ss) => { ss == SyncStatus::NoSync } + None => !Node::is_starting(), + Some(ss) => ss == SyncStatus::NoSync }; let (dark, bright) = (0.3, 1.0); let color_factor = if !idle { @@ -253,89 +259,15 @@ impl Network { Node::start(); }); ui.add_space(2.0); - Self::autorun_node_checkbox(ui); + Self::autorun_node_ui(ui); }); } /// Draw checkbox with setting to run node on app launch. - pub fn autorun_node_checkbox(ui: &mut egui::Ui) { - let autostart: bool = Settings::app_config_to_read().auto_start_node; + pub fn autorun_node_ui(ui: &mut egui::Ui) { + let autostart = AppConfig::autostart_node(); View::checkbox(ui, autostart, t!("network.autorun"), || { - let mut w_app_config = Settings::app_config_to_update(); - w_app_config.auto_start_node = !autostart; - w_app_config.save(); + AppConfig::toggle_node_autostart(); }); } - - /// List of available IP addresses. - pub fn get_ip_list() -> Vec { - let mut addresses = Vec::new(); - for net_if in pnet::datalink::interfaces() { - for ip in net_if.ips { - if ip.is_ipv4() { - addresses.push(ip.ip()); - } - } - } - addresses - } - - /// Draw IP list radio buttons. - pub fn ip_list_ui(ui: &mut egui::Ui, - saved_ip: &String, - ips: &Vec, - on_change: impl FnOnce(&String)) { - let saved_ip_addr = &IpAddr::from_str(saved_ip.as_str()).unwrap(); - let mut selected_ip_addr = saved_ip_addr; - - // Set first IP address as current if saved is not present at system. - if !ips.contains(selected_ip_addr) { - selected_ip_addr = ips.get(0).unwrap(); - } - - // Show available IP addresses on the system. - let _ = ips.chunks(2).map(|x| { - if x.len() == 2 { - ui.columns(2, |columns| { - let ip_addr_l = x.get(0).unwrap(); - columns[0].vertical_centered(|ui| { - View::radio_value(ui, - &mut selected_ip_addr, - ip_addr_l, - ip_addr_l.to_string()); - }); - let ip_addr_r = x.get(1).unwrap(); - columns[1].vertical_centered(|ui| { - View::radio_value(ui, - &mut selected_ip_addr, - ip_addr_r, - ip_addr_r.to_string()); - }) - }); - } else { - let ip_addr = x.get(0).unwrap(); - View::radio_value(ui, - &mut selected_ip_addr, - ip_addr, - ip_addr.to_string()); - } - ui.add_space(12.0); - }).collect::>(); - - if saved_ip_addr != selected_ip_addr { - (on_change)(&selected_ip_addr.to_string()); - } - } - - /// Show message when IP addresses are not available at system. - pub fn no_ip_address_ui(ui: &mut egui::Ui) { - ui.vertical_centered(|ui| { - ui.label(RichText::new(t!("network.no_ips")) - .size(16.0) - .color(Colors::INACTIVE_TEXT) - ); - ui.add_space(6.0); - }); - } -} - +} \ No newline at end of file diff --git a/src/gui/views/network_mining.rs b/src/gui/views/network_mining.rs index 569dc13..513da02 100644 --- a/src/gui/views/network_mining.rs +++ b/src/gui/views/network_mining.rs @@ -23,7 +23,6 @@ use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{Modal, Network, NetworkTab, NetworkTabType, View}; use crate::gui::views::settings_stratum::StratumServerSetup; use crate::node::{Node, NodeConfig}; -use crate::Settings; #[derive(Default)] pub struct NetworkMining { @@ -38,7 +37,7 @@ impl NetworkTab for NetworkMining { fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { let server_stats = Node::get_stats(); - // Show message when node is not running or loading spinner when mining are not available. + // Show message when node is not running or loading spinner when mining is not available. if !server_stats.is_some() || Node::get_sync_status().unwrap() != SyncStatus::NoSync { if !Node::is_running() { Network::disabled_node_ui(ui); @@ -105,7 +104,7 @@ impl NetworkTab for NetworkMining { View::sub_title(ui, format!("{} {}", COMPUTER_TOWER, t!("network_mining.server"))); ui.columns(2, |columns| { columns[0].vertical_centered(|ui| { - let (stratum_addr, stratum_port) = NodeConfig::get_stratum_address_port(); + let (stratum_addr, stratum_port) = NodeConfig::get_stratum_address(); View::rounded_box(ui, format!("{}:{}", stratum_addr, stratum_port), t!("network_mining.address"), @@ -256,8 +255,8 @@ fn draw_workers_stats(ui: &mut egui::Ui, ws: &WorkerStats, rounding: [bool; 2]) ui.add_space(2.0); ui.horizontal_top(|ui| { 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) } + true => (t!("network_mining.connected"), PLUGS_CONNECTED, Colors::BLACK), + false => (t!("network_mining.disconnected"), PLUGS, Colors::INACTIVE_TEXT) }; ui.add_space(5.0); ui.heading(RichText::new(status_icon) diff --git a/src/gui/views/network_node.rs b/src/gui/views/network_node.rs index 67f8488..496a904 100644 --- a/src/gui/views/network_node.rs +++ b/src/gui/views/network_node.rs @@ -33,7 +33,7 @@ impl NetworkTab for NetworkNode { fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { let server_stats = Node::get_stats(); // Show message when node is not running or loading spinner when stats are not available. - if !server_stats.is_some() { + if !server_stats.is_some() || Node::is_restarting() { if !Node::is_running() { Network::disabled_node_ui(ui); } else { @@ -121,10 +121,8 @@ impl NetworkTab for NetworkNode { ui.columns(2, |columns| { columns[0].vertical_centered(|ui| { let tx_stat = match &stats.tx_stats { - None => { "0 (0)".to_string() } - Some(tx) => { - format!("{} ({})", tx.tx_pool_size, tx.tx_pool_kernels) - } + None => "0 (0)".to_string(), + Some(tx) => format!("{} ({})", tx.tx_pool_size, tx.tx_pool_kernels) }; View::rounded_box(ui, tx_stat, @@ -133,10 +131,10 @@ impl NetworkTab for NetworkNode { }); columns[1].vertical_centered(|ui| { let stem_tx_stat = match &stats.tx_stats { - None => { "0 (0)".to_string() } - Some(stx) => { - format!("{} ({})", stx.stem_pool_size, stx.stem_pool_kernels) - } + None => "0 (0)".to_string(), + Some(stx) => format!("{} ({})", + stx.stem_pool_size, + stx.stem_pool_kernels) }; View::rounded_box(ui, stem_tx_stat, diff --git a/src/gui/views/network_settings.rs b/src/gui/views/network_settings.rs index 14c9dc7..e179dd4 100644 --- a/src/gui/views/network_settings.rs +++ b/src/gui/views/network_settings.rs @@ -12,10 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -use egui::ScrollArea; +use std::net::IpAddr; +use std::str::FromStr; + +use egui::{RichText, ScrollArea}; + +use crate::gui::Colors; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, NetworkTab, NetworkTabType}; +use crate::gui::views::{Modal, NetworkTab, NetworkTabType, View}; use crate::gui::views::settings_node::NodeSetup; +use crate::node::Node; #[derive(Default)] pub struct NetworkSettings { @@ -38,10 +44,129 @@ impl NetworkTab for NetworkSettings { fn on_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { match modal.id { + Self::NODE_RESTART_REQUIRED_MODAL => { + self.node_restart_required_modal(ui, modal); + } NodeSetup::API_PORT_MODAL => { - self.node_setup.api_port_modal_ui(ui, modal, cb); + self.node_setup.api_port_modal(ui, modal, cb); }, + NodeSetup::API_SECRET_MODAL => { + self.node_setup.secret_modal(ui, modal, cb); + }, + NodeSetup::FOREIGN_API_SECRET_MODAL => { + self.node_setup.secret_modal(ui, modal, cb); + }, + NodeSetup::FTL_MODAL => { + self.node_setup.ftl_modal(ui, modal, cb); + } _ => {} } } +} + +impl NetworkSettings { + pub const NODE_RESTART_REQUIRED_MODAL: &'static str = "node_restart_required"; + + /// Node restart reminder modal content. + pub fn node_restart_required_modal(&self, ui: &mut egui::Ui, modal: &Modal) { + ui.add_space(6.0); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("network_settings.restart_node_required")) + .size(18.0) + .color(Colors::GRAY)); + ui.add_space(8.0); + }); + + // Show modal buttons. + ui.scope(|ui| { + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0); + + ui.columns(2, |columns| { + columns[0].vertical_centered_justified(|ui| { + View::button(ui, t!("network_settings.restart"), Colors::WHITE, || { + Node::restart(); + modal.close(); + }); + }); + columns[1].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.cancel"), Colors::WHITE, || { + modal.close(); + }); + }); + }); + ui.add_space(6.0); + }); + } + + /// List of available IP addresses. + pub fn get_ip_addrs() -> Vec { + let mut ip_addrs = Vec::new(); + for net_if in pnet::datalink::interfaces() { + for ip in net_if.ips { + if ip.is_ipv4() { + ip_addrs.push(ip.ip()); + } + } + } + ip_addrs + } + + /// Draw IP addresses as radio buttons. + pub fn ip_addrs_ui(ui: &mut egui::Ui, + saved_ip: &String, + ip_addrs: &Vec, + on_change: impl FnOnce(&String)) { + let saved_ip_addr = &IpAddr::from_str(saved_ip.as_str()).unwrap(); + let mut selected_ip_addr = saved_ip_addr; + + // Set first IP address as current if saved is not present at system. + if !ip_addrs.contains(selected_ip_addr) { + selected_ip_addr = ip_addrs.get(0).unwrap(); + } + + // Show available IP addresses on the system. + let _ = ip_addrs.chunks(2).map(|x| { + if x.len() == 2 { + ui.columns(2, |columns| { + let ip_addr_l = x.get(0).unwrap(); + columns[0].vertical_centered(|ui| { + View::radio_value(ui, + &mut selected_ip_addr, + ip_addr_l, + ip_addr_l.to_string()); + }); + let ip_addr_r = x.get(1).unwrap(); + columns[1].vertical_centered(|ui| { + View::radio_value(ui, + &mut selected_ip_addr, + ip_addr_r, + ip_addr_r.to_string()); + }) + }); + } else { + let ip_addr = x.get(0).unwrap(); + View::radio_value(ui, + &mut selected_ip_addr, + ip_addr, + ip_addr.to_string()); + } + ui.add_space(12.0); + }).collect::>(); + + if saved_ip_addr != selected_ip_addr { + (on_change)(&selected_ip_addr.to_string()); + } + } + + /// Show message when IP addresses are not available at system. + pub fn no_ip_address_ui(ui: &mut egui::Ui) { + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("network.no_ips")) + .size(16.0) + .color(Colors::INACTIVE_TEXT) + ); + ui.add_space(6.0); + }); + } } \ No newline at end of file diff --git a/src/gui/views/settings_node.rs b/src/gui/views/settings_node.rs index 221431e..7187f04 100644 --- a/src/gui/views/settings_node.rs +++ b/src/gui/views/settings_node.rs @@ -12,13 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::net::IpAddr; -use std::str::FromStr; -use egui::{RichText, TextStyle, Widget}; +use eframe::emath::Align; +use egui::{Id, Layout, RichText, TextStyle, Widget}; +use grin_core::global::ChainTypes; +use crate::AppConfig; use crate::gui::{Colors, Navigator}; -use crate::gui::icons::{COMPUTER_TOWER, POWER}; +use crate::gui::icons::{ASTERISK, CLIPBOARD_TEXT, COMPUTER_TOWER, COPY, POWER}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{Modal, ModalPosition, Network, View}; +use crate::gui::views::network_settings::NetworkSettings; use crate::node::{Node, NodeConfig}; /// Integrated node server setup ui section. @@ -43,7 +45,7 @@ pub struct NodeSetup { impl Default for NodeSetup { fn default() -> Self { - let (api_ip, api_port) = NodeConfig::get_api_address_port(); + let (api_ip, api_port) = NodeConfig::get_api_address(); let is_api_port_available = NodeConfig::is_api_port_available(&api_ip, &api_port); Self { api_port_edit: api_port, @@ -56,15 +58,19 @@ impl Default for NodeSetup { } } -const SECRET_SYMBOLS: &'static str = "••••••••••••"; - impl NodeSetup { pub const API_PORT_MODAL: &'static str = "api_port"; + pub const API_SECRET_MODAL: &'static str = "api_secret"; + pub const FOREIGN_API_SECRET_MODAL: &'static str = "foreign_api_secret"; + pub const FTL_MODAL: &'static str = "ftl"; pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { View::sub_title(ui, format!("{} {}", COMPUTER_TOWER, t!("network_settings.server"))); View::horizontal_line(ui, Colors::ITEM_STROKE); - ui.add_space(4.0); + ui.add_space(8.0); + + // Show chain type setup. + self.chain_type_ui(ui); // Show loading indicator or controls to stop/start/restart node. if Node::is_stopping() || Node::is_restarting() || Node::is_starting() { @@ -103,16 +109,18 @@ impl NodeSetup { } } + // Autorun node setup. ui.add_space(4.0); ui.vertical_centered(|ui| { - Network::autorun_node_checkbox(ui); + Network::autorun_node_ui(ui); }); ui.add_space(4.0); - let addrs = Network::get_ip_list(); - // Show message when IP addresses are not available on the system. + let addrs = NetworkSettings::get_ip_addrs(); if addrs.is_empty() { - Network::no_ip_address_ui(ui); + // Show message when IP addresses are not available on the system. + NetworkSettings::no_ip_address_ui(ui); + ui.add_space(4.0); } else { View::horizontal_line(ui, Colors::ITEM_STROKE); @@ -125,15 +133,11 @@ impl NodeSetup { ); ui.add_space(6.0); // Show API IP addresses to select. - let (api_ip, api_port) = NodeConfig::get_api_address_port(); - Network::ip_list_ui(ui, &api_ip, &addrs, |selected_ip| { - println!("12345 selected_ip {}", selected_ip); + let (api_ip, api_port) = NodeConfig::get_api_address(); + NetworkSettings::ip_addrs_ui(ui, &api_ip, &addrs, |selected_ip| { let api_available = NodeConfig::is_api_port_available(selected_ip, &api_port); - println!("12345 selected_ip is_api_port_available {}", api_available); self.is_api_port_available = api_available; - println!("12345 before save"); - NodeConfig::save_api_address_port(selected_ip, &api_port); - println!("12345 after save"); + NodeConfig::save_api_address(selected_ip, &api_port); }); ui.label(RichText::new(t!("network_settings.api_port")) @@ -144,16 +148,87 @@ impl NodeSetup { // Show API port setup. self.api_port_setup_ui(ui, cb); - View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.label(RichText::new(t!("network_settings.api_secret")) + .size(16.0) + .color(Colors::GRAY) + ); ui.add_space(6.0); + // Show API secret setup. + self.secret_ui(Self::API_SECRET_MODAL, ui, cb); + + ui.add_space(6.0); + + ui.label(RichText::new(t!("network_settings.foreign_api_secret")) + .size(16.0) + .color(Colors::GRAY) + ); + ui.add_space(6.0); + // Show Foreign API secret setup. + self.secret_ui(Self::FOREIGN_API_SECRET_MODAL, ui, cb); + + if Node::is_running() { + ui.add_space(2.0); + // Show reminder to restart node if settings are changed. + ui.label(RichText::new(t!("network_settings.restart_node_required")) + .size(16.0) + .color(Colors::GREEN) + ); + ui.add_space(2.0); + } }); } + + ui.add_space(6.0); + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(6.0); + + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("network_settings.ftl")) + .size(16.0) + .color(Colors::GRAY) + ); + ui.add_space(6.0); + // Show FTL setup. + self.ftl_ui(ui, cb); + + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(6.0); + }); + } + + /// Draw [`ChainTypes`] setup ui. + fn chain_type_ui(&mut self, ui: &mut egui::Ui) { + let saved_chain_type = AppConfig::chain_type(); + let mut selected_chain_type = saved_chain_type; + + ui.columns(2, |columns| { + columns[0].vertical_centered(|ui| { + let main_type = ChainTypes::Mainnet; + View::radio_value(ui, &mut selected_chain_type, main_type, "Mainnet".to_string()); + }); + columns[1].vertical_centered(|ui| { + let test_type = ChainTypes::Testnet; + View::radio_value(ui, &mut selected_chain_type, test_type, "Testnet".to_string()); + }) + }); + ui.add_space(4.0); + + if saved_chain_type != selected_chain_type { + AppConfig::change_chain_type(&selected_chain_type); + if Node::is_running() { + // Show modal to apply changes by node restart. + let port_modal = Modal::new(NetworkSettings::NODE_RESTART_REQUIRED_MODAL) + .position(ModalPosition::Center) + .title(t!("network.settings")); + Navigator::show_modal(port_modal); + } + } } /// Draw API port setup ui. fn api_port_setup_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { - let (_, port) = NodeConfig::get_api_address_port(); - // Show button to choose API server port. + let (_, port) = NodeConfig::get_api_address(); + // Show button to enter API server port. View::button(ui, port.clone(), Colors::BUTTON, || { // Setup values for modal. self.api_port_edit = port; @@ -162,7 +237,7 @@ impl NodeSetup { // Show API port modal. let port_modal = Modal::new(Self::API_PORT_MODAL) .position(ModalPosition::CenterTop) - .title(t!("network_settings.change_port")); + .title(t!("network_settings.change_value")); Navigator::show_modal(port_modal); cb.show_keyboard(); }); @@ -174,25 +249,19 @@ impl NodeSetup { .size(16.0) .color(Colors::RED)); ui.add_space(6.0); - } else if Node::is_running() { - // Show reminder to restart node if settings are changed. - ui.label(RichText::new(t!("network_settings.restart_node_required")) - .size(16.0) - .color(Colors::INACTIVE_TEXT) - ); - ui.add_space(6.0); } + ui.add_space(6.0); } /// Draw API port [`Modal`] content ui. - pub fn api_port_modal_ui(&mut self, - ui: &mut egui::Ui, - modal: &Modal, - cb: &dyn PlatformCallbacks) { + pub fn api_port_modal(&mut self, + ui: &mut egui::Ui, + modal: &Modal, + cb: &dyn PlatformCallbacks) { ui.add_space(6.0); ui.vertical_centered(|ui| { - ui.label(RichText::new(t!("network_settings.enter_value")) - .size(16.0) + ui.label(RichText::new(t!("network_settings.api_port")) + .size(18.0) .color(Colors::GRAY)); ui.add_space(8.0); @@ -222,6 +291,23 @@ impl NodeSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0); + // Save button callback. + let on_save = || { + // Check if port is available. + let (api_ip, _) = NodeConfig::get_api_address(); + let available = NodeConfig::is_api_port_available(&api_ip, &self.api_port_edit); + self.api_port_available_edit = available; + + if available { + // Save port at config if it's available. + NodeConfig::save_api_address(&api_ip, &self.api_port_edit); + + self.is_api_port_available = true; + cb.hide_keyboard(); + modal.close(); + } + }; + ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::WHITE, || { @@ -230,31 +316,244 @@ impl NodeSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::WHITE, || { - // Check if port is available. - let (ip, _) = NodeConfig::get_api_address_port(); - let available = NodeConfig::is_api_port_available( - &ip, - &self.api_port_edit - ); - self.api_port_available_edit = available; - - if self.api_port_available_edit { - // Save port at config if it's available. - NodeConfig::save_api_address_port( - &ip, - &self.api_port_edit - ); - - self.is_api_port_available = true; - cb.hide_keyboard(); - modal.close(); - } - }); + View::button(ui, t!("modal.save"), Colors::WHITE, on_save); }); }); ui.add_space(6.0); }); }); } + + /// Draw API secret token setup ui. + fn secret_ui(&mut self, modal_id: &'static str, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { + // Setup values for modal + let secret_value = match modal_id { + Self::API_SECRET_MODAL => NodeConfig::get_api_secret(), + _ => NodeConfig::get_foreign_api_secret() + }; + + let secret_text = if secret_value.is_some() { + format!("{}{}{}{}{}{}{}{}{}{}", ASTERISK, ASTERISK, ASTERISK, ASTERISK, + ASTERISK, ASTERISK, ASTERISK, ASTERISK, ASTERISK, ASTERISK) + } else { + t!("network_settings.disabled") + }; + + // Show button to open secret modal. + View::button(ui, secret_text, Colors::BUTTON, || { + // Setup values for modal. + match modal_id { + Self::API_SECRET_MODAL => { + self.api_secret_edit = secret_value.unwrap_or("".to_string()); + }, + _ => { + self.foreign_api_secret_edit = secret_value.unwrap_or("".to_string()); + } + } + // Show secret edit modal. + let port_modal = Modal::new(modal_id) + .position(ModalPosition::CenterTop) + .title(t!("network_settings.change_value")); + Navigator::show_modal(port_modal); + cb.show_keyboard(); + }); + ui.add_space(6.0); + } + + /// Draw API port [`Modal`] content ui. + pub fn secret_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + ui.add_space(6.0); + ui.vertical_centered(|ui| { + let description = match modal.id { + Self::API_SECRET_MODAL => t!("network_settings.api_secret"), + _ => t!("network_settings.foreign_api_secret") + }; + ui.label(RichText::new(description) + .size(18.0) + .color(Colors::GRAY)); + ui.add_space(8.0); + + // let Self { api_secret_edit, foreign_api_secret_edit, .. } = self; + + // Draw API port text edit. + let edit_text = match modal.id { + Self::API_SECRET_MODAL => &mut self.api_secret_edit, + _ => &mut self.foreign_api_secret_edit + }; + let text_edit_resp = egui::TextEdit::singleline(edit_text) + .id(Id::from(modal.id)) + .font(TextStyle::Heading) + .cursor_at_end(true) + .ui(ui); + text_edit_resp.request_focus(); + if text_edit_resp.clicked() { + cb.show_keyboard(); + } + ui.add_space(12.0); + + // Show buttons to copy/paste text. + ui.scope(|ui| { + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(12.0, 0.0); + + ui.columns(2, |columns| { + // Setup spacing between buttons. + columns[0].with_layout(Layout::right_to_left(Align::TOP), |ui| { + let copy_text = format!("{} {}", COPY, t!("network_settings.copy")); + View::button(ui, copy_text, Colors::WHITE, || { + match modal.id { + Self::API_SECRET_MODAL => { + cb.copy_string_to_buffer(self.api_secret_edit.clone()); + }, + _ => { + cb.copy_string_to_buffer(self.foreign_api_secret_edit.clone()); + } + }; + + }); + }); + columns[1].with_layout(Layout::left_to_right(Align::TOP), |ui| { + let copy_text = format!("{} {}", CLIPBOARD_TEXT, t!("network_settings.paste")); + View::button(ui, copy_text, Colors::WHITE, || { + let text = cb.get_string_from_buffer(); + match modal.id { + Self::API_SECRET_MODAL => self.api_secret_edit = text, + _ => self.foreign_api_secret_edit = text + }; + }); + }); + }); + ui.add_space(12.0); + }); + + // Show modal buttons. + ui.scope(|ui| { + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0); + + // Save button callback. + let on_save = || { + match modal.id { + Self::API_SECRET_MODAL => { + let api_secret = &self.api_secret_edit.clone(); + NodeConfig::save_api_secret(api_secret); + } + _ => { + let foreign_api_secret = &self.foreign_api_secret_edit.clone(); + NodeConfig::save_foreign_api_secret(foreign_api_secret); + } + }; + cb.hide_keyboard(); + modal.close(); + }; + + ui.columns(2, |columns| { + columns[0].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.cancel"), Colors::WHITE, || { + cb.hide_keyboard(); + modal.close(); + }); + }); + columns[1].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.save"), Colors::WHITE, on_save); + }); + }); + ui.add_space(6.0); + }); + }); + } + + /// Draw FTL setup ui. + fn ftl_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { + let ftl = NodeConfig::get_ftl(); + // Show button to enter FTL value. + View::button(ui, ftl.clone(), Colors::BUTTON, || { + // Setup values for modal. + self.ftl_edit = ftl; + // Show stratum port modal. + let ftl_modal = Modal::new(Self::FTL_MODAL) + .position(ModalPosition::CenterTop) + .title(t!("network_settings.change_value")); + Navigator::show_modal(ftl_modal); + cb.show_keyboard(); + }); + ui.add_space(6.0); + ui.label(RichText::new(t!("network_settings.ftl_description")) + .size(16.0) + .color(Colors::INACTIVE_TEXT) + ); + + if Node::is_running() { + ui.add_space(2.0); + // Show reminder to restart node if settings are changed. + ui.label(RichText::new(t!("network_settings.restart_node_required")) + .size(16.0) + .color(Colors::GREEN) + ); + ui.add_space(2.0); + } + ui.add_space(6.0); + } + + /// Draw FTL [`Modal`] content. + pub fn ftl_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + ui.add_space(6.0); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("network_settings.ftl")) + .size(18.0) + .color(Colors::GRAY)); + ui.add_space(8.0); + + // Draw stratum port text edit. + let text_edit_resp = egui::TextEdit::singleline(&mut self.ftl_edit) + .id(Id::from(modal.id)) + .font(TextStyle::Heading) + .desired_width(34.0) + .cursor_at_end(true) + .ui(ui); + text_edit_resp.request_focus(); + if text_edit_resp.clicked() { + cb.show_keyboard(); + } + + // Show error when specified value is not valid. + if self.ftl_edit.parse::().is_err() { + ui.add_space(12.0); + ui.label(RichText::new(t!("network_settings.not_valid_value")) + .size(18.0) + .color(Colors::RED)); + } + }); + + ui.add_space(12.0); + + // Show modal buttons. + ui.scope(|ui| { + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); + + // Save button callback + let on_save = || { + if let Ok(ftl) = self.ftl_edit.parse::() { + NodeConfig::save_ftl(ftl); + cb.hide_keyboard(); + modal.close(); + } + }; + + ui.columns(2, |columns| { + columns[0].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.cancel"), Colors::WHITE, || { + // Close modal. + cb.hide_keyboard(); + modal.close(); + }); + }); + columns[1].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.save"), Colors::WHITE, on_save); + }); + }); + ui.add_space(6.0); + }); + } } \ No newline at end of file diff --git a/src/gui/views/settings_stratum.rs b/src/gui/views/settings_stratum.rs index d2df645..19d67c9 100644 --- a/src/gui/views/settings_stratum.rs +++ b/src/gui/views/settings_stratum.rs @@ -12,17 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::net::IpAddr; -use std::str::FromStr; - use egui::{RichText, TextStyle, Widget}; use crate::gui::{Colors, Navigator}; use crate::gui::icons::WRENCH; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, ModalPosition, Network, View}; +use crate::gui::views::{Modal, ModalPosition, View}; +use crate::gui::views::network_settings::NetworkSettings; use crate::node::NodeConfig; -use crate::Settings; /// Stratum server setup ui section. pub struct StratumServerSetup { @@ -37,7 +34,7 @@ pub struct StratumServerSetup { impl Default for StratumServerSetup { fn default() -> Self { - let (ip, port) = NodeConfig::get_stratum_address_port(); + let (ip, port) = NodeConfig::get_stratum_address(); let is_port_available = NodeConfig::is_stratum_port_available(&ip, &port); Self { stratum_port_edit: port, @@ -57,9 +54,9 @@ impl StratumServerSetup { ui.add_space(4.0); // Show message when IP addresses are not available on the system. - let all_ips = Network::get_ip_list(); + let all_ips = NetworkSettings::get_ip_addrs(); if all_ips.is_empty() { - Network::no_ip_address_ui(ui); + NetworkSettings::no_ip_address_ui(ui); return; } @@ -70,10 +67,10 @@ impl StratumServerSetup { ); ui.add_space(6.0); // Show stratum IP addresses to select. - let (ip, port) = NodeConfig::get_stratum_address_port(); - Network::ip_list_ui(ui, &ip, &all_ips, |selected_ip| { + let (ip, port) = NodeConfig::get_stratum_address(); + NetworkSettings::ip_addrs_ui(ui, &ip, &all_ips, |selected_ip| { self.is_port_available = NodeConfig::is_stratum_port_available(selected_ip, &port); - NodeConfig::save_stratum_address_port(selected_ip, &port); + NodeConfig::save_stratum_address(selected_ip, &port); }); ui.label(RichText::new(t!("network_settings.port")) @@ -91,17 +88,16 @@ impl StratumServerSetup { /// Draw stratum port setup ui. fn port_setup_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { - let (_, port) = NodeConfig::get_stratum_address_port(); - // Show button to choose stratum server port. + let (_, port) = NodeConfig::get_stratum_address(); + // Show button to enter stratum server port. View::button(ui, port.clone(), Colors::BUTTON, || { // Setup values for modal. self.stratum_port_edit = port; self.stratum_port_available_edit = self.is_port_available; - // Show stratum port modal. let port_modal = Modal::new(Self::STRATUM_PORT_MODAL) .position(ModalPosition::CenterTop) - .title(t!("network_settings.change_port")); + .title(t!("network_settings.change_value")); Navigator::show_modal(port_modal); cb.show_keyboard(); }); @@ -123,8 +119,8 @@ impl StratumServerSetup { cb: &dyn PlatformCallbacks) { ui.add_space(6.0); ui.vertical_centered(|ui| { - ui.label(RichText::new(t!("network_settings.enter_value")) - .size(16.0) + ui.label(RichText::new(t!("network_settings.stratum_port")) + .size(18.0) .color(Colors::GRAY)); ui.add_space(8.0); @@ -143,7 +139,7 @@ impl StratumServerSetup { if !self.stratum_port_available_edit { ui.add_space(12.0); ui.label(RichText::new(t!("network_settings.port_unavailable")) - .size(16.0) + .size(18.0) .color(Colors::RED)); } @@ -154,6 +150,26 @@ impl StratumServerSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); + // Save button callback + let on_save = || { + // Check if port is available. + let (stratum_ip, _) = NodeConfig::get_stratum_address(); + let available = NodeConfig::is_stratum_port_available( + &stratum_ip, + &self.stratum_port_edit + ); + self.stratum_port_available_edit = available; + + // Save port at config if it's available. + if available { + NodeConfig::save_stratum_address(&stratum_ip, &self.stratum_port_edit); + + self.is_port_available = true; + cb.hide_keyboard(); + modal.close(); + } + }; + ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::WHITE, || { @@ -163,24 +179,7 @@ impl StratumServerSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::WHITE, || { - // Check if port is available. - let (ip, _) = NodeConfig::get_api_address_port(); - let available = NodeConfig::is_stratum_port_available( - &ip, - &self.stratum_port_edit - ); - self.stratum_port_available_edit = available; - - // Save port at config if it's available. - if self.stratum_port_available_edit { - NodeConfig::save_stratum_address_port(&ip, &self.stratum_port_edit); - - self.is_port_available = true; - cb.hide_keyboard(); - modal.close(); - } - }); + View::button(ui, t!("modal.save"), Colors::WHITE, on_save); }); }); ui.add_space(6.0); diff --git a/src/node/config.rs b/src/node/config.rs index 2c622c3..2d29764 100644 --- a/src/node/config.rs +++ b/src/node/config.rs @@ -15,6 +15,7 @@ use std::fs::File; use std::io::{BufRead, BufReader, Write}; use std::net::{IpAddr, Ipv4Addr, SocketAddrV4, TcpListener}; +use std::path::PathBuf; use std::str::FromStr; use grin_config::{config, ConfigError, ConfigMembers, GlobalConfig}; @@ -26,7 +27,7 @@ use grin_servers::common::types::ChainValidationMode; use serde::{Deserialize, Serialize}; use crate::node::Node; -use crate::Settings; +use crate::{AppConfig, Settings}; /// Wrapped node config to be used by [`grin_servers::Server`]. #[derive(Serialize, Deserialize)] @@ -37,9 +38,6 @@ pub struct NodeConfig { impl NodeConfig { /// Initialize integrated node config. pub fn init(chain_type: &ChainTypes) -> Self { - let _ = Self::check_api_secret_files(chain_type, API_SECRET_FILE_NAME); - let _ = Self::check_api_secret_files(chain_type, FOREIGN_API_SECRET_FILE_NAME); - let config_members = Self::for_chain_type(chain_type); Self { members: config_members @@ -48,6 +46,11 @@ impl NodeConfig { /// Initialize config with provided [`ChainTypes`]. pub fn for_chain_type(chain_type: &ChainTypes) -> ConfigMembers { + // Check secret files for current chain type. + let _ = Self::check_api_secret_files(chain_type, API_SECRET_FILE_NAME); + let _ = Self::check_api_secret_files(chain_type, FOREIGN_API_SECRET_FILE_NAME); + + // Create config. let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, Some(chain_type)); let parsed = Settings::read_from_file::(path.clone()); if !path.exists() || parsed.is_err() { @@ -81,9 +84,7 @@ impl NodeConfig { chain_type: &ChainTypes, secret_file_name: &str, ) -> Result<(), ConfigError> { - let grin_path = Settings::get_working_path(Some(chain_type)); - let mut api_secret_path = grin_path; - api_secret_path.push(secret_file_name); + let api_secret_path = Self::get_secret_path(chain_type, secret_file_name); if !api_secret_path.exists() { config::init_api_secret(&api_secret_path) } else { @@ -91,8 +92,16 @@ impl NodeConfig { } } + /// Get path for secret file. + fn get_secret_path(chain_type: &ChainTypes, secret_file_name: &str) -> PathBuf { + let grin_path = Settings::get_working_path(Some(chain_type)); + let mut api_secret_path = grin_path; + api_secret_path.push(secret_file_name); + api_secret_path + } + /// Check whether a port is available on the provided host. - pub fn is_port_available(host: &String, port: &String) -> bool { + fn is_port_available(host: &String, port: &String) -> bool { if let Ok(p) = port.parse::() { let ip_addr = Ipv4Addr::from_str(host.as_str()).unwrap(); let ipv4 = SocketAddrV4::new(ip_addr, p); @@ -102,7 +111,7 @@ impl NodeConfig { } /// Get stratum server IP address and port. - pub fn get_stratum_address_port() -> (String, String) { + pub fn get_stratum_address() -> (String, String) { let r_config = Settings::node_config_to_read(); let saved_stratum_addr = r_config .members @@ -118,7 +127,7 @@ impl NodeConfig { } /// Save stratum server IP address and port. - pub fn save_stratum_address_port(addr: &String, port: &String) { + pub fn save_stratum_address(addr: &String, port: &String) { let addr_to_save = format!("{}:{}", addr, port); let mut w_node_config = Settings::node_config_to_update(); w_node_config @@ -135,7 +144,7 @@ impl NodeConfig { pub fn is_stratum_port_available(ip: &String, port: &String) -> bool { if Self::is_port_available(&ip, &port) { if &Self::get_p2p_port().to_string() != port { - let (api_ip, api_port) = Self::get_api_address_port(); + let (api_ip, api_port) = Self::get_api_address(); return if &api_ip == ip { &api_port != port } else { @@ -180,7 +189,7 @@ impl NodeConfig { } /// Get API server IP address and port. - pub fn get_api_address_port() -> (String, String) { + pub fn get_api_address() -> (String, String) { let r_config = Settings::node_config_to_read(); let saved_api_addr = r_config .members @@ -192,7 +201,7 @@ impl NodeConfig { } /// Save API server IP address and port. - pub fn save_api_address_port(addr: &String, port: &String) { + pub fn save_api_address(addr: &String, port: &String) { let addr_to_save = format!("{}:{}", addr, port); let mut w_node_config = Settings::node_config_to_update(); w_node_config.members.server.api_http_addr = addr_to_save; @@ -219,72 +228,102 @@ impl NodeConfig { } /// Get API secret text. - pub fn get_api_secret() -> String { + pub fn get_api_secret() -> Option { let r_config = Settings::node_config_to_read(); let api_secret_path = r_config .members .server .api_secret_path - .as_ref() - .unwrap(); - let api_secret_file = File::open(api_secret_path).unwrap(); - let buf_reader = BufReader::new(api_secret_file); - let mut lines_iter = buf_reader.lines(); - let first_line = lines_iter.next().unwrap(); - first_line.unwrap() + .clone(); + return if let Some(secret_path) = api_secret_path { + let api_secret_file = File::open(secret_path).unwrap(); + let buf_reader = BufReader::new(api_secret_file); + let mut lines_iter = buf_reader.lines(); + let first_line = lines_iter.next().unwrap(); + Some(first_line.unwrap()) + } else { + None + } } /// Save API secret text. pub fn save_api_secret(api_secret: &String) { - if api_secret.is_empty() { - return; - } - let r_config = Settings::node_config_to_read(); - let api_secret_path = r_config - .members - .server - .api_secret_path - .as_ref() - .unwrap(); - let mut api_secret_file = File::create(api_secret_path).unwrap(); - api_secret_file.write_all(api_secret.as_bytes()).unwrap(); + Self::save_secret(api_secret, API_SECRET_FILE_NAME); } /// Get Foreign API secret text. - pub fn get_foreign_api_secret() -> String { + pub fn get_foreign_api_secret() -> Option { let r_config = Settings::node_config_to_read(); - let foreign_api_secret_path = r_config + let foreign_secret_path = r_config .members .server .foreign_api_secret_path - .as_ref() - .unwrap(); - let foreign_api_secret_file = File::open(foreign_api_secret_path).unwrap(); - let buf_reader = BufReader::new(foreign_api_secret_file); - let mut lines_iter = buf_reader.lines(); - let first_line = lines_iter.next().unwrap(); - first_line.unwrap() + .clone(); + return if let Some(secret_path) = foreign_secret_path { + let foreign_secret_file = File::open(secret_path).unwrap(); + let buf_reader = BufReader::new(foreign_secret_file); + let mut lines_iter = buf_reader.lines(); + let first_line = lines_iter.next().unwrap(); + Some(first_line.unwrap()) + } else { + None + } } - /// Save Foreign API secret text. + /// Update Foreign API secret. pub fn save_foreign_api_secret(api_secret: &String) { - if api_secret.is_empty() { + Self::save_secret(api_secret, FOREIGN_API_SECRET_FILE_NAME); + } + + /// Save secret value into specified file. + fn save_secret(value: &String, file_name: &str) { + // Remove config value to remove authorization. + if value.is_empty() { + let mut w_config = Settings::node_config_to_update(); + match file_name { + API_SECRET_FILE_NAME => w_config.members.server.api_secret_path = None, + _ => w_config.members.server.foreign_api_secret_path = None + } + w_config.save(); return; } - let r_config = Settings::node_config_to_read(); - let foreign_api_secret_path = r_config - .members - .server - .foreign_api_secret_path - .as_ref() - .unwrap(); - let mut foreign_api_secret_file = File::create(foreign_api_secret_path).unwrap(); - foreign_api_secret_file.write_all(api_secret.as_bytes()).unwrap(); + + let mut secret_enabled = true; + // Get path for specified secret file. + let secret_path = { + let r_config = Settings::node_config_to_read(); + let path = match file_name { + API_SECRET_FILE_NAME => r_config.members.server.api_secret_path.clone(), + _ => r_config.members.server.foreign_api_secret_path.clone() + }; + path.unwrap_or_else(|| { + secret_enabled = false; + let chain_type = AppConfig::chain_type(); + let path = Self::get_secret_path(&chain_type, file_name); + path.to_str().unwrap().to_string() + }) + }; + // Update secret path at config if authorization was disabled before. + if !secret_enabled { + let mut w_config = Settings::node_config_to_update(); + match file_name { + API_SECRET_FILE_NAME => w_config + .members + .server + .api_secret_path = Some(secret_path.clone()), + _ => w_config.members.server.foreign_api_secret_path = Some(secret_path.clone()) + }; + + w_config.save(); + } + // Write secret text into file. + let mut secret_file = File::create(secret_path).unwrap(); + secret_file.write_all(value.as_bytes()).unwrap(); } /// Get Future Time Limit. - pub fn get_ftl() -> u64 { - Settings::node_config_to_read().members.server.future_time_limit + pub fn get_ftl() -> String { + Settings::node_config_to_read().members.server.future_time_limit.to_string() } /// Save Future Time Limit. diff --git a/src/settings.rs b/src/settings.rs index 7cec7c3..8535629 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -64,21 +64,44 @@ impl AppConfig { } } - /// Change chain type and load new [`NodeConfig`] accordingly. - pub fn change_chain_type(&mut self, chain_type: &ChainTypes) { - if self.chain_type != *chain_type { - self.chain_type = *chain_type; - self.save(); - - // Load config for selected chain type. - Settings::node_config_to_update().members = NodeConfig::for_chain_type(chain_type); - } - } - /// Save app config to disk. pub fn save(&self) { Settings::write_to_file(self, Settings::get_config_path(APP_CONFIG_FILE_NAME, None)); } + + /// Change chain type and load new [`NodeConfig`] accordingly. + pub fn change_chain_type(chain_type: &ChainTypes) { + let current_chain_type = Self::chain_type(); + if current_chain_type != *chain_type { + let mut w_app_config = Settings::app_config_to_update(); + w_app_config.chain_type = *chain_type; + w_app_config.save(); + // Load config for selected chain type. + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members = NodeConfig::for_chain_type(chain_type); + w_node_config.save(); + } + } + + /// Get current [`ChainTypes`] for node and wallets. + pub fn chain_type() -> ChainTypes { + let r_config = Settings::app_config_to_read(); + r_config.chain_type + } + + /// Check if integrated node is starting with application. + pub fn autostart_node() -> bool { + let r_config = Settings::app_config_to_read(); + r_config.auto_start_node + } + + /// Toggle integrated node autostart. + pub fn toggle_node_autostart() { + let autostart = Self::autostart_node(); + let mut w_app_config = Settings::app_config_to_update(); + w_app_config.auto_start_node = !autostart; + w_app_config.save(); + } } const WORKING_DIRECTORY_NAME: &'static str = ".grim"; @@ -150,7 +173,7 @@ impl Settings { let file_content = fs::read_to_string(config_path.clone())?; let parsed = toml::from_str::(file_content.as_str()); match parsed { - Ok(cfg) => { Ok(cfg) } + Ok(cfg) => Ok(cfg), Err(e) => { return Err(ConfigError::ParseError( config_path.to_str().unwrap().to_string(),