feat(ui): new AccessEditForm using antd

This commit is contained in:
Fu Diwei 2024-12-17 17:11:11 +08:00
parent 047b3bc079
commit c27818b3b0
66 changed files with 2104 additions and 238 deletions

View File

@ -2,8 +2,8 @@
> [!WARNING]
> 当前分支为 `next`,是 v0.3.x 的开发分支,目前还没有稳定,请勿在生产环境中使用。
>
> 如需访问 v0.2.x 源码,请切换至 `main` 分支。
>
> 如需访问之前的版本,请切换至 `main` 分支。
# 🔒Certimate
@ -84,11 +84,11 @@ make local.run
| 华为云 | √ | √ | 可签发在华为云注册的域名;可部署到华为云 CDN、ELB |
| 七牛云 | | √ | 可部署到七牛云 CDN |
| 多吉云 | | √ | 可部署到多吉云 CDN |
| 火山引擎 | √ | √ | 可签发在火山引擎注册的域名;可部署到火山引擎 Live、CDN |
| 火山引擎 | √ | √ | 可签发在火山引擎注册的域名;可部署到火山引擎 Live、CDN |
| AWS | √ | | 可签发在 AWS Route53 托管的域名 |
| CloudFlare | √ | | 可签发在 CloudFlare 注册的域名CloudFlare 服务自带 SSL 证书 |
| GoDaddy | √ | | 可签发在 GoDaddy 注册的域名 |
| Namesilo | √ | | 可签发在 Namesilo 注册的域名 |
| NameSilo | √ | | 可签发在 NameSilo 注册的域名 |
| PowerDNS | √ | | 可签发在 PowerDNS 托管的域名 |
| HTTP 请求 | √ | | 可签发允许通过 HTTP 请求修改 DNS 的域名 |
| 本地部署 | | √ | 可部署到本地服务器 |
@ -194,4 +194,3 @@ Certimate 是一个免费且开源的项目,采用 [MIT 开源协议](LICENSE.
## 十、Star 趋势图
[![Stargazers over time](https://starchart.cc/usual2970/certimate.svg?variant=adaptive)](https://starchart.cc/usual2970/certimate)

View File

@ -2,7 +2,7 @@
> [!WARNING]
> The current branch is `next`, which is the development branch for v0.3.x. It is currently unstable and should not be used in production environments.
>
>
> To access the previous versions, please switch to the `main` branch.
# 🔒Certimate
@ -76,7 +76,7 @@ password1234567890
## List of Supported Providers
| Provider | Registration | Deployment | Remarks |
| :-----------: | :----------: | :--------: |-------------------------------------------------------------------------------------------------------------|
| :-----------: | :----------: | :--------: | ----------------------------------------------------------------------------------------------------------- |
| Alibaba Cloud | √ | √ | Supports domains registered on Alibaba Cloud; supports deployment to Alibaba Cloud OSS, CDN,SLB |
| Tencent Cloud | √ | √ | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud COS, CDN, ECDN, CLB, TEO |
| Baidu Cloud | | √ | Supports deployment to Baidu Cloud CDN |
@ -87,7 +87,7 @@ password1234567890
| AWS | √ | | Supports domains managed on AWS Route53 |
| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
| GoDaddy | √ | | Supports domains registered on GoDaddy |
| Namesilo | √ | | Supports domains registered on Namesilo |
| NameSilo | √ | | Supports domains registered on NameSilo |
| PowerDNS | √ | | Supports domains managed on PowerDNS |
| HTTP Request | √ | | Supports domains which allow managing DNS by HTTP request |
| Local Deploy | | √ | Supports deployment to local servers |

View File

@ -39,7 +39,7 @@ const (
configTypeCloudflare = "cloudflare"
configTypeGoDaddy = "godaddy"
configTypeHuaweiCloud = "huaweicloud"
configTypeNamesilo = "namesilo"
configTypeNameSilo = "namesilo"
configTypePowerDNS = "powerdns"
configTypeTencentCloud = "tencentcloud"
configTypeVolcEngine = "volcengine"
@ -219,7 +219,7 @@ func GetWithTypeOption(t string, option *ApplyOption) (Applicant, error) {
return NewAws(option), nil
case configTypeCloudflare:
return NewCloudflare(option), nil
case configTypeNamesilo:
case configTypeNameSilo:
return NewNamesilo(option), nil
case configTypeGoDaddy:
return NewGodaddy(option), nil

View File

@ -22,7 +22,7 @@ type HuaweiCloudCDNDeployerConfig struct {
AccessKeyId string `json:"accessKeyId"`
// 华为云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// 华为云域。
// 华为云域。
Region string `json:"region"`
// 加速域名(不支持泛域名)。
Domain string `json:"domain"`

View File

@ -27,7 +27,7 @@ type HuaweiCloudELBDeployerConfig struct {
AccessKeyId string `json:"accessKeyId"`
// 华为云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// 华为云域。
// 华为云域。
Region string `json:"region"`
// 部署资源类型。
ResourceType DeployResourceType `json:"resourceType"`

View File

@ -26,7 +26,7 @@ type HuaweiCloudELBUploaderConfig struct {
AccessKeyId string `json:"accessKeyId"`
// 华为云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// 华为云域。
// 华为云域。
Region string `json:"region"`
}

View File

@ -22,7 +22,7 @@ type HuaweiCloudSCMUploaderConfig struct {
AccessKeyId string `json:"accessKeyId"`
// 华为云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// 华为云域。
// 华为云域。
Region string `json:"region"`
}

View File

@ -0,0 +1,193 @@
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import {
ACCESS_PROVIDER_TYPES,
type AccessModel,
type ACMEHttpReqAccessConfig,
type AliyunAccessConfig,
type AWSAccessConfig,
type BaiduCloudAccessConfig,
type BytePlusAccessConfig,
type CloudflareAccessConfig,
type DogeCloudAccessConfig,
type GoDaddyAccessConfig,
type HuaweiCloudAccessConfig,
type KubernetesAccessConfig,
type LocalAccessConfig,
type NameSiloAccessConfig,
type PowerDNSAccessConfig,
type QiniuAccessConfig,
type SSHAccessConfig,
type TencentCloudAccessConfig,
type VolcEngineAccessConfig,
type WebhookAccessConfig,
} from "@/domain/access";
import AccessTypeSelect from "./AccessTypeSelect";
import AccessEditFormACMEHttpReqConfig from "./AccessEditFormACMEHttpReqConfig";
import AccessEditFormAliyunConfig from "./AccessEditFormAliyunConfig";
import AccessEditFormAWSConfig from "./AccessEditFormAWSConfig";
import AccessEditFormBaiduCloudConfig from "./AccessEditFormBaiduCloudConfig";
import AccessEditFormBytePlusConfig from "./AccessEditFormBytePlusConfig";
import AccessEditFormCloudflareConfig from "./AccessEditFormCloudflareConfig";
import AccessEditFormDogeCloudConfig from "./AccessEditFormDogeCloudConfig";
import AccessEditFormGoDaddyConfig from "./AccessEditFormGoDaddyConfig";
import AccessEditFormHuaweiCloudConfig from "./AccessEditFormHuaweiCloudConfig";
import AccessEditFormKubernetesConfig from "./AccessEditFormKubernetesConfig";
import AccessEditFormLocalConfig from "./AccessEditFormLocalConfig";
import AccessEditFormNameSiloConfig from "./AccessEditFormNameSiloConfig";
import AccessEditFormPowerDNSConfig from "./AccessEditFormPowerDNSConfig";
import AccessEditFormQiniuConfig from "./AccessEditFormQiniuConfig";
import AccessEditFormSSHConfig from "./AccessEditFormSSHConfig";
import AccessEditFormTencentCloudConfig from "./AccessEditFormTencentCloudConfig";
import AccessEditFormVolcEngineConfig from "./AccessEditFormVolcEngineConfig";
import AccessEditFormWebhookConfig from "./AccessEditFormWebhookConfig";
type AccessEditFormModelType = Partial<Omit<AccessModel, "id" | "created" | "updated" | "deleted">>;
export type AccessEditFormProps = {
className?: string;
style?: React.CSSProperties;
disabled?: boolean;
loading?: boolean;
mode: "add" | "edit";
model?: AccessEditFormModelType;
onModelChange?: (model: AccessEditFormModelType) => void;
};
export type AccessEditFormInstance = {
getFieldsValue: () => AccessEditFormModelType;
resetFields: () => void;
validateFields: () => Promise<AccessEditFormModelType>;
};
const AccessEditForm = forwardRef<AccessEditFormInstance, AccessEditFormProps>(({ className, style, disabled, loading, mode, model, onModelChange }, ref) => {
const { t } = useTranslation();
const formSchema = z.object({
name: z
.string()
.trim()
.min(1, t("access.form.name.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: z.nativeEnum(ACCESS_PROVIDER_TYPES, { message: t("access.form.type.placeholder") }),
config: z.any(),
});
const formRule = createSchemaFieldRule(formSchema);
const [form] = Form.useForm<z.infer<typeof formSchema>>();
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? {});
useEffect(() => {
setInitialValues(model ?? {});
}, [model]);
const [configType, setConfigType] = useState(model?.configType);
useEffect(() => {
setConfigType(model?.configType);
}, [model?.configType]);
const [configFormInst] = Form.useForm();
const configForm = useMemo(() => {
/*
ASCII
NOTICE: If you add new child component, please keep ASCII order.
*/
switch (configType) {
case ACCESS_PROVIDER_TYPES.ACMEHTTPREQ:
return <AccessEditFormACMEHttpReqConfig form={configFormInst} model={model?.config as ACMEHttpReqAccessConfig} />;
case ACCESS_PROVIDER_TYPES.ALIYUN:
return <AccessEditFormAliyunConfig form={configFormInst} model={model?.config as AliyunAccessConfig} />;
case ACCESS_PROVIDER_TYPES.AWS:
return <AccessEditFormAWSConfig form={configFormInst} model={model?.config as AWSAccessConfig} />;
case ACCESS_PROVIDER_TYPES.BAIDUCLOUD:
return <AccessEditFormBaiduCloudConfig form={configFormInst} model={model?.config as BaiduCloudAccessConfig} />;
case ACCESS_PROVIDER_TYPES.BYTEPLUS:
return <AccessEditFormBytePlusConfig form={configFormInst} model={model?.config as BytePlusAccessConfig} />;
case ACCESS_PROVIDER_TYPES.CLOUDFLARE:
return <AccessEditFormCloudflareConfig form={configFormInst} model={model?.config as CloudflareAccessConfig} />;
case ACCESS_PROVIDER_TYPES.DOGECLOUD:
return <AccessEditFormDogeCloudConfig form={configFormInst} model={model?.config as DogeCloudAccessConfig} />;
case ACCESS_PROVIDER_TYPES.GODADDY:
return <AccessEditFormGoDaddyConfig form={configFormInst} model={model?.config as GoDaddyAccessConfig} />;
case ACCESS_PROVIDER_TYPES.HUAWEICLOUD:
return <AccessEditFormHuaweiCloudConfig form={configFormInst} model={model?.config as HuaweiCloudAccessConfig} />;
case ACCESS_PROVIDER_TYPES.KUBERNETES:
return <AccessEditFormKubernetesConfig form={configFormInst} model={model?.config as KubernetesAccessConfig} />;
case ACCESS_PROVIDER_TYPES.LOCAL:
return <AccessEditFormLocalConfig form={configFormInst} model={model?.config as LocalAccessConfig} />;
case ACCESS_PROVIDER_TYPES.NAMESILO:
return <AccessEditFormNameSiloConfig form={configFormInst} model={model?.config as NameSiloAccessConfig} />;
case ACCESS_PROVIDER_TYPES.POWERDNS:
return <AccessEditFormPowerDNSConfig form={configFormInst} model={model?.config as PowerDNSAccessConfig} />;
case ACCESS_PROVIDER_TYPES.QINIU:
return <AccessEditFormQiniuConfig form={configFormInst} model={model?.config as QiniuAccessConfig} />;
case ACCESS_PROVIDER_TYPES.SSH:
return <AccessEditFormSSHConfig form={configFormInst} model={model?.config as SSHAccessConfig} />;
case ACCESS_PROVIDER_TYPES.TENCENTCLOUD:
return <AccessEditFormTencentCloudConfig form={configFormInst} model={model?.config as TencentCloudAccessConfig} />;
case ACCESS_PROVIDER_TYPES.VOLCENGINE:
return <AccessEditFormVolcEngineConfig form={configFormInst} model={model?.config as VolcEngineAccessConfig} />;
case ACCESS_PROVIDER_TYPES.WEBHOOK:
return <AccessEditFormWebhookConfig form={configFormInst} model={model?.config as WebhookAccessConfig} />;
}
}, [model, configType, configFormInst]);
const handleFormProviderChange = (name: string) => {
if (name === "configForm") {
form.setFieldValue("config", configFormInst.getFieldsValue());
onModelChange?.(form.getFieldsValue());
}
};
const handleFormChange = (_: unknown, fields: AccessEditFormModelType) => {
if (fields.configType !== configType) {
setConfigType(fields.configType);
}
onModelChange?.(fields);
};
useImperativeHandle(ref, () => ({
getFieldsValue: () => {
return form.getFieldsValue();
},
resetFields: () => {
return form.resetFields();
},
validateFields: () => {
const t1 = form.validateFields();
const t2 = configFormInst.validateFields();
return Promise.all([t1, t2]).then(() => t1);
},
}));
return (
<Form.Provider onFormChange={handleFormProviderChange}>
<div className={className} style={style}>
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" onValuesChange={handleFormChange}>
<Form.Item name="name" label={t("access.form.name.label")} rules={[formRule]}>
<Input placeholder={t("access.form.name.placeholder")} />
</Form.Item>
<Form.Item
name="configType"
label={t("access.form.type.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.type.tooltip") }}></span>}
>
<AccessTypeSelect disabled={mode === "edit"} placeholder={t("access.form.type.placeholder")} showSearch={!disabled} />
</Form.Item>
<Form.Item name="config" hidden />
</Form>
{configForm}
</div>
</Form.Provider>
);
});
export default AccessEditForm;

View File

@ -0,0 +1,103 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, Select, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type ACMEHttpReqAccessConfig } from "@/domain/access";
type AccessEditFormACMEHttpReqConfigModelType = Partial<ACMEHttpReqAccessConfig>;
export type AccessEditFormACMEHttpReqConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormACMEHttpReqConfigModelType;
onModelChange?: (model: AccessEditFormACMEHttpReqConfigModelType) => void;
};
const initModel = () => {
return {
endpoint: "https://example.com/api/",
mode: "",
} as AccessEditFormACMEHttpReqConfigModelType;
};
const AccessEditFormACMEHttpReqConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormACMEHttpReqConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
endpoint: z.string().url(t("common.errmsg.url_invalid")),
mode: z.string().min(0, t("access.form.acmehttpreq_mode.placeholder")).nullish(),
username: z
.string()
.trim()
.min(0, t("access.form.acmehttpreq_username.placeholder"))
.max(256, t("common.errmsg.string_max", { max: 256 }))
.nullish(),
password: z
.string()
.trim()
.min(0, t("access.form.acmehttpreq_password.placeholder"))
.max(256, t("common.errmsg.string_max", { max: 256 }))
.nullish(),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormACMEHttpReqConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="endpoint"
label={t("access.form.acmehttpreq_endpoint.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.acmehttpreq_endpoint.tooltip") }}></span>}
>
<Input placeholder={t("access.form.acmehttpreq_endpoint.placeholder")} />
</Form.Item>
<Form.Item
name="mode"
label={t("access.form.acmehttpreq_mode.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.acmehttpreq_mode.tooltip") }}></span>}
>
<Select
options={[
{ value: "", label: "(default)" },
{ value: "RAW", label: "RAW" },
]}
placeholder={t("access.form.acmehttpreq_mode.placeholder")}
/>
</Form.Item>
<Form.Item
name="username"
label={t("access.form.acmehttpreq_username.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.acmehttpreq_username.tooltip") }}></span>}
>
<Input placeholder={t("access.form.acmehttpreq_username.placeholder")} />
</Form.Item>
<Form.Item
name="password"
label={t("access.form.acmehttpreq_password.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.acmehttpreq_password.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.acmehttpreq_password.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormACMEHttpReqConfig;

View File

@ -0,0 +1,106 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type AWSAccessConfig } from "@/domain/access";
type AccessEditFormAWSConfigModelType = Partial<AWSAccessConfig>;
export type AccessEditFormAWSConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormAWSConfigModelType;
onModelChange?: (model: AccessEditFormAWSConfigModelType) => void;
};
const initModel = () => {
return {
region: "us-east-1",
} as AccessEditFormAWSConfigModelType;
};
const AccessEditFormAWSConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormAWSConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKeyId: z
.string()
.trim()
.min(1, t("access.form.aws_access_key_id.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretAccessKey: z
.string()
.trim()
.min(1, t("access.form.aws_secret_access_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
// TODO: 该字段仅用于申请证书,后续迁移到工作流表单中
region: z
.string()
.trim()
.min(0, t("access.form.aws_region.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 }))
.nullish(),
// TODO: 该字段仅用于申请证书,后续迁移到工作流表单中
hostedZoneId: z
.string()
.trim()
.min(0, t("access.form.aws_hosted_zone_id.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 }))
.nullish(),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormAWSConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKeyId"
label={t("access.form.aws_access_key_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.aws_access_key_id.tooltip") }}></span>}
>
<Input placeholder={t("access.form.aws_access_key_id.placeholder")} />
</Form.Item>
<Form.Item
name="secretAccessKey"
label={t("access.form.aws_secret_access_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.aws_secret_access_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.aws_secret_access_key.placeholder")} />
</Form.Item>
<Form.Item
name="region"
label={t("access.form.aws_region.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.aws_region.tooltip") }}></span>}
>
<Input placeholder={t("access.form.aws_region.placeholder")} />
</Form.Item>
<Form.Item
name="hostedZoneId"
label={t("access.form.aws_hosted_zone_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.aws_hosted_zone_id.tooltip") }}></span>}
>
<Input placeholder={t("access.form.aws_hosted_zone_id.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormAWSConfig;

View File

@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type AliyunAccessConfig } from "@/domain/access";
type AccessEditFormAliyunConfigModelType = Partial<AliyunAccessConfig>;
export type AccessEditFormAliyunConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormAliyunConfigModelType;
onModelChange?: (model: AccessEditFormAliyunConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormAliyunConfigModelType;
};
const AccessEditFormAliyunConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormAliyunConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKeyId: z
.string()
.trim()
.min(1, t("access.form.aliyun_access_key_id.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
accessKeySecret: z
.string()
.trim()
.min(1, t("access.form.aliyun_access_key_secret.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormAliyunConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKeyId"
label={t("access.form.aliyun_access_key_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.aliyun_access_key_id.tooltip") }}></span>}
>
<Input placeholder={t("access.form.aliyun_access_key_id.placeholder")} />
</Form.Item>
<Form.Item
name="accessKeySecret"
label={t("access.form.aliyun_access_key_secret.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.aliyun_access_key_secret.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.aliyun_access_key_secret.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormAliyunConfig;

View File

@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type BaiduCloudAccessConfig } from "@/domain/access";
type AccessEditFormBaiduCloudConfigModelType = Partial<BaiduCloudAccessConfig>;
export type AccessEditFormBaiduCloudConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormBaiduCloudConfigModelType;
onModelChange?: (model: AccessEditFormBaiduCloudConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormBaiduCloudConfigModelType;
};
const AccessEditFormBaiduCloudConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormBaiduCloudConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKeyId: z
.string()
.trim()
.min(1, t("access.form.baiducloud_access_key_id.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretAccessKey: z
.string()
.trim()
.min(1, t("access.form.baiducloud_secret_access_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormBaiduCloudConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKeyId"
label={t("access.form.baiducloud_access_key_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.baiducloud_access_key_id.tooltip") }}></span>}
>
<Input placeholder={t("access.form.baiducloud_access_key_id.placeholder")} />
</Form.Item>
<Form.Item
name="secretAccessKey"
label={t("access.form.baiducloud_secret_access_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.baiducloud_secret_access_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.baiducloud_secret_access_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormBaiduCloudConfig;

View File

@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type BytePlusAccessConfig } from "@/domain/access";
type AccessEditFormBytePlusConfigModelType = Partial<BytePlusAccessConfig>;
export type AccessEditFormBytePlusConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormBytePlusConfigModelType;
onModelChange?: (model: AccessEditFormBytePlusConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormBytePlusConfigModelType;
};
const AccessEditFormBytePlusConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormBytePlusConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKey: z
.string()
.trim()
.min(1, t("access.form.byteplus_access_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretKey: z
.string()
.trim()
.min(1, t("access.form.byteplus_secret_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormBytePlusConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKey"
label={t("access.form.byteplus_access_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.byteplus_access_key.tooltip") }}></span>}
>
<Input placeholder={t("access.form.byteplus_access_key.placeholder")} />
</Form.Item>
<Form.Item
name="secretKey"
label={t("access.form.byteplus_secret_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.byteplus_secret_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.byteplus_secret_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormBytePlusConfig;

View File

@ -0,0 +1,58 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type CloudflareAccessConfig } from "@/domain/access";
type AccessEditFormCloudflareConfigModelType = Partial<CloudflareAccessConfig>;
export type AccessEditFormCloudflareConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormCloudflareConfigModelType;
onModelChange?: (model: AccessEditFormCloudflareConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormCloudflareConfigModelType;
};
const AccessEditFormCloudflareConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormCloudflareConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
dnsApiToken: z
.string()
.trim()
.min(1, t("access.form.cloudflare_dns_api_token.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormCloudflareConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="dnsApiToken"
label={t("access.form.cloudflare_dns_api_token.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.cloudflare_dns_api_token.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.cloudflare_dns_api_token.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormCloudflareConfig;

View File

@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type DogeCloudAccessConfig } from "@/domain/access";
type AccessEditFormDogeCloudConfigModelType = Partial<DogeCloudAccessConfig>;
export type AccessEditFormDogeCloudConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormDogeCloudConfigModelType;
onModelChange?: (model: AccessEditFormDogeCloudConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormDogeCloudConfigModelType;
};
const AccessEditFormDogeCloudConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormDogeCloudConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKey: z
.string()
.trim()
.min(1, t("access.form.dogecloud_access_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretKey: z
.string()
.trim()
.min(1, t("access.form.dogecloud_secret_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormDogeCloudConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKey"
label={t("access.form.dogecloud_access_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.dogecloud_access_key.tooltip") }}></span>}
>
<Input placeholder={t("access.form.dogecloud_access_key.placeholder")} />
</Form.Item>
<Form.Item
name="secretKey"
label={t("access.form.dogecloud_secret_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.dogecloud_secret_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.dogecloud_secret_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormDogeCloudConfig;

View File

@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type GoDaddyAccessConfig } from "@/domain/access";
type AccessEditFormGoDaddyConfigModelType = Partial<GoDaddyAccessConfig>;
export type AccessEditFormGoDaddyConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormGoDaddyConfigModelType;
onModelChange?: (model: AccessEditFormGoDaddyConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormGoDaddyConfigModelType;
};
const AccessEditFormGoDaddyConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormGoDaddyConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
apiKey: z
.string()
.trim()
.min(1, t("access.form.godaddy_api_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
apiSecret: z
.string()
.trim()
.min(1, t("access.form.godaddy_api_secret.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormGoDaddyConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="apiKey"
label={t("access.form.godaddy_api_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.godaddy_api_key.tooltip") }}></span>}
>
<Input placeholder={t("access.form.godaddy_api_key.placeholder")} />
</Form.Item>
<Form.Item
name="apiSecret"
label={t("access.form.godaddy_api_secret.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.godaddy_api_secret.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.godaddy_api_secret.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormGoDaddyConfig;

View File

@ -0,0 +1,90 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type HuaweiCloudAccessConfig } from "@/domain/access";
type AccessEditFormHuaweiCloudConfigModelType = Partial<HuaweiCloudAccessConfig>;
export type AccessEditFormHuaweiCloudConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormHuaweiCloudConfigModelType;
onModelChange?: (model: AccessEditFormHuaweiCloudConfigModelType) => void;
};
const initModel = () => {
return {
region: "cn-north-1",
} as AccessEditFormHuaweiCloudConfigModelType;
};
const AccessEditFormHuaweiCloudConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormHuaweiCloudConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKeyId: z
.string()
.trim()
.min(1, t("access.form.huaweicloud_access_key_id.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretAccessKey: z
.string()
.trim()
.min(1, t("access.form.huaweicloud_secret_access_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
// TODO: 该字段仅用于申请证书,后续迁移到工作流表单中
region: z
.string()
.trim()
.min(0, t("access.form.huaweicloud_region.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 }))
.nullish(),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormHuaweiCloudConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKeyId"
label={t("access.form.huaweicloud_access_key_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.huaweicloud_access_key_id.tooltip") }}></span>}
>
<Input placeholder={t("access.form.huaweicloud_access_key_id.placeholder")} />
</Form.Item>
<Form.Item
name="secretAccessKey"
label={t("access.form.huaweicloud_secret_access_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.huaweicloud_secret_access_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.huaweicloud_secret_access_key.placeholder")} />
</Form.Item>
<Form.Item
name="region"
label={t("access.form.huaweicloud_region.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.huaweicloud_region.tooltip") }}></span>}
>
<Input placeholder={t("access.form.huaweicloud_region.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormHuaweiCloudConfig;

View File

@ -0,0 +1,79 @@
import { useEffect, useState } from "react";
import { flushSync } from "react-dom";
import { useTranslation } from "react-i18next";
import { Button, Form, Input, Upload, type FormInstance, type UploadFile, type UploadProps } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { Upload as UploadIcon } from "lucide-react";
import { type KubernetesAccessConfig } from "@/domain/access";
import { readFileContent } from "@/utils/file";
type AccessEditFormKubernetesConfigModelType = Partial<KubernetesAccessConfig>;
export type AccessEditFormKubernetesConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormKubernetesConfigModelType;
onModelChange?: (model: AccessEditFormKubernetesConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormKubernetesConfigModelType;
};
const AccessEditFormKubernetesConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormKubernetesConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
kubeConfig: z
.string()
.trim()
.min(0, t("access.form.k8s_kubeconfig.placeholder"))
.max(20480, t("common.errmsg.string_max", { max: 20480 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
setKubeFileList(model?.kubeConfig?.trim() ? [{ uid: "-1", name: "kubeconfig", status: "done" }] : []);
}, [model]);
const [kubeFileList, setKubeFileList] = useState<UploadFile[]>([]);
const handleFormChange = (_: unknown, fields: AccessEditFormKubernetesConfigModelType) => {
onModelChange?.(fields);
};
const handleUploadChange: UploadProps["onChange"] = async ({ file }) => {
if (file && file.status !== "removed") {
form.setFieldValue("kubeConfig", (await readFileContent(file.originFileObj ?? (file as unknown as File))).trim());
setKubeFileList([file]);
} else {
form.setFieldValue("kubeConfig", "");
setKubeFileList([]);
}
flushSync(() => onModelChange?.(form.getFieldsValue()));
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="kubeConfig"
label={t("access.form.k8s_kubeconfig.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.k8s_kubeconfig.tooltip") }}></span>}
>
<Input.TextArea hidden placeholder={t("access.form.k8s_kubeconfig.placeholder")} value={form.getFieldValue("kubeConfig")} />
<Upload beforeUpload={() => false} fileList={kubeFileList} maxCount={1} onChange={handleUploadChange}>
<Button icon={<UploadIcon size={16} />}>{t("access.form.k8s_kubeconfig.upload")}</Button>
</Upload>
</Form.Item>
</Form>
);
};
export default AccessEditFormKubernetesConfig;

View File

@ -0,0 +1,29 @@
import { useEffect, useState } from "react";
import { Form, type FormInstance } from "antd";
import { type LocalAccessConfig } from "@/domain/access";
type AccessEditFormLocalConfigModelType = Partial<LocalAccessConfig>;
export type AccessEditFormLocalConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormLocalConfigModelType;
onModelChange?: (model: AccessEditFormLocalConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormLocalConfigModelType;
};
const AccessEditFormLocalConfig = ({ form, disabled, loading, model }: AccessEditFormLocalConfigProps) => {
const [initialValues, setInitialValues] = useState(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
return <Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm"></Form>;
};
export default AccessEditFormLocalConfig;

View File

@ -0,0 +1,58 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type NameSiloAccessConfig } from "@/domain/access";
type AccessEditFormNameSiloConfigModelType = Partial<NameSiloAccessConfig>;
export type AccessEditFormNameSiloConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormNameSiloConfigModelType;
onModelChange?: (model: AccessEditFormNameSiloConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormNameSiloConfigModelType;
};
const AccessEditFormNameSiloConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormNameSiloConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
apiKey: z
.string()
.trim()
.min(1, t("access.form.namesilo_api_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormNameSiloConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="apiKey"
label={t("access.form.namesilo_api_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.namesilo_api_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.namesilo_api_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormNameSiloConfig;

View File

@ -0,0 +1,68 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type PowerDNSAccessConfig } from "@/domain/access";
type AccessEditFormPowerDNSConfigModelType = Partial<PowerDNSAccessConfig>;
export type AccessEditFormPowerDNSConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormPowerDNSConfigModelType;
onModelChange?: (model: AccessEditFormPowerDNSConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormPowerDNSConfigModelType;
};
const AccessEditFormPowerDNSConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormPowerDNSConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
apiUrl: z.string().url(t("common.errmsg.url_invalid")),
apiKey: z
.string()
.trim()
.min(1, t("access.form.powerdns_api_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormPowerDNSConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="apiUrl"
label={t("access.form.powerdns_api_url.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.powerdns_api_url.tooltip") }}></span>}
>
<Input placeholder={t("access.form.powerdns_api_url.placeholder")} />
</Form.Item>
<Form.Item
name="apiKey"
label={t("access.form.powerdns_api_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.powerdns_api_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.powerdns_api_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormPowerDNSConfig;

View File

@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type QiniuAccessConfig } from "@/domain/access";
type AccessEditFormQiniuConfigModelType = Partial<QiniuAccessConfig>;
export type AccessEditFormQiniuConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormQiniuConfigModelType;
onModelChange?: (model: AccessEditFormQiniuConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormQiniuConfigModelType;
};
const AccessEditFormQiniuConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormQiniuConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKey: z
.string()
.trim()
.min(1, t("access.form.qiniu_access_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretKey: z
.string()
.trim()
.min(1, t("access.form.qiniu_secret_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormQiniuConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKey"
label={t("access.form.qiniu_access_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.qiniu_access_key.tooltip") }}></span>}
>
<Input placeholder={t("access.form.qiniu_access_key.placeholder")} />
</Form.Item>
<Form.Item
name="secretKey"
label={t("access.form.qiniu_secret_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.qiniu_secret_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.qiniu_secret_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormQiniuConfig;

View File

@ -0,0 +1,162 @@
import { useEffect, useState } from "react";
import { flushSync } from "react-dom";
import { useTranslation } from "react-i18next";
import { Button, Form, Input, InputNumber, Upload, type FormInstance, type UploadFile, type UploadProps } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { Upload as UploadIcon } from "lucide-react";
import { type SSHAccessConfig } from "@/domain/access";
import { readFileContent } from "@/utils/file";
type AccessEditFormSSHConfigModelType = Partial<SSHAccessConfig>;
export type AccessEditFormSSHConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormSSHConfigModelType;
onModelChange?: (model: AccessEditFormSSHConfigModelType) => void;
};
const initModel = () => {
return {
host: "127.0.0.1",
port: 22,
username: "root",
} as AccessEditFormSSHConfigModelType;
};
const AccessEditFormSSHConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormSSHConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
host: z.string().refine(
(str) => {
const reIpv4 =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
const reIpv6 =
/^([\da-fA-F]{1,4}:){6}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)|::([\dafAF]1,4:)0,4((25[05]|2[04]\d|[01]?\d\d?)\.)3(25[05]|2[04]\d|[01]?\d\d?)|::([\dafAF]1,4:)0,4((25[05]|2[04]\d|[01]?\d\d?)\.)3(25[05]|2[04]\d|[01]?\d\d?)|^([\da-fA-F]{1,4}:):([\da-fA-F]{1,4}:){0,3}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)|([\dafAF]1,4:)2:([\dafAF]1,4:)0,2((25[05]|2[04]\d|[01]?\d\d?)\.)3(25[05]|2[04]\d|[01]?\d\d?)|([\dafAF]1,4:)2:([\dafAF]1,4:)0,2((25[05]|2[04]\d|[01]?\d\d?)\.)3(25[05]|2[04]\d|[01]?\d\d?)|^([\da-fA-F]{1,4}:){3}:([\da-fA-F]{1,4}:){0,1}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)|([\dafAF]1,4:)4:((25[05]|2[04]\d|[01]?\d\d?)\.)3(25[05]|2[04]\d|[01]?\d\d?)|([\dafAF]1,4:)4:((25[05]|2[04]\d|[01]?\d\d?)\.)3(25[05]|2[04]\d|[01]?\d\d?)|^([\da-fA-F]{1,4}:){7}[\da-fA-F]{1,4}|:((:[\dafAF]1,4)1,6|:)|:((:[\dafAF]1,4)1,6|:)|^[\da-fA-F]{1,4}:((:[\da-fA-F]{1,4}){1,5}|:)|([\dafAF]1,4:)2((:[\dafAF]1,4)1,4|:)|([\dafAF]1,4:)2((:[\dafAF]1,4)1,4|:)|^([\da-fA-F]{1,4}:){3}((:[\da-fA-F]{1,4}){1,3}|:)|([\dafAF]1,4:)4((:[\dafAF]1,4)1,2|:)|([\dafAF]1,4:)4((:[\dafAF]1,4)1,2|:)|^([\da-fA-F]{1,4}:){5}:([\da-fA-F]{1,4})?|([\dafAF]1,4:)6:|([\dafAF]1,4:)6:/;
const reDomain = /^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/;
return reIpv4.test(str) || reIpv6.test(str) || reDomain.test(str);
},
{ message: t("common.errmsg.host_invalid") }
),
port: z
.number()
.int()
.gte(1, t("common.errmsg.port_invalid"))
.lte(65535, t("common.errmsg.port_invalid"))
.transform((v) => +v),
username: z
.string()
.min(1, "access.form.ssh_username.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
password: z
.string()
.min(0, "access.form.ssh_password.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 }))
.nullish(),
key: z
.string()
.min(0, "access.form.ssh_key.placeholder")
.max(20480, t("common.errmsg.string_max", { max: 20480 }))
.nullish(),
keyPassphrase: z
.string()
.min(0, "access.form.ssh_key_passphrase.placeholder")
.max(20480, t("common.errmsg.string_max", { max: 20480 }))
.nullish(),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
setKeyFileList(model?.key?.trim() ? [{ uid: "-1", name: "sshkey", status: "done" }] : []);
}, [model]);
const [keyFileList, setKeyFileList] = useState<UploadFile[]>([]);
const handleFormChange = (_: unknown, fields: AccessEditFormSSHConfigModelType) => {
onModelChange?.(fields);
};
const handleUploadChange: UploadProps["onChange"] = async ({ file }) => {
if (file && file.status !== "removed") {
form.setFieldValue("kubeConfig", (await readFileContent(file.originFileObj ?? (file as unknown as File))).trim());
setKeyFileList([file]);
} else {
form.setFieldValue("kubeConfig", "");
setKeyFileList([]);
}
flushSync(() => onModelChange?.(form.getFieldsValue()));
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<div className="flex space-x-2">
<div className="w-2/3">
<Form.Item 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="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>
<div className="flex space-x-2">
<div className="w-1/2">
<Form.Item name="username" label={t("access.form.ssh_username.label")} rules={[formRule]}>
<Input placeholder={t("access.form.ssh_username.placeholder")} />
</Form.Item>
</div>
<div className="w-1/2">
<Form.Item
name="password"
label={t("access.form.ssh_password.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_password.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.ssh_password.placeholder")} />
</Form.Item>
</div>
</div>
<div className="flex space-x-2">
<div className="w-1/2">
<Form.Item
name="key"
label={t("access.form.ssh_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_key.tooltip") }}></span>}
>
<Input.TextArea hidden placeholder={t("access.form.ssh_key.placeholder")} value={form.getFieldValue("key")} />
<Upload beforeUpload={() => false} fileList={keyFileList} maxCount={1} onChange={handleUploadChange}>
<Button icon={<UploadIcon size={16} />}>{t("access.form.ssh_key.upload")}</Button>
</Upload>
</Form.Item>
</div>
<div className="w-1/2">
<Form.Item
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 placeholder={t("access.form.ssh_key_passphrase.placeholder")} />
</Form.Item>
</div>
</div>
</Form>
);
};
export default AccessEditFormSSHConfig;

View File

@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type TencentCloudAccessConfig } from "@/domain/access";
type AccessEditFormTencentCloudConfigModelType = Partial<TencentCloudAccessConfig>;
export type AccessEditFormTencentCloudConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormTencentCloudConfigModelType;
onModelChange?: (model: AccessEditFormTencentCloudConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormTencentCloudConfigModelType;
};
const AccessEditFormTencentCloudConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormTencentCloudConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
secretId: z
.string()
.trim()
.min(1, t("access.form.tencentcloud_secret_id.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretKey: z
.string()
.trim()
.min(1, t("access.form.tencentcloud_secret_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormTencentCloudConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="secretId"
label={t("access.form.tencentcloud_secret_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.tencentcloud_secret_id.tooltip") }}></span>}
>
<Input placeholder={t("access.form.tencentcloud_secret_id.placeholder")} />
</Form.Item>
<Form.Item
name="secretKey"
label={t("access.form.tencentcloud_secret_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.tencentcloud_secret_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.tencentcloud_secret_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormTencentCloudConfig;

View File

@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type VolcEngineAccessConfig } from "@/domain/access";
type AccessEditFormVolcEngineConfigModelType = Partial<VolcEngineAccessConfig>;
export type AccessEditFormVolcEngineConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormVolcEngineConfigModelType;
onModelChange?: (model: AccessEditFormVolcEngineConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormVolcEngineConfigModelType;
};
const AccessEditFormVolcEngineConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormVolcEngineConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKeyId: z
.string()
.trim()
.min(1, t("access.form.volcengine_access_key_id.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretAccessKey: z
.string()
.trim()
.min(1, t("access.form.volcengine_secret_access_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormVolcEngineConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKeyId"
label={t("access.form.volcengine_access_key_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.volcengine_access_key_id.tooltip") }}></span>}
>
<Input placeholder={t("access.form.volcengine_access_key_id.placeholder")} />
</Form.Item>
<Form.Item
name="secretAccessKey"
label={t("access.form.volcengine_secret_access_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.volcengine_secret_access_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.volcengine_secret_access_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormVolcEngineConfig;

View File

@ -0,0 +1,49 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type WebhookAccessConfig } from "@/domain/access";
type AccessEditFormWebhookConfigModelType = Partial<WebhookAccessConfig>;
export type AccessEditFormWebhookConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormWebhookConfigModelType;
onModelChange?: (model: AccessEditFormWebhookConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormWebhookConfigModelType;
};
const AccessEditFormWebhookConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormWebhookConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
url: z.string().url(t("common.errmsg.url_invalid")),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormWebhookConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item name="url" label={t("access.form.webhook_url.label")} rules={[formRule]}>
<Input placeholder={t("access.form.webhook_url.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormWebhookConfig;

View File

@ -0,0 +1,108 @@
import { cloneElement, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useControllableValue } from "ahooks";
import { Modal, notification } from "antd";
import { type AccessModel } from "@/domain/access";
import { useAccessStore } from "@/stores/access";
import AccessEditForm, { type AccessEditFormInstance } from "./AccessEditForm";
export type AccessEditModalProps = {
data?: Partial<AccessModel>;
loading?: boolean;
mode: "add" | "edit" | "copy";
open?: boolean;
trigger?: React.ReactElement;
onOpenChange?: (open: boolean) => void;
};
const AccessEditModal = ({ data, loading, mode, trigger, ...props }: AccessEditModalProps) => {
const { t } = useTranslation();
const [notificationApi, NotificationContextHolder] = notification.useNotification();
const { createAccess, updateAccess } = useAccessStore();
const [open, setOpen] = useControllableValue<boolean>(props, {
valuePropName: "open",
defaultValuePropName: "defaultOpen",
trigger: "onOpenChange",
});
const triggerEl = useMemo(() => {
if (!trigger) {
return null;
}
return cloneElement(trigger, {
...trigger.props,
onClick: () => {
setOpen(true);
trigger.props?.onClick?.();
},
});
}, [trigger, setOpen]);
const formRef = useRef<AccessEditFormInstance>(null);
const [formPending, setFormPending] = useState(false);
const handleClickOk = async () => {
setFormPending(true);
try {
await formRef.current!.validateFields();
} catch (err) {
setFormPending(false);
return Promise.reject();
}
try {
if (mode === "add") {
await createAccess(formRef.current!.getFieldsValue() as AccessModel);
} else {
await updateAccess({ ...data, ...formRef.current!.getFieldsValue() } as AccessModel);
}
setOpen(false);
} catch (err) {
notificationApi.error({ message: t("common.text.request_error"), description: <>{String(err)}</> });
throw err;
} finally {
setFormPending(false);
}
};
const handleClickCancel = () => {
if (formPending) return Promise.reject();
setOpen(false);
};
return (
<>
{NotificationContextHolder}
{triggerEl}
<Modal
afterClose={() => setOpen(false)}
cancelButtonProps={{ disabled: formPending }}
closable
confirmLoading={formPending}
destroyOnClose
loading={loading}
okText={mode === "edit" ? t("common.button.save") : t("common.button.submit")}
open={open}
title={t(`access.action.${mode}`)}
onOk={handleClickOk}
onCancel={handleClickCancel}
>
<div className="pt-4 pb-2">
<AccessEditForm ref={formRef} mode={mode === "add" ? "add" : "edit"} model={data} />
</div>
</Modal>
</>
);
};
export default AccessEditModal;

View File

@ -15,17 +15,44 @@ const AccessTypeSelect = memo((props: AccessTypeSelectProps) => {
label: t(item.name),
}));
const renderOption = (key: string) => {
const provider = accessProvidersMap.get(key);
return (
<div className="flex items-center justify-between gap-4 max-w-full overflow-hidden">
<Space className="flex-grow max-w-full truncate" size={4}>
<Avatar src={provider?.icon} size="small" />
<Typography.Text className="leading-loose" ellipsis>
{t(provider?.name ?? "")}
</Typography.Text>
</Space>
<div>
{provider?.usage === "apply" && (
<>
<Tag color="orange">{t("access.props.provider.usage.dns")}</Tag>
</>
)}
{provider?.usage === "deploy" && (
<>
<Tag color="blue">{t("access.props.provider.usage.host")}</Tag>
</>
)}
{provider?.usage === "all" && (
<>
<Tag color="orange">{t("access.props.provider.usage.dns")}</Tag>
<Tag color="blue">{t("access.props.provider.usage.host")}</Tag>
</>
)}
</div>
</div>
);
};
return (
<Select
{...props}
labelRender={({ label, value }) => {
if (label) {
return (
<Space className="max-w-full truncate" size={4}>
<Avatar src={accessProvidersMap.get(String(value))?.icon} size="small" />
{label}
</Space>
);
return renderOption(value as string);
}
return <Typography.Text type="secondary">{props.placeholder}</Typography.Text>;
@ -33,32 +60,7 @@ const AccessTypeSelect = memo((props: AccessTypeSelectProps) => {
options={options}
optionFilterProp={undefined}
optionLabelProp={undefined}
optionRender={(option) => (
<div className="flex items-center justify-between gap-4 max-w-full overflow-hidden">
<Space className="flex-grow max-w-full truncate" size={4}>
<Avatar src={accessProvidersMap.get(option.data.value)?.icon} size="small" />
<Typography.Text ellipsis>{t(accessProvidersMap.get(option.data.value)?.name ?? "")}</Typography.Text>
</Space>
<div>
{accessProvidersMap.get(option.data.value)?.usage === "apply" && (
<>
<Tag color="orange">{t("access.props.provider.usage.dns")}</Tag>
</>
)}
{accessProvidersMap.get(option.data.value)?.usage === "deploy" && (
<>
<Tag color="blue">{t("access.props.provider.usage.host")}</Tag>
</>
)}
{accessProvidersMap.get(option.data.value)?.usage === "all" && (
<>
<Tag color="orange">{t("access.props.provider.usage.dns")}</Tag>
<Tag color="blue">{t("access.props.provider.usage.host")}</Tag>
</>
)}
</div>
</div>
)}
optionRender={(option) => renderOption(option.data.value)}
/>
);
});

View File

@ -7,7 +7,7 @@ import dayjs from "dayjs";
import { type CertificateModel } from "@/domain/certificate";
import { saveFiles2Zip } from "@/utils/file";
type CertificateDetailProps = {
export type CertificateDetailProps = {
className?: string;
style?: React.CSSProperties;
data: CertificateModel;

View File

@ -1,30 +1,26 @@
import { cloneElement, useEffect, useMemo, useState } from "react";
import { cloneElement, useMemo } from "react";
import { useControllableValue } from "ahooks";
import { Drawer } from "antd";
import { type CertificateModel } from "@/domain/certificate";
import CertificateDetail from "./CertificateDetail";
type CertificateDetailDrawerProps = {
export type CertificateDetailDrawerProps = {
data?: CertificateModel;
loading?: boolean;
open?: boolean;
trigger?: React.ReactElement;
onOpenChange?: (open: boolean) => void;
};
const CertificateDetailDrawer = ({ data, trigger, ...props }: CertificateDetailDrawerProps) => {
const CertificateDetailDrawer = ({ data, loading, trigger, ...props }: CertificateDetailDrawerProps) => {
const [open, setOpen] = useControllableValue<boolean>(props, {
valuePropName: "open",
defaultValuePropName: "defaultOpen",
trigger: "onOpenChange",
});
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(data == null);
}, [data]);
const triggerDom = useMemo(() => {
const triggerEl = useMemo(() => {
if (!trigger) {
return null;
}
@ -40,7 +36,7 @@ const CertificateDetailDrawer = ({ data, trigger, ...props }: CertificateDetailD
return (
<>
{triggerDom}
{triggerEl}
<Drawer closable destroyOnClose open={open} loading={loading} placement="right" width={480} onClose={() => setOpen(false)}>
{data ? <CertificateDetail data={data} /> : <></>}

View File

@ -10,7 +10,7 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/component
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
import AccessEditDialog from "@/components/certimate/AccessEditDialog";
import AccessEditModal from "@/components/access/AccessEditModal";
import EmailsEdit from "@/components/certimate/EmailsEdit";
import StringList from "@/components/certimate/StringList";
@ -165,14 +165,14 @@ const ApplyForm = ({ data }: ApplyFormProps) => {
<FormItem>
<FormLabel className="flex justify-between w-full">
<div>{t("domain.application.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
mode="add"
trigger={
<div className="flex items-center font-normal cursor-pointer text-primary hover:underline">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
/>
</FormLabel>
<FormControl>

View File

@ -15,7 +15,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { Button } from "../ui/button";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
@ -106,15 +106,15 @@ const DeployToAliyunALB = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "aliyun" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="aliyun"
/>
</FormLabel>
<FormControl>

View File

@ -16,7 +16,7 @@ import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
@ -86,15 +86,15 @@ const DeployToAliyunCDN = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "aliyun" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="aliyun"
/>
</FormLabel>
<FormControl>

View File

@ -15,7 +15,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { Button } from "../ui/button";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
@ -106,15 +106,15 @@ const DeployToAliyunCLB = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "aliyun" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="aliyun"
/>
</FormLabel>
<FormControl>

View File

@ -17,7 +17,7 @@ import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectVa
import { SelectLabel } from "@radix-ui/react-select";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
@ -97,15 +97,15 @@ const DeployToAliyunOSS = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "aliyun" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="aliyun"
/>
</FormLabel>
<FormControl>

View File

@ -16,7 +16,7 @@ import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
@ -86,15 +86,15 @@ const DeployToBaiduCloudCDN = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "baiducloud" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="baiducloud"
/>
</FormLabel>
<FormControl>

View File

@ -16,7 +16,7 @@ import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
@ -86,15 +86,15 @@ const DeployToByteplusCDN = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "byteplus" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="byteplus"
/>
</FormLabel>
<FormControl>

View File

@ -17,7 +17,7 @@ import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectVa
import { SelectLabel } from "@radix-ui/react-select";
import AccessSelect from "./AccessSelect";
import { Plus } from "lucide-react";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
const selectState = (state: WorkflowState) => ({
updateNode: state.updateNode,
@ -87,15 +87,15 @@ const DeployToDogeCloudCDN = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "dogecloud" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="dogecloud"
/>
</FormLabel>
<FormControl>

View File

@ -17,7 +17,7 @@ import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectVa
import { SelectLabel } from "@radix-ui/react-select";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
@ -92,15 +92,15 @@ const DeployToHuaweiCloudCDN = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "huaweicloud" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="huaweicloud"
/>
</FormLabel>
<FormControl>

View File

@ -15,7 +15,7 @@ import { Button } from "../ui/button";
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
import AccessSelect from "./AccessSelect";
@ -114,15 +114,15 @@ const DeployToHuaweiCloudELB = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "huaweicloud" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="huaweicloud"
/>
</FormLabel>
<FormControl>

View File

@ -15,7 +15,7 @@ import { Button } from "../ui/button";
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
import AccessSelect from "./AccessSelect";
@ -101,15 +101,15 @@ const DeployToKubernetesSecret = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "k8s" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="k8s"
/>
</FormLabel>
<FormControl>

View File

@ -16,7 +16,7 @@ import { WorkflowNode } from "@/domain/workflow";
import { Textarea } from "../ui/textarea";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/dropdown-menu";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
@ -211,15 +211,15 @@ Remove-Item -Path "$pfxPath" -Force
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "local" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="local"
/>
</FormLabel>
<FormControl>

View File

@ -16,7 +16,7 @@ import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
@ -87,15 +87,15 @@ const DeployToQiniuCDN = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "qiniu" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="qiniu"
/>
</FormLabel>
<FormControl>

View File

@ -15,7 +15,7 @@ import i18n from "@/i18n";
import { WorkflowNode } from "@/domain/workflow";
import { Textarea } from "../ui/textarea";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
@ -129,15 +129,15 @@ const DeployToSSH = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "ssh" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="ssh"
/>
</FormLabel>
<FormControl>

View File

@ -16,7 +16,7 @@ import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
@ -86,15 +86,15 @@ const DeployToTencentCDN = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "tencentcloud" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="tencentcloud"
/>
</FormLabel>
<FormControl>

View File

@ -16,7 +16,7 @@ import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
type TencentResourceType = "ssl-deploy" | "loadbalancer" | "listener" | "ruledomain";
@ -126,15 +126,15 @@ const DeployToTencentCLB = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "tencentcloud" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="tencentcloud"
/>
</FormLabel>
<FormControl>

View File

@ -15,7 +15,7 @@ import { Button } from "../ui/button";
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import AccessSelect from "./AccessSelect";
import { Plus } from "lucide-react";
@ -92,15 +92,15 @@ const DeployToTencentCOS = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "tencentcloud" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="tencentcloud"
/>
</FormLabel>
<FormControl>

View File

@ -16,7 +16,7 @@ import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
@ -89,15 +89,15 @@ const DeployToTencentTEO = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "tencentcloud" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="tencentcloud"
/>
</FormLabel>
<FormControl>

View File

@ -16,7 +16,7 @@ import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
@ -86,15 +86,15 @@ const DeployToVolcengineCDN = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "volcengine" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="volcengine"
/>
</FormLabel>
<FormControl>

View File

@ -16,7 +16,7 @@ import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
@ -86,15 +86,15 @@ const DeployToVolcengineLive = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "volcengine" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="volcengine"
/>
</FormLabel>
<FormControl>

View File

@ -16,7 +16,7 @@ import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectVa
import { SelectLabel } from "@radix-ui/react-select";
import KVList from "../certimate/KVList";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
@ -90,15 +90,15 @@ const DeployToWebhook = ({ data }: DeployFormProps) => {
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
<AccessEditModal
data={{ configType: "webhook" }}
mode="add"
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.button.add")}
</div>
}
op="add"
outConfigType="webhook"
/>
</FormLabel>
<FormControl>

View File

@ -65,7 +65,7 @@ export interface AccessModel extends Omit<BaseModel, "created" | "updated"> {
| HuaweiCloudAccessConfig
| KubernetesAccessConfig
| LocalAccessConfig
| NamesiloAccessConfig
| NameSiloAccessConfig
| PowerDNSAccessConfig
| QiniuAccessConfig
| SSHAccessConfig
@ -119,18 +119,18 @@ export type GoDaddyAccessConfig = {
};
export type HuaweiCloudAccessConfig = {
region: string;
accessKeyId: string;
secretAccessKey: string;
region?: string;
};
export type KubernetesAccessConfig = {
kubeConfig: string;
kubeConfig?: string;
};
export type LocalAccessConfig = never;
export type NamesiloAccessConfig = {
export type NameSiloAccessConfig = {
apiKey: string;
};
@ -146,11 +146,10 @@ export type QiniuAccessConfig = {
export type SSHAccessConfig = {
host: string;
port: string;
port: number;
username: string;
password?: string;
key?: string;
keyFile?: string;
keyPassphrase?: string;
};
@ -168,12 +167,10 @@ export type WebhookAccessConfig = {
url: string;
};
type AccessTypes = (typeof ACCESS_PROVIDER_TYPES)[keyof typeof ACCESS_PROVIDER_TYPES];
type AccessUsages = "apply" | "deploy" | "all";
type AccessProvider = {
type: AccessTypes;
type: string;
name: string;
icon: string;
usage: AccessUsages;
@ -203,7 +200,7 @@ export const accessProvidersMap: Map<AccessProvider["type"], AccessProvider> = n
[ACCESS_PROVIDER_TYPE_WEBHOOK, "common.provider.webhook", "/imgs/providers/webhook.svg", "deploy"],
[ACCESS_PROVIDER_TYPE_KUBERNETES, "common.provider.kubernetes", "/imgs/providers/kubernetes.svg", "deploy"],
[ACCESS_PROVIDER_TYPE_ACMEHTTPREQ, "common.provider.acmehttpreq", "/imgs/providers/acmehttpreq.svg", "apply"],
].map(([type, name, icon, usage]) => [type as AccessTypes, { type: type as AccessTypes, name, icon, usage: usage as AccessUsages }])
].map(([type, name, icon, usage]) => [type, { type, name, icon, usage: usage as AccessUsages }])
);
export const accessTypeFormSchema = z.union(

View File

@ -20,6 +20,122 @@
"access.form.name.placeholder": "Please enter authorization name",
"access.form.type.label": "Provider",
"access.form.type.placeholder": "Please select a provider",
"access.form.type.tooltip": "DNS Provider: The provider that hosts your domain names and manages your DNS records.<br>Host Provider: The provider that hosts your servers or cloud services for deploying certificates.<br><br><i>Cannot be edited after saving.</i>",
"access.form.acmehttpreq_endpoint.label": "Endpoint",
"access.form.acmehttpreq_endpoint.placeholder": "Please enter Endpoint",
"access.form.acmehttpreq_endpoint.tooltip": "For more information, see <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
"access.form.acmehttpreq_mode.label": "Mode",
"access.form.acmehttpreq_mode.placeholder": "Please select mode",
"access.form.acmehttpreq_mode.tooltip": "For more information, see <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
"access.form.acmehttpreq_username.label": "HTTP Basic Auth Username",
"access.form.acmehttpreq_username.placeholder": "Please enter HTTP basic auth username",
"access.form.acmehttpreq_username.tooltip": "For more information, see <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
"access.form.acmehttpreq_password.label": "HTTP Basic Auth Password",
"access.form.acmehttpreq_password.placeholder": "Please enter HTTP basic auth password",
"access.form.acmehttpreq_password.tooltip": "For more information, see <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
"access.form.aliyun_access_key_id.label": "Aliyun Access Key ID",
"access.form.aliyun_access_key_id.placeholder": "Please enter Aliyun Access Key ID",
"access.form.aliyun_access_key_id.tooltip": "For more information, see <a href=\"https://www.alibabacloud.com/help/en/acr/create-and-obtain-an-accesskey-pair\" target=\"_blank\">https://www.alibabacloud.com/help/en/acr/create-and-obtain-an-accesskey-pair</a>",
"access.form.aliyun_access_key_secret.label": "Aliyun Access Key Secret",
"access.form.aliyun_access_key_secret.placeholder": "Please enter Aliyun Access Key Secret",
"access.form.aliyun_access_key_secret.tooltip": "For more information, see <a href=\"https://www.alibabacloud.com/help/en/acr/create-and-obtain-an-accesskey-pair\" target=\"_blank\">https://www.alibabacloud.com/help/en/acr/create-and-obtain-an-accesskey-pair</a>",
"access.form.aws_access_key_id.label": "AWS Access Key ID",
"access.form.aws_access_key_id.placeholder": "Please enter AWS Access Key ID",
"access.form.aws_access_key_id.tooltip": "For more information, see <a href=\"https://docs.aws.amazon.com/en_us/IAM/latest/UserGuide/id_credentials_access-keys.html\" target=\"_blank\">https://docs.aws.amazon.com/en_us/IAM/latest/UserGuide/id_credentials_access-keys.html</a>",
"access.form.aws_secret_access_key.label": "AWS Secret Access Key",
"access.form.aws_secret_access_key.placeholder": "Please enter AWS Secret Access Key",
"access.form.aws_secret_access_key.tooltip": "For more information, see <a href=\"https://docs.aws.amazon.com/en_us/IAM/latest/UserGuide/id_credentials_access-keys.html\" target=\"_blank\">https://docs.aws.amazon.com/en_us/IAM/latest/UserGuide/id_credentials_access-keys.html</a>",
"access.form.aws_region.label": "AWS Region",
"access.form.aws_region.placeholder": "Please enter AWS Region (example: us-east-1)",
"access.form.aws_region.tooltip": "For more information, see <a href=\"https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints\" target=\"_blank\">https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints</a>",
"access.form.aws_hosted_zone_id.label": "AWS Hosted Zone ID",
"access.form.aws_hosted_zone_id.placeholder": "Please enter AWS Hosted Zone ID",
"access.form.aws_hosted_zone_id.tooltip": "For more information, see <a href=\"https://docs.aws.amazon.com/en_us/Route53/latest/DeveloperGuide/hosted-zones-working-with.html\" target=\"_blank\">https://docs.aws.amazon.com/en_us/Route53/latest/DeveloperGuide/hosted-zones-working-with.html</a>",
"access.form.baiducloud_access_key_id.label": "Baidu Cloud Access Key ID",
"access.form.baiducloud_access_key_id.placeholder": "Please enter Baidu Cloud Access Key ID",
"access.form.baiducloud_access_key_id.tooltip": "For more information, see <a href=\"https://intl.cloud.baidu.com/doc/Reference/s/jjwvz2e3p-en\" target=\"_blank\">https://intl.cloud.baidu.com/doc/Reference/s/jjwvz2e3p-en</a>",
"access.form.baiducloud_secret_access_key.label": "Baidu Cloud Secret Access Key",
"access.form.baiducloud_secret_access_key.placeholder": "Please enter Baidu Cloud Secret Access Key",
"access.form.baiducloud_secret_access_key.tooltip": "For more information, see <a href=\"https://intl.cloud.baidu.com/doc/Reference/s/jjwvz2e3p-en\" target=\"_blank\">https://intl.cloud.baidu.com/doc/Reference/s/jjwvz2e3p-en</a>",
"access.form.byteplus_access_key.label": "BytePlus Access Key",
"access.form.byteplus_access_key.placeholder": "Please enter BytePlus Access Key",
"access.form.byteplus_access_key.tooltip": "For more information, see <a href=\"https://docs.byteplus.com/en/docs/byteplus-platform/docs-managing-keys\" target=\"_blank\">https://docs.byteplus.com/en/docs/byteplus-platform/docs-managing-keys</a>",
"access.form.byteplus_secret_key.label": "BytePlus Secret Key",
"access.form.byteplus_secret_key.placeholder": "Please enter BytePlus Secret Key",
"access.form.byteplus_secret_key.tooltip": "For more information, see <a href=\"https://docs.byteplus.com/en/docs/byteplus-platform/docs-managing-keys\" target=\"_blank\">https://docs.byteplus.com/en/docs/byteplus-platform/docs-managing-keys</a>",
"access.form.cloudflare_dns_api_token.label": "Cloudflare API Token",
"access.form.cloudflare_dns_api_token.placeholder": "Please enter Cloudflare API Token",
"access.form.cloudflare_dns_api_token.tooltip": "For more information, see <a href=\"https://developers.cloudflare.com/fundamentals/api/get-started/create-token/\" target=\"_blank\">https://developers.cloudflare.com/fundamentals/api/get-started/create-token/</a>",
"access.form.dogecloud_access_key.label": "Doge Cloud Access Key",
"access.form.dogecloud_access_key.placeholder": "Please enter Doge Cloud Access Key",
"access.form.dogecloud_access_key.tooltip": "For more information, see <a href=\"https://console.dogecloud.com/\" target=\"_blank\">https://console.dogecloud.com/</a>",
"access.form.dogecloud_secret_key.label": "Doge Cloud Secret Key",
"access.form.dogecloud_secret_key.placeholder": "Please enter Doge Cloud Secret Key",
"access.form.dogecloud_secret_key.tooltip": "For more information, see <a href=\"https://console.dogecloud.com/\" target=\"_blank\">https://console.dogecloud.com/</a>",
"access.form.godaddy_api_key.label": "GoDaddy API Key",
"access.form.godaddy_api_key.placeholder": "Please enter GoDaddy API Key",
"access.form.godaddy_api_key.tooltip": "For more information, see <a href=\"https://developer.godaddy.com/\" target=\"_blank\">https://developer.godaddy.com/</a>",
"access.form.godaddy_api_secret.label": "GoDaddy API Secret",
"access.form.godaddy_api_secret.placeholder": "Please enter GoDaddy API Secret",
"access.form.godaddy_api_secret.tooltip": "For more information, see <a href=\"https://developer.godaddy.com/\" target=\"_blank\">https://developer.godaddy.com/</a>",
"access.form.huaweicloud_access_key_id.label": "Huawei Cloud Access Key ID",
"access.form.huaweicloud_access_key_id.placeholder": "Please enter Huawei Cloud Access Key ID",
"access.form.huaweicloud_access_key_id.tooltip": "For more information, see <a href=\"https://support.huaweicloud.com/intl/en-us/usermanual-ca/ca_01_0003.html\" target=\"_blank\">https://support.huaweicloud.com/intl/en-us/usermanual-ca/ca_01_0003.html</a>",
"access.form.huaweicloud_secret_access_key.label": "Huawei Cloud Secret Access Key",
"access.form.huaweicloud_secret_access_key.placeholder": "Please enter Huawei Cloud Secret Access Key",
"access.form.huaweicloud_secret_access_key.tooltip": "For more information, see <a href=\"https://support.huaweicloud.com/intl/en-us/usermanual-ca/ca_01_0003.html\" target=\"_blank\">https://support.huaweicloud.com/intl/en-us/usermanual-ca/ca_01_0003.html</a>",
"access.form.huaweicloud_region.label": "Huawei Cloud Region",
"access.form.huaweicloud_region.placeholder": "Please enter Huawei Cloud Region (example: cn-north-1)",
"access.form.huaweicloud_region.tooltip": "For more information, see <a href=\"https://console-intl.huaweicloud.com/apiexplorer/#/endpoint?locale=en-us\" target=\"_blank\">https://console-intl.huaweicloud.com/apiexplorer/#/endpoint</a>",
"access.form.k8s_kubeconfig.label": "KubeConfig",
"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>If empty, the Pod's ServiceAccount will be used.",
"access.form.namesilo_api_key.label": "NameSilo API Key",
"access.form.namesilo_api_key.placeholder": "Please enter NameSilo API Key",
"access.form.namesilo_api_key.tooltip": "For more information, see <a href=\"https://www.namesilo.com/support/v2/articles/account-options/api-manager\" target=\"_blank\">https://www.namesilo.com/support/v2/articles/account-options/api-manager</a>",
"access.form.powerdns_api_url.label": "PowerDNS API URL",
"access.form.powerdns_api_url.placeholder": "Please enter PowerDNS API URL",
"access.form.powerdns_api_url.tooltip": "For more information, see <a href=\"https://doc.powerdns.com/authoritative/http-api/index.html#endpoints-and-objects-in-the-api\" target=\"_blank\">https://doc.powerdns.com/authoritative/http-api/index.html#endpoints-and-objects-in-the-api</a>",
"access.form.powerdns_api_key.label": "PowerDNS API Key",
"access.form.powerdns_api_key.placeholder": "Please enter PowerDNS API Key",
"access.form.powerdns_api_key.tooltip": "For more information, see <a href=\"https://doc.powerdns.com/authoritative/http-api/index.html#enabling-the-api\" target=\"_blank\">https://doc.powerdns.com/authoritative/http-api/index.html#enabling-the-api</a>",
"access.form.qiniu_access_key.label": "Qiniu Access Key",
"access.form.qiniu_access_key.placeholder": "Please enter Qiniu Access Key",
"access.form.qiniu_access_key.tooltip": "For more information, see <a href=\"https://portal.qiniu.com/\" target=\"_blank\">https://portal.qiniu.com/</a>",
"access.form.qiniu_secret_key.label": "Qiniu Secret Key",
"access.form.qiniu_secret_key.placeholder": "Please enter Qiniu Secret Key",
"access.form.qiniu_secret_key.tooltip": "For more information, see <a href=\"https://portal.qiniu.com/\" target=\"_blank\">https://portal.qiniu.com/</a>",
"access.form.tencentcloud_secret_id.label": "Tencent Cloud SecretId",
"access.form.tencentcloud_secret_id.placeholder": "Please enter Tencent Cloud SecretId",
"access.form.tencentcloud_secret_id.tooltip": "For more information, see <a href=\"https://cloud.tencent.com/document/product/598/40488?lang=en\" target=\"_blank\">https://cloud.tencent.com/document/product/598/40488?lang=en</a>",
"access.form.tencentcloud_secret_key.label": "Tencent Cloud SecretKey",
"access.form.tencentcloud_secret_key.placeholder": "Please enter Tencent Cloud SecretKey",
"access.form.tencentcloud_secret_key.tooltip": "For more information, see <a href=\"https://cloud.tencent.com/document/product/598/40488?lang=en\" target=\"_blank\">https://cloud.tencent.com/document/product/598/40488?lang=en</a>",
"access.form.volcengine_access_key_id.label": "VolcEngine Access Key ID",
"access.form.volcengine_access_key_id.placeholder": "Please enter VolcEngine Access Key ID",
"access.form.volcengine_access_key_id.tooltip": "For more information, see <a href=\"https://www.volcengine.com/docs/6291/216571\" target=\"_blank\">https://www.volcengine.com/docs/6291/216571</a>",
"access.form.volcengine_secret_access_key.label": "VolcEngine Secret Access Key",
"access.form.volcengine_secret_access_key.placeholder": "Please enter VolcEngine Secret Access Key",
"access.form.volcengine_secret_access_key.tooltip": "For more information, see <a href=\"https://www.volcengine.com/docs/6291/216571\" target=\"_blank\">https://www.volcengine.com/docs/6291/216571</a>",
"access.form.webhook_url.label": "Webhook URL",
"access.form.webhook_url.placeholder": "Please enter Webhook URL",
"access.form.ssh_host.label": "Server Host",
"access.form.ssh_host.placeholder": "Please enter server host",
"access.form.ssh_port.label": "Server Port",
"access.form.ssh_port.placeholder": "Please enter server port",
"access.form.ssh_username.label": "Username",
"access.form.ssh_username.placeholder": "Please enter username",
"access.form.ssh_password.label": "Password",
"access.form.ssh_password.placeholder": "Please enter password",
"access.form.ssh_password.tooltip": "Required when using password to connect to SSH.",
"access.form.ssh_key.label": "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_passphrase.label": "SSH Key Passphrase",
"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.region.label": "Region",
"access.form.region.placeholder": "Please enter Region",
"access.form.access_key_id.label": "AccessKeyId",
@ -36,12 +152,6 @@
"access.form.secret_access_key.placeholder": "Please enter SecretAccessKey",
"access.form.cloud_dns_api_token.label": "CLOUD_DNS_API_TOKEN",
"access.form.cloud_dns_api_token.placeholder": "Please enter CLOUD_DNS_API_TOKEN",
"access.form.godaddy_api_key.label": "GO_DADDY_API_KEY",
"access.form.godaddy_api_key.placeholder": "Please enter GO_DADDY_API_KEY",
"access.form.godaddy_api_secret.label": "GO_DADDY_API_SECRET",
"access.form.godaddy_api_secret.placeholder": "Please enter GO_DADDY_API_SECRET",
"access.form.namesilo_api_key.label": "NAMESILO_API_KEY",
"access.form.namesilo_api_key.placeholder": "Please enter NAMESILO_API_KEY",
"access.form.pdns_api_url.label": "PDNS_API_URL",
"access.form.pdns_api_url.placeholder": "Please enter PDNS_API_URL",
"access.form.pdns_api_key.label": "PDNS_API_KEY",
@ -53,25 +163,5 @@
"access.form.username.label": "Username",
"access.form.username.placeholder": "Please enter username",
"access.form.password.label": "Password",
"access.form.password.placeholder": "Please enter password",
"access.form.access_group.placeholder": "Please select a group",
"access.form.ssh_group.label": "Authorization Configuration Group (used to deploy a single domain certificate to multiple SSH hosts)",
"access.form.ssh_host.label": "Server Host",
"access.form.ssh_host.placeholder": "Please enter Host",
"access.form.ssh_port.label": "SSH Port",
"access.form.ssh_port.placeholder": "Please enter Port",
"access.form.ssh_username.label": "Username",
"access.form.ssh_username.placeholder": "Please enter username",
"access.form.ssh_password.label": "Password (Log-in using password)",
"access.form.ssh_password.placeholder": "Please enter password",
"access.form.ssh_key.label": "Key (Log-in using private key)",
"access.form.ssh_key.placeholder": "Please enter Key",
"access.form.ssh_key_file.placeholder": "Please select file",
"access.form.ssh_key_passphrase.label": "Key Passphrase (Log-in using private key)",
"access.form.ssh_key_passphrase.placeholder": "Please enter Key Passphrase",
"access.form.webhook_url.label": "Webhook URL",
"access.form.webhook_url.placeholder": "Please enter Webhook URL",
"access.form.k8s_kubeconfig.label": "KubeConfig (Null will use pod's ServiceAccount)",
"access.form.k8s_kubeconfig.placeholder": "Please enter KubeConfig",
"access.form.k8s_kubeconfig_file.placeholder": "Please select file (Null will use pod's ServiceAccount)"
"access.form.password.placeholder": "Please enter password"
}

View File

@ -40,8 +40,9 @@
"common.errmsg.email_empty": "Please enter email",
"common.errmsg.email_duplicate": "Email already exists",
"common.errmsg.domain_invalid": "Please enter domain",
"common.errmsg.host_invalid": "Please enter the correct domain name or IP",
"common.errmsg.ip_invalid": "Please enter IP",
"common.errmsg.host_invalid": "Please enter a valid domain name or IP",
"common.errmsg.port_invalid": "Please enter a valid port",
"common.errmsg.ip_invalid": "Please enter a valid IP",
"common.errmsg.url_invalid": "Please enter a valid URL",
"common.provider.aliyun": "Alibaba Cloud",
@ -68,7 +69,7 @@
"common.provider.dogecloud.cdn": "Doge Cloud - CDN",
"common.provider.aws": "AWS",
"common.provider.cloudflare": "Cloudflare",
"common.provider.namesilo": "Namesilo",
"common.provider.namesilo": "NameSilo",
"common.provider.godaddy": "GoDaddy",
"common.provider.powerdns": "PowerDNS",
"common.provider.acmehttpreq": "Http Request (ACME Proxy)",

View File

@ -20,6 +20,122 @@
"access.form.name.placeholder": "请输入授权名称",
"access.form.type.label": "提供商",
"access.form.type.placeholder": "请选择提供商",
"access.form.type.tooltip": "提供商分为两种类型:<br>【DNS 提供商】您的域名托管方,用于管理您的域名解析记录。<br>【主机提供商】您的服务器或云服务的托管方,用于部署申请后的证书。<br><br>该字段保存后不可修改。",
"access.form.acmehttpreq_endpoint.label": "服务端点",
"access.form.acmehttpreq_endpoint.placeholder": "请输入服务端点",
"access.form.acmehttpreq_endpoint.tooltip": "这是什么?请参阅 <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
"access.form.acmehttpreq_mode.label": "模式",
"access.form.acmehttpreq_mode.placeholder": "请选择模式",
"access.form.acmehttpreq_mode.tooltip": "这是什么?请参阅 <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
"access.form.acmehttpreq_username.label": "HTTP 基本认证用户名",
"access.form.acmehttpreq_username.placeholder": "请输入 HTTP 基本认证用户名",
"access.form.acmehttpreq_username.tooltip": "这是什么?请参阅 <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
"access.form.acmehttpreq_password.label": "HTTP 基本认证密码",
"access.form.acmehttpreq_password.placeholder": "请输入 HTTP 基本认证密码",
"access.form.acmehttpreq_password.tooltip": "这是什么?请参阅 <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
"access.form.aliyun_access_key_id.label": "阿里云 Access Key ID",
"access.form.aliyun_access_key_id.placeholder": "请输入阿里云 Access Key ID",
"access.form.aliyun_access_key_id.tooltip": "这是什么?请参阅 <a href=\"https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair\" target=\"_blank\">https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair</a>",
"access.form.aliyun_access_key_secret.label": "阿里云 Access Key Secret",
"access.form.aliyun_access_key_secret.placeholder": "请输入阿里云 Access Key Secret",
"access.form.aliyun_access_key_secret.tooltip": "这是什么?请参阅 <a href=\"https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair\" target=\"_blank\">https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair</a>",
"access.form.aws_access_key_id.label": "AWS Access Key ID",
"access.form.aws_access_key_id.placeholder": "请输入 AWS Access Key ID",
"access.form.aws_access_key_id.tooltip": "这是什么?请参阅 <a href=\"https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/id_credentials_access-keys.html\" target=\"_blank\">https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/id_credentials_access-keys.html</a>",
"access.form.aws_secret_access_key.label": "AWS Secret Access Key",
"access.form.aws_secret_access_key.placeholder": "请输入 AWS Secret Access Key",
"access.form.aws_secret_access_key.tooltip": "这是什么?请参阅 <a href=\"https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/id_credentials_access-keys.html\" target=\"_blank\">https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/id_credentials_access-keys.html</a>",
"access.form.aws_region.label": "AWS 区域",
"access.form.aws_region.placeholder": "请输入 AWS 区域例如us-east-1",
"access.form.aws_region.tooltip": "这是什么?请参阅 <a href=\"https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints\" target=\"_blank\">https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints</a>",
"access.form.aws_hosted_zone_id.label": "AWS 托管区域 ID",
"access.form.aws_hosted_zone_id.placeholder": "请输入 AWS 托管区域 ID",
"access.form.aws_hosted_zone_id.tooltip": "这是什么?请参阅 <a href=\"https://docs.aws.amazon.com/zh_cn/Route53/latest/DeveloperGuide/hosted-zones-working-with.html\" target=\"_blank\">https://docs.aws.amazon.com/zh_cn/Route53/latest/DeveloperGuide/hosted-zones-working-with.html</a>",
"access.form.baiducloud_access_key_id.label": "百度智能云 Access Key ID",
"access.form.baiducloud_access_key_id.placeholder": "请输入百度智能云 Access Key ID",
"access.form.baiducloud_access_key_id.tooltip": "这是什么?请参阅 <a href=\"https://cloud.baidu.com/doc/Reference/s/jjwvz2e3p\" target=\"_blank\">https://cloud.baidu.com/doc/Reference/s/jjwvz2e3p</a>",
"access.form.baiducloud_secret_access_key.label": "百度智能云 Secret Access Key",
"access.form.baiducloud_secret_access_key.placeholder": "请输入百度智能云 Secret Access Key",
"access.form.baiducloud_secret_access_key.tooltip": "这是什么?请参阅 <a href=\"https://cloud.baidu.com/doc/Reference/s/jjwvz2e3p\" target=\"_blank\">https://cloud.baidu.com/doc/Reference/s/jjwvz2e3p</a>",
"access.form.byteplus_access_key.label": "BytePlus Access Key",
"access.form.byteplus_access_key.placeholder": "请输入 BytePlus Access Key",
"access.form.byteplus_access_key.tooltip": "这是什么?请参阅 <a href=\"https://docs.byteplus.com/zh-CN/docs/byteplus-platform/docs-managing-keys\" target=\"_blank\">https://docs.byteplus.com/zh-CN/docs/byteplus-platform/docs-managing-keys</a>",
"access.form.byteplus_secret_key.label": "BytePlus Secret Key",
"access.form.byteplus_secret_key.placeholder": "请输入 BytePlus Secret Key",
"access.form.byteplus_secret_key.tooltip": "这是什么?请参阅 <a href=\"https://docs.byteplus.com/zh-CN/docs/byteplus-platform/docs-managing-keys\" target=\"_blank\">https://docs.byteplus.com/zh-CN/docs/byteplus-platform/docs-managing-keys</a>",
"access.form.cloudflare_dns_api_token.label": "Cloudflare API Token",
"access.form.cloudflare_dns_api_token.placeholder": "请输入 Cloudflare API Token",
"access.form.cloudflare_dns_api_token.tooltip": "这是什么?请参阅 <a href=\"https://developers.cloudflare.com/fundamentals/api/get-started/create-token/\" target=\"_blank\">https://developers.cloudflare.com/fundamentals/api/get-started/create-token/</a>",
"access.form.dogecloud_access_key.label": "多吉云 Access Key",
"access.form.dogecloud_access_key.placeholder": "请输入多吉云 Access Key",
"access.form.dogecloud_access_key.tooltip": "这是什么?请参阅 <a href=\"https://console.dogecloud.com/\" target=\"_blank\">https://console.dogecloud.com/</a>",
"access.form.dogecloud_secret_key.label": "多吉云 Secret Key",
"access.form.dogecloud_secret_key.placeholder": "请输入多吉云 Secret Key",
"access.form.dogecloud_secret_key.tooltip": "这是什么?请参阅 <a href=\"https://console.dogecloud.com/\" target=\"_blank\">https://console.dogecloud.com/</a>",
"access.form.godaddy_api_key.label": "GoDaddy API Key",
"access.form.godaddy_api_key.placeholder": "请输入 GoDaddy API Key",
"access.form.godaddy_api_key.tooltip": "这是什么?请参阅 <a href=\"https://developer.godaddy.com/\" target=\"_blank\">https://developer.godaddy.com/</a>",
"access.form.godaddy_api_secret.label": "GoDaddy API Secret",
"access.form.godaddy_api_secret.placeholder": "请输入 GoDaddy API Secret",
"access.form.godaddy_api_secret.tooltip": "这是什么?请参阅 <a href=\"https://developer.godaddy.com/\" target=\"_blank\">https://developer.godaddy.com/</a>",
"access.form.huaweicloud_access_key_id.label": "华为云 Access Key ID",
"access.form.huaweicloud_access_key_id.placeholder": "请输入华为云 Access Key ID",
"access.form.huaweicloud_access_key_id.tooltip": "这是什么?请参阅 <a href=\"https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html\" target=\"_blank\">https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html</a>",
"access.form.huaweicloud_secret_access_key.label": "华为云 Secret Access Key",
"access.form.huaweicloud_secret_access_key.placeholder": "请输入华为云 Secret Access Key",
"access.form.huaweicloud_secret_access_key.tooltip": "这是什么?请参阅 <a href=\"https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html\" target=\"_blank\">https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html</a>",
"access.form.huaweicloud_region.label": "华为云 DNS 产品区域",
"access.form.huaweicloud_region.placeholder": "请输入华为云区域例如cn-north-1",
"access.form.huaweicloud_region.tooltip": "这是什么?请参阅 <a href=\"https://console.huaweicloud.com/apiexplorer/#/endpoint\" target=\"_blank\">https://console.huaweicloud.com/apiexplorer/#/endpoint</a>",
"access.form.k8s_kubeconfig.label": "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.namesilo_api_key.label": "NameSilo API Key",
"access.form.namesilo_api_key.placeholder": "请输入 NameSilo API Key",
"access.form.namesilo_api_key.tooltip": "这是什么?请参阅 <a href=\"https://www.namesilo.com/support/v2/articles/account-options/api-manager\" target=\"_blank\">https://www.namesilo.com/support/v2/articles/account-options/api-manager</a>",
"access.form.powerdns_api_url.label": "PowerDNS API URL",
"access.form.powerdns_api_url.placeholder": "请输入 PowerDNS API URL",
"access.form.powerdns_api_url.tooltip": "这是什么?请参阅 <a href=\"https://doc.powerdns.com/authoritative/http-api/index.html#endpoints-and-objects-in-the-api\" target=\"_blank\">https://doc.powerdns.com/authoritative/http-api/index.html#endpoints-and-objects-in-the-api</a>",
"access.form.powerdns_api_key.label": "PowerDNS API Key",
"access.form.powerdns_api_key.placeholder": "请输入 PowerDNS API Key",
"access.form.powerdns_api_key.tooltip": "这是什么?请参阅 <a href=\"https://doc.powerdns.com/authoritative/http-api/index.html#enabling-the-api\" target=\"_blank\">https://doc.powerdns.com/authoritative/http-api/index.html#enabling-the-api</a>",
"access.form.qiniu_access_key.label": "七牛云 Access Key",
"access.form.qiniu_access_key.placeholder": "请输入七牛云 Access Key",
"access.form.qiniu_access_key.tooltip": "这是什么?请参阅 <a href=\"https://portal.qiniu.com/\" target=\"_blank\">https://portal.qiniu.com/</a>",
"access.form.qiniu_secret_key.label": "七牛云 Secret Key",
"access.form.qiniu_secret_key.placeholder": "请输入七牛云 Secret Key",
"access.form.qiniu_secret_key.tooltip": "这是什么?请参阅 <a href=\"https://portal.qiniu.com/\" target=\"_blank\">https://portal.qiniu.com/</a>",
"access.form.tencentcloud_secret_id.label": "腾讯云 SecretId",
"access.form.tencentcloud_secret_id.placeholder": "请输入腾讯云 SecretId",
"access.form.tencentcloud_secret_id.tooltip": "这是什么?请参阅 <a href=\"https://cloud.tencent.com/document/product/598/40488\" target=\"_blank\">https://cloud.tencent.com/document/product/598/40488</a>",
"access.form.tencentcloud_secret_key.label": "腾讯云 SecretKey",
"access.form.tencentcloud_secret_key.placeholder": "请输入腾讯云 SecretKey",
"access.form.tencentcloud_secret_key.tooltip": "这是什么?请参阅 <a href=\"https://cloud.tencent.com/document/product/598/40488\" target=\"_blank\">https://cloud.tencent.com/document/product/598/40488</a>",
"access.form.volcengine_access_key_id.label": "火山引擎 Access Key ID",
"access.form.volcengine_access_key_id.placeholder": "请输入火山引擎 Access Key ID",
"access.form.volcengine_access_key_id.tooltip": "这是什么?请参阅 <a href=\"https://www.volcengine.com/docs/6291/216571\" target=\"_blank\">https://www.volcengine.com/docs/6291/216571</a>",
"access.form.volcengine_secret_access_key.label": "火山引擎 Secret Access Key",
"access.form.volcengine_secret_access_key.placeholder": "请输入火山引擎 Secret Access Key",
"access.form.volcengine_secret_access_key.tooltip": "这是什么?请参阅 <a href=\"https://www.volcengine.com/docs/6291/216571\" target=\"_blank\">https://www.volcengine.com/docs/6291/216571</a>",
"access.form.webhook_url.label": "Webhook URL",
"access.form.webhook_url.placeholder": "请输入 Webhook URL",
"access.form.ssh_host.label": "服务器地址",
"access.form.ssh_host.placeholder": "请输入服务器地址",
"access.form.ssh_port.label": "服务器端口",
"access.form.ssh_port.placeholder": "请输入服务器端口",
"access.form.ssh_username.label": "用户名",
"access.form.ssh_username.placeholder": "请输入用户名",
"access.form.ssh_password.label": "密码",
"access.form.ssh_password.placeholder": "请输入密码",
"access.form.ssh_password.tooltip": "使用密码连接到 SSH 时必填。",
"access.form.ssh_key.label": "SSH 密钥",
"access.form.ssh_key.placeholder": "请输入 SSH 密钥文件",
"access.form.ssh_key.upload": "选择文件",
"access.form.ssh_key.tooltip": "使用 SSH 密钥连接到 SSH 时必填。",
"access.form.ssh_key_passphrase.label": "SSH 密钥口令",
"access.form.ssh_key_passphrase.placeholder": "请输入 SSH 密钥口令",
"access.form.ssh_key_passphrase.tooltip": "使用 SSH 密钥连接到 SSH 时选填。",
"access.form.region.label": "Region",
"access.form.region.placeholder": "请输入区域",
"access.form.access_key_id.label": "AccessKeyId",
@ -36,12 +152,6 @@
"access.form.secret_access_key.placeholder": "请输入 SecretAccessKey",
"access.form.cloud_dns_api_token.label": "CLOUD_DNS_API_TOKEN",
"access.form.cloud_dns_api_token.placeholder": "请输入 CLOUD_DNS_API_TOKEN",
"access.form.godaddy_api_key.label": "GO_DADDY_API_KEY",
"access.form.godaddy_api_key.placeholder": "请输入 GO_DADDY_API_KEY",
"access.form.godaddy_api_secret.label": "GO_DADDY_API_SECRET",
"access.form.godaddy_api_secret.placeholder": "请输入 GO_DADDY_API_SECRET",
"access.form.namesilo_api_key.label": "NAMESILO_API_KEY",
"access.form.namesilo_api_key.placeholder": "请输入 NAMESILO_API_KEY",
"access.form.pdns_api_url.label": "PDNS_API_URL",
"access.form.pdns_api_url.placeholder": "请输入 PDNS_API_URL",
"access.form.pdns_api_key.label": "PDNS_API_KEY",
@ -53,25 +163,5 @@
"access.form.username.label": "用户名",
"access.form.username.placeholder": "请输入用户名",
"access.form.password.label": "密码",
"access.form.password.placeholder": "请输入密码",
"access.form.access_group.placeholder": "请选择分组",
"access.form.ssh_group.label": "授权配置组(用于将一个域名证书部署到多个 SSH 主机)",
"access.form.ssh_host.label": "服务器 Host",
"access.form.ssh_host.placeholder": "请输入 Host",
"access.form.ssh_port.label": "SSH 端口",
"access.form.ssh_port.placeholder": "请输入 Port",
"access.form.ssh_username.label": "用户名",
"access.form.ssh_username.placeholder": "请输入用户名",
"access.form.ssh_password.label": "密码(使用密码登录)",
"access.form.ssh_password.placeholder": "请输入密码",
"access.form.ssh_key.label": "Key使用私钥登录",
"access.form.ssh_key.placeholder": "请输入 Key",
"access.form.ssh_key_file.placeholder": "请选择文件",
"access.form.ssh_key_passphrase.label": "Key 口令(使用私钥登录)",
"access.form.ssh_key_passphrase.placeholder": "请输入 Key 口令",
"access.form.webhook_url.label": "Webhook URL",
"access.form.webhook_url.placeholder": "请输入 Webhook URL",
"access.form.k8s_kubeconfig.label": "KubeConfig不选将使用Pod的ServiceAccount",
"access.form.k8s_kubeconfig.placeholder": "请输入 KubeConfig",
"access.form.k8s_kubeconfig_file.placeholder": "请选择文件"
"access.form.password.placeholder": "请输入密码"
}

View File

@ -41,6 +41,7 @@
"common.errmsg.email_duplicate": "邮箱已存在",
"common.errmsg.domain_invalid": "请输入正确的域名",
"common.errmsg.host_invalid": "请输入正确的域名或 IP 地址",
"common.errmsg.port_invalid": "请输入正确的端口号",
"common.errmsg.ip_invalid": "请输入正确的 IP 地址",
"common.errmsg.url_invalid": "请输入正确的 URL",
@ -68,7 +69,7 @@
"common.provider.dogecloud.cdn": "多吉云 - 内容分发网络 CDN",
"common.provider.aws": "AWS",
"common.provider.cloudflare": "Cloudflare",
"common.provider.namesilo": "Namesilo",
"common.provider.namesilo": "NameSilo",
"common.provider.godaddy": "GoDaddy",
"common.provider.powerdns": "PowerDNS",
"common.provider.acmehttpreq": "Http Request (ACME Proxy)",

View File

@ -1,6 +1,6 @@
import { Navigate, Outlet } from "react-router-dom";
import Version from "@/components/certimate/Version";
import Version from "@/components/Version";
import { getPocketBase } from "@/repository/pocketbase";
const AuthLayout = () => {

View File

@ -15,7 +15,7 @@ import {
Workflow as WorkflowIcon,
} from "lucide-react";
import Version from "@/components/certimate/Version";
import Version from "@/components/Version";
import { useTheme } from "@/hooks";
import { getPocketBase } from "@/repository/pocketbase";

View File

@ -6,7 +6,7 @@ import { Copy as CopyIcon, Pencil as PencilIcon, Plus as PlusIcon, Trash2 as Tra
import dayjs from "dayjs";
import { ClientResponseError } from "pocketbase";
import AccessEditDialog from "@/components/certimate/AccessEditDialog";
import AccessEditModal from "@/components/access/AccessEditModal";
import { accessProvidersMap, type AccessModel } from "@/domain/access";
import { useAccessStore } from "@/stores/access";
@ -71,24 +71,24 @@ const AccessList = () => {
render: (_, record) => (
<>
<Space size={0}>
<AccessEditDialog
<AccessEditModal
data={record}
mode="edit"
trigger={
<Tooltip title={t("access.action.edit")}>
<Button type="link" icon={<PencilIcon size={16} />} />
</Tooltip>
}
op="edit"
data={record}
/>
<AccessEditDialog
<AccessEditModal
data={{ ...record, id: undefined, name: `${record.name}-copy` }}
mode="copy"
trigger={
<Tooltip title={t("access.action.copy")}>
<Button type="link" icon={<CopyIcon size={16} />} />
</Tooltip>
}
op="copy"
data={record}
/>
<Tooltip title={t("access.action.delete")}>
@ -113,7 +113,14 @@ const AccessList = () => {
const [pageSize, setPageSize] = useState<number>(10);
useEffect(() => {
fetchAccesses();
fetchAccesses().catch((err) => {
if (err instanceof ClientResponseError && err.isAbort) {
return;
}
console.error(err);
notificationApi.error({ message: t("common.text.request_error"), description: <>{String(err)}</> });
});
}, []);
const fetchTableData = useCallback(async () => {
@ -167,14 +174,14 @@ const AccessList = () => {
<PageHeader
title={t("access.page.title")}
extra={[
<AccessEditDialog
<AccessEditModal
key="create"
mode="add"
trigger={
<Button key="create" type="primary" icon={<PlusIcon size={16} />}>
<Button type="primary" icon={<PlusIcon size={16} />}>
{t("access.action.add")}
</Button>
}
op="add"
/>,
]}
/>
@ -190,6 +197,7 @@ const AccessList = () => {
current: page,
pageSize: pageSize,
total: tableTotal,
showSizeChanger: true,
onChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);

View File

@ -222,6 +222,7 @@ const CertificateList = () => {
current: page,
pageSize: pageSize,
total: tableTotal,
showSizeChanger: true,
onChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);

View File

@ -20,19 +20,18 @@ const Login = () => {
});
const formRule = createSchemaFieldRule(formSchema);
const [form] = Form.useForm<z.infer<typeof formSchema>>();
const [formPending, setFormPending] = useState(false);
const [isPending, setIsPending] = useState(false);
const onSubmit = async (values: z.infer<typeof formSchema>) => {
setIsPending(true);
const handleFormFinish = async (fields: z.infer<typeof formSchema>) => {
setFormPending(true);
try {
await getPocketBase().admins.authWithPassword(values.username, values.password);
await getPocketBase().admins.authWithPassword(fields.username, fields.password);
navigage("/");
} catch (err) {
notificationApi.error({ message: t("common.text.request_error"), description: <>{String(err)}</> });
} finally {
setIsPending(false);
setFormPending(false);
}
};
@ -45,7 +44,7 @@ const Login = () => {
<img src="/logo.svg" className="w-16" />
</div>
<Form form={form} disabled={isPending} layout="vertical" onFinish={onSubmit}>
<Form form={form} disabled={formPending} layout="vertical" onFinish={handleFormFinish}>
<Form.Item name="username" label={t("login.username.label")} rules={[formRule]}>
<Input placeholder={t("login.username.placeholder")} />
</Form.Item>
@ -55,7 +54,7 @@ const Login = () => {
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" block loading={isPending}>
<Button type="primary" htmlType="submit" block loading={formPending}>
{t("login.submit")}
</Button>
</Form.Item>

View File

@ -313,6 +313,7 @@ const WorkflowList = () => {
current: page,
pageSize: pageSize,
total: tableTotal,
showSizeChanger: true,
onChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);

View File

@ -12,7 +12,7 @@ export const list = async () => {
});
};
export const save = async (record: AccessModel) => {
export const save = async (record: Partial<AccessModel>) => {
if (record.id) {
return await getPocketBase().collection(COLLECTION_NAME).update<AccessModel>(record.id, record);
}
@ -20,7 +20,7 @@ export const save = async (record: AccessModel) => {
return await getPocketBase().collection(COLLECTION_NAME).create<AccessModel>(record);
};
export const remove = async (record: AccessModel) => {
export const remove = async (record: Partial<AccessModel>) => {
record = { ...record, deleted: dayjs.utc().format("YYYY-MM-DD HH:mm:ss") };
await getPocketBase().collection(COLLECTION_NAME).update<AccessModel>(record.id, record);
};

View File

@ -6,7 +6,7 @@ import { list as listAccess, save as saveAccess, remove as removeAccess } from "
export interface AccessState {
accesses: AccessModel[];
createAccess: (access: AccessModel) => void;
createAccess: (access: Omit<AccessModel, "id" | "created" | "udpated" | "deleted">) => void;
updateAccess: (access: AccessModel) => void;
deleteAccess: (access: AccessModel) => void;
fetchAccesses: () => Promise<void>;
@ -17,22 +17,24 @@ export const useAccessStore = create<AccessState>((set) => {
accesses: [],
createAccess: async (access) => {
access = await saveAccess(access);
const record = await saveAccess(access);
set(
produce((state: AccessState) => {
state.accesses.unshift(access);
state.accesses.unshift(record);
})
);
},
updateAccess: async (access) => {
access = await saveAccess(access);
const record = await saveAccess(access);
set(
produce((state: AccessState) => {
const index = state.accesses.findIndex((e) => e.id === access.id);
state.accesses[index] = access;
const index = state.accesses.findIndex((e) => e.id === record.id);
if (index !== -1) {
state.accesses[index] = record;
}
})
);
},

View File

@ -14,7 +14,7 @@ export function readFileContent(file: File): Promise<string> {
reader.onerror = () => reject(reader.error);
reader.readAsText(file);
reader.readAsText(file, "utf-8");
});
}