fix: validate Quick Connect and ~/.ssh/config imports; unknown enum values no longer reset a profile's config

- Quick Connect runs the same validation as saved profiles, so an SSH
  connection without a username fails up front with a clear message (F5).
- ~/.ssh/config imports fill a missing User (and jump-host user) with the
  local user name, as ssh does, and skip blocks that still don't validate,
  reporting how many (F6).
- An unrecognised hostKeyPolicy/lineEnding/parity/flowControl/direction in
  a stored config falls back to that field's default instead of failing
  the whole JSON column and silently resetting every other setting; parse
  failures that remain are logged (F7).
This commit is contained in:
Andy Dixon
2026-10-12 09:19:03 +01:00
parent 3b490fa6c3
commit bd1dc54e20
7 changed files with 90 additions and 14 deletions
+7 -3
View File
@@ -586,9 +586,13 @@ impl App {
Request::Connect(id) => self.connect_profile(&id, false),
Request::ConnectQuick(p) => {
let name = if p.display_name.is_empty() { p.host.clone() } else { p.display_name.clone() };
let sid = self.s.svc.connect_quick(p);
self.add_session(&sid, true);
self.s.info(format!("Connecting to {name}…"));
match self.s.svc.connect_quick(p) {
Ok(sid) => {
self.add_session(&sid, true);
self.s.info(format!("Connecting to {name}…"));
}
Err(e) => self.s.error(format!("Can't connect to {name}: {e}")),
}
}
Request::ReloadProfiles => self.s.reload_profiles(),
Request::ReloadPreferences => {
+1 -1
View File
@@ -80,7 +80,7 @@ pub fn title_bar(app: &mut App, ui: &mut Ui) {
if item(ui, icon::FILE_ARROW_DOWN, "Import ~/.ssh/config", "") {
match app.s.svc.import_ssh_config(None, "SSH Config") {
Ok(n) => {
app.s.success(format!("Imported {n} host(s) from ~/.ssh/config"));
app.s.success(crate::settings::ssh_import_summary(n));
app.s.reload_profiles();
}
Err(e) => app.s.error(format!("Import failed: {e}")),
+17
View File
@@ -184,4 +184,21 @@ mod tests {
let p = q.profile().unwrap();
assert_eq!((p.port, p.username.as_str()), (0, ""));
}
#[test]
fn quick_profiles_pass_validation_unless_incomplete() {
use nexus_core::validation::validate_profile;
for proto in PROTOCOLS {
let mut q = QuickConnect { host: "box".into(), username: "me".into(), ..Default::default() };
q.set_protocol(proto);
let mut p = q.profile().unwrap();
p.normalize();
assert!(validate_profile(&p).is_ok(), "{proto:?}: {:?}", validate_profile(&p).err().map(|e| e.to_string()));
}
let mut q = QuickConnect { host: "box".into(), ..Default::default() };
q.set_protocol(Protocol::Ssh);
let mut p = q.profile().unwrap();
p.normalize();
assert!(validate_profile(&p).is_err(), "SSH needs a username");
}
}
+7 -1
View File
@@ -543,7 +543,7 @@ impl Settings {
if widgets::button(ui, icon::DOWNLOAD_SIMPLE, "Import ~/.ssh/config").clicked() {
match s.svc.import_ssh_config(None, "SSH Config") {
Ok(n) => {
s.success(format!("Imported {n} host(s) from ~/.ssh/config"));
s.success(ssh_import_summary(n));
s.request(Request::ReloadProfiles);
}
Err(e) => s.error(format!("SSH config import failed: {e}")),
@@ -934,6 +934,12 @@ Open two or more sessions and press {Mod+Shift+B} to turn on broadcast mode. Eve
),
];
pub fn ssh_import_summary((n, skipped): (usize, usize)) -> String {
match skipped {
0 => format!("Imported {n} host(s) from ~/.ssh/config"),
k => format!("Imported {n} host(s) from ~/.ssh/config; skipped {k} that couldn't be used as-is"),
}
}
#[cfg(test)]
mod tests {
use super::*;