feat: ssh jump server support

This commit is contained in:
神楽坂ニャン
2025-05-17 19:47:31 +08:00
parent 2906576de0
commit 4a8eaa9ffa
6 changed files with 217 additions and 10 deletions

View File

@@ -1,11 +1,12 @@
import { useTranslation } from "react-i18next";
import { Form, type FormInstance, Input, InputNumber } from "antd";
import { Button, Collapse, Form, type FormInstance, Input, InputNumber, Space } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import TextFileInput from "@/components/TextFileInput";
import { type AccessConfigForSSH } from "@/domain/access";
import { validDomainName, validIPv4Address, validIPv6Address, validPortNumber } from "@/utils/validators";
import { ArrowDownOutlined, ArrowUpOutlined, CloseOutlined, PlusOutlined } from "@ant-design/icons";
type AccessFormSSHConfigFieldValues = Nullish<AccessConfigForSSH>;
@@ -114,8 +115,108 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
>
<Input.Password allowClear autoComplete="new-password" placeholder={t("access.form.ssh_key_passphrase.placeholder")} />
</Form.Item>
<Form.Item
label={t("access.form.ssh_jump_server_config.label")}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_jump_server_config.tooltip") }}></span>}
>
<Form.List name="jumpServerConfig">
{(fields, { add, remove, move }) => (
<Space className="w-full" direction="vertical" size="small">
{fields?.length > 0 ? (
<Collapse
items={fields.map((field, index) => {
const Label = () => {
const itemHost = Form.useWatch(["jumpServerConfig", field.name, "host"], formInst);
return (
<span style={{ userSelect: "none" }}>
[{t("access.form.ssh_jump_server_config.item.label")} {field.name + 1}] {itemHost ?? ""}
</span>
);
};
return {
key: field.key,
label: <Label />, // 这里用组件渲染
extra: (
<Space>
<ArrowUpOutlined
onClick={(e) => {
move(index, index - 1);
e.stopPropagation();
}}
/>
<ArrowDownOutlined
onClick={(e) => {
move(index, index + 1);
e.stopPropagation();
}}
/>
<CloseOutlined
onClick={(e) => {
remove(field.name);
e.stopPropagation();
}}
/>
</Space>
),
children: (
<>
<div className="flex space-x-2">
<div className="w-2/3">
<Form.Item name={[field.name, "host"]} label={t("access.form.ssh_host.label")} rules={[formRule]}>
<Input placeholder={t("access.form.ssh_host.placeholder")} />
</Form.Item>
</div>
<div className="w-1/3">
<Form.Item name={[field.name, "port"]} label={t("access.form.ssh_port.label")} rules={[formRule]}>
<InputNumber className="w-full" placeholder={t("access.form.ssh_port.placeholder")} min={1} max={65535} />
</Form.Item>
</div>
</div>
<Form.Item name={[field.name, "username"]} label={t("access.form.ssh_username.label")} rules={[formRule]}>
<Input autoComplete="new-password" placeholder={t("access.form.ssh_username.placeholder")} />
</Form.Item>
<Form.Item
name={[field.name, "password"]}
label={t("access.form.ssh_password.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_password.tooltip") }}></span>}
>
<Input.Password allowClear autoComplete="new-password" placeholder={t("access.form.ssh_password.placeholder")} />
</Form.Item>
<Form.Item
name={[field.name, "key"]}
label={t("access.form.ssh_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_key.tooltip") }}></span>}
>
<TextFileInput allowClear autoSize={{ minRows: 1, maxRows: 5 }} placeholder={t("access.form.ssh_key.placeholder")} />
</Form.Item>
<Form.Item
name={[field.name, "keyPassphrase"]}
label={t("access.form.ssh_key_passphrase.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_key_passphrase.tooltip") }}></span>}
>
<Input.Password allowClear autoComplete="new-password" placeholder={t("access.form.ssh_key_passphrase.placeholder")} />
</Form.Item>
</>
),
};
})}
/>
) : null}
<Button type="dashed" className="w-full" icon={<PlusOutlined />} onClick={() => add()}>
{t("access.form.ssh_jump_server_config.add")}
</Button>
</Space>
)}
</Form.List>
</Form.Item>
</Form>
);
};
export default AccessFormSSHConfig;

View File

@@ -334,6 +334,10 @@
"access.form.ssh_key_passphrase.label": "SSH key passphrase (Optional)",
"access.form.ssh_key_passphrase.placeholder": "Please enter SSH key passphrase",
"access.form.ssh_key_passphrase.tooltip": "Optional when using key to connect to SSH.",
"access.form.ssh_jump_server_config.label": "SSH jump server (Optional)",
"access.form.ssh_jump_server_config.tooltip": "Optional when using a jump server to connect to the server.",
"access.form.ssh_jump_server_config.item.label": "Jump Server",
"access.form.ssh_jump_server_config.add": "Add Jump Server",
"access.form.sslcom_eab_kid.label": "ACME EAB KID",
"access.form.sslcom_eab_kid.placeholder": "Please enter ACME EAB KID",
"access.form.sslcom_eab_kid.tooltip": "For more information, see <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",

View File

@@ -328,6 +328,10 @@
"access.form.ssh_key_passphrase.label": "SSH 密钥口令(可选)",
"access.form.ssh_key_passphrase.placeholder": "请输入 SSH 密钥口令",
"access.form.ssh_key_passphrase.tooltip": "使用 SSH 密钥连接到 SSH 时选填。",
"access.form.ssh_jump_server_config.label": "SSH 跳板机(可选)",
"access.form.ssh_jump_server_config.tooltip": "使用跳板机连接到服务器时选填。",
"access.form.ssh_jump_server_config.item.label": "跳板机",
"access.form.ssh_jump_server_config.add": "添加跳板机",
"access.form.sslcom_eab_kid.label": "ACME EAB KID",
"access.form.sslcom_eab_kid.placeholder": "请输入 ACME EAB KID",
"access.form.sslcom_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",