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
|
// Locals
|
||||||
use super::{Color, Popup, SetupActivity};
|
use super::SetupActivity;
|
||||||
|
use crate::ui::layout::Payload;
|
||||||
// Ext
|
// Ext
|
||||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
impl SetupActivity {
|
impl SetupActivity {
|
||||||
/// ### callback_nothing_to_do
|
/// ### action_save_config
|
||||||
///
|
///
|
||||||
/// Self titled
|
/// Save configuration
|
||||||
pub(super) fn callback_nothing_to_do(&mut self) {}
|
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
|
/// Reset configuration input fields
|
||||||
pub(super) fn callback_save_config_and_quit(&mut self) {
|
pub(super) fn action_reset_config(&mut self) -> Result<(), String> {
|
||||||
match self.save_config() {
|
match self.reset_config_changes() {
|
||||||
Ok(_) => self.quit = true, // Quit after successful save
|
Err(err) => Err(err),
|
||||||
Err(err) => self.popup = Some(Popup::Alert(Color::Red, err)), // Show error and don't quit
|
Ok(_) => {
|
||||||
|
self.load_input_values();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### callback_save_config
|
/// ### action_delete_ssh_key
|
||||||
///
|
///
|
||||||
/// Save configuration callback
|
/// delete of a ssh key
|
||||||
pub(super) fn callback_save_config(&mut self) {
|
pub(super) fn action_delete_ssh_key(&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) {
|
|
||||||
// Get key
|
// Get key
|
||||||
if let Some(config_cli) = self.context.as_mut().unwrap().config_client.as_mut() {
|
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) {
|
// get index
|
||||||
Some(k) => Some(k.clone()),
|
let idx: Option<usize> = match self.view.get_value(super::COMPONENT_LIST_SSH_KEYS) {
|
||||||
None => None,
|
Some(Payload::Unsigned(idx)) => Some(idx),
|
||||||
|
_ => None,
|
||||||
};
|
};
|
||||||
if let Some(key) = key {
|
if let Some(idx) = idx {
|
||||||
match config_cli.get_ssh_key(&key) {
|
let key: Option<String> = match config_cli.iter_ssh_keys().nth(idx) {
|
||||||
Ok(opt) => {
|
Some(k) => Some(k.clone()),
|
||||||
if let Some((host, username, _)) = opt {
|
None => None,
|
||||||
if let Err(err) = self.delete_ssh_key(host.as_str(), username.as_str())
|
};
|
||||||
{
|
if let Some(key) = key {
|
||||||
// Report error
|
match config_cli.get_ssh_key(&key) {
|
||||||
self.popup = Some(Popup::Alert(Color::Red, err));
|
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
|
/// Create a new ssh key
|
||||||
pub(super) fn callback_new_ssh_key(&mut self, host: String, username: String) {
|
pub(super) fn action_new_ssh_key(&mut self) {
|
||||||
if let Some(cli) = self.context.as_mut().unwrap().config_client.as_mut() {
|
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
|
// Prepare text editor
|
||||||
env::set_var("EDITOR", cli.get_text_editor());
|
env::set_var("EDITOR", cli.get_text_editor());
|
||||||
let placeholder: String = format!("# Type private SSH key for {}@{}\n", username, host);
|
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(), "");
|
let rsa_key: String = rsa_key.as_str().replace(placeholder.as_str(), "");
|
||||||
if rsa_key.is_empty() {
|
if rsa_key.is_empty() {
|
||||||
// Report error: empty key
|
// 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 {
|
} else {
|
||||||
// Add key
|
// Add key
|
||||||
if let Err(err) =
|
if let Err(err) =
|
||||||
self.add_ssh_key(host.as_str(), username.as_str(), rsa_key.as_str())
|
self.add_ssh_key(host.as_str(), username.as_str(), rsa_key.as_str())
|
||||||
{
|
{
|
||||||
self.popup = Some(Popup::Alert(
|
self.mount_error(
|
||||||
Color::Red,
|
format!("Could not create new private key: {}", err).as_str(),
|
||||||
format!("Could not create new private key: {}", err),
|
);
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// Report error
|
// Report error
|
||||||
self.popup = Some(Popup::Alert(
|
self.mount_error(
|
||||||
Color::Red,
|
format!("Could not write private key to file: {}", err).as_str(),
|
||||||
format!("Could not write private key to file: {}", err),
|
);
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Restore terminal
|
// Restore terminal
|
||||||
@@ -77,7 +77,7 @@ impl SetupActivity {
|
|||||||
/// ### edit_ssh_key
|
/// ### edit_ssh_key
|
||||||
///
|
///
|
||||||
/// Edit selected 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() {
|
match self.context.as_mut() {
|
||||||
None => Ok(()),
|
None => Ok(()),
|
||||||
Some(ctx) => {
|
Some(ctx) => {
|
||||||
@@ -91,7 +91,7 @@ impl SetupActivity {
|
|||||||
ctx.leave_alternate_screen();
|
ctx.leave_alternate_screen();
|
||||||
// Get result
|
// Get result
|
||||||
let result: Result<(), String> = match ctx.config_client.as_ref() {
|
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) => {
|
Some(key) => {
|
||||||
// Get key path
|
// Get key path
|
||||||
match config_cli.get_ssh_key(key) {
|
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
|
// Submodules
|
||||||
mod callbacks; // TOREM: this
|
mod actions;
|
||||||
mod config;
|
mod config;
|
||||||
mod input; // TOREM: this
|
mod update;
|
||||||
mod layout; // TOREM: this
|
mod view;
|
||||||
mod misc;
|
|
||||||
|
|
||||||
// Deps
|
// Deps
|
||||||
extern crate crossterm;
|
extern crate crossterm;
|
||||||
@@ -37,14 +36,14 @@ extern crate tui;
|
|||||||
|
|
||||||
// Locals
|
// Locals
|
||||||
use super::{Activity, Context};
|
use super::{Activity, Context};
|
||||||
|
use crate::ui::layout::view::View;
|
||||||
// Ext
|
// Ext
|
||||||
use crossterm::event::Event as InputEvent;
|
|
||||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||||
use tui::style::Color;
|
|
||||||
|
|
||||||
// -- components
|
// -- components
|
||||||
const COMPONENT_TEXT_HELP: &str = "TEXT_HELP";
|
const COMPONENT_TEXT_HELP: &str = "TEXT_HELP";
|
||||||
const COMPONENT_TEXT_FOOTER: &str = "TEXT_FOOTER";
|
const COMPONENT_TEXT_FOOTER: &str = "TEXT_FOOTER";
|
||||||
|
const COMPONENT_TEXT_ERROR: &str = "TEXT_ERROR";
|
||||||
const COMPONENT_RADIO_QUIT: &str = "RADIO_QUIT";
|
const COMPONENT_RADIO_QUIT: &str = "RADIO_QUIT";
|
||||||
const COMPONENT_RADIO_SAVE: &str = "RADIO_SAVE";
|
const COMPONENT_RADIO_SAVE: &str = "RADIO_SAVE";
|
||||||
const COMPONENT_INPUT_TEXT_EDITOR: &str = "INPUT_TEXT_EDITOR";
|
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_INPUT_FILE_FMT: &str = "INPUT_FILE_FMT";
|
||||||
const COMPONENT_RADIO_TAB: &str = "RADIO_TAB";
|
const COMPONENT_RADIO_TAB: &str = "RADIO_TAB";
|
||||||
const COMPONENT_LIST_SSH_KEYS: &str = "LIST_SSH_KEYS";
|
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";
|
const COMPONENT_RADIO_DEL_SSH_KEY: &str = "RADIO_DEL_SSH_KEY";
|
||||||
|
|
||||||
// Types
|
/// ### ViewLayout
|
||||||
type OnChoiceCallback = fn(&mut SetupActivity);
|
|
||||||
|
|
||||||
/// ### UserInterfaceInputField
|
|
||||||
///
|
///
|
||||||
/// Input field selected in user interface
|
/// Current view layout
|
||||||
#[derive(std::cmp::PartialEq, Clone)]
|
|
||||||
enum UserInterfaceInputField {
|
|
||||||
DefaultProtocol,
|
|
||||||
TextEditor,
|
|
||||||
ShowHiddenFiles,
|
|
||||||
CheckForUpdates,
|
|
||||||
GroupDirs,
|
|
||||||
FileFmt,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ### SetupTab
|
|
||||||
///
|
|
||||||
/// Selected setup tab
|
|
||||||
#[derive(std::cmp::PartialEq)]
|
#[derive(std::cmp::PartialEq)]
|
||||||
enum SetupTab {
|
enum ViewLayout {
|
||||||
UserInterface(UserInterfaceInputField),
|
SetupForm,
|
||||||
SshConfig,
|
SshKeys,
|
||||||
}
|
|
||||||
|
|
||||||
/// ### 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ## SetupActivity
|
/// ## SetupActivity
|
||||||
@@ -120,14 +73,9 @@ enum Popup {
|
|||||||
pub struct SetupActivity {
|
pub struct SetupActivity {
|
||||||
pub quit: bool, // Becomes true when user requests the activity to terminate
|
pub quit: bool, // Becomes true when user requests the activity to terminate
|
||||||
context: Option<Context>, // Context holder
|
context: Option<Context>, // Context holder
|
||||||
tab: SetupTab, // Current setup tab
|
view: View, // View
|
||||||
popup: Option<Popup>, // Active popup
|
layout: ViewLayout, // View layout
|
||||||
user_input: Vec<String>, // User input holder
|
redraw: bool,
|
||||||
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?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SetupActivity {
|
impl Default for SetupActivity {
|
||||||
@@ -140,13 +88,8 @@ impl Default for SetupActivity {
|
|||||||
SetupActivity {
|
SetupActivity {
|
||||||
quit: false,
|
quit: false,
|
||||||
context: None,
|
context: None,
|
||||||
tab: SetupTab::UserInterface(UserInterfaceInputField::TextEditor),
|
view: View::init(),
|
||||||
popup: None,
|
layout: ViewLayout::SetupForm,
|
||||||
user_input: user_input_buffer, // Max 16
|
|
||||||
user_input_ptr: 0,
|
|
||||||
quit_opt: QuitDialogOption::Save,
|
|
||||||
yesno_opt: YesNoDialogOption::Yes,
|
|
||||||
ssh_key_idx: 0,
|
|
||||||
redraw: true, // Draw at first `on_draw`
|
redraw: true, // Draw at first `on_draw`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,9 +108,11 @@ impl Activity for SetupActivity {
|
|||||||
self.context.as_mut().unwrap().clear_screen();
|
self.context.as_mut().unwrap().clear_screen();
|
||||||
// Put raw mode on enabled
|
// Put raw mode on enabled
|
||||||
let _ = enable_raw_mode();
|
let _ = enable_raw_mode();
|
||||||
|
// Init view
|
||||||
|
self.init_setup();
|
||||||
// Verify error state from context
|
// Verify error state from context
|
||||||
if let Some(err) = self.context.as_mut().unwrap().get_error() {
|
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
|
// Set redraw to true
|
||||||
self.redraw = true;
|
self.redraw = true;
|
||||||
// Handle event
|
// Handle event
|
||||||
self.handle_input_event(&event);
|
let msg = self.view.on(event);
|
||||||
|
self.update(msg);
|
||||||
}
|
}
|
||||||
// Redraw if necessary
|
// Redraw if necessary
|
||||||
if self.redraw {
|
if self.redraw {
|
||||||
// Draw
|
// View
|
||||||
self.draw();
|
self.view();
|
||||||
// Redraw back to false
|
// Redraw back to false
|
||||||
self.redraw = false;
|
self.redraw = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,11 @@
|
|||||||
|
|
||||||
// locals
|
// locals
|
||||||
use super::{
|
use super::{
|
||||||
AuthActivity, COMPONENT_INPUT_FILE_FMT, COMPONENT_INPUT_TEXT_EDITOR, COMPONENT_LIST_SSH_KEYS, COMPONENT_RADIO_DEFAULT_PROTOCOL, COMPONENT_RADIO_GROUP_DIRS,
|
SetupActivity, COMPONENT_INPUT_FILE_FMT, COMPONENT_INPUT_SSH_HOST,
|
||||||
COMPONENT_RADIO_HIDDEN_FILES, COMPONENT_RADIO_TAB, COMPONENT_RADIO_UPDATES, COMPONENT_TEXT_FOOTER, COMPONENT_TEXT_HELP,
|
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};
|
use crate::ui::layout::{Msg, Payload};
|
||||||
// ext
|
// ext
|
||||||
@@ -50,14 +53,6 @@ const MSG_KEY_DOWN: Msg = Msg::OnKey(KeyEvent {
|
|||||||
code: KeyCode::Down,
|
code: KeyCode::Down,
|
||||||
modifiers: KeyModifiers::NONE,
|
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 {
|
const MSG_KEY_UP: Msg = Msg::OnKey(KeyEvent {
|
||||||
code: KeyCode::Up,
|
code: KeyCode::Up,
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
@@ -66,11 +61,7 @@ const MSG_KEY_DEL: Msg = Msg::OnKey(KeyEvent {
|
|||||||
code: KeyCode::Delete,
|
code: KeyCode::Delete,
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
});
|
});
|
||||||
const MSG_KEY_CHAR_E: Msg = Msg::OnKey(KeyEvent {
|
const MSG_KEY_CTRL_E: Msg = Msg::OnKey(KeyEvent {
|
||||||
code: KeyCode::Char('c'),
|
|
||||||
modifiers: KeyModifiers::NONE,
|
|
||||||
});
|
|
||||||
const MSG_KEY_CTRL_C: Msg = Msg::OnKey(KeyEvent {
|
|
||||||
code: KeyCode::Char('c'),
|
code: KeyCode::Char('c'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
modifiers: KeyModifiers::CONTROL,
|
||||||
});
|
});
|
||||||
@@ -78,6 +69,14 @@ const MSG_KEY_CTRL_H: Msg = Msg::OnKey(KeyEvent {
|
|||||||
code: KeyCode::Char('h'),
|
code: KeyCode::Char('h'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
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 {
|
const MSG_KEY_CTRL_S: Msg = Msg::OnKey(KeyEvent {
|
||||||
code: KeyCode::Char('s'),
|
code: KeyCode::Char('s'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
modifiers: KeyModifiers::CONTROL,
|
||||||
@@ -85,7 +84,7 @@ const MSG_KEY_CTRL_S: Msg = Msg::OnKey(KeyEvent {
|
|||||||
|
|
||||||
// -- update
|
// -- update
|
||||||
|
|
||||||
impl AuthActivity {
|
impl SetupActivity {
|
||||||
/// ### update
|
/// ### update
|
||||||
///
|
///
|
||||||
/// Update auth activity model based on msg
|
/// Update auth activity model based on msg
|
||||||
@@ -97,6 +96,215 @@ impl AuthActivity {
|
|||||||
};
|
};
|
||||||
// Match msg
|
// Match msg
|
||||||
match ref_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