diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 5496e213..d1d72408 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -962,8 +962,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer return nil, fmt.Errorf("failed to populate provider access config: %w", err) } - jumpServers := make([]pSSH.JumpServerConfig, len(access.JumpServerConfig)) - for i, jumpServer := range access.JumpServerConfig { + jumpServers := make([]pSSH.JumpServerConfig, len(access.JumpServers)) + for i, jumpServer := range access.JumpServers { jumpServers[i] = pSSH.JumpServerConfig{ SshHost: jumpServer.Host, SshPort: jumpServer.Port, @@ -981,19 +981,19 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer SshPassword: access.Password, SshKey: access.Key, SshKeyPassphrase: access.KeyPassphrase, - JumpServerConfig: jumpServers, - UseSCP: maputil.GetBool(options.ProviderExtendedConfig, "useSCP"), - PreCommand: maputil.GetString(options.ProviderExtendedConfig, "preCommand"), - PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"), - OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))), - OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"), - OutputServerCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForServerOnly"), - OutputIntermediaCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForIntermediaOnly"), - OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"), - PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"), - JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"), - JksKeypass: maputil.GetString(options.ProviderExtendedConfig, "jksKeypass"), - JksStorepass: maputil.GetString(options.ProviderExtendedConfig, "jksStorepass"), + JumpServers: jumpServers, + UseSCP: maputil.GetBool(options.ProviderServiceConfig, "useSCP"), + PreCommand: maputil.GetString(options.ProviderServiceConfig, "preCommand"), + PostCommand: maputil.GetString(options.ProviderServiceConfig, "postCommand"), + OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderServiceConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))), + OutputCertPath: maputil.GetString(options.ProviderServiceConfig, "certPath"), + OutputServerCertPath: maputil.GetString(options.ProviderServiceConfig, "certPathForServerOnly"), + OutputIntermediaCertPath: maputil.GetString(options.ProviderServiceConfig, "certPathForIntermediaOnly"), + OutputKeyPath: maputil.GetString(options.ProviderServiceConfig, "keyPath"), + PfxPassword: maputil.GetString(options.ProviderServiceConfig, "pfxPassword"), + JksAlias: maputil.GetString(options.ProviderServiceConfig, "jksAlias"), + JksKeypass: maputil.GetString(options.ProviderServiceConfig, "jksKeypass"), + JksStorepass: maputil.GetString(options.ProviderServiceConfig, "jksStorepass"), }) return deployer, err } diff --git a/internal/domain/access.go b/internal/domain/access.go index 454ae954..13975392 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -284,20 +284,20 @@ type AccessConfigForSafeLine struct { } type AccessConfigForSSH struct { - Host string `json:"host"` - Port int32 `json:"port"` - Username string `json:"username"` - Password string `json:"password,omitempty"` - Key string `json:"key,omitempty"` - KeyPassphrase string `json:"keyPassphrase,omitempty"` - JumpServerConfig []struct { + Host string `json:"host"` + Port int32 `json:"port"` + Username string `json:"username"` + Password string `json:"password,omitempty"` + Key string `json:"key,omitempty"` + KeyPassphrase string `json:"keyPassphrase,omitempty"` + JumpServers []struct { Host string `json:"host"` Port int32 `json:"port"` Username string `json:"username"` Password string `json:"password,omitempty"` Key string `json:"key,omitempty"` KeyPassphrase string `json:"keyPassphrase,omitempty"` - } `json:"jumpServerConfig,omitempty"` + } `json:"jumpServers,omitempty"` } type AccessConfigForSSLCom struct { diff --git a/internal/pkg/core/deployer/providers/ssh/ssh.go b/internal/pkg/core/deployer/providers/ssh/ssh.go index 2a67b441..ae6e459f 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh.go @@ -49,8 +49,8 @@ type DeployerConfig struct { SshKey string `json:"sshKey,omitempty"` // SSH 登录私钥口令。 SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"` - // 跳板机配置 - JumpServerConfig []JumpServerConfig `json:"jumpServerConfig,omitempty"` + // 跳板机配置数组。 + JumpServers []JumpServerConfig `json:"jumpServers,omitempty"` // 是否回退使用 SCP。 UseSCP bool `json:"useSCP,omitempty"` // 前置命令。 @@ -120,9 +120,9 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE var targetConn net.Conn // 连接到跳板机 - if len(d.config.JumpServerConfig) > 0 { + if len(d.config.JumpServers) > 0 { var jumpClient *ssh.Client - for i, jumpServerConf := range d.config.JumpServerConfig { + for i, jumpServerConf := range d.config.JumpServers { d.logger.Info(fmt.Sprintf("connecting to jump server [%d]", i+1), slog.String("host", jumpServerConf.SshHost)) var jumpConn net.Conn @@ -154,13 +154,14 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE jumpClient = newClient d.logger.Info(fmt.Sprintf("jump server connected [%d]", i+1), slog.String("host", jumpServerConf.SshHost)) } - // 通过跳板机发起到目标服务器的TCP连接 + + // 通过跳板机发起 TCP 连接到目标服务器 targetConn, err = jumpClient.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", d.config.SshHost, d.config.SshPort)) if err != nil { return nil, fmt.Errorf("failed to connect to target server: %w", err) } } else { - // 直接TCP连接到目标服务器 + // 直接发起 TCP 连接到目标服务器 targetConn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", d.config.SshHost, d.config.SshPort)) if err != nil { return nil, fmt.Errorf("failed to connect to target server: %w", err) @@ -168,7 +169,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE } defer targetConn.Close() - // 通过已有的连接创建目标服务器SSH客户端 + // 通过已有的连接创建目标服务器 SSH 客户端 client, err := createSshClient( targetConn, d.config.SshHost, diff --git a/ui/src/components/access/AccessFormSSHConfig.tsx b/ui/src/components/access/AccessFormSSHConfig.tsx index cdd3c8e2..32d1b8bc 100644 --- a/ui/src/components/access/AccessFormSSHConfig.tsx +++ b/ui/src/components/access/AccessFormSSHConfig.tsx @@ -1,4 +1,5 @@ import { useTranslation } from "react-i18next"; +import { ArrowDownOutlined, ArrowUpOutlined, CloseOutlined, PlusOutlined } from "@ant-design/icons"; import { Button, Collapse, Form, type FormInstance, Input, InputNumber, Space } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -6,7 +7,6 @@ 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; @@ -29,42 +29,6 @@ const initFormModel = (): AccessFormSSHConfigFieldValues => { const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormSSHConfigProps) => { const { t } = useTranslation(); - const jumpServerConfigItemSchema = z - .object({ - host: z.string().refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")), - port: z.preprocess( - (v) => Number(v), - z - .number() - .int(t("access.form.ssh_port.placeholder")) - .refine((v) => validPortNumber(v), t("common.errmsg.port_invalid")) - ), - username: z - .string() - .min(1, t("access.form.ssh_username.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })), - password: z - .string() - .max(64, t("common.errmsg.string_max", { max: 64 })) - .nullish(), - key: z - .string() - .max(20480, t("common.errmsg.string_max", { max: 20480 })) - .nullish(), - keyPassphrase: z - .string() - .max(20480, t("common.errmsg.string_max", { max: 20480 })) - .nullish(), - }) - .superRefine((data, ctx) => { - if (data.keyPassphrase && !data.key) { - ctx.addIssue({ - path: ["keyPassphrase"], - code: z.ZodIssueCode.custom, - message: t("access.form.ssh_key.placeholder"), - }); - } - }); const formSchema = z.object({ host: z.string().refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")), port: z.preprocess( @@ -91,7 +55,46 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues .max(20480, t("common.errmsg.string_max", { max: 20480 })) .nullish() .refine((v) => !v || formInst.getFieldValue("key"), t("access.form.ssh_key.placeholder")), - jumpServerConfig: jumpServerConfigItemSchema.array().nullish(), + jumpServers: z + .array( + z + .object({ + host: z.string().refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")), + port: z.preprocess( + (v) => Number(v), + z + .number() + .int(t("access.form.ssh_port.placeholder")) + .refine((v) => validPortNumber(v), t("common.errmsg.port_invalid")) + ), + username: z + .string() + .min(1, t("access.form.ssh_username.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })), + password: z + .string() + .max(64, t("common.errmsg.string_max", { max: 64 })) + .nullish(), + key: z + .string() + .max(20480, t("common.errmsg.string_max", { max: 20480 })) + .nullish(), + keyPassphrase: z + .string() + .max(20480, t("common.errmsg.string_max", { max: 20480 })) + .nullish(), + }) + .superRefine((data, ctx) => { + if (data.keyPassphrase && !data.key) { + ctx.addIssue({ + path: ["keyPassphrase"], + code: z.ZodIssueCode.custom, + message: t("access.form.ssh_key.placeholder"), + }); + } + }) + ) + .nullish(), }); const formRule = createSchemaFieldRule(formSchema); @@ -153,49 +156,63 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues - } - > - + + {(fields, { add, remove, move }) => ( {fields?.length > 0 ? ( { const Label = () => { - const itemHost = Form.useWatch(["jumpServerConfig", field.name, "host"], formInst); + const host = Form.useWatch(["jumpServers", field.name, "host"], formInst); + const port = Form.useWatch(["jumpServers", field.name, "port"], formInst); + const addr = !!host && !!port ? `${host}:${port}` : host ? host : port ? `:${port}` : "unknown"; return ( - - [{t("access.form.ssh_jump_server_config.item.label")} {field.name + 1}] {itemHost ?? ""} + + [{t("access.form.ssh_jump_servers.item.label")} {field.name + 1}] {addr} ); }; return { key: field.key, - label: