mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
Changed activity paths
This commit is contained in:
156
src/ui/activities/setup/actions.rs
Normal file
156
src/ui/activities/setup/actions.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
//! ## SetupActivity
|
||||
//!
|
||||
//! `setup_activity` is the module which implements the Setup activity, which is the activity to
|
||||
//! work on termscp configuration
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 Christian Visintin
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
// Locals
|
||||
use super::SetupActivity;
|
||||
// Ext
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use std::env;
|
||||
use tuirealm::{Payload, Value};
|
||||
|
||||
impl SetupActivity {
|
||||
/// ### action_save_config
|
||||
///
|
||||
/// Save configuration
|
||||
pub(super) fn action_save_config(&mut self) -> Result<(), String> {
|
||||
// Collect input values
|
||||
self.collect_input_values();
|
||||
self.save_config()
|
||||
}
|
||||
|
||||
/// ### action_reset_config
|
||||
///
|
||||
/// Reset configuration input fields
|
||||
pub(super) fn action_reset_config(&mut self) -> Result<(), String> {
|
||||
match self.reset_config_changes() {
|
||||
Err(err) => Err(err),
|
||||
Ok(_) => {
|
||||
self.load_input_values();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### action_delete_ssh_key
|
||||
///
|
||||
/// delete of a ssh key
|
||||
pub(super) fn action_delete_ssh_key(&mut self) {
|
||||
// Get key
|
||||
if let Some(config_cli) = self.context.as_mut().unwrap().config_client.as_mut() {
|
||||
// get index
|
||||
let idx: Option<usize> = match self.view.get_state(super::COMPONENT_LIST_SSH_KEYS) {
|
||||
Some(Payload::One(Value::Usize(idx))) => Some(idx),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(idx) = idx {
|
||||
let key: Option<String> = config_cli.iter_ssh_keys().nth(idx).cloned();
|
||||
if let Some(key) = key {
|
||||
match config_cli.get_ssh_key(&key) {
|
||||
Ok(opt) => {
|
||||
if let Some((host, username, _)) = opt {
|
||||
if let Err(err) =
|
||||
self.delete_ssh_key(host.as_str(), username.as_str())
|
||||
{
|
||||
// Report error
|
||||
self.mount_error(err.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
// Report error
|
||||
self.mount_error(
|
||||
format!("Could not get ssh key \"{}\": {}", key, err).as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### action_new_ssh_key
|
||||
///
|
||||
/// Create a new ssh key
|
||||
pub(super) fn action_new_ssh_key(&mut self) {
|
||||
if let Some(cli) = self.context.as_mut().unwrap().config_client.as_mut() {
|
||||
// get parameters
|
||||
let host: String = match self.view.get_state(super::COMPONENT_INPUT_SSH_HOST) {
|
||||
Some(Payload::One(Value::Str(host))) => host,
|
||||
_ => String::new(),
|
||||
};
|
||||
let username: String = match self.view.get_state(super::COMPONENT_INPUT_SSH_USERNAME) {
|
||||
Some(Payload::One(Value::Str(user))) => user,
|
||||
_ => String::new(),
|
||||
};
|
||||
// Prepare text editor
|
||||
env::set_var("EDITOR", cli.get_text_editor());
|
||||
let placeholder: String = format!("# Type private SSH key for {}@{}\n", username, host);
|
||||
// Put input mode back to normal
|
||||
let _ = disable_raw_mode();
|
||||
// Leave alternate mode
|
||||
if let Some(ctx) = self.context.as_mut() {
|
||||
ctx.leave_alternate_screen();
|
||||
}
|
||||
// Re-enable raw mode
|
||||
let _ = enable_raw_mode();
|
||||
// Write key to file
|
||||
match edit::edit(placeholder.as_bytes()) {
|
||||
Ok(rsa_key) => {
|
||||
// Remove placeholder from `rsa_key`
|
||||
let rsa_key: String = rsa_key.as_str().replace(placeholder.as_str(), "");
|
||||
if rsa_key.is_empty() {
|
||||
// Report error: empty key
|
||||
self.mount_error("SSH key is empty!");
|
||||
} else {
|
||||
// Add key
|
||||
if let Err(err) =
|
||||
self.add_ssh_key(host.as_str(), username.as_str(), rsa_key.as_str())
|
||||
{
|
||||
self.mount_error(
|
||||
format!("Could not create new private key: {}", err).as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
// Report error
|
||||
self.mount_error(
|
||||
format!("Could not write private key to file: {}", err).as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Restore terminal
|
||||
if let Some(ctx) = self.context.as_mut() {
|
||||
// Clear screen
|
||||
ctx.clear_screen();
|
||||
// Enter alternate mode
|
||||
ctx.enter_alternate_screen();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
151
src/ui/activities/setup/config.rs
Normal file
151
src/ui/activities/setup/config.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
//! ## SetupActivity
|
||||
//!
|
||||
//! `setup_activity` is the module which implements the Setup activity, which is the activity to
|
||||
//! work on termscp configuration
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 Christian Visintin
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
// Locals
|
||||
use super::SetupActivity;
|
||||
// Ext
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use std::env;
|
||||
|
||||
impl SetupActivity {
|
||||
/// ### save_config
|
||||
///
|
||||
/// Save configuration
|
||||
pub(super) fn save_config(&mut self) -> Result<(), String> {
|
||||
match self.context.as_ref().unwrap().config_client.as_ref() {
|
||||
Some(cli) => match cli.write_config() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(format!("Could not save configuration: {}", err)),
|
||||
},
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### reset_config_changes
|
||||
///
|
||||
/// Reset configuration changes; pratically read config from file, overwriting any change made
|
||||
/// since last write action
|
||||
pub(super) fn reset_config_changes(&mut self) -> Result<(), String> {
|
||||
match self.context.as_mut().unwrap().config_client.as_mut() {
|
||||
Some(cli) => match cli.read_config() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(format!("Could not restore configuration: {}", err)),
|
||||
},
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### delete_ssh_key
|
||||
///
|
||||
/// Delete ssh key from config cli
|
||||
pub(super) fn delete_ssh_key(&mut self, host: &str, username: &str) -> Result<(), String> {
|
||||
match self.context.as_mut().unwrap().config_client.as_mut() {
|
||||
Some(cli) => match cli.del_ssh_key(host, username) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(format!(
|
||||
"Could not delete ssh key \"{}@{}\": {}",
|
||||
host, username, err
|
||||
)),
|
||||
},
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### edit_ssh_key
|
||||
///
|
||||
/// Edit selected ssh key
|
||||
pub(super) fn edit_ssh_key(&mut self, idx: usize) -> Result<(), String> {
|
||||
match self.context.as_mut() {
|
||||
None => Ok(()),
|
||||
Some(ctx) => {
|
||||
// Set editor if config client exists
|
||||
if let Some(config_cli) = ctx.config_client.as_ref() {
|
||||
env::set_var("EDITOR", config_cli.get_text_editor());
|
||||
}
|
||||
// Prepare terminal
|
||||
let _ = disable_raw_mode();
|
||||
// Leave alternate mode
|
||||
ctx.leave_alternate_screen();
|
||||
// Get result
|
||||
let result: Result<(), String> = match ctx.config_client.as_ref() {
|
||||
Some(config_cli) => match config_cli.iter_ssh_keys().nth(idx) {
|
||||
Some(key) => {
|
||||
// Get key path
|
||||
match config_cli.get_ssh_key(key) {
|
||||
Ok(ssh_key) => match ssh_key {
|
||||
None => Ok(()),
|
||||
Some((_, _, key_path)) => {
|
||||
match edit::edit_file(key_path.as_path()) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
Err(format!("Could not edit ssh key: {}", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => Err(format!("Could not read ssh key: {}", err)),
|
||||
}
|
||||
}
|
||||
None => Ok(()),
|
||||
},
|
||||
None => Ok(()),
|
||||
};
|
||||
// Restore terminal
|
||||
// Clear screen
|
||||
ctx.clear_screen();
|
||||
// Enter alternate mode
|
||||
ctx.enter_alternate_screen();
|
||||
// Re-enable raw mode
|
||||
let _ = enable_raw_mode();
|
||||
// Return result
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### add_ssh_key
|
||||
///
|
||||
/// Add provided ssh key to config client
|
||||
pub(super) fn add_ssh_key(
|
||||
&mut self,
|
||||
host: &str,
|
||||
username: &str,
|
||||
rsa_key: &str,
|
||||
) -> Result<(), String> {
|
||||
match self.context.as_mut().unwrap().config_client.as_mut() {
|
||||
Some(cli) => {
|
||||
// Add key to client
|
||||
match cli.add_ssh_key(host, username, rsa_key) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(format!("Could not add SSH key: {}", err)),
|
||||
}
|
||||
}
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
175
src/ui/activities/setup/mod.rs
Normal file
175
src/ui/activities/setup/mod.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
//! ## SetupActivity
|
||||
//!
|
||||
//! `setup_activity` is the module which implements the Setup activity, which is the activity to
|
||||
//! work on termscp configuration
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 Christian Visintin
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
// Submodules
|
||||
mod actions;
|
||||
mod config;
|
||||
mod update;
|
||||
mod view;
|
||||
|
||||
// Deps
|
||||
extern crate crossterm;
|
||||
extern crate tuirealm;
|
||||
|
||||
// Locals
|
||||
use super::{Activity, Context, ExitReason};
|
||||
// Ext
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use tuirealm::View;
|
||||
|
||||
// -- components
|
||||
const COMPONENT_TEXT_HELP: &str = "TEXT_HELP";
|
||||
const COMPONENT_TEXT_FOOTER: &str = "TEXT_FOOTER";
|
||||
const COMPONENT_TEXT_ERROR: &str = "TEXT_ERROR";
|
||||
const COMPONENT_RADIO_QUIT: &str = "RADIO_QUIT";
|
||||
const COMPONENT_RADIO_SAVE: &str = "RADIO_SAVE";
|
||||
const COMPONENT_INPUT_TEXT_EDITOR: &str = "INPUT_TEXT_EDITOR";
|
||||
const COMPONENT_RADIO_DEFAULT_PROTOCOL: &str = "RADIO_DEFAULT_PROTOCOL";
|
||||
const COMPONENT_RADIO_HIDDEN_FILES: &str = "RADIO_HIDDEN_FILES";
|
||||
const COMPONENT_RADIO_UPDATES: &str = "RADIO_CHECK_UPDATES";
|
||||
const COMPONENT_RADIO_GROUP_DIRS: &str = "RADIO_GROUP_DIRS";
|
||||
const COMPONENT_INPUT_LOCAL_FILE_FMT: &str = "INPUT_LOCAL_FILE_FMT";
|
||||
const COMPONENT_INPUT_REMOTE_FILE_FMT: &str = "INPUT_REMOTE_FILE_FMT";
|
||||
const COMPONENT_RADIO_TAB: &str = "RADIO_TAB";
|
||||
const COMPONENT_LIST_SSH_KEYS: &str = "LIST_SSH_KEYS";
|
||||
const COMPONENT_INPUT_SSH_HOST: &str = "INPUT_SSH_HOST";
|
||||
const COMPONENT_INPUT_SSH_USERNAME: &str = "INPUT_SSH_USERNAME";
|
||||
const COMPONENT_RADIO_DEL_SSH_KEY: &str = "RADIO_DEL_SSH_KEY";
|
||||
|
||||
/// ### ViewLayout
|
||||
///
|
||||
/// Current view layout
|
||||
#[derive(std::cmp::PartialEq)]
|
||||
enum ViewLayout {
|
||||
SetupForm,
|
||||
SshKeys,
|
||||
}
|
||||
|
||||
/// ## SetupActivity
|
||||
///
|
||||
/// Setup activity states holder
|
||||
pub struct SetupActivity {
|
||||
exit_reason: Option<ExitReason>,
|
||||
context: Option<Context>, // Context holder
|
||||
view: View, // View
|
||||
layout: ViewLayout, // View layout
|
||||
redraw: bool,
|
||||
}
|
||||
|
||||
impl Default for SetupActivity {
|
||||
fn default() -> Self {
|
||||
// Initialize user input
|
||||
let mut user_input_buffer: Vec<String> = Vec::with_capacity(16);
|
||||
for _ in 0..16 {
|
||||
user_input_buffer.push(String::new());
|
||||
}
|
||||
SetupActivity {
|
||||
exit_reason: None,
|
||||
context: None,
|
||||
view: View::init(),
|
||||
layout: ViewLayout::SetupForm,
|
||||
redraw: true, // Draw at first `on_draw`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Activity for SetupActivity {
|
||||
/// ### on_create
|
||||
///
|
||||
/// `on_create` is the function which must be called to initialize the activity.
|
||||
/// `on_create` must initialize all the data structures used by the activity
|
||||
/// Context is taken from activity manager and will be released only when activity is destroyed
|
||||
fn on_create(&mut self, context: Context) {
|
||||
// Set context
|
||||
self.context = Some(context);
|
||||
// Clear terminal
|
||||
self.context.as_mut().unwrap().clear_screen();
|
||||
// Put raw mode on enabled
|
||||
let _ = enable_raw_mode();
|
||||
// Init view
|
||||
self.init_setup();
|
||||
// Verify error state from context
|
||||
if let Some(err) = self.context.as_mut().unwrap().get_error() {
|
||||
self.mount_error(err.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
/// ### on_draw
|
||||
///
|
||||
/// `on_draw` is the function which draws the graphical interface.
|
||||
/// This function must be called at each tick to refresh the interface
|
||||
fn on_draw(&mut self) {
|
||||
// Context must be something
|
||||
if self.context.is_none() {
|
||||
return;
|
||||
}
|
||||
// Read one event
|
||||
if let Ok(Some(event)) = self.context.as_ref().unwrap().input_hnd.read_event() {
|
||||
// Set redraw to true
|
||||
self.redraw = true;
|
||||
// Handle event
|
||||
let msg = self.view.on(event);
|
||||
self.update(msg);
|
||||
}
|
||||
// Redraw if necessary
|
||||
if self.redraw {
|
||||
// View
|
||||
self.view();
|
||||
// Redraw back to false
|
||||
self.redraw = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// ### will_umount
|
||||
///
|
||||
/// `will_umount` is the method which must be able to report to the activity manager, whether
|
||||
/// the activity should be terminated or not.
|
||||
/// If not, the call will return `None`, otherwise return`Some(ExitReason)`
|
||||
fn will_umount(&self) -> Option<&ExitReason> {
|
||||
self.exit_reason.as_ref()
|
||||
}
|
||||
|
||||
/// ### on_destroy
|
||||
///
|
||||
/// `on_destroy` is the function which cleans up runtime variables and data before terminating the activity.
|
||||
/// This function must be called once before terminating the activity.
|
||||
/// This function finally releases the context
|
||||
fn on_destroy(&mut self) -> Option<Context> {
|
||||
// Disable raw mode
|
||||
let _ = disable_raw_mode();
|
||||
self.context.as_ref()?;
|
||||
// Clear terminal and return
|
||||
match self.context.take() {
|
||||
Some(mut ctx) => {
|
||||
ctx.clear_screen();
|
||||
Some(ctx)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
272
src/ui/activities/setup/update.rs
Normal file
272
src/ui/activities/setup/update.rs
Normal file
@@ -0,0 +1,272 @@
|
||||
//! ## SetupActivity
|
||||
//!
|
||||
//! `setup_activity` is the module which implements the Setup activity, which is the activity to
|
||||
//! work on termscp configuration
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 Christian Visintin
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
// locals
|
||||
use super::{
|
||||
SetupActivity, COMPONENT_INPUT_LOCAL_FILE_FMT, COMPONENT_INPUT_REMOTE_FILE_FMT,
|
||||
COMPONENT_INPUT_SSH_HOST, COMPONENT_INPUT_SSH_USERNAME, COMPONENT_INPUT_TEXT_EDITOR,
|
||||
COMPONENT_LIST_SSH_KEYS, COMPONENT_RADIO_DEFAULT_PROTOCOL, COMPONENT_RADIO_DEL_SSH_KEY,
|
||||
COMPONENT_RADIO_GROUP_DIRS, COMPONENT_RADIO_HIDDEN_FILES, COMPONENT_RADIO_QUIT,
|
||||
COMPONENT_RADIO_SAVE, COMPONENT_RADIO_UPDATES, COMPONENT_TEXT_ERROR, COMPONENT_TEXT_HELP,
|
||||
};
|
||||
use crate::ui::keymap::*;
|
||||
|
||||
// ext
|
||||
use tuirealm::{Msg, Payload, Value};
|
||||
|
||||
impl SetupActivity {
|
||||
/// ### update
|
||||
///
|
||||
/// Update auth activity model based on msg
|
||||
/// The function exits when returns None
|
||||
pub(super) fn update(&mut self, msg: Option<(String, Msg)>) -> Option<(String, Msg)> {
|
||||
let ref_msg: Option<(&str, &Msg)> = msg.as_ref().map(|(s, msg)| (s.as_str(), msg));
|
||||
// Match msg
|
||||
match ref_msg {
|
||||
None => None,
|
||||
Some(msg) => match msg {
|
||||
// Input field <DOWN>
|
||||
(COMPONENT_INPUT_TEXT_EDITOR, &MSG_KEY_DOWN) => {
|
||||
self.view.active(COMPONENT_RADIO_DEFAULT_PROTOCOL);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_DEFAULT_PROTOCOL, &MSG_KEY_DOWN) => {
|
||||
self.view.active(COMPONENT_RADIO_HIDDEN_FILES);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_HIDDEN_FILES, &MSG_KEY_DOWN) => {
|
||||
self.view.active(COMPONENT_RADIO_UPDATES);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_UPDATES, &MSG_KEY_DOWN) => {
|
||||
self.view.active(COMPONENT_RADIO_GROUP_DIRS);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_GROUP_DIRS, &MSG_KEY_DOWN) => {
|
||||
self.view.active(COMPONENT_INPUT_LOCAL_FILE_FMT);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_LOCAL_FILE_FMT, &MSG_KEY_DOWN) => {
|
||||
self.view.active(COMPONENT_INPUT_REMOTE_FILE_FMT);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_REMOTE_FILE_FMT, &MSG_KEY_DOWN) => {
|
||||
self.view.active(COMPONENT_INPUT_TEXT_EDITOR);
|
||||
None
|
||||
}
|
||||
// Input field <UP>
|
||||
(COMPONENT_INPUT_REMOTE_FILE_FMT, &MSG_KEY_UP) => {
|
||||
self.view.active(COMPONENT_INPUT_LOCAL_FILE_FMT);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_LOCAL_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_REMOTE_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::One(Value::Usize(0)))) => {
|
||||
// Save changes
|
||||
if let Err(err) = self.action_save_config() {
|
||||
self.mount_error(err.as_str());
|
||||
}
|
||||
// Exit
|
||||
self.exit_reason = Some(super::ExitReason::Quit);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_QUIT, Msg::OnSubmit(Payload::One(Value::Usize(1)))) => {
|
||||
// Quit
|
||||
self.exit_reason = Some(super::ExitReason::Quit);
|
||||
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::One(Value::Usize(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::One(Value::Usize(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::One(Value::Usize(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
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
805
src/ui/activities/setup/view.rs
Normal file
805
src/ui/activities/setup/view.rs
Normal file
@@ -0,0 +1,805 @@
|
||||
//! ## SetupActivity
|
||||
//!
|
||||
//! `setup_activity` is the module which implements the Setup activity, which is the activity to
|
||||
//! work on termscp configuration
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 Christian Visintin
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
// Locals
|
||||
use super::{Context, SetupActivity, ViewLayout};
|
||||
use crate::filetransfer::FileTransferProtocol;
|
||||
use crate::fs::explorer::GroupDirs;
|
||||
use crate::ui::components::{
|
||||
bookmark_list::{BookmarkList, BookmarkListPropsBuilder},
|
||||
msgbox::{MsgBox, MsgBoxPropsBuilder},
|
||||
};
|
||||
use crate::utils::ui::draw_area_in;
|
||||
// Ext
|
||||
use std::path::PathBuf;
|
||||
use tuirealm::components::{
|
||||
input::{Input, InputPropsBuilder},
|
||||
radio::{Radio, RadioPropsBuilder},
|
||||
span::{Span, SpanPropsBuilder},
|
||||
table::{Table, TablePropsBuilder},
|
||||
};
|
||||
use tuirealm::tui::{
|
||||
layout::{Constraint, Direction, Layout},
|
||||
style::Color,
|
||||
widgets::{BorderType, Borders, Clear},
|
||||
};
|
||||
use tuirealm::{
|
||||
props::{PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder},
|
||||
Payload, Value, View,
|
||||
};
|
||||
|
||||
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(Radio::new(
|
||||
RadioPropsBuilder::default()
|
||||
.with_color(Color::LightYellow)
|
||||
.with_inverted_color(Color::Black)
|
||||
.with_borders(Borders::BOTTOM, BorderType::Thick, Color::White)
|
||||
.with_options(
|
||||
None,
|
||||
vec![String::from("User Interface"), String::from("SSH Keys")],
|
||||
)
|
||||
.with_value(0)
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
// Footer
|
||||
self.view.mount(
|
||||
super::COMPONENT_TEXT_FOOTER,
|
||||
Box::new(Span::new(
|
||||
SpanPropsBuilder::default()
|
||||
.with_spans(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(
|
||||
InputPropsBuilder::default()
|
||||
.with_foreground(Color::LightGreen)
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightGreen)
|
||||
.with_label(String::from("Text editor"))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.active(super::COMPONENT_INPUT_TEXT_EDITOR); // <-- Focus
|
||||
self.view.mount(
|
||||
super::COMPONENT_RADIO_DEFAULT_PROTOCOL,
|
||||
Box::new(Radio::new(
|
||||
RadioPropsBuilder::default()
|
||||
.with_color(Color::LightCyan)
|
||||
.with_inverted_color(Color::Black)
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightCyan)
|
||||
.with_options(
|
||||
Some(String::from("Default file transfer protocol")),
|
||||
vec![
|
||||
String::from("SFTP"),
|
||||
String::from("SCP"),
|
||||
String::from("FTP"),
|
||||
String::from("FTPS"),
|
||||
],
|
||||
)
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.mount(
|
||||
super::COMPONENT_RADIO_HIDDEN_FILES,
|
||||
Box::new(Radio::new(
|
||||
RadioPropsBuilder::default()
|
||||
.with_color(Color::LightRed)
|
||||
.with_inverted_color(Color::Black)
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightRed)
|
||||
.with_options(
|
||||
Some(String::from("Show hidden files (by default)")),
|
||||
vec![String::from("Yes"), String::from("No")],
|
||||
)
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.mount(
|
||||
super::COMPONENT_RADIO_UPDATES,
|
||||
Box::new(Radio::new(
|
||||
RadioPropsBuilder::default()
|
||||
.with_color(Color::LightYellow)
|
||||
.with_inverted_color(Color::Black)
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow)
|
||||
.with_options(
|
||||
Some(String::from("Check for updates?")),
|
||||
vec![String::from("Yes"), String::from("No")],
|
||||
)
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.mount(
|
||||
super::COMPONENT_RADIO_GROUP_DIRS,
|
||||
Box::new(Radio::new(
|
||||
RadioPropsBuilder::default()
|
||||
.with_color(Color::LightMagenta)
|
||||
.with_inverted_color(Color::Black)
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightMagenta)
|
||||
.with_options(
|
||||
Some(String::from("Group directories")),
|
||||
vec![
|
||||
String::from("Display first"),
|
||||
String::from("Display Last"),
|
||||
String::from("No"),
|
||||
],
|
||||
)
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.mount(
|
||||
super::COMPONENT_INPUT_LOCAL_FILE_FMT,
|
||||
Box::new(Input::new(
|
||||
InputPropsBuilder::default()
|
||||
.with_foreground(Color::LightBlue)
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightBlue)
|
||||
.with_label(String::from("File formatter syntax (local)"))
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.mount(
|
||||
super::COMPONENT_INPUT_REMOTE_FILE_FMT,
|
||||
Box::new(Input::new(
|
||||
InputPropsBuilder::default()
|
||||
.with_foreground(Color::LightGreen)
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightGreen)
|
||||
.with_label(String::from("File formatter syntax (remote)"))
|
||||
.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(Radio::new(
|
||||
RadioPropsBuilder::default()
|
||||
.with_color(Color::LightYellow)
|
||||
.with_inverted_color(Color::Black)
|
||||
.with_borders(Borders::BOTTOM, BorderType::Thick, Color::LightYellow)
|
||||
.with_options(
|
||||
None,
|
||||
vec![String::from("User Interface"), String::from("SSH Keys")],
|
||||
)
|
||||
.with_value(1)
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
// Footer
|
||||
self.view.mount(
|
||||
super::COMPONENT_TEXT_FOOTER,
|
||||
Box::new(Span::new(
|
||||
SpanPropsBuilder::default()
|
||||
.with_spans(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(
|
||||
BookmarkListPropsBuilder::default()
|
||||
.with_bookmarks(Some(String::from("SSH Keys")), vec![])
|
||||
.with_borders(Borders::ALL, BorderType::Plain, Color::LightGreen)
|
||||
.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), // Local Format input
|
||||
Constraint::Length(3), // Remote 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_LOCAL_FILE_FMT, f, ui_cfg_chunks[5]);
|
||||
self.view
|
||||
.render(super::COMPONENT_INPUT_REMOTE_FILE_FMT, f, ui_cfg_chunks[6]);
|
||||
}
|
||||
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(props) = self.view.get_props(super::COMPONENT_TEXT_ERROR) {
|
||||
if props.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(props) = self.view.get_props(super::COMPONENT_RADIO_QUIT) {
|
||||
if props.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(props) = self.view.get_props(super::COMPONENT_TEXT_HELP) {
|
||||
if props.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(props) = self.view.get_props(super::COMPONENT_RADIO_SAVE) {
|
||||
if props.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(props) = self.view.get_props(super::COMPONENT_RADIO_DEL_SSH_KEY) {
|
||||
if props.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(props) = self.view.get_props(super::COMPONENT_INPUT_SSH_HOST) {
|
||||
if props.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(MsgBox::new(
|
||||
MsgBoxPropsBuilder::default()
|
||||
.with_foreground(Color::Red)
|
||||
.bold()
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::Red)
|
||||
.with_texts(None, 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(Radio::new(
|
||||
RadioPropsBuilder::default()
|
||||
.with_color(Color::LightRed)
|
||||
.with_inverted_color(Color::Black)
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightRed)
|
||||
.with_options(
|
||||
Some(String::from("Delete key?")),
|
||||
vec![String::from("Yes"), String::from("No")],
|
||||
)
|
||||
.with_value(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(
|
||||
InputPropsBuilder::default()
|
||||
.with_label(String::from("Hostname or address"))
|
||||
.with_borders(
|
||||
Borders::TOP | Borders::RIGHT | Borders::LEFT,
|
||||
BorderType::Plain,
|
||||
Color::Reset,
|
||||
)
|
||||
.build(),
|
||||
)),
|
||||
);
|
||||
self.view.mount(
|
||||
super::COMPONENT_INPUT_SSH_USERNAME,
|
||||
Box::new(Input::new(
|
||||
InputPropsBuilder::default()
|
||||
.with_label(String::from("Username"))
|
||||
.with_borders(
|
||||
Borders::ALL | Borders::RIGHT | Borders::LEFT,
|
||||
BorderType::Plain,
|
||||
Color::Reset,
|
||||
)
|
||||
.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(Radio::new(
|
||||
RadioPropsBuilder::default()
|
||||
.with_color(Color::LightRed)
|
||||
.with_inverted_color(Color::Black)
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightRed)
|
||||
.with_options(
|
||||
Some(String::from("Exit setup?")),
|
||||
vec![
|
||||
String::from("Save"),
|
||||
String::from("Don't save"),
|
||||
String::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(Radio::new(
|
||||
RadioPropsBuilder::default()
|
||||
.with_color(Color::LightYellow)
|
||||
.with_inverted_color(Color::Black)
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow)
|
||||
.with_options(
|
||||
Some(String::from("Save changes?")),
|
||||
vec![String::from("Yes"), String::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(
|
||||
TablePropsBuilder::default()
|
||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::White)
|
||||
.with_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) {
|
||||
let text_editor: String =
|
||||
String::from(cli.get_text_editor().as_path().to_string_lossy());
|
||||
let props = InputPropsBuilder::from(props)
|
||||
.with_value(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) {
|
||||
let protocol: usize = match cli.get_default_protocol() {
|
||||
FileTransferProtocol::Sftp => 0,
|
||||
FileTransferProtocol::Scp => 1,
|
||||
FileTransferProtocol::Ftp(false) => 2,
|
||||
FileTransferProtocol::Ftp(true) => 3,
|
||||
};
|
||||
let props = RadioPropsBuilder::from(props).with_value(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) {
|
||||
let hidden: usize = match cli.get_show_hidden_files() {
|
||||
true => 0,
|
||||
false => 1,
|
||||
};
|
||||
let props = RadioPropsBuilder::from(props).with_value(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) {
|
||||
let updates: usize = match cli.get_check_for_updates() {
|
||||
true => 0,
|
||||
false => 1,
|
||||
};
|
||||
let props = RadioPropsBuilder::from(props).with_value(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) {
|
||||
let dirs: usize = match cli.get_group_dirs() {
|
||||
Some(GroupDirs::First) => 0,
|
||||
Some(GroupDirs::Last) => 1,
|
||||
None => 2,
|
||||
};
|
||||
let props = RadioPropsBuilder::from(props).with_value(dirs).build();
|
||||
let _ = self.view.update(super::COMPONENT_RADIO_GROUP_DIRS, props);
|
||||
}
|
||||
// Local File Fmt
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_LOCAL_FILE_FMT) {
|
||||
let file_fmt: String = cli.get_local_file_fmt().unwrap_or_default();
|
||||
let props = InputPropsBuilder::from(props).with_value(file_fmt).build();
|
||||
let _ = self
|
||||
.view
|
||||
.update(super::COMPONENT_INPUT_LOCAL_FILE_FMT, props);
|
||||
}
|
||||
// Remote File Fmt
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_REMOTE_FILE_FMT) {
|
||||
let file_fmt: String = cli.get_remote_file_fmt().unwrap_or_default();
|
||||
let props = InputPropsBuilder::from(props).with_value(file_fmt).build();
|
||||
let _ = self
|
||||
.view
|
||||
.update(super::COMPONENT_INPUT_REMOTE_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::One(Value::Str(editor))) =
|
||||
self.view.get_state(super::COMPONENT_INPUT_TEXT_EDITOR)
|
||||
{
|
||||
cli.set_text_editor(PathBuf::from(editor.as_str()));
|
||||
}
|
||||
if let Some(Payload::One(Value::Usize(protocol))) =
|
||||
self.view.get_state(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::One(Value::Usize(opt))) =
|
||||
self.view.get_state(super::COMPONENT_RADIO_HIDDEN_FILES)
|
||||
{
|
||||
let show: bool = matches!(opt, 0);
|
||||
cli.set_show_hidden_files(show);
|
||||
}
|
||||
if let Some(Payload::One(Value::Usize(opt))) =
|
||||
self.view.get_state(super::COMPONENT_RADIO_UPDATES)
|
||||
{
|
||||
let check: bool = matches!(opt, 0);
|
||||
cli.set_check_for_updates(check);
|
||||
}
|
||||
if let Some(Payload::One(Value::Str(fmt))) =
|
||||
self.view.get_state(super::COMPONENT_INPUT_LOCAL_FILE_FMT)
|
||||
{
|
||||
cli.set_local_file_fmt(fmt);
|
||||
}
|
||||
if let Some(Payload::One(Value::Str(fmt))) =
|
||||
self.view.get_state(super::COMPONENT_INPUT_REMOTE_FILE_FMT)
|
||||
{
|
||||
cli.set_remote_file_fmt(fmt);
|
||||
}
|
||||
if let Some(Payload::One(Value::Usize(opt))) =
|
||||
self.view.get_state(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) {
|
||||
// Create texts
|
||||
let keys: Vec<String> = cli
|
||||
.iter_ssh_keys()
|
||||
.map(|x| {
|
||||
let (addr, username, _) = cli.get_ssh_key(x).ok().unwrap().unwrap();
|
||||
format!("{} at {}", addr, username)
|
||||
})
|
||||
.collect();
|
||||
let props = BookmarkListPropsBuilder::from(props)
|
||||
.with_bookmarks(Some(String::from("SSH Keys")), keys)
|
||||
.build();
|
||||
self.view.update(super::COMPONENT_LIST_SSH_KEYS, props);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user