mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
Migrated setup activity to new activity lifecycle
This commit is contained in:
@@ -25,82 +25,89 @@
|
||||
*/
|
||||
|
||||
// Locals
|
||||
use super::{Color, Popup, SetupActivity};
|
||||
use super::SetupActivity;
|
||||
use crate::ui::layout::Payload;
|
||||
// Ext
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use std::env;
|
||||
|
||||
impl SetupActivity {
|
||||
/// ### callback_nothing_to_do
|
||||
/// ### action_save_config
|
||||
///
|
||||
/// Self titled
|
||||
pub(super) fn callback_nothing_to_do(&mut self) {}
|
||||
/// Save configuration
|
||||
pub(super) fn action_save_config(&mut self) -> Result<(), String> {
|
||||
// Collect input values
|
||||
self.collect_input_values();
|
||||
self.save_config()
|
||||
}
|
||||
|
||||
/// ### callback_save_config_and_quit
|
||||
/// ### action_reset_config
|
||||
///
|
||||
/// Save configuration and quit
|
||||
pub(super) fn callback_save_config_and_quit(&mut self) {
|
||||
match self.save_config() {
|
||||
Ok(_) => self.quit = true, // Quit after successful save
|
||||
Err(err) => self.popup = Some(Popup::Alert(Color::Red, err)), // Show error and don't quit
|
||||
/// Reset configuration input fields
|
||||
pub(super) fn action_reset_config(&mut self) -> Result<(), String> {
|
||||
match self.reset_config_changes() {
|
||||
Err(err) => Err(err),
|
||||
Ok(_) => {
|
||||
self.load_input_values();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### callback_save_config
|
||||
/// ### action_delete_ssh_key
|
||||
///
|
||||
/// Save configuration callback
|
||||
pub(super) fn callback_save_config(&mut self) {
|
||||
if let Err(err) = self.save_config() {
|
||||
self.popup = Some(Popup::Alert(Color::Red, err)); // Show save error
|
||||
}
|
||||
}
|
||||
|
||||
/// ### callback_reset_config_changes
|
||||
///
|
||||
/// Reset config changes callback
|
||||
pub(super) fn callback_reset_config_changes(&mut self) {
|
||||
if let Err(err) = self.reset_config_changes() {
|
||||
self.popup = Some(Popup::Alert(Color::Red, err)); // Show reset error
|
||||
}
|
||||
}
|
||||
|
||||
/// ### callback_delete_ssh_key
|
||||
///
|
||||
/// Callback for performing the delete of a ssh key
|
||||
pub(super) fn callback_delete_ssh_key(&mut self) {
|
||||
/// delete of a ssh key
|
||||
pub(super) fn action_delete_ssh_key(&mut self) {
|
||||
// Get key
|
||||
if let Some(config_cli) = self.context.as_mut().unwrap().config_client.as_mut() {
|
||||
let key: Option<String> = match config_cli.iter_ssh_keys().nth(self.ssh_key_idx) {
|
||||
Some(k) => Some(k.clone()),
|
||||
None => None,
|
||||
// get index
|
||||
let idx: Option<usize> = match self.view.get_value(super::COMPONENT_LIST_SSH_KEYS) {
|
||||
Some(Payload::Unsigned(idx)) => Some(idx),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(key) = key {
|
||||
match config_cli.get_ssh_key(&key) {
|
||||
Ok(opt) => {
|
||||
if let Some((host, username, _)) = opt {
|
||||
if let Err(err) = self.delete_ssh_key(host.as_str(), username.as_str())
|
||||
{
|
||||
// Report error
|
||||
self.popup = Some(Popup::Alert(Color::Red, err));
|
||||
if let Some(idx) = idx {
|
||||
let key: Option<String> = match config_cli.iter_ssh_keys().nth(idx) {
|
||||
Some(k) => Some(k.clone()),
|
||||
None => None,
|
||||
};
|
||||
if let Some(key) = key {
|
||||
match config_cli.get_ssh_key(&key) {
|
||||
Ok(opt) => {
|
||||
if let Some((host, username, _)) = opt {
|
||||
if let Err(err) =
|
||||
self.delete_ssh_key(host.as_str(), username.as_str())
|
||||
{
|
||||
// Report error
|
||||
self.mount_error(err.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
// Report error
|
||||
self.mount_error(
|
||||
format!("Could not get ssh key \"{}\": {}", key, err).as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
self.popup = Some(Popup::Alert(
|
||||
Color::Red,
|
||||
format!("Could not get ssh key \"{}\": {}", key, err),
|
||||
))
|
||||
} // Report error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### callback_new_ssh_key
|
||||
/// ### action_new_ssh_key
|
||||
///
|
||||
/// Create a new ssh key with provided parameters
|
||||
pub(super) fn callback_new_ssh_key(&mut self, host: String, username: String) {
|
||||
/// Create a new ssh key
|
||||
pub(super) fn action_new_ssh_key(&mut self) {
|
||||
if let Some(cli) = self.context.as_mut().unwrap().config_client.as_mut() {
|
||||
// get parameters
|
||||
let host: String = match self.view.get_value(super::COMPONENT_INPUT_SSH_HOST) {
|
||||
Some(Payload::Text(host)) => host,
|
||||
_ => String::new(),
|
||||
};
|
||||
let username: String = match self.view.get_value(super::COMPONENT_INPUT_SSH_USERNAME) {
|
||||
Some(Payload::Text(user)) => user,
|
||||
_ => String::new(),
|
||||
};
|
||||
// Prepare text editor
|
||||
env::set_var("EDITOR", cli.get_text_editor());
|
||||
let placeholder: String = format!("# Type private SSH key for {}@{}\n", username, host);
|
||||
@@ -119,25 +126,23 @@ impl SetupActivity {
|
||||
let rsa_key: String = rsa_key.as_str().replace(placeholder.as_str(), "");
|
||||
if rsa_key.is_empty() {
|
||||
// Report error: empty key
|
||||
self.popup = Some(Popup::Alert(Color::Red, "SSH Key is empty".to_string()));
|
||||
self.mount_error("SSH key is empty!");
|
||||
} else {
|
||||
// Add key
|
||||
if let Err(err) =
|
||||
self.add_ssh_key(host.as_str(), username.as_str(), rsa_key.as_str())
|
||||
{
|
||||
self.popup = Some(Popup::Alert(
|
||||
Color::Red,
|
||||
format!("Could not create new private key: {}", err),
|
||||
))
|
||||
self.mount_error(
|
||||
format!("Could not create new private key: {}", err).as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
// Report error
|
||||
self.popup = Some(Popup::Alert(
|
||||
Color::Red,
|
||||
format!("Could not write private key to file: {}", err),
|
||||
))
|
||||
self.mount_error(
|
||||
format!("Could not write private key to file: {}", err).as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Restore terminal
|
||||
@@ -77,7 +77,7 @@ impl SetupActivity {
|
||||
/// ### edit_ssh_key
|
||||
///
|
||||
/// Edit selected ssh key
|
||||
pub(super) fn edit_ssh_key(&mut self) -> Result<(), String> {
|
||||
pub(super) fn edit_ssh_key(&mut self, idx: usize) -> Result<(), String> {
|
||||
match self.context.as_mut() {
|
||||
None => Ok(()),
|
||||
Some(ctx) => {
|
||||
@@ -91,7 +91,7 @@ impl SetupActivity {
|
||||
ctx.leave_alternate_screen();
|
||||
// Get result
|
||||
let result: Result<(), String> = match ctx.config_client.as_ref() {
|
||||
Some(config_cli) => match config_cli.iter_ssh_keys().nth(self.ssh_key_idx) {
|
||||
Some(config_cli) => match config_cli.iter_ssh_keys().nth(idx) {
|
||||
Some(key) => {
|
||||
// Get key path
|
||||
match config_cli.get_ssh_key(key) {
|
||||
|
||||
@@ -1,572 +0,0 @@
|
||||
//! ## SetupActivity
|
||||
//!
|
||||
//! `setup_activity` is the module which implements the Setup activity, which is the activity to
|
||||
//! work on termscp configuration
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
* TermSCP is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* TermSCP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with TermSCP. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Locals
|
||||
use super::{
|
||||
InputEvent, OnChoiceCallback, Popup, QuitDialogOption, SetupActivity, SetupTab,
|
||||
UserInterfaceInputField, YesNoDialogOption,
|
||||
};
|
||||
use crate::filetransfer::FileTransferProtocol;
|
||||
use crate::fs::explorer::GroupDirs;
|
||||
// Ext
|
||||
use crossterm::event::{KeyCode, KeyModifiers};
|
||||
use std::path::PathBuf;
|
||||
use tui::style::Color;
|
||||
|
||||
impl SetupActivity {
|
||||
/// ### handle_input_event
|
||||
///
|
||||
/// Handle input event, based on current input mode
|
||||
pub(super) fn handle_input_event(&mut self, ev: &InputEvent) {
|
||||
let popup: Option<Popup> = match &self.popup {
|
||||
Some(ptype) => Some(ptype.clone()),
|
||||
None => None,
|
||||
};
|
||||
match &self.popup {
|
||||
Some(_) => self.handle_input_event_popup(ev, popup.unwrap()),
|
||||
None => self.handle_input_event_forms(ev),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### handle_input_event_forms
|
||||
///
|
||||
/// Handle input event when popup is not visible.
|
||||
/// InputEvent is handled based on current tab
|
||||
fn handle_input_event_forms(&mut self, ev: &InputEvent) {
|
||||
// Match tab
|
||||
match &self.tab {
|
||||
SetupTab::SshConfig => self.handle_input_event_forms_ssh_config(ev),
|
||||
SetupTab::UserInterface(_) => self.handle_input_event_forms_ui(ev),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### handle_input_event_forms_ssh_config
|
||||
///
|
||||
/// Handle input event when in ssh config tab
|
||||
fn handle_input_event_forms_ssh_config(&mut self, ev: &InputEvent) {
|
||||
// Match input event
|
||||
if let InputEvent::Key(key) = ev {
|
||||
// Match key code
|
||||
match key.code {
|
||||
KeyCode::Esc => self.popup = Some(Popup::Quit), // Prompt quit
|
||||
KeyCode::Tab => {
|
||||
self.tab = SetupTab::UserInterface(UserInterfaceInputField::DefaultProtocol)
|
||||
} // Switch tab to user interface config
|
||||
KeyCode::Up => {
|
||||
if let Some(config_cli) = self.context.as_ref().unwrap().config_client.as_ref()
|
||||
{
|
||||
// Move ssh key index up
|
||||
let ssh_key_size: usize = config_cli.iter_ssh_keys().count();
|
||||
if self.ssh_key_idx > 0 {
|
||||
// Decrement
|
||||
self.ssh_key_idx -= 1;
|
||||
} else {
|
||||
// Set ssh key index to `ssh_key_size -1`
|
||||
self.ssh_key_idx = ssh_key_size - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::Down => {
|
||||
if let Some(config_cli) = self.context.as_ref().unwrap().config_client.as_ref()
|
||||
{
|
||||
// Move ssh key index down
|
||||
let ssh_key_size: usize = config_cli.iter_ssh_keys().count();
|
||||
if self.ssh_key_idx + 1 < ssh_key_size {
|
||||
// Increment index
|
||||
self.ssh_key_idx += 1;
|
||||
} else {
|
||||
// Wrap to 0
|
||||
self.ssh_key_idx = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::Delete => {
|
||||
// Prompt to delete selected key
|
||||
self.yesno_opt = YesNoDialogOption::No; // Default to no
|
||||
self.popup = Some(Popup::YesNo(
|
||||
String::from("Delete key?"),
|
||||
Self::callback_delete_ssh_key,
|
||||
Self::callback_nothing_to_do,
|
||||
));
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
// Edit selected key
|
||||
if let Err(err) = self.edit_ssh_key() {
|
||||
self.popup = Some(Popup::Alert(Color::Red, err)); // Report error
|
||||
}
|
||||
}
|
||||
KeyCode::Char(ch) => {
|
||||
// Check if <CTRL> is enabled
|
||||
if key.modifiers.intersects(KeyModifiers::CONTROL) {
|
||||
// Match char
|
||||
match ch {
|
||||
'e' | 'E' => {
|
||||
// Prompt to delete selected key
|
||||
self.yesno_opt = YesNoDialogOption::No; // Default to no
|
||||
self.popup = Some(Popup::YesNo(
|
||||
String::from("Delete key?"),
|
||||
Self::callback_delete_ssh_key,
|
||||
Self::callback_nothing_to_do,
|
||||
));
|
||||
}
|
||||
'h' | 'H' => {
|
||||
// Show help
|
||||
self.popup = Some(Popup::Help);
|
||||
}
|
||||
'n' | 'N' => {
|
||||
// New ssh key
|
||||
self.popup = Some(Popup::NewSshKey);
|
||||
}
|
||||
'r' | 'R' => {
|
||||
// Show reset changes dialog
|
||||
self.popup = Some(Popup::YesNo(
|
||||
String::from("Reset changes?"),
|
||||
Self::callback_reset_config_changes,
|
||||
Self::callback_nothing_to_do,
|
||||
));
|
||||
}
|
||||
's' | 'S' => {
|
||||
// Show save dialog
|
||||
self.popup = Some(Popup::YesNo(
|
||||
String::from("Save changes to configuration?"),
|
||||
Self::callback_save_config,
|
||||
Self::callback_nothing_to_do,
|
||||
));
|
||||
}
|
||||
_ => { /* Nothing to do */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => { /* Nothing to do */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### handle_input_event_forms_ui
|
||||
///
|
||||
/// Handle input event when in UserInterface config tab
|
||||
fn handle_input_event_forms_ui(&mut self, ev: &InputEvent) {
|
||||
// Get `UserInterfaceInputField`
|
||||
let field: UserInterfaceInputField = match &self.tab {
|
||||
SetupTab::UserInterface(field) => field.clone(),
|
||||
_ => return,
|
||||
};
|
||||
// Match input event
|
||||
if let InputEvent::Key(key) = ev {
|
||||
// Match key code
|
||||
match key.code {
|
||||
KeyCode::Esc => self.popup = Some(Popup::Quit), // Prompt quit
|
||||
KeyCode::Tab => self.tab = SetupTab::SshConfig, // Switch tab to ssh config
|
||||
KeyCode::Backspace => {
|
||||
// Pop character from selected input
|
||||
if let Some(config_cli) = self.context.as_mut().unwrap().config_client.as_mut()
|
||||
{
|
||||
match field {
|
||||
UserInterfaceInputField::TextEditor => {
|
||||
// Pop from text editor
|
||||
let mut input: String = String::from(
|
||||
config_cli.get_text_editor().as_path().to_string_lossy(),
|
||||
);
|
||||
input.pop();
|
||||
// Update text editor value
|
||||
config_cli.set_text_editor(PathBuf::from(input.as_str()));
|
||||
}
|
||||
UserInterfaceInputField::FileFmt => {
|
||||
// Push char to current file fmt
|
||||
let mut file_fmt = config_cli.get_file_fmt().unwrap_or_default();
|
||||
// Pop from file fmt
|
||||
file_fmt.pop();
|
||||
// If len is 0, will become None
|
||||
config_cli.set_file_fmt(file_fmt);
|
||||
}
|
||||
_ => { /* Not a text field */ }
|
||||
}
|
||||
// NOTE: replace with match if other text fields are added
|
||||
if matches!(field, UserInterfaceInputField::TextEditor) {}
|
||||
}
|
||||
}
|
||||
KeyCode::Left => {
|
||||
// Move left on fields which are tabs
|
||||
if let Some(config_cli) = self.context.as_mut().unwrap().config_client.as_mut()
|
||||
{
|
||||
match field {
|
||||
UserInterfaceInputField::DefaultProtocol => {
|
||||
// Move left
|
||||
config_cli.set_default_protocol(
|
||||
match config_cli.get_default_protocol() {
|
||||
FileTransferProtocol::Ftp(secure) => match secure {
|
||||
true => FileTransferProtocol::Ftp(false),
|
||||
false => FileTransferProtocol::Scp,
|
||||
},
|
||||
FileTransferProtocol::Scp => FileTransferProtocol::Sftp,
|
||||
FileTransferProtocol::Sftp => {
|
||||
FileTransferProtocol::Ftp(true)
|
||||
} // Wrap
|
||||
},
|
||||
);
|
||||
}
|
||||
UserInterfaceInputField::GroupDirs => {
|
||||
// Move left
|
||||
config_cli.set_group_dirs(match config_cli.get_group_dirs() {
|
||||
None => Some(GroupDirs::Last),
|
||||
Some(val) => match val {
|
||||
GroupDirs::Last => Some(GroupDirs::First),
|
||||
GroupDirs::First => None,
|
||||
},
|
||||
});
|
||||
}
|
||||
UserInterfaceInputField::ShowHiddenFiles => {
|
||||
// Move left
|
||||
config_cli.set_show_hidden_files(true);
|
||||
}
|
||||
UserInterfaceInputField::CheckForUpdates => {
|
||||
// move left
|
||||
config_cli.set_check_for_updates(true);
|
||||
}
|
||||
_ => { /* Not a tab field */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::Right => {
|
||||
// Move right on fields which are tabs
|
||||
if let Some(config_cli) = self.context.as_mut().unwrap().config_client.as_mut()
|
||||
{
|
||||
match field {
|
||||
UserInterfaceInputField::DefaultProtocol => {
|
||||
// Move left
|
||||
config_cli.set_default_protocol(
|
||||
match config_cli.get_default_protocol() {
|
||||
FileTransferProtocol::Sftp => FileTransferProtocol::Scp,
|
||||
FileTransferProtocol::Scp => {
|
||||
FileTransferProtocol::Ftp(false)
|
||||
}
|
||||
FileTransferProtocol::Ftp(secure) => match secure {
|
||||
false => FileTransferProtocol::Ftp(true),
|
||||
true => FileTransferProtocol::Sftp, // Wrap
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
UserInterfaceInputField::GroupDirs => {
|
||||
// Move right
|
||||
config_cli.set_group_dirs(match config_cli.get_group_dirs() {
|
||||
Some(val) => match val {
|
||||
GroupDirs::First => Some(GroupDirs::Last),
|
||||
GroupDirs::Last => None,
|
||||
},
|
||||
None => Some(GroupDirs::First),
|
||||
});
|
||||
}
|
||||
UserInterfaceInputField::ShowHiddenFiles => {
|
||||
// Move right
|
||||
config_cli.set_show_hidden_files(false);
|
||||
}
|
||||
UserInterfaceInputField::CheckForUpdates => {
|
||||
// move right
|
||||
config_cli.set_check_for_updates(false);
|
||||
}
|
||||
_ => { /* Not a tab field */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::Up => {
|
||||
// Change selected field
|
||||
self.tab = SetupTab::UserInterface(match field {
|
||||
UserInterfaceInputField::FileFmt => UserInterfaceInputField::GroupDirs,
|
||||
UserInterfaceInputField::GroupDirs => {
|
||||
UserInterfaceInputField::CheckForUpdates
|
||||
}
|
||||
UserInterfaceInputField::CheckForUpdates => {
|
||||
UserInterfaceInputField::ShowHiddenFiles
|
||||
}
|
||||
UserInterfaceInputField::ShowHiddenFiles => {
|
||||
UserInterfaceInputField::DefaultProtocol
|
||||
}
|
||||
UserInterfaceInputField::DefaultProtocol => {
|
||||
UserInterfaceInputField::TextEditor
|
||||
}
|
||||
UserInterfaceInputField::TextEditor => UserInterfaceInputField::FileFmt, // Wrap
|
||||
});
|
||||
}
|
||||
KeyCode::Down => {
|
||||
// Change selected field
|
||||
self.tab = SetupTab::UserInterface(match field {
|
||||
UserInterfaceInputField::TextEditor => {
|
||||
UserInterfaceInputField::DefaultProtocol
|
||||
}
|
||||
UserInterfaceInputField::DefaultProtocol => {
|
||||
UserInterfaceInputField::ShowHiddenFiles
|
||||
}
|
||||
UserInterfaceInputField::ShowHiddenFiles => {
|
||||
UserInterfaceInputField::CheckForUpdates
|
||||
}
|
||||
UserInterfaceInputField::CheckForUpdates => {
|
||||
UserInterfaceInputField::GroupDirs
|
||||
}
|
||||
UserInterfaceInputField::GroupDirs => UserInterfaceInputField::FileFmt,
|
||||
UserInterfaceInputField::FileFmt => UserInterfaceInputField::TextEditor, // Wrap
|
||||
});
|
||||
}
|
||||
KeyCode::Char(ch) => {
|
||||
// Check if <CTRL> is enabled
|
||||
if key.modifiers.intersects(KeyModifiers::CONTROL) {
|
||||
// Match char
|
||||
match ch {
|
||||
'h' | 'H' => {
|
||||
// Show help
|
||||
self.popup = Some(Popup::Help);
|
||||
}
|
||||
'r' | 'R' => {
|
||||
// Show reset changes dialog
|
||||
self.popup = Some(Popup::YesNo(
|
||||
String::from("Reset changes?"),
|
||||
Self::callback_reset_config_changes,
|
||||
Self::callback_nothing_to_do,
|
||||
));
|
||||
}
|
||||
's' | 'S' => {
|
||||
// Show save dialog
|
||||
self.popup = Some(Popup::YesNo(
|
||||
String::from("Save changes to configuration?"),
|
||||
Self::callback_save_config,
|
||||
Self::callback_nothing_to_do,
|
||||
));
|
||||
}
|
||||
_ => { /* Nothing to do */ }
|
||||
}
|
||||
} else {
|
||||
// Push character to input field
|
||||
if let Some(config_cli) =
|
||||
self.context.as_mut().unwrap().config_client.as_mut()
|
||||
{
|
||||
// NOTE: change to match if other fields are added
|
||||
match field {
|
||||
UserInterfaceInputField::TextEditor => {
|
||||
// Get current text editor and push character
|
||||
let mut input: String = String::from(
|
||||
config_cli.get_text_editor().as_path().to_string_lossy(),
|
||||
);
|
||||
input.push(ch);
|
||||
// Update text editor value
|
||||
config_cli.set_text_editor(PathBuf::from(input.as_str()));
|
||||
}
|
||||
UserInterfaceInputField::FileFmt => {
|
||||
// Push char to current file fmt
|
||||
let mut file_fmt =
|
||||
config_cli.get_file_fmt().unwrap_or_default();
|
||||
file_fmt.push(ch);
|
||||
// update value
|
||||
config_cli.set_file_fmt(file_fmt);
|
||||
}
|
||||
_ => { /* Not a text field */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => { /* Nothing to do */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### handle_input_event_popup
|
||||
///
|
||||
/// Handler for input event when popup is visible
|
||||
fn handle_input_event_popup(&mut self, ev: &InputEvent, ptype: Popup) {
|
||||
match ptype {
|
||||
Popup::Alert(_, _) => self.handle_input_event_mode_popup_alert(ev),
|
||||
Popup::Fatal(_) => self.handle_input_event_mode_popup_fatal(ev),
|
||||
Popup::Help => self.handle_input_event_mode_popup_help(ev),
|
||||
Popup::NewSshKey => self.handle_input_event_mode_popup_newsshkey(ev),
|
||||
Popup::Quit => self.handle_input_event_mode_popup_quit(ev),
|
||||
Popup::YesNo(_, yes_cb, no_cb) => {
|
||||
self.handle_input_event_mode_popup_yesno(ev, yes_cb, no_cb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### handle_input_event_mode_popup_alert
|
||||
///
|
||||
/// Handle input event when the input mode is popup, and popup type is alert
|
||||
fn handle_input_event_mode_popup_alert(&mut self, ev: &InputEvent) {
|
||||
// Only enter should be allowed here
|
||||
if let InputEvent::Key(key) = ev {
|
||||
if matches!(key.code, KeyCode::Esc | KeyCode::Enter) {
|
||||
self.popup = None; // Hide popup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### handle_input_event_mode_popup_fatal
|
||||
///
|
||||
/// Handle input event when the input mode is popup, and popup type is fatal
|
||||
fn handle_input_event_mode_popup_fatal(&mut self, ev: &InputEvent) {
|
||||
// Only enter should be allowed here
|
||||
if let InputEvent::Key(key) = ev {
|
||||
if matches!(key.code, KeyCode::Esc | KeyCode::Enter) {
|
||||
// Quit after acknowelding fatal error
|
||||
self.quit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### handle_input_event_mode_popup_help
|
||||
///
|
||||
/// Input event handler for popup help
|
||||
fn handle_input_event_mode_popup_help(&mut self, ev: &InputEvent) {
|
||||
// If enter, close popup
|
||||
if let InputEvent::Key(key) = ev {
|
||||
if matches!(key.code, KeyCode::Esc | KeyCode::Enter) {
|
||||
self.popup = None; // Hide popup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### handle_input_event_mode_popup_newsshkey
|
||||
///
|
||||
/// Handle input events for `Popup::NewSshKey`
|
||||
fn handle_input_event_mode_popup_newsshkey(&mut self, ev: &InputEvent) {
|
||||
// If enter, close popup, otherwise push chars to input
|
||||
if let InputEvent::Key(key) = ev {
|
||||
match key.code {
|
||||
KeyCode::Esc => {
|
||||
// Abort input
|
||||
// Clear buffer
|
||||
self.clear_user_input();
|
||||
// Hide popup
|
||||
self.popup = None;
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
// Submit
|
||||
let address: String = self.user_input.get(0).unwrap().to_string();
|
||||
let username: String = self.user_input.get(1).unwrap().to_string();
|
||||
// Clear buffer
|
||||
self.clear_user_input();
|
||||
// Close popup BEFORE CALLBACKS!!! Callback can then overwrite this, clever uh?
|
||||
self.popup = None;
|
||||
// Reset user ptr
|
||||
self.user_input_ptr = 0;
|
||||
// Call cb
|
||||
self.callback_new_ssh_key(address, username);
|
||||
}
|
||||
KeyCode::Up => {
|
||||
// Move ptr up, or to maximum index (1)
|
||||
self.user_input_ptr = match self.user_input_ptr {
|
||||
1 => 0,
|
||||
_ => 1, // Wrap
|
||||
};
|
||||
}
|
||||
KeyCode::Down => {
|
||||
// Move ptr down, or to minimum index (0)
|
||||
self.user_input_ptr = match self.user_input_ptr {
|
||||
0 => 1,
|
||||
_ => 0, // Wrap
|
||||
}
|
||||
}
|
||||
KeyCode::Char(ch) => {
|
||||
// Get current input
|
||||
let input: &mut String = self.user_input.get_mut(self.user_input_ptr).unwrap();
|
||||
input.push(ch);
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
let input: &mut String = self.user_input.get_mut(self.user_input_ptr).unwrap();
|
||||
input.pop();
|
||||
}
|
||||
_ => { /* Nothing to do */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### handle_input_event_mode_popup_quit
|
||||
///
|
||||
/// Handle input events for `Popup::Quit`
|
||||
fn handle_input_event_mode_popup_quit(&mut self, ev: &InputEvent) {
|
||||
if let InputEvent::Key(key) = ev {
|
||||
match key.code {
|
||||
KeyCode::Esc => {
|
||||
// Hide popup
|
||||
self.popup = None;
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
// Perform enter, based on current choice
|
||||
match self.quit_opt {
|
||||
QuitDialogOption::Cancel => self.popup = None, // Hide popup
|
||||
QuitDialogOption::DontSave => self.quit = true, // Just quit
|
||||
QuitDialogOption::Save => self.callback_save_config_and_quit(), // Save and quit
|
||||
}
|
||||
// Reset choice
|
||||
self.quit_opt = QuitDialogOption::Save;
|
||||
}
|
||||
KeyCode::Right => {
|
||||
// Change option
|
||||
self.quit_opt = match self.quit_opt {
|
||||
QuitDialogOption::Save => QuitDialogOption::DontSave,
|
||||
QuitDialogOption::DontSave => QuitDialogOption::Cancel,
|
||||
QuitDialogOption::Cancel => QuitDialogOption::Save, // Wrap
|
||||
}
|
||||
}
|
||||
KeyCode::Left => {
|
||||
// Change option
|
||||
self.quit_opt = match self.quit_opt {
|
||||
QuitDialogOption::Cancel => QuitDialogOption::DontSave,
|
||||
QuitDialogOption::DontSave => QuitDialogOption::Save,
|
||||
QuitDialogOption::Save => QuitDialogOption::Cancel, // Wrap
|
||||
}
|
||||
}
|
||||
_ => { /* Nothing to do */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### handle_input_event_mode_popup_yesno
|
||||
///
|
||||
/// Input event handler for popup alert
|
||||
fn handle_input_event_mode_popup_yesno(
|
||||
&mut self,
|
||||
ev: &InputEvent,
|
||||
yes_cb: OnChoiceCallback,
|
||||
no_cb: OnChoiceCallback,
|
||||
) {
|
||||
// If enter, close popup, otherwise move dialog option
|
||||
if let InputEvent::Key(key) = ev {
|
||||
match key.code {
|
||||
KeyCode::Enter => {
|
||||
// Hide popup BEFORE CALLBACKS!!! Callback can then overwrite this, clever uh?
|
||||
self.popup = None;
|
||||
// Check if user selected yes or not
|
||||
match self.yesno_opt {
|
||||
YesNoDialogOption::No => no_cb(self),
|
||||
YesNoDialogOption::Yes => yes_cb(self),
|
||||
}
|
||||
// Reset choice option to yes
|
||||
self.yesno_opt = YesNoDialogOption::Yes;
|
||||
}
|
||||
KeyCode::Right => self.yesno_opt = YesNoDialogOption::No, // Set to NO
|
||||
KeyCode::Left => self.yesno_opt = YesNoDialogOption::Yes, // Set to YES
|
||||
_ => { /* Nothing to do */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,788 +0,0 @@
|
||||
//! ## SetupActivity
|
||||
//!
|
||||
//! `setup_activity` is the module which implements the Setup activity, which is the activity to
|
||||
//! work on termscp configuration
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
* TermSCP is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* TermSCP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with TermSCP. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use super::{
|
||||
Context, Popup, QuitDialogOption, SetupActivity, SetupTab, UserInterfaceInputField,
|
||||
YesNoDialogOption,
|
||||
};
|
||||
use crate::filetransfer::FileTransferProtocol;
|
||||
use crate::fs::explorer::GroupDirs;
|
||||
use crate::system::config_client::ConfigClient;
|
||||
use crate::utils::fmt::align_text_center;
|
||||
// Ext
|
||||
use tui::{
|
||||
layout::{Constraint, Corner, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Span, Spans, Text},
|
||||
widgets::{Block, BorderType, Borders, Clear, List, ListItem, ListState, Paragraph, Tabs},
|
||||
};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
impl SetupActivity {
|
||||
/// ### draw
|
||||
///
|
||||
/// Draw UI
|
||||
pub(super) fn draw(&mut self) {
|
||||
let mut ctx: Context = self.context.take().unwrap();
|
||||
let config_client: Option<&ConfigClient> = ctx.config_client.as_ref();
|
||||
let _ = ctx.terminal.draw(|f| {
|
||||
// Prepare main chunks
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(1)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3), // Current tab
|
||||
Constraint::Percentage(90), // Main body
|
||||
Constraint::Length(3), // Help footer
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(f.size());
|
||||
// Prepare selected tab
|
||||
f.render_widget(self.draw_selected_tab(), chunks[0]);
|
||||
// Draw main layout
|
||||
match &self.tab {
|
||||
SetupTab::SshConfig => {
|
||||
// Draw ssh config
|
||||
// Create explorer chunks
|
||||
let sshcfg_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Percentage(100)].as_ref())
|
||||
.split(chunks[1]);
|
||||
if let Some(ssh_key_tab) = self.draw_ssh_keys_list(config_client) {
|
||||
// Create ssh list state
|
||||
let mut ssh_key_state: ListState = ListState::default();
|
||||
ssh_key_state.select(Some(self.ssh_key_idx));
|
||||
// Render ssh keys
|
||||
f.render_stateful_widget(ssh_key_tab, sshcfg_chunks[0], &mut ssh_key_state);
|
||||
}
|
||||
}
|
||||
SetupTab::UserInterface(form_field) => {
|
||||
// Create chunks
|
||||
let ui_cfg_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(1),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(chunks[1]);
|
||||
// Render input forms
|
||||
if let Some(field) = self.draw_text_editor_input(config_client) {
|
||||
f.render_widget(field, ui_cfg_chunks[0]);
|
||||
}
|
||||
if let Some(tab) = self.draw_default_protocol_tab(config_client) {
|
||||
f.render_widget(tab, ui_cfg_chunks[1]);
|
||||
}
|
||||
if let Some(tab) = self.draw_hidden_files_tab(config_client) {
|
||||
f.render_widget(tab, ui_cfg_chunks[2]);
|
||||
}
|
||||
if let Some(tab) = self.draw_check_for_updates_tab(config_client) {
|
||||
f.render_widget(tab, ui_cfg_chunks[3]);
|
||||
}
|
||||
if let Some(tab) = self.draw_default_group_dirs_tab(config_client) {
|
||||
f.render_widget(tab, ui_cfg_chunks[4]);
|
||||
}
|
||||
if let Some(tab) = self.draw_file_fmt_input(config_client) {
|
||||
f.render_widget(tab, ui_cfg_chunks[5]);
|
||||
}
|
||||
// Set cursor
|
||||
if let Some(cli) = config_client {
|
||||
match form_field {
|
||||
UserInterfaceInputField::TextEditor => {
|
||||
let editor_text: String =
|
||||
String::from(cli.get_text_editor().as_path().to_string_lossy());
|
||||
f.set_cursor(
|
||||
ui_cfg_chunks[0].x + editor_text.width() as u16 + 1,
|
||||
ui_cfg_chunks[0].y + 1,
|
||||
);
|
||||
}
|
||||
UserInterfaceInputField::FileFmt => {
|
||||
let file_fmt: String = cli.get_file_fmt().unwrap_or_default();
|
||||
f.set_cursor(
|
||||
ui_cfg_chunks[5].x + file_fmt.width() as u16 + 1,
|
||||
ui_cfg_chunks[5].y + 1,
|
||||
);
|
||||
}
|
||||
_ => { /* Not a text field */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Draw footer
|
||||
f.render_widget(self.draw_footer(), chunks[2]);
|
||||
// Draw popup
|
||||
if let Some(popup) = &self.popup {
|
||||
// Calculate popup size
|
||||
let (width, height): (u16, u16) = match popup {
|
||||
Popup::Alert(_, _) | Popup::Fatal(_) => (50, 10),
|
||||
Popup::Help => (50, 70),
|
||||
Popup::NewSshKey => (50, 20),
|
||||
Popup::Quit => (40, 10),
|
||||
Popup::YesNo(_, _, _) => (30, 10),
|
||||
};
|
||||
let popup_area: Rect = self.draw_popup_area(f.size(), width, height);
|
||||
f.render_widget(Clear, popup_area); //this clears out the background
|
||||
match popup {
|
||||
Popup::Alert(color, txt) => f.render_widget(
|
||||
self.draw_popup_alert(*color, txt.clone(), popup_area.width),
|
||||
popup_area,
|
||||
),
|
||||
Popup::Fatal(txt) => f.render_widget(
|
||||
self.draw_popup_fatal(txt.clone(), popup_area.width),
|
||||
popup_area,
|
||||
),
|
||||
Popup::Help => f.render_widget(self.draw_popup_help(), popup_area),
|
||||
Popup::NewSshKey => {
|
||||
let popup_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3), // Address form
|
||||
Constraint::Length(3), // Username form
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(popup_area);
|
||||
let (address_form, username_form): (Paragraph, Paragraph) =
|
||||
self.draw_popup_new_ssh_key();
|
||||
// Render parts
|
||||
f.render_widget(address_form, popup_chunks[0]);
|
||||
f.render_widget(username_form, popup_chunks[1]);
|
||||
// Set cursor to popup form
|
||||
if self.user_input_ptr < 2 {
|
||||
if let Some(selected_text) = self.user_input.get(self.user_input_ptr) {
|
||||
// Set cursor
|
||||
f.set_cursor(
|
||||
popup_chunks[self.user_input_ptr].x
|
||||
+ selected_text.width() as u16
|
||||
+ 1,
|
||||
popup_chunks[self.user_input_ptr].y + 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Popup::Quit => f.render_widget(self.draw_popup_quit(), popup_area),
|
||||
Popup::YesNo(txt, _, _) => {
|
||||
f.render_widget(self.draw_popup_yesno(txt.clone()), popup_area)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
self.context = Some(ctx);
|
||||
}
|
||||
|
||||
/// ### draw_selecte_tab
|
||||
///
|
||||
/// Draw selected tab tab
|
||||
fn draw_selected_tab(&self) -> Tabs {
|
||||
let choices: Vec<Spans> = vec![Spans::from("User Interface"), Spans::from("SSH Keys")];
|
||||
let index: usize = match self.tab {
|
||||
SetupTab::UserInterface(_) => 0,
|
||||
SetupTab::SshConfig => 1,
|
||||
};
|
||||
Tabs::new(choices)
|
||||
.block(Block::default().borders(Borders::BOTTOM).title("Setup"))
|
||||
.select(index)
|
||||
.style(Style::default())
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.add_modifier(Modifier::BOLD)
|
||||
.fg(Color::Yellow),
|
||||
)
|
||||
}
|
||||
|
||||
/// ### draw_footer
|
||||
///
|
||||
/// Draw authentication page footer
|
||||
fn draw_footer(&self) -> Paragraph {
|
||||
// Write header
|
||||
let (footer, h_style) = (
|
||||
vec![
|
||||
Span::raw("Press "),
|
||||
Span::styled(
|
||||
"<CTRL+H>",
|
||||
Style::default()
|
||||
.add_modifier(Modifier::BOLD)
|
||||
.fg(Color::Cyan),
|
||||
),
|
||||
Span::raw(" to show keybindings"),
|
||||
],
|
||||
Style::default().add_modifier(Modifier::BOLD),
|
||||
);
|
||||
let mut footer_text = Text::from(Spans::from(footer));
|
||||
footer_text.patch_style(h_style);
|
||||
Paragraph::new(footer_text)
|
||||
}
|
||||
|
||||
/// ### draw_text_editor_input
|
||||
///
|
||||
/// Draw input text field for text editor parameter
|
||||
fn draw_text_editor_input(&self, config_cli: Option<&ConfigClient>) -> Option<Paragraph> {
|
||||
match config_cli.as_ref() {
|
||||
Some(cli) => Some(
|
||||
Paragraph::new(String::from(
|
||||
cli.get_text_editor().as_path().to_string_lossy(),
|
||||
))
|
||||
.style(Style::default().fg(match &self.tab {
|
||||
SetupTab::SshConfig => Color::White,
|
||||
SetupTab::UserInterface(field) => match field {
|
||||
UserInterfaceInputField::TextEditor => Color::LightGreen,
|
||||
_ => Color::White,
|
||||
},
|
||||
}))
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.title("Text Editor"),
|
||||
),
|
||||
),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// ### draw_default_protocol_tab
|
||||
///
|
||||
/// Draw default protocol input tab
|
||||
fn draw_default_protocol_tab(&self, config_cli: Option<&ConfigClient>) -> Option<Tabs> {
|
||||
// Check if config client is some
|
||||
match config_cli.as_ref() {
|
||||
Some(cli) => {
|
||||
let choices: Vec<Spans> = vec![
|
||||
Spans::from("SFTP"),
|
||||
Spans::from("SCP"),
|
||||
Spans::from("FTP"),
|
||||
Spans::from("FTPS"),
|
||||
];
|
||||
let index: usize = match cli.get_default_protocol() {
|
||||
FileTransferProtocol::Sftp => 0,
|
||||
FileTransferProtocol::Scp => 1,
|
||||
FileTransferProtocol::Ftp(secure) => match secure {
|
||||
false => 2,
|
||||
true => 3,
|
||||
},
|
||||
};
|
||||
let (bg, fg, block_fg): (Color, Color, Color) = match &self.tab {
|
||||
SetupTab::UserInterface(field) => match field {
|
||||
UserInterfaceInputField::DefaultProtocol => {
|
||||
(Color::Cyan, Color::Black, Color::Cyan)
|
||||
}
|
||||
_ => (Color::Reset, Color::Cyan, Color::Reset),
|
||||
},
|
||||
_ => (Color::Reset, Color::Reset, Color::Reset),
|
||||
};
|
||||
Some(
|
||||
Tabs::new(choices)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.style(Style::default().fg(block_fg))
|
||||
.title("Default File Transfer Protocol"),
|
||||
)
|
||||
.select(index)
|
||||
.style(Style::default())
|
||||
.highlight_style(
|
||||
Style::default().add_modifier(Modifier::BOLD).fg(fg).bg(bg),
|
||||
),
|
||||
)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// ### draw_hidden_files_tab
|
||||
///
|
||||
/// Draw default hidden files tab
|
||||
fn draw_hidden_files_tab(&self, config_cli: Option<&ConfigClient>) -> Option<Tabs> {
|
||||
// Check if config client is some
|
||||
match config_cli.as_ref() {
|
||||
Some(cli) => {
|
||||
let choices: Vec<Spans> = vec![Spans::from("Yes"), Spans::from("No")];
|
||||
let index: usize = match cli.get_show_hidden_files() {
|
||||
true => 0,
|
||||
false => 1,
|
||||
};
|
||||
let (bg, fg, block_fg): (Color, Color, Color) = match &self.tab {
|
||||
SetupTab::UserInterface(field) => match field {
|
||||
UserInterfaceInputField::ShowHiddenFiles => {
|
||||
(Color::LightRed, Color::Black, Color::LightRed)
|
||||
}
|
||||
_ => (Color::Reset, Color::LightRed, Color::Reset),
|
||||
},
|
||||
_ => (Color::Reset, Color::Reset, Color::Reset),
|
||||
};
|
||||
Some(
|
||||
Tabs::new(choices)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.style(Style::default().fg(block_fg))
|
||||
.title("Show hidden files (by default)"),
|
||||
)
|
||||
.select(index)
|
||||
.style(Style::default())
|
||||
.highlight_style(
|
||||
Style::default().add_modifier(Modifier::BOLD).fg(fg).bg(bg),
|
||||
),
|
||||
)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// ### draw_check_for_updates_tab
|
||||
///
|
||||
/// Draw check for updates tab
|
||||
fn draw_check_for_updates_tab(&self, config_cli: Option<&ConfigClient>) -> Option<Tabs> {
|
||||
// Check if config client is some
|
||||
match config_cli.as_ref() {
|
||||
Some(cli) => {
|
||||
let choices: Vec<Spans> = vec![Spans::from("Yes"), Spans::from("No")];
|
||||
let index: usize = match cli.get_check_for_updates() {
|
||||
true => 0,
|
||||
false => 1,
|
||||
};
|
||||
let (bg, fg, block_fg): (Color, Color, Color) = match &self.tab {
|
||||
SetupTab::UserInterface(field) => match field {
|
||||
UserInterfaceInputField::CheckForUpdates => {
|
||||
(Color::LightYellow, Color::Black, Color::LightYellow)
|
||||
}
|
||||
_ => (Color::Reset, Color::LightYellow, Color::Reset),
|
||||
},
|
||||
_ => (Color::Reset, Color::Reset, Color::Reset),
|
||||
};
|
||||
Some(
|
||||
Tabs::new(choices)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.style(Style::default().fg(block_fg))
|
||||
.title("Check for updates?"),
|
||||
)
|
||||
.select(index)
|
||||
.style(Style::default())
|
||||
.highlight_style(
|
||||
Style::default().add_modifier(Modifier::BOLD).fg(fg).bg(bg),
|
||||
),
|
||||
)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// ### draw_default_group_dirs_tab
|
||||
///
|
||||
/// Draw group dirs input tab
|
||||
fn draw_default_group_dirs_tab(&self, config_cli: Option<&ConfigClient>) -> Option<Tabs> {
|
||||
// Check if config client is some
|
||||
match config_cli.as_ref() {
|
||||
Some(cli) => {
|
||||
let choices: Vec<Spans> = vec![
|
||||
Spans::from("Display First"),
|
||||
Spans::from("Display Last"),
|
||||
Spans::from("No"),
|
||||
];
|
||||
let index: usize = match cli.get_group_dirs() {
|
||||
None => 2,
|
||||
Some(val) => match val {
|
||||
GroupDirs::First => 0,
|
||||
GroupDirs::Last => 1,
|
||||
},
|
||||
};
|
||||
let (bg, fg, block_fg): (Color, Color, Color) = match &self.tab {
|
||||
SetupTab::UserInterface(field) => match field {
|
||||
UserInterfaceInputField::GroupDirs => {
|
||||
(Color::LightMagenta, Color::Black, Color::LightMagenta)
|
||||
}
|
||||
_ => (Color::Reset, Color::LightMagenta, Color::Reset),
|
||||
},
|
||||
_ => (Color::Reset, Color::Reset, Color::Reset),
|
||||
};
|
||||
Some(
|
||||
Tabs::new(choices)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.style(Style::default().fg(block_fg))
|
||||
.title("Group directories"),
|
||||
)
|
||||
.select(index)
|
||||
.style(Style::default())
|
||||
.highlight_style(
|
||||
Style::default().add_modifier(Modifier::BOLD).fg(fg).bg(bg),
|
||||
),
|
||||
)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// ### draw_file_fmt_input
|
||||
///
|
||||
/// Draw input text field for file fmt
|
||||
fn draw_file_fmt_input(&self, config_cli: Option<&ConfigClient>) -> Option<Paragraph> {
|
||||
match config_cli.as_ref() {
|
||||
Some(cli) => Some(
|
||||
Paragraph::new(cli.get_file_fmt().unwrap_or_default())
|
||||
.style(Style::default().fg(match &self.tab {
|
||||
SetupTab::SshConfig => Color::White,
|
||||
SetupTab::UserInterface(field) => match field {
|
||||
UserInterfaceInputField::FileFmt => Color::LightCyan,
|
||||
_ => Color::White,
|
||||
},
|
||||
}))
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.title("File formatter syntax"),
|
||||
),
|
||||
),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// ### draw_ssh_keys_list
|
||||
///
|
||||
/// Draw ssh keys list
|
||||
fn draw_ssh_keys_list(&self, config_cli: Option<&ConfigClient>) -> Option<List> {
|
||||
// Check if config client is some
|
||||
match config_cli.as_ref() {
|
||||
Some(cli) => {
|
||||
// Iterate over ssh keys
|
||||
let mut ssh_keys: Vec<ListItem> = Vec::with_capacity(cli.iter_ssh_keys().count());
|
||||
for key in cli.iter_ssh_keys() {
|
||||
if let Ok(Some((addr, username, _))) = cli.get_ssh_key(key) {
|
||||
ssh_keys.push(ListItem::new(Span::from(format!(
|
||||
"{} at {}",
|
||||
username, addr,
|
||||
))));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Return list
|
||||
Some(
|
||||
List::new(ssh_keys)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(Color::LightGreen))
|
||||
.title("SSH Keys"),
|
||||
)
|
||||
.start_corner(Corner::TopLeft)
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(Color::Black)
|
||||
.bg(Color::LightGreen)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// ### draw_popup_area
|
||||
///
|
||||
/// Draw popup area
|
||||
fn draw_popup_area(&self, area: Rect, width: u16, height: u16) -> Rect {
|
||||
let popup_layout = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Percentage((100 - height) / 2),
|
||||
Constraint::Percentage(height),
|
||||
Constraint::Percentage((100 - height) / 2),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(area);
|
||||
Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Percentage((100 - width) / 2),
|
||||
Constraint::Percentage(width),
|
||||
Constraint::Percentage((100 - width) / 2),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(popup_layout[1])[1]
|
||||
}
|
||||
|
||||
/// ### draw_popup_alert
|
||||
///
|
||||
/// Draw alert popup
|
||||
fn draw_popup_alert(&self, color: Color, text: String, width: u16) -> List {
|
||||
// Wraps texts
|
||||
let message_rows = textwrap::wrap(text.as_str(), width as usize);
|
||||
let mut lines: Vec<ListItem> = Vec::new();
|
||||
for msg in message_rows.iter() {
|
||||
lines.push(ListItem::new(Spans::from(align_text_center(msg, width))));
|
||||
}
|
||||
List::new(lines)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(color))
|
||||
.border_type(BorderType::Rounded)
|
||||
.title("Alert"),
|
||||
)
|
||||
.start_corner(Corner::TopLeft)
|
||||
.style(Style::default().fg(color))
|
||||
}
|
||||
|
||||
/// ### draw_popup_fatal
|
||||
///
|
||||
/// Draw fatal error popup
|
||||
fn draw_popup_fatal(&self, text: String, width: u16) -> List {
|
||||
self.draw_popup_alert(Color::Red, text, width)
|
||||
}
|
||||
|
||||
/// ### draw_popup_new_ssh_key
|
||||
///
|
||||
/// Draw new ssh key form popup
|
||||
fn draw_popup_new_ssh_key(&self) -> (Paragraph, Paragraph) {
|
||||
let address: Paragraph = Paragraph::new(self.user_input.get(0).unwrap().as_str())
|
||||
.style(Style::default().fg(match self.user_input_ptr {
|
||||
0 => Color::LightCyan,
|
||||
_ => Color::White,
|
||||
}))
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::TOP | Borders::RIGHT | Borders::LEFT)
|
||||
.border_type(BorderType::Rounded)
|
||||
.style(Style::default().fg(Color::White))
|
||||
.title("Host name or address"),
|
||||
);
|
||||
let username: Paragraph = Paragraph::new(self.user_input.get(1).unwrap().as_str())
|
||||
.style(Style::default().fg(match self.user_input_ptr {
|
||||
1 => Color::LightMagenta,
|
||||
_ => Color::White,
|
||||
}))
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::BOTTOM | Borders::RIGHT | Borders::LEFT)
|
||||
.border_type(BorderType::Rounded)
|
||||
.style(Style::default().fg(Color::White))
|
||||
.title("Username"),
|
||||
);
|
||||
(address, username)
|
||||
}
|
||||
|
||||
/// ### draw_popup_quit
|
||||
///
|
||||
/// Draw quit select popup
|
||||
fn draw_popup_quit(&self) -> Tabs {
|
||||
let choices: Vec<Spans> = vec![
|
||||
Spans::from("Save"),
|
||||
Spans::from("Don't save"),
|
||||
Spans::from("Cancel"),
|
||||
];
|
||||
let index: usize = match self.quit_opt {
|
||||
QuitDialogOption::Save => 0,
|
||||
QuitDialogOption::DontSave => 1,
|
||||
QuitDialogOption::Cancel => 2,
|
||||
};
|
||||
Tabs::new(choices)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.title("Exit setup?"),
|
||||
)
|
||||
.select(index)
|
||||
.style(Style::default())
|
||||
.highlight_style(Style::default().add_modifier(Modifier::BOLD).fg(Color::Red))
|
||||
}
|
||||
|
||||
/// ### draw_popup_yesno
|
||||
///
|
||||
/// Draw yes/no select popup
|
||||
fn draw_popup_yesno(&self, text: String) -> Tabs {
|
||||
let choices: Vec<Spans> = vec![Spans::from("Yes"), Spans::from("No")];
|
||||
let index: usize = match self.yesno_opt {
|
||||
YesNoDialogOption::Yes => 0,
|
||||
YesNoDialogOption::No => 1,
|
||||
};
|
||||
Tabs::new(choices)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.title(text),
|
||||
)
|
||||
.select(index)
|
||||
.style(Style::default())
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.add_modifier(Modifier::BOLD)
|
||||
.fg(Color::Yellow),
|
||||
)
|
||||
}
|
||||
|
||||
/// ### draw_popup_help
|
||||
///
|
||||
/// Draw authentication page help popup
|
||||
fn draw_popup_help(&self) -> List {
|
||||
// Write header
|
||||
let cmds: Vec<ListItem> = vec![
|
||||
ListItem::new(Spans::from(vec![
|
||||
Span::styled(
|
||||
"<ESC>",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::raw("Exit setup"),
|
||||
])),
|
||||
ListItem::new(Spans::from(vec![
|
||||
Span::styled(
|
||||
"<TAB>",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::raw("Change setup page"),
|
||||
])),
|
||||
ListItem::new(Spans::from(vec![
|
||||
Span::styled(
|
||||
"<RIGHT/LEFT>",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::raw("Change selected element in tab"),
|
||||
])),
|
||||
ListItem::new(Spans::from(vec![
|
||||
Span::styled(
|
||||
"<UP/DOWN>",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::raw("Change input field"),
|
||||
])),
|
||||
ListItem::new(Spans::from(vec![
|
||||
Span::styled(
|
||||
"<ENTER>",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::raw("Submit / Dismiss popup"),
|
||||
])),
|
||||
ListItem::new(Spans::from(vec![
|
||||
Span::styled(
|
||||
"<DEL>",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::raw("Delete entry"),
|
||||
])),
|
||||
ListItem::new(Spans::from(vec![
|
||||
Span::styled(
|
||||
"<CTRL+E>",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::raw("Delete entry"),
|
||||
])),
|
||||
ListItem::new(Spans::from(vec![
|
||||
Span::styled(
|
||||
"<CTRL+H>",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::raw("Show help"),
|
||||
])),
|
||||
ListItem::new(Spans::from(vec![
|
||||
Span::styled(
|
||||
"<CTRL+N>",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::raw("New SSH key"),
|
||||
])),
|
||||
ListItem::new(Spans::from(vec![
|
||||
Span::styled(
|
||||
"<CTRL+R>",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::raw("Revert changes"),
|
||||
])),
|
||||
ListItem::new(Spans::from(vec![
|
||||
Span::styled(
|
||||
"<CTRL+S>",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::raw("Save configuration"),
|
||||
])),
|
||||
];
|
||||
List::new(cmds)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default())
|
||||
.border_type(BorderType::Rounded)
|
||||
.title("Help"),
|
||||
)
|
||||
.start_corner(Corner::TopLeft)
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
//! ## SetupActivity
|
||||
//!
|
||||
//! `setup_activity` is the module which implements the Setup activity, which is the activity to
|
||||
//! work on termscp configuration
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
* TermSCP is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* TermSCP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with TermSCP. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use super::SetupActivity;
|
||||
|
||||
impl SetupActivity {
|
||||
/// ### clear_user_input
|
||||
///
|
||||
/// Clear user input buffers
|
||||
pub(super) fn clear_user_input(&mut self) {
|
||||
for s in self.user_input.iter_mut() {
|
||||
s.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,10 @@
|
||||
*/
|
||||
|
||||
// Submodules
|
||||
mod callbacks; // TOREM: this
|
||||
mod actions;
|
||||
mod config;
|
||||
mod input; // TOREM: this
|
||||
mod layout; // TOREM: this
|
||||
mod misc;
|
||||
mod update;
|
||||
mod view;
|
||||
|
||||
// Deps
|
||||
extern crate crossterm;
|
||||
@@ -37,14 +36,14 @@ extern crate tui;
|
||||
|
||||
// Locals
|
||||
use super::{Activity, Context};
|
||||
use crate::ui::layout::view::View;
|
||||
// Ext
|
||||
use crossterm::event::Event as InputEvent;
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use tui::style::Color;
|
||||
|
||||
// -- components
|
||||
const COMPONENT_TEXT_HELP: &str = "TEXT_HELP";
|
||||
const COMPONENT_TEXT_FOOTER: &str = "TEXT_FOOTER";
|
||||
const COMPONENT_TEXT_ERROR: &str = "TEXT_ERROR";
|
||||
const COMPONENT_RADIO_QUIT: &str = "RADIO_QUIT";
|
||||
const COMPONENT_RADIO_SAVE: &str = "RADIO_SAVE";
|
||||
const COMPONENT_INPUT_TEXT_EDITOR: &str = "INPUT_TEXT_EDITOR";
|
||||
@@ -55,63 +54,17 @@ const COMPONENT_RADIO_GROUP_DIRS: &str = "RADIO_GROUP_DIRS";
|
||||
const COMPONENT_INPUT_FILE_FMT: &str = "INPUT_FILE_FMT";
|
||||
const COMPONENT_RADIO_TAB: &str = "RADIO_TAB";
|
||||
const COMPONENT_LIST_SSH_KEYS: &str = "LIST_SSH_KEYS";
|
||||
const COMPONENT_INPUT_SSH_HOST: &str = "INPUT_SSH_HOST";
|
||||
const COMPONENT_INPUT_SSH_USERNAME: &str = "INPUT_SSH_USERNAME";
|
||||
const COMPONENT_RADIO_DEL_SSH_KEY: &str = "RADIO_DEL_SSH_KEY";
|
||||
|
||||
// Types
|
||||
type OnChoiceCallback = fn(&mut SetupActivity);
|
||||
|
||||
/// ### UserInterfaceInputField
|
||||
/// ### ViewLayout
|
||||
///
|
||||
/// Input field selected in user interface
|
||||
#[derive(std::cmp::PartialEq, Clone)]
|
||||
enum UserInterfaceInputField {
|
||||
DefaultProtocol,
|
||||
TextEditor,
|
||||
ShowHiddenFiles,
|
||||
CheckForUpdates,
|
||||
GroupDirs,
|
||||
FileFmt,
|
||||
}
|
||||
|
||||
/// ### SetupTab
|
||||
///
|
||||
/// Selected setup tab
|
||||
/// Current view layout
|
||||
#[derive(std::cmp::PartialEq)]
|
||||
enum SetupTab {
|
||||
UserInterface(UserInterfaceInputField),
|
||||
SshConfig,
|
||||
}
|
||||
|
||||
/// ### QuitDialogOption
|
||||
///
|
||||
/// Quit dialog options
|
||||
#[derive(std::cmp::PartialEq, Clone)]
|
||||
enum QuitDialogOption {
|
||||
Save,
|
||||
DontSave,
|
||||
Cancel,
|
||||
}
|
||||
|
||||
/// ### YesNoDialogOption
|
||||
///
|
||||
/// YesNo dialog options
|
||||
#[derive(std::cmp::PartialEq, Clone)]
|
||||
enum YesNoDialogOption {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
/// ## Popup
|
||||
///
|
||||
/// Popup describes the type of popup
|
||||
#[derive(Clone)]
|
||||
enum Popup {
|
||||
Alert(Color, String), // Block color; Block text
|
||||
Fatal(String), // Must quit after being hidden
|
||||
Help, // Show Help
|
||||
NewSshKey, //
|
||||
Quit, // Quit dialog
|
||||
YesNo(String, OnChoiceCallback, OnChoiceCallback), // Yes/No Dialog
|
||||
enum ViewLayout {
|
||||
SetupForm,
|
||||
SshKeys,
|
||||
}
|
||||
|
||||
/// ## SetupActivity
|
||||
@@ -120,14 +73,9 @@ enum Popup {
|
||||
pub struct SetupActivity {
|
||||
pub quit: bool, // Becomes true when user requests the activity to terminate
|
||||
context: Option<Context>, // Context holder
|
||||
tab: SetupTab, // Current setup tab
|
||||
popup: Option<Popup>, // Active popup
|
||||
user_input: Vec<String>, // User input holder
|
||||
user_input_ptr: usize, // Selected user input
|
||||
quit_opt: QuitDialogOption, // Popup::Quit selected option
|
||||
yesno_opt: YesNoDialogOption, // Popup::YesNo selected option
|
||||
ssh_key_idx: usize, // Index of selected ssh key in list
|
||||
redraw: bool, // Redraw ui?
|
||||
view: View, // View
|
||||
layout: ViewLayout, // View layout
|
||||
redraw: bool,
|
||||
}
|
||||
|
||||
impl Default for SetupActivity {
|
||||
@@ -140,13 +88,8 @@ impl Default for SetupActivity {
|
||||
SetupActivity {
|
||||
quit: false,
|
||||
context: None,
|
||||
tab: SetupTab::UserInterface(UserInterfaceInputField::TextEditor),
|
||||
popup: None,
|
||||
user_input: user_input_buffer, // Max 16
|
||||
user_input_ptr: 0,
|
||||
quit_opt: QuitDialogOption::Save,
|
||||
yesno_opt: YesNoDialogOption::Yes,
|
||||
ssh_key_idx: 0,
|
||||
view: View::init(),
|
||||
layout: ViewLayout::SetupForm,
|
||||
redraw: true, // Draw at first `on_draw`
|
||||
}
|
||||
}
|
||||
@@ -165,9 +108,11 @@ impl Activity for SetupActivity {
|
||||
self.context.as_mut().unwrap().clear_screen();
|
||||
// Put raw mode on enabled
|
||||
let _ = enable_raw_mode();
|
||||
// Init view
|
||||
self.init_setup();
|
||||
// Verify error state from context
|
||||
if let Some(err) = self.context.as_mut().unwrap().get_error() {
|
||||
self.popup = Some(Popup::Fatal(err));
|
||||
self.mount_error(err.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,12 +130,13 @@ impl Activity for SetupActivity {
|
||||
// Set redraw to true
|
||||
self.redraw = true;
|
||||
// Handle event
|
||||
self.handle_input_event(&event);
|
||||
let msg = self.view.on(event);
|
||||
self.update(msg);
|
||||
}
|
||||
// Redraw if necessary
|
||||
if self.redraw {
|
||||
// Draw
|
||||
self.draw();
|
||||
// View
|
||||
self.view();
|
||||
// Redraw back to false
|
||||
self.redraw = false;
|
||||
}
|
||||
|
||||
@@ -26,8 +26,11 @@
|
||||
|
||||
// locals
|
||||
use super::{
|
||||
AuthActivity, COMPONENT_INPUT_FILE_FMT, COMPONENT_INPUT_TEXT_EDITOR, COMPONENT_LIST_SSH_KEYS, COMPONENT_RADIO_DEFAULT_PROTOCOL, COMPONENT_RADIO_GROUP_DIRS,
|
||||
COMPONENT_RADIO_HIDDEN_FILES, COMPONENT_RADIO_TAB, COMPONENT_RADIO_UPDATES, COMPONENT_TEXT_FOOTER, COMPONENT_TEXT_HELP,
|
||||
SetupActivity, COMPONENT_INPUT_FILE_FMT, COMPONENT_INPUT_SSH_HOST,
|
||||
COMPONENT_INPUT_SSH_USERNAME, COMPONENT_INPUT_TEXT_EDITOR, COMPONENT_LIST_SSH_KEYS,
|
||||
COMPONENT_RADIO_DEFAULT_PROTOCOL, COMPONENT_RADIO_DEL_SSH_KEY, COMPONENT_RADIO_GROUP_DIRS,
|
||||
COMPONENT_RADIO_HIDDEN_FILES, COMPONENT_RADIO_QUIT, COMPONENT_RADIO_SAVE,
|
||||
COMPONENT_RADIO_UPDATES, COMPONENT_TEXT_ERROR, COMPONENT_TEXT_HELP,
|
||||
};
|
||||
use crate::ui::layout::{Msg, Payload};
|
||||
// ext
|
||||
@@ -50,14 +53,6 @@ const MSG_KEY_DOWN: Msg = Msg::OnKey(KeyEvent {
|
||||
code: KeyCode::Down,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
});
|
||||
const MSG_KEY_LEFT: Msg = Msg::OnKey(KeyEvent {
|
||||
code: KeyCode::Left,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
});
|
||||
const MSG_KEY_RIGHT: Msg = Msg::OnKey(KeyEvent {
|
||||
code: KeyCode::Right,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
});
|
||||
const MSG_KEY_UP: Msg = Msg::OnKey(KeyEvent {
|
||||
code: KeyCode::Up,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
@@ -66,11 +61,7 @@ const MSG_KEY_DEL: Msg = Msg::OnKey(KeyEvent {
|
||||
code: KeyCode::Delete,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
});
|
||||
const MSG_KEY_CHAR_E: Msg = Msg::OnKey(KeyEvent {
|
||||
code: KeyCode::Char('c'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
});
|
||||
const MSG_KEY_CTRL_C: Msg = Msg::OnKey(KeyEvent {
|
||||
const MSG_KEY_CTRL_E: Msg = Msg::OnKey(KeyEvent {
|
||||
code: KeyCode::Char('c'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
});
|
||||
@@ -78,6 +69,14 @@ const MSG_KEY_CTRL_H: Msg = Msg::OnKey(KeyEvent {
|
||||
code: KeyCode::Char('h'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
});
|
||||
const MSG_KEY_CTRL_N: Msg = Msg::OnKey(KeyEvent {
|
||||
code: KeyCode::Char('n'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
});
|
||||
const MSG_KEY_CTRL_R: Msg = Msg::OnKey(KeyEvent {
|
||||
code: KeyCode::Char('r'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
});
|
||||
const MSG_KEY_CTRL_S: Msg = Msg::OnKey(KeyEvent {
|
||||
code: KeyCode::Char('s'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
@@ -85,7 +84,7 @@ const MSG_KEY_CTRL_S: Msg = Msg::OnKey(KeyEvent {
|
||||
|
||||
// -- update
|
||||
|
||||
impl AuthActivity {
|
||||
impl SetupActivity {
|
||||
/// ### update
|
||||
///
|
||||
/// Update auth activity model based on msg
|
||||
@@ -97,6 +96,215 @@ impl AuthActivity {
|
||||
};
|
||||
// Match msg
|
||||
match ref_msg {
|
||||
None => None,
|
||||
Some(msg) => match msg {
|
||||
// Input field <DOWN>
|
||||
(COMPONENT_INPUT_TEXT_EDITOR, &MSG_KEY_DOWN) => {
|
||||
self.view.active(COMPONENT_RADIO_DEFAULT_PROTOCOL);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_DEFAULT_PROTOCOL, &MSG_KEY_DOWN) => {
|
||||
self.view.active(COMPONENT_RADIO_HIDDEN_FILES);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_HIDDEN_FILES, &MSG_KEY_DOWN) => {
|
||||
self.view.active(COMPONENT_RADIO_UPDATES);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_UPDATES, &MSG_KEY_DOWN) => {
|
||||
self.view.active(COMPONENT_RADIO_GROUP_DIRS);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_GROUP_DIRS, &MSG_KEY_DOWN) => {
|
||||
self.view.active(COMPONENT_INPUT_FILE_FMT);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_FILE_FMT, &MSG_KEY_DOWN) => {
|
||||
self.view.active(COMPONENT_INPUT_TEXT_EDITOR);
|
||||
None
|
||||
}
|
||||
// Input field <UP>
|
||||
(COMPONENT_INPUT_FILE_FMT, &MSG_KEY_UP) => {
|
||||
self.view.active(COMPONENT_RADIO_GROUP_DIRS);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_GROUP_DIRS, &MSG_KEY_UP) => {
|
||||
self.view.active(COMPONENT_RADIO_UPDATES);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_UPDATES, &MSG_KEY_UP) => {
|
||||
self.view.active(COMPONENT_RADIO_HIDDEN_FILES);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_HIDDEN_FILES, &MSG_KEY_UP) => {
|
||||
self.view.active(COMPONENT_RADIO_DEFAULT_PROTOCOL);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_DEFAULT_PROTOCOL, &MSG_KEY_UP) => {
|
||||
self.view.active(COMPONENT_INPUT_TEXT_EDITOR);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_TEXT_EDITOR, &MSG_KEY_UP) => {
|
||||
self.view.active(COMPONENT_INPUT_FILE_FMT);
|
||||
None
|
||||
}
|
||||
// Error <ENTER> or <ESC>
|
||||
(COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) => {
|
||||
// Umount text error
|
||||
self.umount_error();
|
||||
None
|
||||
}
|
||||
// Exit
|
||||
(COMPONENT_RADIO_QUIT, Msg::OnSubmit(Payload::Unsigned(0))) => {
|
||||
// Save changes
|
||||
if let Err(err) = self.action_save_config() {
|
||||
self.mount_error(err.as_str());
|
||||
}
|
||||
// Exit
|
||||
self.quit = true;
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_QUIT, Msg::OnSubmit(Payload::Unsigned(1))) => {
|
||||
// Quit
|
||||
self.quit = true;
|
||||
self.umount_quit();
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_QUIT, Msg::OnSubmit(_)) => {
|
||||
// Umount popup
|
||||
self.umount_quit();
|
||||
None
|
||||
}
|
||||
// Close help
|
||||
(COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => {
|
||||
// Umount help
|
||||
self.umount_help();
|
||||
None
|
||||
}
|
||||
// Delete key
|
||||
(COMPONENT_RADIO_DEL_SSH_KEY, Msg::OnSubmit(Payload::Unsigned(0))) => {
|
||||
// Delete key
|
||||
self.action_delete_ssh_key();
|
||||
// Reload ssh keys
|
||||
self.reload_ssh_keys();
|
||||
// Delete popup
|
||||
self.umount_del_ssh_key();
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_DEL_SSH_KEY, Msg::OnSubmit(_)) => {
|
||||
// Umount
|
||||
self.umount_del_ssh_key();
|
||||
None
|
||||
}
|
||||
// Save popup
|
||||
(COMPONENT_RADIO_SAVE, Msg::OnSubmit(Payload::Unsigned(0))) => {
|
||||
// Save config
|
||||
if let Err(err) = self.action_save_config() {
|
||||
self.mount_error(err.as_str());
|
||||
}
|
||||
self.umount_save_popup();
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_SAVE, Msg::OnSubmit(_)) => {
|
||||
// Umount radio save
|
||||
self.umount_save_popup();
|
||||
None
|
||||
}
|
||||
// Edit SSH Key
|
||||
// <TAB> Change view
|
||||
(COMPONENT_LIST_SSH_KEYS, &MSG_KEY_TAB) => {
|
||||
// Change view
|
||||
self.init_setup();
|
||||
None
|
||||
}
|
||||
// <CTRL+H> Show help
|
||||
(_, &MSG_KEY_CTRL_H) => {
|
||||
// Show help
|
||||
self.mount_help();
|
||||
None
|
||||
}
|
||||
// New key <DOWN>
|
||||
(COMPONENT_INPUT_SSH_HOST, &MSG_KEY_DOWN) => {
|
||||
self.view.active(COMPONENT_INPUT_SSH_USERNAME);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_DOWN) => {
|
||||
self.view.active(COMPONENT_INPUT_SSH_HOST);
|
||||
None
|
||||
}
|
||||
// New key <UP>
|
||||
(COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_UP) | (COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_TAB) => {
|
||||
self.view.active(COMPONENT_INPUT_SSH_HOST);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_SSH_HOST, &MSG_KEY_UP) | (COMPONENT_INPUT_SSH_HOST, &MSG_KEY_TAB) => {
|
||||
self.view.active(COMPONENT_INPUT_SSH_USERNAME);
|
||||
None
|
||||
}
|
||||
// New key <ENTER>
|
||||
(COMPONENT_INPUT_SSH_HOST, Msg::OnSubmit(_))
|
||||
| (COMPONENT_INPUT_SSH_USERNAME, Msg::OnSubmit(_)) => {
|
||||
// Save ssh key
|
||||
self.action_new_ssh_key();
|
||||
self.umount_new_ssh_key();
|
||||
self.reload_ssh_keys();
|
||||
None
|
||||
}
|
||||
// New key <ESC>
|
||||
(COMPONENT_INPUT_SSH_HOST, &MSG_KEY_ESC)
|
||||
| (COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_ESC) => {
|
||||
// Umount new ssh key
|
||||
self.umount_new_ssh_key();
|
||||
None
|
||||
}
|
||||
// <CTRL+N> New key
|
||||
(COMPONENT_LIST_SSH_KEYS, &MSG_KEY_CTRL_N) => {
|
||||
// Show new key popup
|
||||
self.mount_new_ssh_key();
|
||||
None
|
||||
}
|
||||
// <ENTER> Edit key
|
||||
(COMPONENT_LIST_SSH_KEYS, Msg::OnSubmit(Payload::Unsigned(idx))) => {
|
||||
// Edit ssh key
|
||||
if let Err(err) = self.edit_ssh_key(*idx) {
|
||||
self.mount_error(err.as_str());
|
||||
}
|
||||
None
|
||||
}
|
||||
// <DEL | CTRL+E> Show delete
|
||||
(COMPONENT_LIST_SSH_KEYS, &MSG_KEY_CTRL_E)
|
||||
| (COMPONENT_LIST_SSH_KEYS, &MSG_KEY_DEL) => {
|
||||
// Show delete key
|
||||
self.mount_del_ssh_key();
|
||||
None
|
||||
}
|
||||
(_, &MSG_KEY_TAB) => {
|
||||
// Change view
|
||||
self.init_ssh_keys();
|
||||
None
|
||||
}
|
||||
// <CTRL+R> Revert changes
|
||||
(_, &MSG_KEY_CTRL_R) => {
|
||||
// Revert changes
|
||||
if let Err(err) = self.action_reset_config() {
|
||||
self.mount_error(err.as_str());
|
||||
}
|
||||
None
|
||||
}
|
||||
// <CTRL+S> Save
|
||||
(_, &MSG_KEY_CTRL_S) => {
|
||||
// Show save
|
||||
self.mount_save_popup();
|
||||
None
|
||||
}
|
||||
// <ESC>
|
||||
(_, &MSG_KEY_ESC) => {
|
||||
// Mount quit prompt
|
||||
self.mount_quit();
|
||||
None
|
||||
}
|
||||
(_, _) => None, // Nothing to do
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
790
src/ui/activities/setup_activity/view.rs
Normal file
790
src/ui/activities/setup_activity/view.rs
Normal file
@@ -0,0 +1,790 @@
|
||||
//! ## SetupActivity
|
||||
//!
|
||||
//! `setup_activity` is the module which implements the Setup activity, which is the activity to
|
||||
//! work on termscp configuration
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
* TermSCP is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* TermSCP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with TermSCP. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Locals
|
||||
use super::{Context, SetupActivity, ViewLayout};
|
||||
use crate::filetransfer::FileTransferProtocol;
|
||||
use crate::fs::explorer::GroupDirs;
|
||||
use crate::ui::layout::components::{
|
||||
bookmark_list::BookmarkList, ctext::CText, input::Input, radio_group::RadioGroup, table::Table,
|
||||
text::Text,
|
||||
};
|
||||
use crate::ui::layout::props::{
|
||||
PropValue, PropsBuilder, TableBuilder, TextParts, TextSpan, TextSpanBuilder,
|
||||
};
|
||||
use crate::ui::layout::utils::draw_area_in;
|
||||
use crate::ui::layout::view::View;
|
||||
use crate::ui::layout::Payload;
|
||||
// Ext
|
||||
use std::path::PathBuf;
|
||||
use tui::{
|
||||
layout::{Constraint, Direction, Layout},
|
||||
style::Color,
|
||||
widgets::{Borders, Clear},
|
||||
};
|
||||
|
||||
impl SetupActivity {
|
||||
// -- view
|
||||
|
||||
/// ### init_setup
|
||||
///
|
||||
/// Initialize setup view
|
||||
pub(super) fn init_setup(&mut self) {
|
||||
// Init view
|
||||
self.view = View::init();
|
||||
// Common stuff
|
||||
// Radio tab
|
||||
self.view.mount(
|
||||
super::COMPONENT_RADIO_TAB,
|
||||
Box::new(RadioGroup::new(
|
||||
PropsBuilder::default()
|
||||
.with_foreground(Color::LightYellow)
|
||||
.with_background(Color::Black)
|
||||
.with_borders(Borders::BOTTOM)
|
||||
.with_texts(TextParts::new(
|
||||
None,
|
||||
Some(vec![
|
||||
TextSpan::from("User Interface"),
|
||||
TextSpan::from("SSH Keys"),
|
||||
]),
|
||||
))
|
||||
.with_value(PropValue::Unsigned(0))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
// Footer
|
||||
self.view.mount(
|
||||
super::COMPONENT_TEXT_FOOTER,
|
||||
Box::new(Text::new(
|
||||
PropsBuilder::default()
|
||||
.with_texts(TextParts::new(
|
||||
None,
|
||||
Some(vec![
|
||||
TextSpanBuilder::new("Press ").bold().build(),
|
||||
TextSpanBuilder::new("<CTRL+H>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
TextSpanBuilder::new(" to show keybindings").bold().build(),
|
||||
]),
|
||||
))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
// Input fields
|
||||
self.view.mount(
|
||||
super::COMPONENT_INPUT_TEXT_EDITOR,
|
||||
Box::new(Input::new(
|
||||
PropsBuilder::default()
|
||||
.with_foreground(Color::LightGreen)
|
||||
.with_texts(TextParts::new(Some(String::from("Text editor")), None))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.active(super::COMPONENT_INPUT_TEXT_EDITOR); // <-- Focus
|
||||
self.view.mount(
|
||||
super::COMPONENT_RADIO_DEFAULT_PROTOCOL,
|
||||
Box::new(RadioGroup::new(
|
||||
PropsBuilder::default()
|
||||
.with_foreground(Color::LightCyan)
|
||||
.with_background(Color::Black)
|
||||
.with_texts(TextParts::new(
|
||||
Some(String::from("Default file transfer protocol")),
|
||||
Some(vec![
|
||||
TextSpan::from("SFTP"),
|
||||
TextSpan::from("SCP"),
|
||||
TextSpan::from("FTP"),
|
||||
TextSpan::from("FTPS"),
|
||||
]),
|
||||
))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.mount(
|
||||
super::COMPONENT_RADIO_HIDDEN_FILES,
|
||||
Box::new(RadioGroup::new(
|
||||
PropsBuilder::default()
|
||||
.with_foreground(Color::LightRed)
|
||||
.with_background(Color::Black)
|
||||
.with_texts(TextParts::new(
|
||||
Some(String::from("Show hidden files (by default)")),
|
||||
Some(vec![TextSpan::from("Yes"), TextSpan::from("No")]),
|
||||
))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.mount(
|
||||
super::COMPONENT_RADIO_UPDATES,
|
||||
Box::new(RadioGroup::new(
|
||||
PropsBuilder::default()
|
||||
.with_foreground(Color::LightYellow)
|
||||
.with_background(Color::Black)
|
||||
.with_texts(TextParts::new(
|
||||
Some(String::from("Check for updates?")),
|
||||
Some(vec![TextSpan::from("Yes"), TextSpan::from("No")]),
|
||||
))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.mount(
|
||||
super::COMPONENT_RADIO_GROUP_DIRS,
|
||||
Box::new(RadioGroup::new(
|
||||
PropsBuilder::default()
|
||||
.with_foreground(Color::LightMagenta)
|
||||
.with_background(Color::Black)
|
||||
.with_texts(TextParts::new(
|
||||
Some(String::from("Group directories")),
|
||||
Some(vec![
|
||||
TextSpan::from("Display first"),
|
||||
TextSpan::from("Display Last"),
|
||||
TextSpan::from("No"),
|
||||
]),
|
||||
))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.mount(
|
||||
super::COMPONENT_INPUT_FILE_FMT,
|
||||
Box::new(Input::new(
|
||||
PropsBuilder::default()
|
||||
.with_foreground(Color::LightBlue)
|
||||
.with_texts(TextParts::new(
|
||||
Some(String::from("File formatter syntax")),
|
||||
None,
|
||||
))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
// Load values
|
||||
self.load_input_values();
|
||||
// Set view
|
||||
self.layout = ViewLayout::SetupForm;
|
||||
}
|
||||
|
||||
/// ### init_ssh_keys
|
||||
///
|
||||
/// Initialize ssh keys view
|
||||
pub(super) fn init_ssh_keys(&mut self) {
|
||||
// Init view
|
||||
self.view = View::init();
|
||||
// Common stuff
|
||||
// Radio tab
|
||||
self.view.mount(
|
||||
super::COMPONENT_RADIO_TAB,
|
||||
Box::new(RadioGroup::new(
|
||||
PropsBuilder::default()
|
||||
.with_foreground(Color::LightYellow)
|
||||
.with_background(Color::Black)
|
||||
.with_borders(Borders::BOTTOM)
|
||||
.with_texts(TextParts::new(
|
||||
None,
|
||||
Some(vec![
|
||||
TextSpan::from("User Interface"),
|
||||
TextSpan::from("SSH Keys"),
|
||||
]),
|
||||
))
|
||||
.with_value(PropValue::Unsigned(1))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
// Footer
|
||||
self.view.mount(
|
||||
super::COMPONENT_TEXT_FOOTER,
|
||||
Box::new(Text::new(
|
||||
PropsBuilder::default()
|
||||
.with_texts(TextParts::new(
|
||||
None,
|
||||
Some(vec![
|
||||
TextSpanBuilder::new("Press ").bold().build(),
|
||||
TextSpanBuilder::new("<CTRL+H>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
TextSpanBuilder::new(" to show keybindings").bold().build(),
|
||||
]),
|
||||
))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.mount(
|
||||
super::COMPONENT_LIST_SSH_KEYS,
|
||||
Box::new(BookmarkList::new(
|
||||
PropsBuilder::default()
|
||||
.with_texts(TextParts::new(Some(String::from("SSH Keys")), Some(vec![])))
|
||||
.with_background(Color::LightGreen)
|
||||
.with_foreground(Color::Black)
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
// Give focus
|
||||
self.view.active(super::COMPONENT_LIST_SSH_KEYS);
|
||||
// Load keys
|
||||
self.reload_ssh_keys();
|
||||
// Set view
|
||||
self.layout = ViewLayout::SshKeys;
|
||||
}
|
||||
|
||||
/// ### view
|
||||
///
|
||||
/// View gui
|
||||
pub(super) fn view(&mut self) {
|
||||
let mut ctx: Context = self.context.take().unwrap();
|
||||
let _ = ctx.terminal.draw(|f| {
|
||||
// Prepare main chunks
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(1)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3), // Current tab
|
||||
Constraint::Percentage(90), // Main body
|
||||
Constraint::Length(3), // Help footer
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(f.size());
|
||||
// Render common widget
|
||||
self.view.render(super::COMPONENT_RADIO_TAB, f, chunks[0]);
|
||||
self.view.render(super::COMPONENT_TEXT_FOOTER, f, chunks[2]);
|
||||
match self.layout {
|
||||
ViewLayout::SetupForm => {
|
||||
// Make chunks
|
||||
let ui_cfg_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3), // Text editor
|
||||
Constraint::Length(3), // Protocol tab
|
||||
Constraint::Length(3), // Hidden files
|
||||
Constraint::Length(3), // Updates tab
|
||||
Constraint::Length(3), // Group dirs
|
||||
Constraint::Length(3), // Format input
|
||||
Constraint::Length(1), // Empty ?
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(chunks[1]);
|
||||
self.view
|
||||
.render(super::COMPONENT_INPUT_TEXT_EDITOR, f, ui_cfg_chunks[0]);
|
||||
self.view
|
||||
.render(super::COMPONENT_RADIO_DEFAULT_PROTOCOL, f, ui_cfg_chunks[1]);
|
||||
self.view
|
||||
.render(super::COMPONENT_RADIO_HIDDEN_FILES, f, ui_cfg_chunks[2]);
|
||||
self.view
|
||||
.render(super::COMPONENT_RADIO_UPDATES, f, ui_cfg_chunks[3]);
|
||||
self.view
|
||||
.render(super::COMPONENT_RADIO_GROUP_DIRS, f, ui_cfg_chunks[4]);
|
||||
self.view
|
||||
.render(super::COMPONENT_INPUT_FILE_FMT, f, ui_cfg_chunks[5]);
|
||||
}
|
||||
ViewLayout::SshKeys => {
|
||||
let sshcfg_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Percentage(100)].as_ref())
|
||||
.split(chunks[1]);
|
||||
self.view
|
||||
.render(super::COMPONENT_LIST_SSH_KEYS, f, sshcfg_chunks[0]);
|
||||
}
|
||||
}
|
||||
// Popups
|
||||
if let Some(mut props) = self.view.get_props(super::COMPONENT_TEXT_ERROR) {
|
||||
if props.build().visible {
|
||||
let popup = draw_area_in(f.size(), 50, 10);
|
||||
f.render_widget(Clear, popup);
|
||||
// make popup
|
||||
self.view.render(super::COMPONENT_TEXT_ERROR, f, popup);
|
||||
}
|
||||
}
|
||||
if let Some(mut props) = self.view.get_props(super::COMPONENT_RADIO_QUIT) {
|
||||
if props.build().visible {
|
||||
// make popup
|
||||
let popup = draw_area_in(f.size(), 40, 10);
|
||||
f.render_widget(Clear, popup);
|
||||
self.view.render(super::COMPONENT_RADIO_QUIT, f, popup);
|
||||
}
|
||||
}
|
||||
if let Some(mut props) = self.view.get_props(super::COMPONENT_TEXT_HELP) {
|
||||
if props.build().visible {
|
||||
// make popup
|
||||
let popup = draw_area_in(f.size(), 50, 70);
|
||||
f.render_widget(Clear, popup);
|
||||
self.view.render(super::COMPONENT_TEXT_HELP, f, popup);
|
||||
}
|
||||
}
|
||||
if let Some(mut props) = self.view.get_props(super::COMPONENT_RADIO_SAVE) {
|
||||
if props.build().visible {
|
||||
// make popup
|
||||
let popup = draw_area_in(f.size(), 30, 10);
|
||||
f.render_widget(Clear, popup);
|
||||
self.view.render(super::COMPONENT_RADIO_SAVE, f, popup);
|
||||
}
|
||||
}
|
||||
if let Some(mut props) = self.view.get_props(super::COMPONENT_RADIO_DEL_SSH_KEY) {
|
||||
if props.build().visible {
|
||||
// make popup
|
||||
let popup = draw_area_in(f.size(), 30, 10);
|
||||
f.render_widget(Clear, popup);
|
||||
self.view
|
||||
.render(super::COMPONENT_RADIO_DEL_SSH_KEY, f, popup);
|
||||
}
|
||||
}
|
||||
if let Some(mut props) = self.view.get_props(super::COMPONENT_INPUT_SSH_HOST) {
|
||||
if props.build().visible {
|
||||
// make popup
|
||||
let popup = draw_area_in(f.size(), 50, 20);
|
||||
f.render_widget(Clear, popup);
|
||||
let popup_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3), // Host
|
||||
Constraint::Length(3), // Username
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(popup);
|
||||
self.view
|
||||
.render(super::COMPONENT_INPUT_SSH_HOST, f, popup_chunks[0]);
|
||||
self.view
|
||||
.render(super::COMPONENT_INPUT_SSH_USERNAME, f, popup_chunks[1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Put context back to context
|
||||
self.context = Some(ctx);
|
||||
}
|
||||
|
||||
// -- mount
|
||||
|
||||
/// ### mount_error
|
||||
///
|
||||
/// Mount error box
|
||||
pub(super) fn mount_error(&mut self, text: &str) {
|
||||
// Mount
|
||||
self.view.mount(
|
||||
super::COMPONENT_TEXT_ERROR,
|
||||
Box::new(CText::new(
|
||||
PropsBuilder::default()
|
||||
.with_foreground(Color::Red)
|
||||
.bold()
|
||||
.with_texts(TextParts::new(None, Some(vec![TextSpan::from(text)])))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
// Give focus to error
|
||||
self.view.active(super::COMPONENT_TEXT_ERROR);
|
||||
}
|
||||
|
||||
/// ### umount_error
|
||||
///
|
||||
/// Umount error message
|
||||
pub(super) fn umount_error(&mut self) {
|
||||
self.view.umount(super::COMPONENT_TEXT_ERROR);
|
||||
}
|
||||
|
||||
/// ### mount_del_ssh_key
|
||||
///
|
||||
/// Mount delete ssh key component
|
||||
pub(super) fn mount_del_ssh_key(&mut self) {
|
||||
self.view.mount(
|
||||
super::COMPONENT_RADIO_DEL_SSH_KEY,
|
||||
Box::new(RadioGroup::new(
|
||||
PropsBuilder::default()
|
||||
.with_foreground(Color::LightRed)
|
||||
.bold()
|
||||
.with_texts(TextParts::new(
|
||||
Some(String::from("Delete key?")),
|
||||
Some(vec![TextSpan::from("Yes"), TextSpan::from("No")]),
|
||||
))
|
||||
.with_value(PropValue::Unsigned(1)) // Default: No
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
// Active
|
||||
self.view.active(super::COMPONENT_RADIO_DEL_SSH_KEY);
|
||||
}
|
||||
|
||||
/// ### umount_del_ssh_key
|
||||
///
|
||||
/// Umount delete ssh key
|
||||
pub(super) fn umount_del_ssh_key(&mut self) {
|
||||
self.view.umount(super::COMPONENT_RADIO_DEL_SSH_KEY);
|
||||
}
|
||||
|
||||
/// ### mount_new_ssh_key
|
||||
///
|
||||
/// Mount new ssh key prompt
|
||||
pub(super) fn mount_new_ssh_key(&mut self) {
|
||||
self.view.mount(
|
||||
super::COMPONENT_INPUT_SSH_HOST,
|
||||
Box::new(Input::new(
|
||||
PropsBuilder::default()
|
||||
.with_texts(TextParts::new(
|
||||
Some(String::from("Hostname or address")),
|
||||
None,
|
||||
))
|
||||
.with_borders(Borders::TOP | Borders::RIGHT | Borders::LEFT)
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.mount(
|
||||
super::COMPONENT_INPUT_SSH_USERNAME,
|
||||
Box::new(Input::new(
|
||||
PropsBuilder::default()
|
||||
.with_texts(TextParts::new(Some(String::from("Username")), None))
|
||||
.with_borders(Borders::BOTTOM | Borders::RIGHT | Borders::LEFT)
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.active(super::COMPONENT_INPUT_SSH_HOST);
|
||||
}
|
||||
|
||||
/// ### umount_new_ssh_key
|
||||
///
|
||||
/// Umount new ssh key prompt
|
||||
pub(super) fn umount_new_ssh_key(&mut self) {
|
||||
self.view.umount(super::COMPONENT_INPUT_SSH_HOST);
|
||||
self.view.umount(super::COMPONENT_INPUT_SSH_USERNAME);
|
||||
}
|
||||
|
||||
/// ### mount_quit
|
||||
///
|
||||
/// Mount quit popup
|
||||
pub(super) fn mount_quit(&mut self) {
|
||||
self.view.mount(
|
||||
super::COMPONENT_RADIO_QUIT,
|
||||
Box::new(RadioGroup::new(
|
||||
PropsBuilder::default()
|
||||
.with_foreground(Color::LightRed)
|
||||
.bold()
|
||||
.with_texts(TextParts::new(
|
||||
Some(String::from("Exit setup?")),
|
||||
Some(vec![
|
||||
TextSpan::from("Save"),
|
||||
TextSpan::from("Don't save"),
|
||||
TextSpan::from("Cancel"),
|
||||
]),
|
||||
))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
// Active
|
||||
self.view.active(super::COMPONENT_RADIO_QUIT);
|
||||
}
|
||||
|
||||
/// ### umount_quit
|
||||
///
|
||||
/// Umount quit
|
||||
pub(super) fn umount_quit(&mut self) {
|
||||
self.view.umount(super::COMPONENT_RADIO_QUIT);
|
||||
}
|
||||
|
||||
/// ### mount_save_popup
|
||||
///
|
||||
/// Mount save popup
|
||||
pub(super) fn mount_save_popup(&mut self) {
|
||||
self.view.mount(
|
||||
super::COMPONENT_RADIO_SAVE,
|
||||
Box::new(RadioGroup::new(
|
||||
PropsBuilder::default()
|
||||
.with_foreground(Color::LightYellow)
|
||||
.bold()
|
||||
.with_texts(TextParts::new(
|
||||
Some(String::from("Save changes?")),
|
||||
Some(vec![TextSpan::from("Yes"), TextSpan::from("No")]),
|
||||
))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
// Active
|
||||
self.view.active(super::COMPONENT_RADIO_SAVE);
|
||||
}
|
||||
|
||||
/// ### umount_quit
|
||||
///
|
||||
/// Umount quit
|
||||
pub(super) fn umount_save_popup(&mut self) {
|
||||
self.view.umount(super::COMPONENT_RADIO_SAVE);
|
||||
}
|
||||
|
||||
/// ### mount_help
|
||||
///
|
||||
/// Mount help
|
||||
pub(super) fn mount_help(&mut self) {
|
||||
self.view.mount(
|
||||
super::COMPONENT_TEXT_HELP,
|
||||
Box::new(Table::new(
|
||||
PropsBuilder::default()
|
||||
.with_texts(TextParts::table(
|
||||
Some(String::from("Help")),
|
||||
TableBuilder::default()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<ESC>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::from(" Exit setup"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<TAB>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::from(" Change setup page"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<RIGHT/LEFT>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::from(" Change cursor"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<UP/DOWN>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::from(" Change input field"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<ENTER>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::from(" Select / Dismiss popup"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<DEL|E>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::from(" Delete SSH key"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<CTRL+N>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::from(" New SSH key"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<CTRL+R>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::from(" Revert changes"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<CTRL+S>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::from(" Save configuration"))
|
||||
.build(),
|
||||
))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
// Active help
|
||||
self.view.active(super::COMPONENT_TEXT_HELP);
|
||||
}
|
||||
|
||||
/// ### umount_help
|
||||
///
|
||||
/// Umount help
|
||||
pub(super) fn umount_help(&mut self) {
|
||||
self.view.umount(super::COMPONENT_TEXT_HELP);
|
||||
}
|
||||
|
||||
/// ### load_input_values
|
||||
///
|
||||
/// Load values from configuration into input fields
|
||||
pub(super) fn load_input_values(&mut self) {
|
||||
if let Some(cli) = self.context.as_mut().unwrap().config_client.as_mut() {
|
||||
// Text editor
|
||||
if let Some(props) = self
|
||||
.view
|
||||
.get_props(super::COMPONENT_INPUT_TEXT_EDITOR)
|
||||
.as_mut()
|
||||
{
|
||||
let text_editor: String =
|
||||
String::from(cli.get_text_editor().as_path().to_string_lossy());
|
||||
let props = props.with_value(PropValue::Str(text_editor)).build();
|
||||
let _ = self.view.update(super::COMPONENT_INPUT_TEXT_EDITOR, props);
|
||||
}
|
||||
// Protocol
|
||||
if let Some(props) = self
|
||||
.view
|
||||
.get_props(super::COMPONENT_RADIO_DEFAULT_PROTOCOL)
|
||||
.as_mut()
|
||||
{
|
||||
let protocol: usize = match cli.get_default_protocol() {
|
||||
FileTransferProtocol::Sftp => 0,
|
||||
FileTransferProtocol::Scp => 1,
|
||||
FileTransferProtocol::Ftp(false) => 2,
|
||||
FileTransferProtocol::Ftp(true) => 3,
|
||||
};
|
||||
let props = props.with_value(PropValue::Unsigned(protocol)).build();
|
||||
let _ = self
|
||||
.view
|
||||
.update(super::COMPONENT_RADIO_DEFAULT_PROTOCOL, props);
|
||||
}
|
||||
// Hidden files
|
||||
if let Some(props) = self
|
||||
.view
|
||||
.get_props(super::COMPONENT_RADIO_HIDDEN_FILES)
|
||||
.as_mut()
|
||||
{
|
||||
let hidden: usize = match cli.get_show_hidden_files() {
|
||||
true => 0,
|
||||
false => 1,
|
||||
};
|
||||
let props = props.with_value(PropValue::Unsigned(hidden)).build();
|
||||
let _ = self.view.update(super::COMPONENT_RADIO_HIDDEN_FILES, props);
|
||||
}
|
||||
// Updates
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_RADIO_UPDATES).as_mut() {
|
||||
let updates: usize = match cli.get_check_for_updates() {
|
||||
true => 0,
|
||||
false => 1,
|
||||
};
|
||||
let props = props.with_value(PropValue::Unsigned(updates)).build();
|
||||
let _ = self.view.update(super::COMPONENT_RADIO_UPDATES, props);
|
||||
}
|
||||
// Group dirs
|
||||
if let Some(props) = self
|
||||
.view
|
||||
.get_props(super::COMPONENT_RADIO_GROUP_DIRS)
|
||||
.as_mut()
|
||||
{
|
||||
let dirs: usize = match cli.get_group_dirs() {
|
||||
Some(GroupDirs::First) => 0,
|
||||
Some(GroupDirs::Last) => 1,
|
||||
None => 2,
|
||||
};
|
||||
let props = props.with_value(PropValue::Unsigned(dirs)).build();
|
||||
let _ = self.view.update(super::COMPONENT_RADIO_GROUP_DIRS, props);
|
||||
}
|
||||
// File Fmt
|
||||
if let Some(props) = self
|
||||
.view
|
||||
.get_props(super::COMPONENT_INPUT_FILE_FMT)
|
||||
.as_mut()
|
||||
{
|
||||
let file_fmt: String = cli.get_file_fmt().unwrap_or(String::new());
|
||||
let props = props.with_value(PropValue::Str(file_fmt)).build();
|
||||
let _ = self.view.update(super::COMPONENT_INPUT_FILE_FMT, props);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### collect_input_values
|
||||
///
|
||||
/// Collect values from input and put them into the configuration
|
||||
pub(super) fn collect_input_values(&mut self) {
|
||||
if let Some(cli) = self.context.as_mut().unwrap().config_client.as_mut() {
|
||||
if let Some(Payload::Text(editor)) =
|
||||
self.view.get_value(super::COMPONENT_INPUT_TEXT_EDITOR)
|
||||
{
|
||||
cli.set_text_editor(PathBuf::from(editor.as_str()));
|
||||
}
|
||||
if let Some(Payload::Unsigned(protocol)) =
|
||||
self.view.get_value(super::COMPONENT_RADIO_DEFAULT_PROTOCOL)
|
||||
{
|
||||
let protocol: FileTransferProtocol = match protocol {
|
||||
1 => FileTransferProtocol::Scp,
|
||||
2 => FileTransferProtocol::Ftp(false),
|
||||
3 => FileTransferProtocol::Ftp(true),
|
||||
_ => FileTransferProtocol::Sftp,
|
||||
};
|
||||
cli.set_default_protocol(protocol);
|
||||
}
|
||||
if let Some(Payload::Unsigned(opt)) =
|
||||
self.view.get_value(super::COMPONENT_RADIO_HIDDEN_FILES)
|
||||
{
|
||||
let show: bool = match opt {
|
||||
0 => true,
|
||||
_ => false,
|
||||
};
|
||||
cli.set_show_hidden_files(show);
|
||||
}
|
||||
if let Some(Payload::Unsigned(opt)) =
|
||||
self.view.get_value(super::COMPONENT_RADIO_UPDATES)
|
||||
{
|
||||
let check: bool = match opt {
|
||||
0 => true,
|
||||
_ => false,
|
||||
};
|
||||
cli.set_check_for_updates(check);
|
||||
}
|
||||
if let Some(Payload::Text(fmt)) = self.view.get_value(super::COMPONENT_INPUT_FILE_FMT) {
|
||||
cli.set_file_fmt(fmt);
|
||||
}
|
||||
if let Some(Payload::Unsigned(opt)) =
|
||||
self.view.get_value(super::COMPONENT_RADIO_GROUP_DIRS)
|
||||
{
|
||||
let dirs: Option<GroupDirs> = match opt {
|
||||
0 => Some(GroupDirs::First),
|
||||
1 => Some(GroupDirs::Last),
|
||||
_ => None,
|
||||
};
|
||||
cli.set_group_dirs(dirs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### reload_ssh_keys
|
||||
///
|
||||
/// Reload ssh keys
|
||||
pub(super) fn reload_ssh_keys(&mut self) {
|
||||
if let Some(cli) = self.context.as_ref().unwrap().config_client.as_ref() {
|
||||
// get props
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_LIST_SSH_KEYS).as_mut() {
|
||||
// Create texts
|
||||
let keys: Vec<TextSpan> = cli
|
||||
.iter_ssh_keys()
|
||||
.map(|x| {
|
||||
let (addr, username, _) = cli.get_ssh_key(x).ok().unwrap().unwrap();
|
||||
TextSpan::from(format!("{} at {}", addr, username).as_str())
|
||||
})
|
||||
.collect();
|
||||
let props = props
|
||||
.with_texts(TextParts::new(Some(String::from("SSH Keys")), Some(keys)))
|
||||
.build();
|
||||
self.view.update(super::COMPONENT_LIST_SSH_KEYS, props);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user