diff --git a/CHANGELOG.md b/CHANGELOG.md index 35b1c46..7c581eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,9 @@ Released on FIXME: ?? - Enhancements - Added a status bar in the file explorer showing whether the sync browser is enabled and which file sorting mode is selected - Removed the goold old figlet title + - Protocol input as first field in UI + - Port is now updated to standard for selected protocol + - when you change the protocol in the authentication form and the current port is standard (`< 1024`), the port will be automatically changed to default value for the selected protocol (e.g. current port: `123`, protocol is changes to `FTP`, port becomes `21`) - Bugfix: - Fixed wrong text wrap in log box - Fixed error message not being shown after an upload failure diff --git a/src/ui/activities/auth_activity/misc.rs b/src/ui/activities/auth_activity/misc.rs new file mode 100644 index 0000000..acda009 --- /dev/null +++ b/src/ui/activities/auth_activity/misc.rs @@ -0,0 +1,71 @@ +//! ## AuthActivity +//! +//! `auth_activity` is the module which implements the authentication activity + +/** + * 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. + */ +use super::{AuthActivity, FileTransferProtocol}; + +impl AuthActivity { + /// ### protocol_opt_to_enum + /// + /// Convert radio index for protocol into a `FileTransferProtocol` + pub(super) fn protocol_opt_to_enum(protocol: usize) -> FileTransferProtocol { + match protocol { + 1 => FileTransferProtocol::Scp, + 2 => FileTransferProtocol::Ftp(false), + 3 => FileTransferProtocol::Ftp(true), + _ => FileTransferProtocol::Sftp, + } + } + + /// ### protocol_enum_to_opt + /// + /// Convert `FileTransferProtocol` enum into radio group index + pub(super) fn protocol_enum_to_opt(protocol: FileTransferProtocol) -> usize { + match protocol { + FileTransferProtocol::Sftp => 0, + FileTransferProtocol::Scp => 1, + FileTransferProtocol::Ftp(false) => 2, + FileTransferProtocol::Ftp(true) => 3, + } + } + + /// ### get_default_port_for_protocol + /// + /// Get the default port for protocol + pub(super) fn get_default_port_for_protocol(protocol: FileTransferProtocol) -> u16 { + match protocol { + FileTransferProtocol::Sftp | FileTransferProtocol::Scp => 22, + FileTransferProtocol::Ftp(_) => 21, + } + } + + /// ### is_port_standard + /// + /// Returns whether the port is standard or not + pub(super) fn is_port_standard(port: u16) -> bool { + port < 1024 + } +} diff --git a/src/ui/activities/auth_activity/mod.rs b/src/ui/activities/auth_activity/mod.rs index 6aa5369..790cd1f 100644 --- a/src/ui/activities/auth_activity/mod.rs +++ b/src/ui/activities/auth_activity/mod.rs @@ -27,6 +27,7 @@ */ // Sub modules mod bookmarks; +mod misc; mod update; mod view; diff --git a/src/ui/activities/auth_activity/update.rs b/src/ui/activities/auth_activity/update.rs index 1372796..8970387 100644 --- a/src/ui/activities/auth_activity/update.rs +++ b/src/ui/activities/auth_activity/update.rs @@ -27,15 +27,16 @@ */ // locals use super::{ - AuthActivity, FileTransferParams, COMPONENT_BOOKMARKS_LIST, COMPONENT_INPUT_ADDR, - COMPONENT_INPUT_BOOKMARK_NAME, COMPONENT_INPUT_PASSWORD, COMPONENT_INPUT_PORT, - COMPONENT_INPUT_USERNAME, COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, + AuthActivity, FileTransferParams, FileTransferProtocol, COMPONENT_BOOKMARKS_LIST, + COMPONENT_INPUT_ADDR, COMPONENT_INPUT_BOOKMARK_NAME, COMPONENT_INPUT_PASSWORD, + COMPONENT_INPUT_PORT, COMPONENT_INPUT_USERNAME, COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, COMPONENT_RADIO_BOOKMARK_DEL_RECENT, COMPONENT_RADIO_BOOKMARK_SAVE_PWD, COMPONENT_RADIO_PROTOCOL, COMPONENT_RADIO_QUIT, COMPONENT_RECENTS_LIST, COMPONENT_TEXT_ERROR, COMPONENT_TEXT_HELP, }; use crate::ui::activities::keymap::*; -use tuirealm::{Msg, Payload, Value}; +use tuirealm::components::InputPropsBuilder; +use tuirealm::{Msg, Payload, PropsBuilder, Value}; // -- update @@ -51,17 +52,17 @@ impl AuthActivity { None => None, // Exit after None Some(msg) => match msg { // Focus ( DOWN ) + (COMPONENT_RADIO_PROTOCOL, &MSG_KEY_DOWN) => { + // Give focus to port + self.view.active(COMPONENT_INPUT_ADDR); + None + } (COMPONENT_INPUT_ADDR, &MSG_KEY_DOWN) => { // Give focus to port self.view.active(COMPONENT_INPUT_PORT); None } (COMPONENT_INPUT_PORT, &MSG_KEY_DOWN) => { - // Give focus to port - self.view.active(COMPONENT_RADIO_PROTOCOL); - None - } - (COMPONENT_RADIO_PROTOCOL, &MSG_KEY_DOWN) => { // Give focus to port self.view.active(COMPONENT_INPUT_USERNAME); None @@ -73,7 +74,7 @@ impl AuthActivity { } (COMPONENT_INPUT_PASSWORD, &MSG_KEY_DOWN) => { // Give focus to port - self.view.active(COMPONENT_INPUT_ADDR); + self.view.active(COMPONENT_RADIO_PROTOCOL); None } // Focus ( UP ) @@ -83,11 +84,6 @@ impl AuthActivity { None } (COMPONENT_INPUT_USERNAME, &MSG_KEY_UP) => { - // Give focus to port - self.view.active(COMPONENT_RADIO_PROTOCOL); - None - } - (COMPONENT_RADIO_PROTOCOL, &MSG_KEY_UP) => { // Give focus to port self.view.active(COMPONENT_INPUT_PORT); None @@ -98,10 +94,28 @@ impl AuthActivity { None } (COMPONENT_INPUT_ADDR, &MSG_KEY_UP) => { + // Give focus to port + self.view.active(COMPONENT_RADIO_PROTOCOL); + None + } + (COMPONENT_RADIO_PROTOCOL, &MSG_KEY_UP) => { // Give focus to port self.view.active(COMPONENT_INPUT_PASSWORD); None } + // Protocol - On Change + (COMPONENT_RADIO_PROTOCOL, Msg::OnChange(Payload::One(Value::Usize(protocol)))) => { + // If port is standard, update the current port with default for selected protocol + let protocol: FileTransferProtocol = Self::protocol_opt_to_enum(*protocol); + // Get port + let port: u16 = self.get_input_port(); + match Self::is_port_standard(port) { + false => None, // Return None + true => { + self.update_input_port(Self::get_default_port_for_protocol(protocol)) + } + } + } // bookmarks (COMPONENT_BOOKMARKS_LIST, &MSG_KEY_TAB) | (COMPONENT_RECENTS_LIST, &MSG_KEY_TAB) => { @@ -322,4 +336,16 @@ impl AuthActivity { }, } } + + fn update_input_port(&mut self, port: u16) -> Option<(String, Msg)> { + match self.view.get_props(COMPONENT_INPUT_PORT) { + None => None, + Some(props) => { + let props = InputPropsBuilder::from(props) + .with_value(port.to_string()) + .build(); + self.view.update(COMPONENT_INPUT_PORT, props) + } + } + } } diff --git a/src/ui/activities/auth_activity/view.rs b/src/ui/activities/auth_activity/view.rs index a03bcec..1133bda 100644 --- a/src/ui/activities/auth_activity/view.rs +++ b/src/ui/activities/auth_activity/view.rs @@ -99,42 +99,12 @@ impl AuthActivity { .build(), )), ); - // Address - self.view.mount( - super::COMPONENT_INPUT_ADDR, - Box::new(Input::new( - InputPropsBuilder::default() - .with_foreground(Color::Yellow) - .with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow) - .with_label(String::from("Remote address")) - .build(), - )), - ); // Get default protocol let default_protocol: FileTransferProtocol = match self.context.as_ref().unwrap().config_client.as_ref() { Some(cli) => cli.get_default_protocol(), None => FileTransferProtocol::Sftp, }; - // Calc default port - let default_port: String = String::from(match default_protocol { - FileTransferProtocol::Ftp(_) => "21", - FileTransferProtocol::Sftp | FileTransferProtocol::Scp => "22", - }); - // Port - self.view.mount( - super::COMPONENT_INPUT_PORT, - Box::new(Input::new( - InputPropsBuilder::default() - .with_foreground(Color::LightCyan) - .with_borders(Borders::ALL, BorderType::Rounded, Color::LightCyan) - .with_label(String::from("Port number")) - .with_input(InputType::Number) - .with_input_len(5) - .with_value(default_port) - .build(), - )), - ); // Protocol self.view.mount( super::COMPONENT_RADIO_PROTOCOL, @@ -156,6 +126,36 @@ impl AuthActivity { .build(), )), ); + // Address + self.view.mount( + super::COMPONENT_INPUT_ADDR, + Box::new(Input::new( + InputPropsBuilder::default() + .with_foreground(Color::Yellow) + .with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow) + .with_label(String::from("Remote address")) + .build(), + )), + ); + // Calc default port + let default_port: String = String::from(match default_protocol { + FileTransferProtocol::Ftp(_) => "21", + FileTransferProtocol::Sftp | FileTransferProtocol::Scp => "22", + }); + // Port + self.view.mount( + super::COMPONENT_INPUT_PORT, + Box::new(Input::new( + InputPropsBuilder::default() + .with_foreground(Color::LightCyan) + .with_borders(Borders::ALL, BorderType::Rounded, Color::LightCyan) + .with_label(String::from("Port number")) + .with_input(InputType::Number) + .with_input_len(5) + .with_value(default_port) + .build(), + )), + ); // Username self.view.mount( super::COMPONENT_INPUT_USERNAME, @@ -229,8 +229,8 @@ impl AuthActivity { )), ); let _ = self.view_recent_connections(); - // Active address - self.view.active(super::COMPONENT_INPUT_ADDR); + // Active protocol + self.view.active(super::COMPONENT_RADIO_PROTOCOL); } /// ### view @@ -258,9 +258,9 @@ impl AuthActivity { Constraint::Length(1), // h1 Constraint::Length(1), // h2 Constraint::Length(1), // Version + Constraint::Length(3), // protocol Constraint::Length(3), // host Constraint::Length(3), // port - Constraint::Length(3), // protocol Constraint::Length(3), // username Constraint::Length(3), // password Constraint::Length(3), // footer @@ -283,11 +283,11 @@ impl AuthActivity { self.view .render(super::COMPONENT_TEXT_NEW_VERSION, f, auth_chunks[2]); self.view - .render(super::COMPONENT_INPUT_ADDR, f, auth_chunks[3]); + .render(super::COMPONENT_RADIO_PROTOCOL, f, auth_chunks[3]); self.view - .render(super::COMPONENT_INPUT_PORT, f, auth_chunks[4]); + .render(super::COMPONENT_INPUT_ADDR, f, auth_chunks[4]); self.view - .render(super::COMPONENT_RADIO_PROTOCOL, f, auth_chunks[5]); + .render(super::COMPONENT_INPUT_PORT, f, auth_chunks[5]); self.view .render(super::COMPONENT_INPUT_USERNAME, f, auth_chunks[6]); self.view @@ -716,53 +716,46 @@ impl AuthActivity { /// /// Collect input values from view pub(super) fn get_input(&self) -> (String, u16, FileTransferProtocol, String, String) { - let addr: String = match self.view.get_state(super::COMPONENT_INPUT_ADDR) { - Some(Payload::One(Value::Str(a))) => a, - _ => String::new(), - }; - let port: u16 = match self.view.get_state(super::COMPONENT_INPUT_PORT) { - Some(Payload::One(Value::Usize(p))) => p as u16, - _ => 0, - }; - let protocol: FileTransferProtocol = - match self.view.get_state(super::COMPONENT_RADIO_PROTOCOL) { - Some(Payload::One(Value::Usize(p))) => Self::protocol_opt_to_enum(p), - _ => FileTransferProtocol::Sftp, - }; - let username: String = match self.view.get_state(super::COMPONENT_INPUT_USERNAME) { - Some(Payload::One(Value::Str(a))) => a, - _ => String::new(), - }; - let password: String = match self.view.get_state(super::COMPONENT_INPUT_PASSWORD) { - Some(Payload::One(Value::Str(a))) => a, - _ => String::new(), - }; + let addr: String = self.get_input_addr(); + let port: u16 = self.get_input_port(); + let protocol: FileTransferProtocol = self.get_input_protocol(); + let username: String = self.get_input_username(); + let password: String = self.get_input_password(); (addr, port, protocol, username, password) } - // -- utils + pub(super) fn get_input_addr(&self) -> String { + match self.view.get_state(super::COMPONENT_INPUT_ADDR) { + Some(Payload::One(Value::Str(x))) => x, + _ => String::new(), + } + } - /// ### protocol_opt_to_enum - /// - /// Convert radio index for protocol into a `FileTransferProtocol` - pub(crate) fn protocol_opt_to_enum(protocol: usize) -> FileTransferProtocol { - match protocol { - 1 => FileTransferProtocol::Scp, - 2 => FileTransferProtocol::Ftp(false), - 3 => FileTransferProtocol::Ftp(true), + pub(super) fn get_input_port(&self) -> u16 { + match self.view.get_state(super::COMPONENT_INPUT_PORT) { + Some(Payload::One(Value::Usize(x))) => x as u16, + _ => Self::get_default_port_for_protocol(FileTransferProtocol::Sftp), + } + } + + pub(super) fn get_input_protocol(&self) -> FileTransferProtocol { + match self.view.get_state(super::COMPONENT_RADIO_PROTOCOL) { + Some(Payload::One(Value::Usize(x))) => Self::protocol_opt_to_enum(x), _ => FileTransferProtocol::Sftp, } } - /// ### protocol_enum_to_opt - /// - /// Convert `FileTransferProtocol` enum into radio group index - pub(crate) fn protocol_enum_to_opt(protocol: FileTransferProtocol) -> usize { - match protocol { - FileTransferProtocol::Sftp => 0, - FileTransferProtocol::Scp => 1, - FileTransferProtocol::Ftp(false) => 2, - FileTransferProtocol::Ftp(true) => 3, + pub(super) fn get_input_username(&self) -> String { + match self.view.get_state(super::COMPONENT_INPUT_USERNAME) { + Some(Payload::One(Value::Str(x))) => x, + _ => String::new(), + } + } + + pub(super) fn get_input_password(&self) -> String { + match self.view.get_state(super::COMPONENT_INPUT_PASSWORD) { + Some(Payload::One(Value::Str(x))) => x, + _ => String::new(), } } }