主机名称留空时默认使用连接地址
This commit is contained in:
1 parent
0dfa3fdf77
commit
78fd216fb6
9 files changed
+153
-29
No files matched your search
+1
-1
@@ -12,7 +12,7 @@ ShellRS 同一时间只运行一个:已经开着时再打开一次,会把已
|
||||
|
||||
## 主机列表
|
||||
|
||||
左侧栏里保存的每一项是一台主机:地址、端口和认证方式。同一台机器用不同用户登录,就保存成两台主机。
|
||||
左侧栏里保存的每一项是一台主机:地址、端口和认证方式。同一台机器用不同用户登录,就保存成两台主机。名称可以留空,保存时默认使用地址;也可以填写自定义名称。
|
||||
|
||||
主机对话框的「认证方式」有三种:
|
||||
|
||||
|
||||
+2
-2
@@ -127,8 +127,8 @@ host.dialog.name:
|
||||
zh-CN: 名称
|
||||
en: Name
|
||||
host.dialog.name_placeholder:
|
||||
zh-CN: 例如 web-01
|
||||
en: e.g. web-01
|
||||
zh-CN: 可选,默认用地址
|
||||
en: Optional; the address if left empty
|
||||
host.dialog.temporary_name_placeholder:
|
||||
zh-CN: 可选,默认用地址
|
||||
en: Optional; the address if left empty
|
||||
|
||||
+26
-2
@@ -740,7 +740,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_host_needs_a_name_and_an_address_and_takes_the_forms_defaults() {
|
||||
fn a_new_host_needs_an_address_and_takes_the_forms_defaults() {
|
||||
let (store, _, _) = store();
|
||||
let plan = plan_host(
|
||||
fields(serde_json::json!({ "name": " db ", "host": "10.0.0.2" })),
|
||||
@@ -761,7 +761,11 @@ mod tests {
|
||||
);
|
||||
|
||||
for (json, message) in [
|
||||
(serde_json::json!({ "host": "10.0.0.2" }), "请输入名称"),
|
||||
(serde_json::json!({}), "请输入地址"),
|
||||
(
|
||||
serde_json::json!({ "name": "", "host": " " }),
|
||||
"请输入地址",
|
||||
),
|
||||
(serde_json::json!({ "name": "db" }), "请输入地址"),
|
||||
(
|
||||
serde_json::json!({ "name": "db", "host": "h", "port": 0 }),
|
||||
@@ -793,6 +797,26 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_host_with_no_name_uses_its_address() {
|
||||
let (store, web, _) = store();
|
||||
for json in [
|
||||
serde_json::json!({ "host": " 10.0.0.2 " }),
|
||||
serde_json::json!({ "name": "", "host": " 10.0.0.2 " }),
|
||||
serde_json::json!({ "name": " ", "host": " 10.0.0.2 " }),
|
||||
] {
|
||||
let plan = plan_host(fields(json), None, &store).unwrap();
|
||||
assert_eq!(plan.draft.name.as_ref(), "10.0.0.2");
|
||||
}
|
||||
let plan = plan_host(
|
||||
fields(serde_json::json!({ "name": " " })),
|
||||
Some(web),
|
||||
&store,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(plan.draft.name, plan.draft.address);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_group_path_names_a_group_or_the_groups_to_make() {
|
||||
let (mut store, web, _) = store();
|
||||
|
||||
+9
-11
@@ -677,17 +677,13 @@ impl HostForm {
|
||||
/// Validate and write to the store. Returns whether the dialog may close.
|
||||
pub fn commit(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
|
||||
let name = self.name.read(cx).value().trim().to_string();
|
||||
let checked = if name.is_empty() {
|
||||
Err(HostDraftError::Name.message())
|
||||
} else {
|
||||
self.endpoint(cx).and_then(|endpoint| {
|
||||
Ok((
|
||||
endpoint,
|
||||
self.committed_login(cx)?,
|
||||
self.committed_route(cx)?,
|
||||
))
|
||||
})
|
||||
};
|
||||
let checked = self.endpoint(cx).and_then(|endpoint| {
|
||||
Ok((
|
||||
endpoint,
|
||||
self.committed_login(cx)?,
|
||||
self.committed_route(cx)?,
|
||||
))
|
||||
});
|
||||
let ((host, port), login, route) = match checked {
|
||||
Ok(checked) => checked,
|
||||
Err(error) => {
|
||||
@@ -696,6 +692,8 @@ impl HostForm {
|
||||
}
|
||||
};
|
||||
|
||||
let name = if name.is_empty() { host.clone() } else { name };
|
||||
|
||||
let group = self
|
||||
.group
|
||||
.read(cx)
|
||||
|
||||
+39
-5
@@ -585,20 +585,21 @@ impl HostDraft {
|
||||
SecretRef::password(self.user.as_ref(), self.address.as_ref(), self.port)
|
||||
}
|
||||
|
||||
/// The draft with surrounding blanks removed and an empty user name
|
||||
/// taken as [`super::DEFAULT_USER`], or the first thing wrong with it,
|
||||
/// The draft with surrounding blanks removed, an empty name taken as
|
||||
/// the address and an empty user name as [`super::DEFAULT_USER`], or the
|
||||
/// first thing wrong with it,
|
||||
/// in the order the host form checks. What the form cannot leave out
|
||||
/// of a draft (a credential to pick, a proxy password without a user)
|
||||
/// is the caller's to check.
|
||||
pub fn validated(mut self) -> Result<Self, HostDraftError> {
|
||||
self.name = self.name.trim().to_string().into();
|
||||
if self.name.is_empty() {
|
||||
return Err(HostDraftError::Name);
|
||||
}
|
||||
self.address = self.address.trim().to_string().into();
|
||||
if self.address.is_empty() {
|
||||
return Err(HostDraftError::Address);
|
||||
}
|
||||
if self.name.is_empty() {
|
||||
self.name = self.address.clone();
|
||||
}
|
||||
if self.port == 0 {
|
||||
return Err(HostDraftError::Port);
|
||||
}
|
||||
@@ -671,6 +672,39 @@ impl std::error::Error for HostDraftError {}
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_blank_host_name_uses_the_trimmed_address() {
|
||||
for (name, expected) in [
|
||||
("", "example.com"),
|
||||
(" \t ", "example.com"),
|
||||
(" web-01 ", "web-01"),
|
||||
] {
|
||||
let draft = HostDraft::new(name, " example.com ", 2222, "", AuthKind::Password, None)
|
||||
.validated()
|
||||
.unwrap();
|
||||
assert_eq!(draft.name.as_ref(), expected);
|
||||
assert_eq!(draft.address.as_ref(), "example.com");
|
||||
assert_eq!(draft.user.as_ref(), super::super::DEFAULT_USER);
|
||||
assert_eq!(draft.port, 2222);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_host_still_requires_an_address_with_or_without_a_name() {
|
||||
for name in ["", " ", "web-01"] {
|
||||
for address in ["", " \t "] {
|
||||
let error = HostDraft::new(name, address, 22, "root", AuthKind::Password, None)
|
||||
.validated()
|
||||
.unwrap_err();
|
||||
assert_eq!(error, HostDraftError::Address);
|
||||
}
|
||||
}
|
||||
let error = HostDraft::new("", "example.com", 0, "root", AuthKind::Password, None)
|
||||
.validated()
|
||||
.unwrap_err();
|
||||
assert_eq!(error, HostDraftError::Port);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_distributions_are_linux_and_the_rest_are_not() {
|
||||
let not_linux: Vec<_> = HostOs::ALL
|
||||
|
||||
+13
-2
@@ -98,10 +98,21 @@ fn a_host_from_the_cli_lands_in_the_tree_with_its_group_and_password(cx: &mut Te
|
||||
host: Some("10.0.9.2".into()),
|
||||
..HostFields::default()
|
||||
};
|
||||
let error = apply(cx, handle, &workspace, CliChange::CreateHost(nameless)).unwrap_err();
|
||||
let Ok(Reply::Host(created)) = apply(cx, handle, &workspace, CliChange::CreateHost(nameless))
|
||||
else {
|
||||
panic!("host without a name not created");
|
||||
};
|
||||
assert_eq!(created.name, "10.0.9.2");
|
||||
let error = apply(
|
||||
cx,
|
||||
handle,
|
||||
&workspace,
|
||||
CliChange::CreateHost(HostFields::default()),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
(error.code, error.message.as_str()),
|
||||
(ErrorCode::BadRequest, "请输入名称")
|
||||
(ErrorCode::BadRequest, "请输入地址")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -234,8 +234,6 @@ async fn a_host_can_use_a_credential_instead_of_typing_a_login(cx: &mut TestAppC
|
||||
);
|
||||
in_frame(cx, handle, |window, cx| window.click("new-host-panel", cx));
|
||||
in_frame(cx, handle, |window, cx| {
|
||||
window.click("host-name", cx);
|
||||
window.input("web-01", cx);
|
||||
window.click("host-address", cx);
|
||||
window.input("10.0.1.12", cx);
|
||||
window.within("host-auth-source").click(1usize, cx);
|
||||
@@ -274,8 +272,8 @@ async fn a_host_can_use_a_credential_instead_of_typing_a_login(cx: &mut TestAppC
|
||||
let created = store
|
||||
.hosts()
|
||||
.iter()
|
||||
.find(|host| host.name == "web-01")
|
||||
.expect("web-01 inserted");
|
||||
.find(|host| host.name == "10.0.1.12")
|
||||
.expect("host named after its address inserted");
|
||||
assert_eq!(created.credential, Some(credential));
|
||||
assert_eq!(created.user.as_ref(), "deploy");
|
||||
assert_eq!(created.auth, AuthKind::Password);
|
||||
|
||||
+60
-1
@@ -479,6 +479,65 @@ async fn a_form_error_sits_above_the_dialog_and_a_click_puts_it_away(cx: &mut Te
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui_kit::test]
|
||||
async fn a_host_without_an_address_is_not_saved(cx: &mut TestAppContext) {
|
||||
let (handle, workspace) = open_workspace_with_store(cx, HostStore::empty());
|
||||
for (name, address) in [("", ""), ("", " "), ("web-01", "")] {
|
||||
in_frame(cx, handle, |window, cx| window.click("new-host-panel", cx));
|
||||
in_frame(cx, handle, |window, cx| {
|
||||
window.click("host-name", cx);
|
||||
window.input(name, cx);
|
||||
window.click("host-address", cx);
|
||||
window.input(address, cx);
|
||||
window.click("commit", cx);
|
||||
});
|
||||
in_frame(cx, handle, |window, _| {
|
||||
assert!(window.find("commit").visible());
|
||||
assert_eq!(window.find("form-error").label(), Some("请输入地址"));
|
||||
});
|
||||
cx.update(|cx| assert!(workspace.read(cx).store().read(cx).hosts().is_empty()));
|
||||
in_frame(cx, handle, |window, cx| window.press("escape", cx));
|
||||
wait_for_dialog_to_close(cx, handle).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui_kit::test]
|
||||
async fn a_blank_host_name_uses_the_address_and_is_saved(cx: &mut TestAppContext) {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let path = directory.path().join("shellrs.db");
|
||||
let store = HostStore::load(HostDatabase::open(&path).unwrap()).unwrap();
|
||||
let (handle, workspace) = open_workspace_with_store(cx, store);
|
||||
for (name, address) in [("", "example.com"), (" ", "10.0.0.2")] {
|
||||
in_frame(cx, handle, |window, cx| {
|
||||
// Saving closes the dialog and leaves focus outside the host panel.
|
||||
window.click("host-search", cx);
|
||||
window.click("new-host-panel", cx);
|
||||
});
|
||||
in_frame(cx, handle, |window, cx| {
|
||||
window.click("host-name", cx);
|
||||
window.input(name, cx);
|
||||
window.click("host-address", cx);
|
||||
window.input(&format!(" {address} "), cx);
|
||||
window.click("commit", cx);
|
||||
});
|
||||
wait_for_dialog_to_close(cx, handle).await;
|
||||
cx.update(|cx| {
|
||||
let store = workspace.read(cx).store().read(cx);
|
||||
let host = store
|
||||
.hosts()
|
||||
.iter()
|
||||
.find(|host| host.address == address)
|
||||
.unwrap();
|
||||
assert_eq!(host.name.as_ref(), address);
|
||||
});
|
||||
}
|
||||
let saved = HostDatabase::open(&path).unwrap().load().unwrap();
|
||||
assert_eq!(saved.hosts.len(), 2);
|
||||
for host in saved.hosts {
|
||||
assert_eq!(host.name, host.address);
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui_kit::test]
|
||||
async fn new_host_dialog_validates_then_inserts(cx: &mut TestAppContext) {
|
||||
let (handle, workspace) = open_workspace(cx);
|
||||
@@ -527,7 +586,7 @@ async fn new_host_dialog_validates_then_inserts(cx: &mut TestAppContext) {
|
||||
// The error pops up as a notification over the dialog, not as a line
|
||||
// at the bottom of the form.
|
||||
assert_eq!(window.notifications(cx).len(), 1);
|
||||
assert_eq!(window.find("form-error").label(), Some("请输入名称"));
|
||||
assert_eq!(window.find("form-error").label(), Some("请输入地址"));
|
||||
window.click("commit", cx);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -749,7 +749,7 @@ async fn a_dialog_says_what_is_wrong_in_a_notification_that_goes_with_it(cx: &mu
|
||||
cx.run_until_parked();
|
||||
|
||||
for (open, field, error) in [
|
||||
("new-host-panel", "host-name", "请输入名称"),
|
||||
("new-host-panel", "host-address", "请输入地址"),
|
||||
("temporary-connection", "host-address", "请输入地址"),
|
||||
("new-group", "group-name", "请输入分组名称"),
|
||||
("new-forward", "forward-name", "请选择端口转发经由的主机"),
|
||||
|
||||
Reference in new issue
Block a user