mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
@@ -52,16 +52,21 @@ pub struct UserInterfaceConfig {
|
||||
pub check_for_updates: Option<bool>, // @! Since 0.3.3
|
||||
pub prompt_on_file_replace: Option<bool>, // @! Since 0.7.0; Default True
|
||||
pub group_dirs: Option<String>,
|
||||
pub file_fmt: Option<String>, // Refers to local host (for backward compatibility)
|
||||
pub remote_file_fmt: Option<String>, // @! Since 0.5.0
|
||||
pub notifications: Option<bool>, // @! Since 0.7.0; Default true
|
||||
/// file fmt. Refers to local host (for backward compatibility)
|
||||
pub file_fmt: Option<String>,
|
||||
pub remote_file_fmt: Option<String>, // @! Since 0.5.0
|
||||
pub notifications: Option<bool>, // @! Since 0.7.0; Default true
|
||||
pub notification_threshold: Option<u64>, // @! Since 0.7.0; Default 512MB
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Default)]
|
||||
/// Contains configuratio related to remote hosts
|
||||
pub struct RemoteConfig {
|
||||
pub ssh_keys: HashMap<String, PathBuf>, // Association between host name and path to private key
|
||||
/// Ssh configuration path. If NONE, won't be read
|
||||
pub ssh_config: Option<String>,
|
||||
/// Association between host name and path to private key
|
||||
/// NOTE: this parameter must stay as last: <https://github.com/alexcrichton/toml-rs/issues/142>
|
||||
pub ssh_keys: HashMap<String, PathBuf>,
|
||||
}
|
||||
|
||||
impl Default for UserInterfaceConfig {
|
||||
@@ -99,7 +104,10 @@ mod tests {
|
||||
String::from("192.168.1.31"),
|
||||
PathBuf::from("/tmp/private.key"),
|
||||
);
|
||||
let remote: RemoteConfig = RemoteConfig { ssh_keys: keys };
|
||||
let remote: RemoteConfig = RemoteConfig {
|
||||
ssh_keys: keys,
|
||||
ssh_config: Some(String::from("~/.ssh/config")),
|
||||
};
|
||||
let ui: UserInterfaceConfig = UserInterfaceConfig {
|
||||
default_protocol: String::from("SFTP"),
|
||||
text_editor: PathBuf::from("nano"),
|
||||
@@ -130,6 +138,10 @@ mod tests {
|
||||
.unwrap(),
|
||||
PathBuf::from("/tmp/private.key")
|
||||
);
|
||||
assert_eq!(
|
||||
cfg.remote.ssh_config.as_deref().unwrap(),
|
||||
String::from("~/.ssh/config")
|
||||
);
|
||||
assert_eq!(cfg.user_interface.default_protocol, String::from("SFTP"));
|
||||
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("nano"));
|
||||
assert_eq!(cfg.user_interface.show_hidden_files, true);
|
||||
|
||||
@@ -197,6 +197,11 @@ mod tests {
|
||||
assert_eq!(cfg.user_interface.notifications.unwrap(), false);
|
||||
assert_eq!(cfg.user_interface.notification_threshold.unwrap(), 1024);
|
||||
assert_eq!(cfg.user_interface.group_dirs, Some(String::from("last")));
|
||||
// Remote
|
||||
assert_eq!(
|
||||
cfg.remote.ssh_config.as_deref(),
|
||||
Some("/home/omar/.ssh/config")
|
||||
);
|
||||
assert_eq!(
|
||||
cfg.user_interface.file_fmt,
|
||||
Some(String::from("{NAME} {PEX}"))
|
||||
@@ -244,6 +249,7 @@ mod tests {
|
||||
assert!(cfg.user_interface.remote_file_fmt.is_none());
|
||||
assert!(cfg.user_interface.notifications.is_none());
|
||||
assert!(cfg.user_interface.notification_threshold.is_none());
|
||||
assert!(cfg.remote.ssh_config.is_none());
|
||||
// Verify keys
|
||||
assert_eq!(
|
||||
*cfg.remote
|
||||
@@ -322,6 +328,9 @@ mod tests {
|
||||
notifications = false
|
||||
notification_threshold = 1024
|
||||
|
||||
[remote]
|
||||
ssh_config = "/home/omar/.ssh/config"
|
||||
|
||||
[remote.ssh_keys]
|
||||
"192.168.1.31" = "/home/omar/.ssh/raspberry.key"
|
||||
"192.168.1.32" = "/home/omar/.ssh/beaglebone.key"
|
||||
|
||||
@@ -36,6 +36,7 @@ use remotefs::client::{
|
||||
ssh::{ScpFs, SftpFs, SshOpts},
|
||||
};
|
||||
use remotefs::RemoteFs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Remotefs builder
|
||||
pub struct Builder;
|
||||
@@ -117,6 +118,9 @@ impl Builder {
|
||||
if let Some(password) = params.password {
|
||||
opts = opts.password(password);
|
||||
}
|
||||
if let Some(config_path) = config_client.get_ssh_config() {
|
||||
opts = opts.config_file(PathBuf::from(config_path));
|
||||
}
|
||||
opts
|
||||
}
|
||||
|
||||
|
||||
@@ -239,6 +239,18 @@ impl ConfigClient {
|
||||
self.config.user_interface.notification_threshold = Some(value);
|
||||
}
|
||||
|
||||
// Remote params
|
||||
|
||||
/// Get ssh config path
|
||||
pub fn get_ssh_config(&self) -> Option<&str> {
|
||||
self.config.remote.ssh_config.as_deref()
|
||||
}
|
||||
|
||||
/// Set ssh config path
|
||||
pub fn set_ssh_config(&mut self, p: Option<String>) {
|
||||
self.config.remote.ssh_config = p;
|
||||
}
|
||||
|
||||
// SSH Keys
|
||||
|
||||
/// Save a SSH key into configuration.
|
||||
@@ -655,6 +667,20 @@ mod tests {
|
||||
assert_eq!(client.get_notification_threshold(), 64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_config_remote_ssh_config() {
|
||||
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
assert_eq!(client.get_ssh_config(), None); // Null ?
|
||||
client.set_ssh_config(Some(String::from("/tmp/ssh_config")));
|
||||
assert_eq!(client.get_ssh_config(), Some("/tmp/ssh_config"));
|
||||
client.set_ssh_config(None);
|
||||
assert_eq!(client.get_ssh_config(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_config_ssh_keys() {
|
||||
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
||||
|
||||
@@ -391,6 +391,7 @@ impl BookmarkName {
|
||||
impl Component<Msg, NoUserEvent> for BookmarkName {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::CloseSaveBookmark),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
|
||||
@@ -368,6 +368,43 @@ impl Component<Msg, NoUserEvent> for RemoteFileFmt {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct SshConfig {
|
||||
component: Input,
|
||||
}
|
||||
|
||||
impl SshConfig {
|
||||
pub fn new(value: &str) -> Self {
|
||||
Self {
|
||||
component: Input::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(Color::LightBlue)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(Color::LightBlue)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
"~/.ssh/config",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
.title("SSH configuration path", Alignment::Left)
|
||||
.value(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for SshConfig {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_input_ev(
|
||||
self,
|
||||
ev,
|
||||
Msg::Config(ConfigMsg::SshConfigBlurDown),
|
||||
Msg::Config(ConfigMsg::SshConfigBlurUp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct TextEditor {
|
||||
component: Input,
|
||||
@@ -448,7 +485,7 @@ fn handle_input_ev(
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(ch),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
|
||||
}) => {
|
||||
component.perform(Cmd::Type(ch));
|
||||
Some(Msg::Config(ConfigMsg::ConfigChanged))
|
||||
|
||||
@@ -35,7 +35,7 @@ mod theme;
|
||||
pub(super) use commons::{ErrorPopup, Footer, Header, Keybindings, QuitPopup, SavePopup};
|
||||
pub(super) use config::{
|
||||
CheckUpdates, DefaultProtocol, GroupDirs, HiddenFiles, LocalFileFmt, NotificationsEnabled,
|
||||
NotificationsThreshold, PromptOnFileReplace, RemoteFileFmt, TextEditor,
|
||||
NotificationsThreshold, PromptOnFileReplace, RemoteFileFmt, SshConfig, TextEditor,
|
||||
};
|
||||
pub(super) use ssh::{DelSshKeyPopup, SshHost, SshKeys, SshUsername};
|
||||
pub(super) use theme::*;
|
||||
|
||||
@@ -36,7 +36,10 @@ impl SetupActivity {
|
||||
pub(super) fn save_config(&mut self) -> Result<(), String> {
|
||||
match self.config().write_config() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(format!("Could not save configuration: {}", err)),
|
||||
Err(err) => {
|
||||
error!("Could not save configuration: {}", err);
|
||||
Err(format!("Could not save configuration: {}", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ enum IdConfig {
|
||||
NotificationsThreshold,
|
||||
PromptOnFileReplace,
|
||||
RemoteFileFmt,
|
||||
SshConfig,
|
||||
TextEditor,
|
||||
}
|
||||
|
||||
@@ -167,6 +168,8 @@ pub enum ConfigMsg {
|
||||
PromptOnFileReplaceBlurUp,
|
||||
RemoteFileFmtBlurDown,
|
||||
RemoteFileFmtBlurUp,
|
||||
SshConfigBlurDown,
|
||||
SshConfigBlurUp,
|
||||
TextEditorBlurDown,
|
||||
TextEditorBlurUp,
|
||||
}
|
||||
|
||||
@@ -80,11 +80,13 @@ impl SetupActivity {
|
||||
CommonMsg::RevertChanges => match self.layout {
|
||||
ViewLayout::Theme => {
|
||||
if let Err(err) = self.action_reset_theme() {
|
||||
error!("Failed to reset theme: {}", err);
|
||||
self.mount_error(err);
|
||||
}
|
||||
}
|
||||
ViewLayout::SshKeys | ViewLayout::SetupForm => {
|
||||
if let Err(err) = self.action_reset_config() {
|
||||
error!("Failed to reset config: {}", err);
|
||||
self.mount_error(err);
|
||||
}
|
||||
}
|
||||
@@ -92,6 +94,7 @@ impl SetupActivity {
|
||||
CommonMsg::SaveAndQuit => {
|
||||
// Save changes
|
||||
if let Err(err) = self.action_save_all() {
|
||||
error!("Failed to save config: {}", err);
|
||||
self.mount_error(err.as_str());
|
||||
}
|
||||
// Exit
|
||||
@@ -99,6 +102,7 @@ impl SetupActivity {
|
||||
}
|
||||
CommonMsg::SaveConfig => {
|
||||
if let Err(err) = self.action_save_all() {
|
||||
error!("Failed to save config: {}", err);
|
||||
self.mount_error(err.as_str());
|
||||
}
|
||||
self.umount_save_popup();
|
||||
@@ -173,7 +177,7 @@ impl SetupActivity {
|
||||
.is_ok());
|
||||
}
|
||||
ConfigMsg::NotificationsThresholdBlurDown => {
|
||||
assert!(self.app.active(&Id::Config(IdConfig::TextEditor)).is_ok());
|
||||
assert!(self.app.active(&Id::Config(IdConfig::SshConfig)).is_ok());
|
||||
}
|
||||
ConfigMsg::NotificationsThresholdBlurUp => {
|
||||
assert!(self
|
||||
@@ -203,6 +207,12 @@ impl SetupActivity {
|
||||
.is_ok());
|
||||
}
|
||||
ConfigMsg::TextEditorBlurUp => {
|
||||
assert!(self.app.active(&Id::Config(IdConfig::SshConfig)).is_ok());
|
||||
}
|
||||
ConfigMsg::SshConfigBlurDown => {
|
||||
assert!(self.app.active(&Id::Config(IdConfig::TextEditor)).is_ok());
|
||||
}
|
||||
ConfigMsg::SshConfigBlurUp => {
|
||||
assert!(self
|
||||
.app
|
||||
.active(&Id::Config(IdConfig::NotificationsThreshold))
|
||||
@@ -230,6 +240,7 @@ impl SetupActivity {
|
||||
}
|
||||
SshMsg::EditSshKey(i) => {
|
||||
if let Err(err) = self.edit_ssh_key(i) {
|
||||
error!("Failed to edit ssh key: {}", err);
|
||||
self.mount_error(err.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ impl SetupActivity {
|
||||
Constraint::Length(3), // Remote Format input
|
||||
Constraint::Length(3), // Notifications enabled
|
||||
Constraint::Length(3), // Notifications threshold
|
||||
Constraint::Length(3), // Ssh config
|
||||
Constraint::Length(1), // Filler
|
||||
]
|
||||
.as_ref(),
|
||||
@@ -144,6 +145,8 @@ impl SetupActivity {
|
||||
f,
|
||||
ui_cfg_chunks_col2[3],
|
||||
);
|
||||
self.app
|
||||
.view(&Id::Config(IdConfig::SshConfig), f, ui_cfg_chunks_col2[4]);
|
||||
// Popups
|
||||
self.view_popups(f);
|
||||
});
|
||||
@@ -261,6 +264,17 @@ impl SetupActivity {
|
||||
vec![]
|
||||
)
|
||||
.is_ok());
|
||||
// Ssh config
|
||||
assert!(self
|
||||
.app
|
||||
.remount(
|
||||
Id::Config(IdConfig::SshConfig),
|
||||
Box::new(components::SshConfig::new(
|
||||
self.config().get_ssh_config().unwrap_or("")
|
||||
)),
|
||||
vec![]
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
/// Collect values from input and put them into the configuration
|
||||
@@ -332,5 +346,19 @@ impl SetupActivity {
|
||||
{
|
||||
self.config_mut().set_notification_threshold(bytes);
|
||||
}
|
||||
if let Ok(State::One(StateValue::String(mut path))) =
|
||||
self.app.state(&Id::Config(IdConfig::SshConfig))
|
||||
{
|
||||
if path.is_empty() {
|
||||
self.config_mut().set_ssh_config(None);
|
||||
} else {
|
||||
// Replace '~' with home path
|
||||
if path.starts_with('~') {
|
||||
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/root"));
|
||||
path = path.replacen('~', &home_dir.to_string_lossy(), 1);
|
||||
}
|
||||
self.config_mut().set_ssh_config(Some(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user