feat(ui): TextFileInput

This commit is contained in:
Fu Diwei 2025-05-14 00:40:18 +08:00
parent 355059df3c
commit 04abf9dd76
11 changed files with 144 additions and 171 deletions

View File

@ -0,0 +1,51 @@
import { type ChangeEvent, useRef } from "react";
import { useTranslation } from "react-i18next";
import { UploadOutlined as UploadOutlinedIcon } from "@ant-design/icons";
import { Button, type ButtonProps, Input, Space, type UploadProps } from "antd";
import { type TextAreaProps } from "antd/es/input/TextArea";
import { mergeCls } from "@/utils/css";
import { readFileContent } from "@/utils/file";
export interface TextFileInputProps extends Omit<TextAreaProps, "onChange"> {
accept?: UploadProps["accept"];
uploadButtonProps?: Omit<ButtonProps, "disabled" | "onClick">;
uploadText?: string;
onChange?: (value: string) => void;
}
const TextFileInput = ({ className, style, accept, disabled, readOnly, uploadText, uploadButtonProps, onChange, ...props }: TextFileInputProps) => {
const { t } = useTranslation();
const fileInputRef = useRef<HTMLInputElement>(null);
const handleButtonClick = () => {
if (fileInputRef.current) {
fileInputRef.current.click();
}
};
const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
const { files } = e.target as HTMLInputElement;
if (files?.length) {
const value = await readFileContent(files[0]);
onChange?.(value);
}
};
return (
<Space className={mergeCls("w-full", className)} style={style} direction="vertical" size="small">
<Input.TextArea {...props} disabled={disabled} readOnly={readOnly} onChange={(e) => onChange?.(e.target.value)} />
{!readOnly && (
<>
<Button {...uploadButtonProps} block disabled={disabled} icon={<UploadOutlinedIcon />} onClick={handleButtonClick}>
{uploadText ?? t("common.text.import_from_file")}
</Button>
<input ref={fileInputRef} type="file" style={{ display: "none" }} accept={accept} onChange={handleFileChange} />
</>
)}
</Space>
);
};
export default TextFileInput;

View File

@ -1,12 +1,10 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { UploadOutlined as UploadOutlinedIcon } from "@ant-design/icons"; import { Form, type FormInstance } from "antd";
import { Button, Form, type FormInstance, Input, Upload, type UploadFile, type UploadProps } from "antd";
import { createSchemaFieldRule } from "antd-zod"; import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod"; import { z } from "zod";
import TextFileInput from "@/components/TextFileInput";
import { type AccessConfigForKubernetes } from "@/domain/access"; import { type AccessConfigForKubernetes } from "@/domain/access";
import { readFileContent } from "@/utils/file";
type AccessFormKubernetesConfigFieldValues = Nullish<AccessConfigForKubernetes>; type AccessFormKubernetesConfigFieldValues = Nullish<AccessConfigForKubernetes>;
@ -34,24 +32,6 @@ const AccessFormKubernetesConfig = ({ form: formInst, formName, disabled, initia
}); });
const formRule = createSchemaFieldRule(formSchema); const formRule = createSchemaFieldRule(formSchema);
const fieldKubeConfig = Form.useWatch("kubeConfig", formInst);
const [fieldKubeFileList, setFieldKubeFileList] = useState<UploadFile[]>([]);
useEffect(() => {
setFieldKubeFileList(initialValues?.kubeConfig?.trim() ? [{ uid: "-1", name: "kubeconfig", status: "done" }] : []);
}, [initialValues?.kubeConfig]);
const handleKubeFileChange: UploadProps["onChange"] = async ({ file }) => {
if (file && file.status !== "removed") {
formInst.setFieldValue("kubeConfig", await readFileContent(file.originFileObj ?? (file as unknown as File)));
setFieldKubeFileList([file]);
} else {
formInst.setFieldValue("kubeConfig", "");
setFieldKubeFileList([]);
}
onValuesChange?.(formInst.getFieldsValue(true));
};
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => { const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
onValuesChange?.(values); onValuesChange?.(values);
}; };
@ -65,16 +45,13 @@ const AccessFormKubernetesConfig = ({ form: formInst, formName, disabled, initia
name={formName} name={formName}
onValuesChange={handleFormChange} onValuesChange={handleFormChange}
> >
<Form.Item name="kubeConfig" noStyle rules={[formRule]}>
<Input.TextArea autoComplete="new-password" hidden placeholder={t("access.form.k8s_kubeconfig.placeholder")} value={fieldKubeConfig} />
</Form.Item>
<Form.Item <Form.Item
name="kubeConfig"
label={t("access.form.k8s_kubeconfig.label")} label={t("access.form.k8s_kubeconfig.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.k8s_kubeconfig.tooltip") }}></span>} tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.k8s_kubeconfig.tooltip") }}></span>}
> >
<Upload beforeUpload={() => false} fileList={fieldKubeFileList} maxCount={1} onChange={handleKubeFileChange}> <TextFileInput allowClear autoSize={{ minRows: 3, maxRows: 10 }} placeholder={t("access.form.k8s_kubeconfig.placeholder")} />
<Button icon={<UploadOutlinedIcon />}>{t("access.form.k8s_kubeconfig.upload")}</Button>
</Upload>
</Form.Item> </Form.Item>
</Form> </Form>
); );

View File

@ -1,12 +1,10 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { UploadOutlined as UploadOutlinedIcon } from "@ant-design/icons"; import { Form, type FormInstance, Input, InputNumber } from "antd";
import { Button, Form, type FormInstance, Input, InputNumber, Upload, type UploadFile, type UploadProps } from "antd";
import { createSchemaFieldRule } from "antd-zod"; import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod"; import { z } from "zod";
import TextFileInput from "@/components/TextFileInput";
import { type AccessConfigForSSH } from "@/domain/access"; import { type AccessConfigForSSH } from "@/domain/access";
import { readFileContent } from "@/utils/file";
import { validDomainName, validIPv4Address, validIPv6Address, validPortNumber } from "@/utils/validators"; import { validDomainName, validIPv4Address, validIPv6Address, validPortNumber } from "@/utils/validators";
type AccessFormSSHConfigFieldValues = Nullish<AccessConfigForSSH>; type AccessFormSSHConfigFieldValues = Nullish<AccessConfigForSSH>;
@ -59,24 +57,6 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
}); });
const formRule = createSchemaFieldRule(formSchema); const formRule = createSchemaFieldRule(formSchema);
const fieldKey = Form.useWatch("key", formInst);
const [fieldKeyFileList, setFieldKeyFileList] = useState<UploadFile[]>([]);
useEffect(() => {
setFieldKeyFileList(initialValues?.key?.trim() ? [{ uid: "-1", name: "sshkey", status: "done" }] : []);
}, [initialValues?.key]);
const handleKeyFileChange: UploadProps["onChange"] = async ({ file }) => {
if (file && file.status !== "removed") {
formInst.setFieldValue("key", await readFileContent(file.originFileObj ?? (file as unknown as File)));
setFieldKeyFileList([file]);
} else {
formInst.setFieldValue("key", "");
setFieldKeyFileList([]);
}
onValuesChange?.(formInst.getFieldsValue(true));
};
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => { const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
onValuesChange?.(values); onValuesChange?.(values);
}; };
@ -104,48 +84,36 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
</div> </div>
</div> </div>
<div className="flex space-x-2"> <Form.Item name="username" label={t("access.form.ssh_username.label")} rules={[formRule]}>
<div className="w-1/2"> <Input autoComplete="new-password" placeholder={t("access.form.ssh_username.placeholder")} />
<Form.Item name="username" label={t("access.form.ssh_username.label")} rules={[formRule]}> </Form.Item>
<Input autoComplete="new-password" placeholder={t("access.form.ssh_username.placeholder")} />
</Form.Item>
</div>
<div className="w-1/2"> <Form.Item
<Form.Item name="password"
name="password" label={t("access.form.ssh_password.label")}
label={t("access.form.ssh_password.label")} rules={[formRule]}
rules={[formRule]} tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_password.tooltip") }}></span>}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_password.tooltip") }}></span>} >
> <Input.Password allowClear autoComplete="new-password" placeholder={t("access.form.ssh_password.placeholder")} />
<Input.Password autoComplete="new-password" placeholder={t("access.form.ssh_password.placeholder")} /> </Form.Item>
</Form.Item>
</div>
</div>
<div className="flex space-x-2"> <Form.Item
<div className="w-1/2"> name="key"
<Form.Item name="key" noStyle rules={[formRule]}> label={t("access.form.ssh_key.label")}
<Input.TextArea autoComplete="new-password" hidden placeholder={t("access.form.ssh_key.placeholder")} value={fieldKey} /> rules={[formRule]}
</Form.Item> tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_key.tooltip") }}></span>}
<Form.Item label={t("access.form.ssh_key.label")} tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_key.tooltip") }}></span>}> >
<Upload beforeUpload={() => false} fileList={fieldKeyFileList} maxCount={1} onChange={handleKeyFileChange}> <TextFileInput allowClear autoSize={{ minRows: 1, maxRows: 5 }} placeholder={t("access.form.ssh_key.placeholder")} />
<Button icon={<UploadOutlinedIcon />}>{t("access.form.ssh_key.upload")}</Button> </Form.Item>
</Upload>
</Form.Item>
</div>
<div className="w-1/2"> <Form.Item
<Form.Item name="keyPassphrase"
name="keyPassphrase" label={t("access.form.ssh_key_passphrase.label")}
label={t("access.form.ssh_key_passphrase.label")} rules={[formRule]}
rules={[formRule]} tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_key_passphrase.tooltip") }}></span>}
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")} />
<Input.Password autoComplete="new-password" placeholder={t("access.form.ssh_key_passphrase.placeholder")} /> </Form.Item>
</Form.Item>
</div>
</div>
</Form> </Form>
); );
}; };

View File

@ -177,7 +177,7 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
</Form.Item> </Form.Item>
<Form.Item name="message" label={t("workflow_node.notify.form.message.label")} rules={[formRule]}> <Form.Item name="message" label={t("workflow_node.notify.form.message.label")} rules={[formRule]}>
<Input.TextArea autoSize={{ minRows: 3, maxRows: 5 }} placeholder={t("workflow_node.notify.form.message.placeholder")} /> <Input.TextArea autoSize={{ minRows: 3, maxRows: 10 }} placeholder={t("workflow_node.notify.form.message.placeholder")} />
</Form.Item> </Form.Item>
<Form.Item className="mb-0" htmlFor="null"> <Form.Item className="mb-0" htmlFor="null">

View File

@ -1,15 +1,14 @@
import { forwardRef, memo, useImperativeHandle } from "react"; import { forwardRef, memo, useImperativeHandle } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { UploadOutlined as UploadOutlinedIcon } from "@ant-design/icons"; import { Form, type FormInstance, Input } from "antd";
import { Button, Form, type FormInstance, Input, Upload, type UploadProps } from "antd";
import { createSchemaFieldRule } from "antd-zod"; import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod"; import { z } from "zod";
import { validateCertificate, validatePrivateKey } from "@/api/certificates"; import { validateCertificate, validatePrivateKey } from "@/api/certificates";
import TextFileInput from "@/components/TextFileInput";
import { type WorkflowNodeConfigForUpload } from "@/domain/workflow"; import { type WorkflowNodeConfigForUpload } from "@/domain/workflow";
import { useAntdForm } from "@/hooks"; import { useAntdForm } from "@/hooks";
import { getErrMsg } from "@/utils/error"; import { getErrMsg } from "@/utils/error";
import { readFileContent } from "@/utils/file";
type UploadNodeConfigFormFieldValues = Partial<WorkflowNodeConfigForUpload>; type UploadNodeConfigFormFieldValues = Partial<WorkflowNodeConfigForUpload>;
@ -70,65 +69,53 @@ const UploadNodeConfigForm = forwardRef<UploadNodeConfigFormInstance, UploadNode
} as UploadNodeConfigFormInstance; } as UploadNodeConfigFormInstance;
}); });
const handleCertificateFileChange: UploadProps["onChange"] = async ({ file }) => { const handleCertificateChange = async (value: string) => {
if (file && file.status !== "removed") { try {
const certificate = await readFileContent(file.originFileObj ?? (file as unknown as File)); const resp = await validateCertificate(value);
formInst.setFields([
try { {
const resp = await validateCertificate(certificate); name: "domains",
formInst.setFields([ value: resp.data.domains,
{ },
name: "domains", {
value: resp.data.domains, name: "certificate",
}, value: value,
{ },
name: "certificate", ]);
value: certificate, } catch (e) {
}, formInst.setFields([
]); {
} catch (e) { name: "domains",
formInst.setFields([ value: "",
{ },
name: "domains", {
value: "", name: "certificate",
}, value: value,
{ errors: [getErrMsg(e)],
name: "certificate", },
value: "", ]);
errors: [getErrMsg(e)],
},
]);
}
} else {
formInst.setFieldValue("certificate", "");
} }
onValuesChange?.(formInst.getFieldsValue(true)); onValuesChange?.(formInst.getFieldsValue(true));
}; };
const handlePrivateKeyFileChange: UploadProps["onChange"] = async ({ file }) => { const handlePrivateKeyChange = async (value: string) => {
if (file && file.status !== "removed") { try {
const privateKey = await readFileContent(file.originFileObj ?? (file as unknown as File)); await validatePrivateKey(value);
formInst.setFields([
try { {
await validatePrivateKey(privateKey); name: "privateKey",
formInst.setFields([ value: value,
{ },
name: "privateKey", ]);
value: privateKey, } catch (e) {
}, formInst.setFields([
]); {
} catch (e) { name: "privateKey",
formInst.setFields([ value: value,
{ errors: [getErrMsg(e)],
name: "privateKey", },
value: "", ]);
errors: [getErrMsg(e)],
},
]);
}
} else {
formInst.setFieldValue("privateKey", "");
} }
onValuesChange?.(formInst.getFieldsValue(true)); onValuesChange?.(formInst.getFieldsValue(true));
@ -141,23 +128,19 @@ const UploadNodeConfigForm = forwardRef<UploadNodeConfigFormInstance, UploadNode
</Form.Item> </Form.Item>
<Form.Item name="certificate" label={t("workflow_node.upload.form.certificate.label")} rules={[formRule]}> <Form.Item name="certificate" label={t("workflow_node.upload.form.certificate.label")} rules={[formRule]}>
<Input.TextArea readOnly autoSize={{ minRows: 5, maxRows: 5 }} placeholder={t("workflow_node.upload.form.certificate.placeholder")} /> <TextFileInput
</Form.Item> autoSize={{ minRows: 3, maxRows: 10 }}
placeholder={t("workflow_node.upload.form.certificate.placeholder")}
<Form.Item> onChange={handleCertificateChange}
<Upload beforeUpload={() => false} maxCount={1} onChange={handleCertificateFileChange}> />
<Button icon={<UploadOutlinedIcon />}>{t("workflow_node.upload.form.certificate.button")}</Button>
</Upload>
</Form.Item> </Form.Item>
<Form.Item name="privateKey" label={t("workflow_node.upload.form.private_key.label")} rules={[formRule]}> <Form.Item name="privateKey" label={t("workflow_node.upload.form.private_key.label")} rules={[formRule]}>
<Input.TextArea readOnly autoSize={{ minRows: 5, maxRows: 5 }} placeholder={t("workflow_node.upload.form.private_key.placeholder")} /> <TextFileInput
</Form.Item> autoSize={{ minRows: 3, maxRows: 10 }}
placeholder={t("workflow_node.upload.form.private_key.placeholder")}
<Form.Item> onChange={handlePrivateKeyChange}
<Upload beforeUpload={() => false} maxCount={1} onChange={handlePrivateKeyFileChange}> />
<Button icon={<UploadOutlinedIcon />}>{t("workflow_node.upload.form.private_key.button")}</Button>
</Upload>
</Form.Item> </Form.Item>
</Form> </Form>
); );

View File

@ -230,7 +230,6 @@
"access.form.jdcloud_access_key_secret.tooltip": "For more information, see <a href=\"https://docs.jdcloud.com/en/account-management/accesskey-management\" target=\"_blank\">https://docs.jdcloud.com/en/account-management/accesskey-management</a>", "access.form.jdcloud_access_key_secret.tooltip": "For more information, see <a href=\"https://docs.jdcloud.com/en/account-management/accesskey-management\" target=\"_blank\">https://docs.jdcloud.com/en/account-management/accesskey-management</a>",
"access.form.k8s_kubeconfig.label": "KubeConfig", "access.form.k8s_kubeconfig.label": "KubeConfig",
"access.form.k8s_kubeconfig.placeholder": "Please enter KubeConfig file", "access.form.k8s_kubeconfig.placeholder": "Please enter KubeConfig file",
"access.form.k8s_kubeconfig.upload": "Choose File ...",
"access.form.k8s_kubeconfig.tooltip": "For more information, see <a href=\"https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/\" target=\"_blank\">https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/</a><br><br>Leave it blank to use the Pod's ServiceAccount.", "access.form.k8s_kubeconfig.tooltip": "For more information, see <a href=\"https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/\" target=\"_blank\">https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/</a><br><br>Leave it blank to use the Pod's ServiceAccount.",
"access.form.larkbot_webhook_url.label": "Lark bot Webhook URL", "access.form.larkbot_webhook_url.label": "Lark bot Webhook URL",
"access.form.larkbot_webhook_url.placeholder": "Please enter Lark bot Webhook URL", "access.form.larkbot_webhook_url.placeholder": "Please enter Lark bot Webhook URL",
@ -315,7 +314,6 @@
"access.form.ssh_password.tooltip": "Required when using password to connect to SSH.", "access.form.ssh_password.tooltip": "Required when using password to connect to SSH.",
"access.form.ssh_key.label": "SSH key (Optional)", "access.form.ssh_key.label": "SSH key (Optional)",
"access.form.ssh_key.placeholder": "Please enter SSH key", "access.form.ssh_key.placeholder": "Please enter SSH key",
"access.form.ssh_key.upload": "Choose file ...",
"access.form.ssh_key.tooltip": "Required when using key to connect to SSH.", "access.form.ssh_key.tooltip": "Required when using key to connect to SSH.",
"access.form.ssh_key_passphrase.label": "SSH key passphrase (Optional)", "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.placeholder": "Please enter SSH key passphrase",

View File

@ -11,6 +11,7 @@
"common.button.submit": "Submit", "common.button.submit": "Submit",
"common.text.copied": "Copied", "common.text.copied": "Copied",
"common.text.import_from_file": "Import from file ...",
"common.text.nodata": "No data available", "common.text.nodata": "No data available",
"common.text.operation_confirm": "Operation confirm", "common.text.operation_confirm": "Operation confirm",
"common.text.operation_succeeded": "Operation succeeded", "common.text.operation_succeeded": "Operation succeeded",

View File

@ -732,10 +732,8 @@
"workflow_node.upload.form.domains.placholder": "Please select certificate file", "workflow_node.upload.form.domains.placholder": "Please select certificate file",
"workflow_node.upload.form.certificate.label": "Certificate (PEM format)", "workflow_node.upload.form.certificate.label": "Certificate (PEM format)",
"workflow_node.upload.form.certificate.placeholder": "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----", "workflow_node.upload.form.certificate.placeholder": "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----",
"workflow_node.upload.form.certificate.button": "Choose file ...",
"workflow_node.upload.form.private_key.label": "Private key (PEM format)", "workflow_node.upload.form.private_key.label": "Private key (PEM format)",
"workflow_node.upload.form.private_key.placeholder": "-----BEGIN (RSA|EC) PRIVATE KEY-----...-----END(RSA|EC) PRIVATE KEY-----", "workflow_node.upload.form.private_key.placeholder": "-----BEGIN (RSA|EC) PRIVATE KEY-----...-----END(RSA|EC) PRIVATE KEY-----",
"workflow_node.upload.form.private_key.button": "Choose file ...",
"workflow_node.notify.label": "Notification", "workflow_node.notify.label": "Notification",
"workflow_node.notify.form.subject.label": "Subject", "workflow_node.notify.form.subject.label": "Subject",

View File

@ -223,8 +223,7 @@
"access.form.jdcloud_access_key_secret.placeholder": "请输入京东云 AccessKeySecret", "access.form.jdcloud_access_key_secret.placeholder": "请输入京东云 AccessKeySecret",
"access.form.jdcloud_access_key_secret.tooltip": "这是什么?请参阅 <a href=\"https://docs.jdcloud.com/cn/account-management/accesskey-management\" target=\"_blank\">https://docs.jdcloud.com/cn/account-management/accesskey-management</a>", "access.form.jdcloud_access_key_secret.tooltip": "这是什么?请参阅 <a href=\"https://docs.jdcloud.com/cn/account-management/accesskey-management\" target=\"_blank\">https://docs.jdcloud.com/cn/account-management/accesskey-management</a>",
"access.form.k8s_kubeconfig.label": "KubeConfig", "access.form.k8s_kubeconfig.label": "KubeConfig",
"access.form.k8s_kubeconfig.placeholder": "请选择 KubeConfig 文件", "access.form.k8s_kubeconfig.placeholder": "请输入 KubeConfig 文件内容",
"access.form.k8s_kubeconfig.upload": "选择文件",
"access.form.k8s_kubeconfig.tooltip": "这是什么?请参阅 <a href=\"https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/\" target=\"_blank\">https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/</a><br><br>为空时,将使用 Pod 的 ServiceAccount 作为凭证。", "access.form.k8s_kubeconfig.tooltip": "这是什么?请参阅 <a href=\"https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/\" target=\"_blank\">https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/</a><br><br>为空时,将使用 Pod 的 ServiceAccount 作为凭证。",
"access.form.larkbot_webhook_url.label": "飞书群机器人 Webhook 地址", "access.form.larkbot_webhook_url.label": "飞书群机器人 Webhook 地址",
"access.form.larkbot_webhook_url.placeholder": "请输入飞书群机器人 Webhook 地址", "access.form.larkbot_webhook_url.placeholder": "请输入飞书群机器人 Webhook 地址",
@ -308,8 +307,7 @@
"access.form.ssh_password.placeholder": "请输入密码", "access.form.ssh_password.placeholder": "请输入密码",
"access.form.ssh_password.tooltip": "使用密码连接到 SSH 时必填。<br>该字段与密钥文件字段二选一,如果同时填写优先使用 SSH 密钥登录。", "access.form.ssh_password.tooltip": "使用密码连接到 SSH 时必填。<br>该字段与密钥文件字段二选一,如果同时填写优先使用 SSH 密钥登录。",
"access.form.ssh_key.label": "SSH 密钥(可选)", "access.form.ssh_key.label": "SSH 密钥(可选)",
"access.form.ssh_key.placeholder": "请输入 SSH 密钥文件", "access.form.ssh_key.placeholder": "请输入 SSH 密钥文件内容",
"access.form.ssh_key.upload": "选择文件",
"access.form.ssh_key.tooltip": "使用 SSH 密钥连接到 SSH 时必填。<br>该字段与密码字段二选一,如果同时填写优先使用 SSH 密钥登录。", "access.form.ssh_key.tooltip": "使用 SSH 密钥连接到 SSH 时必填。<br>该字段与密码字段二选一,如果同时填写优先使用 SSH 密钥登录。",
"access.form.ssh_key_passphrase.label": "SSH 密钥口令(可选)", "access.form.ssh_key_passphrase.label": "SSH 密钥口令(可选)",
"access.form.ssh_key_passphrase.placeholder": "请输入 SSH 密钥口令", "access.form.ssh_key_passphrase.placeholder": "请输入 SSH 密钥口令",

View File

@ -11,6 +11,7 @@
"common.button.submit": "提交", "common.button.submit": "提交",
"common.text.copied": "已复制", "common.text.copied": "已复制",
"common.text.import_from_file": "从文件导入 ……",
"common.text.nodata": "暂无数据", "common.text.nodata": "暂无数据",
"common.text.operation_confirm": "操作确认", "common.text.operation_confirm": "操作确认",
"common.text.operation_succeeded": "操作成功", "common.text.operation_succeeded": "操作成功",

View File

@ -731,10 +731,8 @@
"workflow_node.upload.form.domains.placeholder": "上传证书文件后显示", "workflow_node.upload.form.domains.placeholder": "上传证书文件后显示",
"workflow_node.upload.form.certificate.label": "证书文件PEM 格式)", "workflow_node.upload.form.certificate.label": "证书文件PEM 格式)",
"workflow_node.upload.form.certificate.placeholder": "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----", "workflow_node.upload.form.certificate.placeholder": "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----",
"workflow_node.upload.form.certificate.button": "选择文件",
"workflow_node.upload.form.private_key.label": "私钥文件PEM 格式)", "workflow_node.upload.form.private_key.label": "私钥文件PEM 格式)",
"workflow_node.upload.form.private_key.placeholder": "-----BEGIN (RSA|EC) PRIVATE KEY-----...-----END(RSA|EC) PRIVATE KEY-----", "workflow_node.upload.form.private_key.placeholder": "-----BEGIN (RSA|EC) PRIVATE KEY-----...-----END(RSA|EC) PRIVATE KEY-----",
"workflow_node.upload.form.private_key.button": "选择文件",
"workflow_node.notify.label": "推送通知", "workflow_node.notify.label": "推送通知",
"workflow_node.notify.form.subject.label": "通知主题", "workflow_node.notify.form.subject.label": "通知主题",