Skip to content

Commit

Permalink
feat(launchpad): enable user to reset nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
RolandSherwin committed Jun 4, 2024
1 parent 2b57591 commit fbc5293
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 13 deletions.
5 changes: 3 additions & 2 deletions node-launchpad/.config/config.json5
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
"<Ctrl-g>": {"HomeActions":"TriggerManageNodes"},
"<Ctrl-G>": {"HomeActions":"TriggerManageNodes"},
"<Ctrl-Shift-g>": {"HomeActions":"TriggerManageNodes"},
"<Shift-o>": {"HomeActions":"TriggerManageNodes"},
"<h>": {"HomeActions":"TriggerHelp"},
"<H>": {"HomeActions":"TriggerHelp"},
"<Shift-h>": {"HomeActions":"TriggerHelp"},

"<Ctrl-r>": {"HomeActions":"TriggerResetNodesPopUp"},
"<Ctrl-R>": {"HomeActions":"TriggerResetNodesPopUp"},
"<Ctrl-Shift-r>": {"HomeActions":"TriggerResetNodesPopUp"},

"<q>": "Quit",
"<Shift-q>": "Quit",
Expand Down
4 changes: 3 additions & 1 deletion node-launchpad/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ pub enum Action {

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Display, Deserialize)]
pub enum HomeActions {
ResetNodes,
StartNodes,
StopNodes,
StartNodesCompleted,
StopNodesCompleted,
ResetNodesCompleted,
ResetNodesCompleted { trigger_start_node: bool },
SuccessfullyDetectedNatStatus,
ErrorWhileRunningNatDetection,

Expand All @@ -49,6 +50,7 @@ pub enum HomeActions {
TriggerBetaProgramme,
TriggerManageNodes,
TriggerHelp,
TriggerResetNodesPopUp,

PreviousTableItem,
NextTableItem,
Expand Down
4 changes: 3 additions & 1 deletion node-launchpad/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{
action::Action,
components::{
beta_programme::BetaProgramme, footer::Footer, help::HelpPopUp, home::Home,
manage_nodes::ManageNodes, Component,
manage_nodes::ManageNodes, reset_popup::ResetNodesPopup, Component,
},
config::{AppData, Config},
mode::{InputMode, Scene},
Expand Down Expand Up @@ -58,6 +58,7 @@ impl App {
let manage_nodes = ManageNodes::new(app_data.nodes_to_start)?;
let footer = Footer::new(app_data.nodes_to_start > 0);
let help = HelpPopUp::default();
let reset_nodes = ResetNodesPopup::default();

Ok(Self {
config,
Expand All @@ -70,6 +71,7 @@ impl App {
Box::new(discord_username_input),
Box::new(manage_nodes),
Box::new(help),
Box::new(reset_nodes),
],
should_quit: false,
should_suspend: false,
Expand Down
1 change: 1 addition & 0 deletions node-launchpad/src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub mod help;
pub mod home;
pub mod manage_nodes;
pub mod options;
pub mod reset_popup;
pub mod tab;
pub mod utils;

Expand Down
6 changes: 5 additions & 1 deletion node-launchpad/src/components/footer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ impl Component for Footer {
};

let (line1, line2) = match self.current_scene {
Scene::Home | Scene::BetaProgramme | Scene::HelpPopUp | Scene::ManageNodes => {
Scene::Home
| Scene::BetaProgramme
| Scene::HelpPopUp
| Scene::ManageNodes
| Scene::ResetPopUp => {
let line1 = Line::from(vec![
Span::styled(" [Ctrl+S] ", command_style),
Span::styled("Start all Nodes ", text_style),
Expand Down
39 changes: 31 additions & 8 deletions node-launchpad/src/components/home.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,10 @@ impl Component for Home {
// make sure we're in navigation mode
return Ok(Some(Action::SwitchInputMode(InputMode::Navigation)));
}
Scene::BetaProgramme | Scene::ManageNodes | Scene::HelpPopUp => self.active = true,
Scene::BetaProgramme
| Scene::ManageNodes
| Scene::HelpPopUp
| Scene::ResetPopUp => self.active = true,
_ => self.active = false,
},
Action::StoreNodesToStart(count) => {
Expand All @@ -243,7 +246,7 @@ impl Component for Home {
self.lock_registry = Some(LockRegistryState::ResettingNodes);
info!("Resetting safenode services because the discord username was reset.");
let action_sender = self.get_actions_sender()?;
reset_nodes(action_sender);
reset_nodes(action_sender, true);
}
}
Action::HomeActions(HomeActions::StartNodes) => {
Expand Down Expand Up @@ -283,6 +286,18 @@ impl Component for Home {

stop_nodes(running_nodes, action_sender);
}
Action::HomeActions(HomeActions::ResetNodes) => {
if self.lock_registry.is_some() {
error!("Registry is locked. Cannot reset nodes now.");
return Ok(None);
}

self.lock_registry = Some(LockRegistryState::ResettingNodes);
let action_sender = self.get_actions_sender()?;
info!("Got action to reset nodes");
reset_nodes(action_sender, false);
}

Action::Tick => {
self.try_update_node_stats(false)?;
}
Expand All @@ -294,12 +309,15 @@ impl Component for Home {
self.lock_registry = None;
self.load_node_registry_and_update_states()?;
}
Action::HomeActions(HomeActions::ResetNodesCompleted) => {
Action::HomeActions(HomeActions::ResetNodesCompleted { trigger_start_node }) => {
self.lock_registry = None;
self.load_node_registry_and_update_states()?;

// trigger start nodes.
return Ok(Some(Action::HomeActions(HomeActions::StartNodes)));
if trigger_start_node {
debug!("Reset nodes completed. Triggering start nodes.");
return Ok(Some(Action::HomeActions(HomeActions::StartNodes)));
}
debug!("Reset nodes completed");
}
Action::HomeActions(HomeActions::SuccessfullyDetectedNatStatus) => {
debug!("Successfully detected nat status, is_nat_status_determined set to true");
Expand All @@ -322,7 +340,9 @@ impl Component for Home {
Action::HomeActions(HomeActions::TriggerHelp) => {
return Ok(Some(Action::SwitchScene(Scene::HelpPopUp)));
}

Action::HomeActions(HomeActions::TriggerResetNodesPopUp) => {
return Ok(Some(Action::SwitchScene(Scene::ResetPopUp)));
}
Action::HomeActions(HomeActions::PreviousTableItem) => {
self.select_previous_table_item();
}
Expand Down Expand Up @@ -650,14 +670,17 @@ fn maintain_n_running_nodes(
});
}

fn reset_nodes(action_sender: UnboundedSender<Action>) {
fn reset_nodes(action_sender: UnboundedSender<Action>, start_nodes_after_reset: bool) {
tokio::task::spawn_local(async move {
if let Err(err) = sn_node_manager::cmd::node::reset(true, VerbosityLevel::Minimal).await {
error!("Error while resetting services {err:?}");
} else {
info!("Successfully reset services");
}
if let Err(err) = action_sender.send(Action::HomeActions(HomeActions::ResetNodesCompleted))
if let Err(err) =
action_sender.send(Action::HomeActions(HomeActions::ResetNodesCompleted {
trigger_start_node: start_nodes_after_reset,
}))
{
error!("Error while sending action: {err:?}");
}
Expand Down
193 changes: 193 additions & 0 deletions node-launchpad/src/components/reset_popup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// Copyright 2024 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use super::{utils::centered_rect_fixed, Component};
use crate::{
action::{Action, HomeActions},
mode::{InputMode, Scene},
style::{clear_area, GHOST_WHITE, LIGHT_PERIWINKLE, VIVID_SKY_BLUE},
};
use color_eyre::Result;
use crossterm::event::{Event, KeyCode, KeyEvent};
use ratatui::{prelude::*, widgets::*};
use tui_input::{backend::crossterm::EventHandler, Input};

#[derive(Default)]
pub struct ResetNodesPopup {
/// Whether the component is active right now, capturing keystrokes + draw things.
active: bool,
confirmation_input_field: Input,
}

impl Component for ResetNodesPopup {
fn handle_key_events(&mut self, key: KeyEvent) -> Result<Vec<Action>> {
if !self.active {
return Ok(vec![]);
}
let send_back = match key.code {
KeyCode::Enter => {
let input = self.confirmation_input_field.value().to_string();

if input.to_lowercase() == "reset" {
debug!("Got reset, sending Reset action and switching to home");
vec![
Action::SwitchScene(Scene::Home),
Action::HomeActions(HomeActions::ResetNodes),
]
} else {
debug!("Got Enter, but RESET is not typed. Switching to home");
vec![Action::SwitchScene(Scene::Home)]
}
}
KeyCode::Esc => {
debug!("Got Esc, switching to home");
vec![Action::SwitchScene(Scene::Home)]
}
KeyCode::Char(' ') => vec![],
KeyCode::Backspace => {
// if max limit reached, we should allow Backspace to work.
self.confirmation_input_field.handle_event(&Event::Key(key));
vec![]
}
_ => {
// max char limit
if self.confirmation_input_field.value().chars().count() < 10 {
self.confirmation_input_field.handle_event(&Event::Key(key));
}
vec![]
}
};
Ok(send_back)
}

fn update(&mut self, action: Action) -> Result<Option<Action>> {
let send_back = match action {
Action::SwitchScene(scene) => match scene {
Scene::ResetPopUp => {
self.active = true;
self.confirmation_input_field = self
.confirmation_input_field
.clone()
.with_value(String::new());
// set to entry input mode as we want to handle everything within our handle_key_events
// so by default if this scene is active, we capture inputs.
Some(Action::SwitchInputMode(InputMode::Entry))
}
_ => {
self.active = false;
None
}
},
_ => None,
};
Ok(send_back)
}

fn draw(&mut self, f: &mut crate::tui::Frame<'_>, area: Rect) -> Result<()> {
if !self.active {
return Ok(());
}

let layer_zero = centered_rect_fixed(52, 15, area);

let layer_one = Layout::new(
Direction::Vertical,
[
// for the pop_up_border
Constraint::Length(2),
// for the input field
Constraint::Min(1),
// for the pop_up_border
Constraint::Length(1),
],
)
.split(layer_zero);

// layer zero
let pop_up_border = Paragraph::new("").block(
Block::default()
.borders(Borders::ALL)
.title("Reset Nodes")
.title_style(Style::new().fg(VIVID_SKY_BLUE))
.padding(Padding::uniform(2))
.border_style(Style::new().fg(VIVID_SKY_BLUE)),
);
clear_area(f, layer_zero);

// split into 4 parts, for the prompt, input, text, dash , and buttons
let layer_two = Layout::new(
Direction::Vertical,
[
// for the prompt text
Constraint::Length(4),
// for the input
Constraint::Length(2),
// for the text
Constraint::Length(3),
// gap
Constraint::Length(3),
// for the buttons
Constraint::Length(1),
],
)
.split(layer_one[1]);

let prompt = Paragraph::new("Type in 'reset' and press Enter to Reset all your nodes")
.wrap(Wrap { trim: false })
.alignment(Alignment::Center)
.fg(GHOST_WHITE);

f.render_widget(prompt, layer_two[0]);

let input = Paragraph::new(self.confirmation_input_field.value())
.alignment(Alignment::Center)
.fg(VIVID_SKY_BLUE);
f.set_cursor(
// Put cursor past the end of the input text
layer_two[1].x
+ (layer_two[1].width / 2) as u16
+ (self.confirmation_input_field.value().len() / 2) as u16
+ if self.confirmation_input_field.value().len() % 2 != 0 {
1
} else {
0
},
layer_two[1].y,
);
f.render_widget(input, layer_two[1]);

let text = Paragraph::new(" This will clear out all the nodes and all \n the stored data. You should still keep all\n your earned rewards.");
f.render_widget(text.fg(GHOST_WHITE), layer_two[2]);

let dash = Block::new()
.borders(Borders::BOTTOM)
.border_style(Style::new().fg(GHOST_WHITE));
f.render_widget(dash, layer_two[3]);

let buttons_layer =
Layout::horizontal(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
.split(layer_two[4]);

let button_no = Line::from(vec![Span::styled(
" No, Cancel [Esc]",
Style::default().fg(LIGHT_PERIWINKLE),
)]);

f.render_widget(button_no, buttons_layer[0]);

let button_yes = Line::from(vec![Span::styled(
"Reset Nodes [Enter]",
Style::default().fg(LIGHT_PERIWINKLE),
)]);
f.render_widget(button_yes, buttons_layer[1]);

f.render_widget(pop_up_border, layer_zero);

Ok(())
}
}
1 change: 1 addition & 0 deletions node-launchpad/src/mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub enum Scene {
BetaProgramme,
ManageNodes,
HelpPopUp,
ResetPopUp,
}

#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
Expand Down

0 comments on commit fbc5293

Please sign in to comment.