From f79b028d0b34e94d0ed2efdb0ff9edcdf1d0cfe2 Mon Sep 17 00:00:00 2001 From: imlonghao Date: Sat, 5 Jul 2025 14:07:24 +0800 Subject: [PATCH 1/6] feat: support custom certificate lifetime --- go.mod | 1 + go.sum | 2 ++ internal/applicant/applicant.go | 9 +++++++++ internal/applicant/providers.go | 1 + internal/domain/workflow.go | 2 ++ .../workflow/node/ApplyNodeConfigForm.tsx | 17 +++++++++++++++++ ui/src/domain/workflow.ts | 1 + ui/src/i18n/locales/en/nls.workflow.nodes.json | 3 +++ ui/src/i18n/locales/zh/nls.workflow.nodes.json | 3 +++ 9 files changed, 39 insertions(+) diff --git a/go.mod b/go.mod index 712a9904..f17c5aff 100644 --- a/go.mod +++ b/go.mod @@ -64,6 +64,7 @@ require ( github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.17 github.com/volcengine/volc-sdk-golang v1.0.212 github.com/volcengine/volcengine-go-sdk v1.1.18 + github.com/xhit/go-str2duration/v2 v2.1.0 gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1 gitlab.ecloud.com/ecloud/ecloudsdkcore v1.0.0 golang.org/x/crypto v0.39.0 diff --git a/go.sum b/go.sum index 149f9151..3dc87db1 100644 --- a/go.sum +++ b/go.sum @@ -901,6 +901,8 @@ github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+ github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go index c6d5608d..6be0ca77 100644 --- a/internal/applicant/applicant.go +++ b/internal/applicant/applicant.go @@ -16,6 +16,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/lego" + "github.com/xhit/go-str2duration/v2" "golang.org/x/exp/slices" "golang.org/x/time/rate" @@ -64,6 +65,7 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err CAProviderAccessConfig: make(map[string]any), CAProviderServiceConfig: nodeCfg.CAProviderConfig, KeyAlgorithm: nodeCfg.KeyAlgorithm, + LifeTime: nodeCfg.LifeTime, ACMEProfile: nodeCfg.ACMEProfile, Nameservers: xslices.Filter(strings.Split(nodeCfg.Nameservers, ";"), func(s string) bool { return s != "" }), DnsPropagationWait: nodeCfg.DnsPropagationWait, @@ -238,6 +240,13 @@ func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOpt Bundle: true, Profile: options.ACMEProfile, } + if options.LifeTime != "" { + lifeTime, err := str2duration.ParseDuration(options.LifeTime) + if err != nil { + return nil, fmt.Errorf("invalid lifetime: %w", err) + } + certRequest.NotAfter = time.Now().Add(lifeTime) + } if options.ARIReplaceAcct == user.Registration.URI { certRequest.ReplacesCertID = options.ARIReplaceCert } diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 7b07997f..b1f147c1 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -63,6 +63,7 @@ type applicantProviderOptions struct { DnsPropagationWait int32 DnsPropagationTimeout int32 DnsTTL int32 + LifeTime string ACMEProfile string DisableFollowCNAME bool ARIReplaceAcct string diff --git a/internal/domain/workflow.go b/internal/domain/workflow.go index 16895229..c51f513a 100644 --- a/internal/domain/workflow.go +++ b/internal/domain/workflow.go @@ -75,6 +75,7 @@ type WorkflowNodeConfigForApply struct { CAProviderAccessId string `json:"caProviderAccessId,omitempty"` // CA 提供商授权记录 ID CAProviderConfig map[string]any `json:"caProviderConfig,omitempty"` // CA 提供商额外配置 KeyAlgorithm string `json:"keyAlgorithm,omitempty"` // 证书算法 + LifeTime string `json:"lifeTime,omitempty"` // 证书有效期 ACMEProfile string `json:"acmeProfile,omitempty"` // ACME Profiles Extension Nameservers string `json:"nameservers,omitempty"` // DNS 服务器列表,以半角分号分隔 DnsPropagationWait int32 `json:"dnsPropagationWait,omitempty"` // DNS 传播等待时间,等同于 lego 的 `--dns-propagation-wait` 参数 @@ -132,6 +133,7 @@ func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply { CAProviderAccessId: xmaps.GetString(n.Config, "caProviderAccessId"), CAProviderConfig: xmaps.GetKVMapAny(n.Config, "caProviderConfig"), KeyAlgorithm: xmaps.GetOrDefaultString(n.Config, "keyAlgorithm", string(CertificateKeyAlgorithmTypeRSA2048)), + LifeTime: xmaps.GetString(n.Config, "lifeTime"), ACMEProfile: xmaps.GetString(n.Config, "acmeProfile"), Nameservers: xmaps.GetString(n.Config, "nameservers"), DnsPropagationWait: xmaps.GetInt32(n.Config, "dnsPropagationWait"), diff --git a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx index cdcb689c..43df598d 100644 --- a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx @@ -113,6 +113,10 @@ const ApplyNodeConfigForm = forwardRef (v == null || v === "" ? undefined : Number(v)), z.number().int(t("workflow_node.apply.form.dns_ttl.placeholder")).gte(1, t("workflow_node.apply.form.dns_ttl.placeholder")).nullish() ), + lifeTime: z.string().nullish().refine((v) => { + if (!v) return true; + return /^[+-]?(\d+[ns|us|ms|s|m|h|d|w])+$/.test(v); + }, t("workflow_node.apply.form.life_time.placeholder")), acmeProfile: z.string().nullish(), disableFollowCNAME: z.boolean().nullish(), disableARI: z.boolean().nullish(), @@ -456,6 +460,19 @@ const ApplyNodeConfigForm = forwardRef + } + > + + + ; keyAlgorithm: string; + lifeTime?: string; acmeProfile?: string; nameservers?: string; dnsPropagationTimeout?: number; diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 12254221..7c6b10f5 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -71,6 +71,9 @@ "workflow_node.apply.form.ca_provider_access.button": "Create", "workflow_node.apply.form.key_algorithm.label": "Certificate key algorithm", "workflow_node.apply.form.key_algorithm.placeholder": "Please select certificate key algorithm", + "workflow_node.apply.form.life_time.label": "Certificate lifetime (Optional)", + "workflow_node.apply.form.life_time.placeholder": "Please enter certificate lifetime, unit support: ns, us, ms, s, m, h, d, w. e.g. 30d", + "workflow_node.apply.form.life_time.tooltip": "It determines the lifetime of the certificate. If you don't understand this option, just keep it by default.", "workflow_node.apply.form.acme_profile.label": "ACME certificate profile (Optional)", "workflow_node.apply.form.acme_profile.placeholder": "Please enter ACME certificate profile", "workflow_node.apply.form.acme_profile.tooltip": "It determines the ACME profile which will be used to affect issuance of the certificate requested. If you don't understand this option, just keep it by default.Learn more.", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index a3f4278e..ae112573 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -70,6 +70,9 @@ "workflow_node.apply.form.ca_provider_access.button": "新建", "workflow_node.apply.form.key_algorithm.label": "证书算法", "workflow_node.apply.form.key_algorithm.placeholder": "请选择证书算法", + "workflow_node.apply.form.life_time.label": "证书有效期(可选)", + "workflow_node.apply.form.life_time.placeholder": "请输入证书有效期,单位支持:ns、us、ms、s、m、h、d、w,例如:30d", + "workflow_node.apply.form.life_time.tooltip": "表示证书的有效期。如果你不了解该选项的用途,保持默认即可。", "workflow_node.apply.form.acme_profile.label": "ACME 证书配置(可选)", "workflow_node.apply.form.acme_profile.placeholder": "请输入 ACME 证书配置", "workflow_node.apply.form.acme_profile.tooltip": "表示证书颁发时使用的 ACME 证书配置。如果你不了解该选项的用途,保持默认即可。点此了解更多。", From 43acea2e77ed6866825ef9b532ad4301c19d2ed6 Mon Sep 17 00:00:00 2001 From: "Aldrich J. Xing" Date: Fri, 15 Aug 2025 01:45:39 +0800 Subject: [PATCH 2/6] support Provider ACME DNS in UI. --- ui/public/imgs/providers/acmedns.svg | 2 + ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormACMEDNSConfig.tsx | 85 +++++++++++++++++++ ui/src/domain/access.ts | 7 ++ ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 9 ++ ui/src/i18n/locales/en/nls.provider.json | 1 + ui/src/i18n/locales/zh/nls.access.json | 9 ++ ui/src/i18n/locales/zh/nls.provider.json | 1 + 9 files changed, 121 insertions(+) create mode 100644 ui/public/imgs/providers/acmedns.svg create mode 100644 ui/src/components/access/AccessFormACMEDNSConfig.tsx diff --git a/ui/public/imgs/providers/acmedns.svg b/ui/public/imgs/providers/acmedns.svg new file mode 100644 index 00000000..936ca077 --- /dev/null +++ b/ui/public/imgs/providers/acmedns.svg @@ -0,0 +1,2 @@ + + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index ba9e1318..204eb6e2 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -13,6 +13,7 @@ import { useAntdForm, useAntdFormName } from "@/hooks"; import AccessForm1PanelConfig from "./AccessForm1PanelConfig"; import AccessFormACMECAConfig from "./AccessFormACMECAConfig"; +import AccessFormACMEDNSConfig from "./AccessFormACMEDNSConfig"; import AccessFormACMEHttpReqConfig from "./AccessFormACMEHttpReqConfig"; import AccessFormAliyunConfig from "./AccessFormAliyunConfig"; import AccessFormAPISIXConfig from "./AccessFormAPISIXConfig"; @@ -193,6 +194,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.ACMECA: return ; + case ACCESS_PROVIDERS.ACMEDNS: + return ; case ACCESS_PROVIDERS.ACMEHTTPREQ: return ; case ACCESS_PROVIDERS.ALIYUN: diff --git a/ui/src/components/access/AccessFormACMEDNSConfig.tsx b/ui/src/components/access/AccessFormACMEDNSConfig.tsx new file mode 100644 index 00000000..c842d97f --- /dev/null +++ b/ui/src/components/access/AccessFormACMEDNSConfig.tsx @@ -0,0 +1,85 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod/v4"; + +import { type AccessConfigForACMEDNS } from "@/domain/access"; + +type AccessFormACMEDNSConfigFieldValues = Nullish; + +export interface AccessFormACMEDNSConfigProps { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormACMEDNSConfigFieldValues; + onValuesChange?: (values: AccessFormACMEDNSConfigFieldValues) => void; +} + +const initFormModel = (): AccessFormACMEDNSConfigFieldValues => { + return { + apiBase: "https://auth.acme-dns.io/", + storageBaseUrl: "", + storagePath: "", + }; +}; + +const AccessFormACMEDNSConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormACMEDNSConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiBase: z.url(t("common.errmsg.url_invalid")), + storageBaseUrl: z + .string() + .max(256, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + storagePath: z + .string() + .max(256, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormACMEDNSConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 51555f07..fcf5ebaf 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -8,6 +8,7 @@ export interface AccessModel extends BaseModel { ( | AccessConfigFor1Panel | AccessConfigForACMECA + | AccessConfigForACMEDNS | AccessConfigForACMEHttpReq | AccessConfigForAliyun | AccessConfigForAPISIX @@ -96,6 +97,12 @@ export type AccessConfigForACMECA = { eabHmacKey?: string; }; +export type AccessConfigForACMEDNS = { + apiBase: string; + storageBaseUrl?: string; + storagePath?: string; +}; + export type AccessConfigForACMEHttpReq = { endpoint: string; mode?: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 9c098fc5..37525536 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -6,6 +6,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ ["1PANEL"]: "1panel", ACMECA: "acmeca", + ACMEDNS: "acmedns", ACMEHTTPREQ: "acmehttpreq", ALIYUN: "aliyun", APISIX: "apisix", @@ -173,6 +174,7 @@ export const accessProvidersMap: Map = new NOTICE: If you add new constant, please keep ASCII order. */ export const ACME_DNS01_PROVIDERS = Object.freeze({ + ACMEDNS: `${ACCESS_PROVIDERS.ACMEDNS}`, ACMEHTTPREQ: `${ACCESS_PROVIDERS.ACMEHTTPREQ}`, ALIYUN: `${ACCESS_PROVIDERS.ALIYUN}`, // 兼容旧值,等同于 `ALIYUN_DNS` ALIYUN_DNS: `${ACCESS_PROVIDERS.ALIYUN}-dns`, @@ -365,6 +368,7 @@ export const acmeDns01ProvidersMap: Map [ type, { diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 0dd233d6..efcf44f6 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -51,6 +51,15 @@ "access.form.acmeca_eab_kid.placeholder": "Please enter ACME EAB KID", "access.form.acmeca_eab_hmac_key.label": "ACME EAB HMAC key (Optional)", "access.form.acmeca_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key", + "access.form.acmedns_api_base.label": "ACME-DNS API URL", + "access.form.acmedns_api_base.placeholder": "Please enter ACME-DNS API URL", + "access.form.acmedns_api_base.tooltip": "For more information, see https://go-acme.github.io/lego/dns/acme-dns/", + "access.form.acmedns_storage_base_url.label": "ACME-DNS Credentials URL PATH", + "access.form.acmedns_storage_base_url.placeholder": "Enter the URL path to the ACME-DNS JSON credentials JSON file. Each credentials are stored in a separate JSON file. This file will be used for TXT record updates.", + "access.form.acmedns_storage_base_url.tooltip": "For more information, see https://go-acme.github.io/lego/dns/acme-dns/", + "access.form.acmedns_storage_path.label": "ACME-DNS Credentials Local Path", + "access.form.acmedns_storage_path.placeholder": "Please enter the ACME-DNS JSON Credentials JSON File Path. Each credentials are in a separate JSON file. It will be used for TXT record updates.", + "access.form.acmedns_storage_path.tooltip": "For more information, see https://go-acme.github.io/lego/dns/acme-dns/", "access.form.acmehttpreq_endpoint.label": "Endpoint", "access.form.acmehttpreq_endpoint.placeholder": "Please enter endpoint", "access.form.acmehttpreq_endpoint.tooltip": "For more information, see https://go-acme.github.io/lego/dns/httpreq/", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 9c7d0053..c227be35 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -3,6 +3,7 @@ "provider.1panel.console": "1Panel - Console", "provider.1panel.site": "1Panel - Website", "provider.acmeca": "ACME Custom CA Endpoint", + "provider.acmedns": "ACME DNS", "provider.acmehttpreq": "ACME Custom HTTP Endpoint", "provider.aliyun": "Alibaba Cloud", "provider.aliyun.alb": "Alibaba Cloud - ALB (Application Load Balancer)", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index b6a4a0be..1a310773 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -51,6 +51,15 @@ "access.form.acmeca_eab_kid.placeholder": "请输入 ACME EAB KID", "access.form.acmeca_eab_hmac_key.label": "ACME EAB HMAC Key(可选)", "access.form.acmeca_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key", + "access.form.acmedns_api_base.label": "ACME-DNS API 地址", + "access.form.acmedns_api_base.placeholder": "请输入 ACME-DNS API 地址", + "access.form.acmedns_api_base.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/acme-dns/", + "access.form.acmedns_storage_base_url.label": "ACME-DNS JSON 帐户数据服务器", + "access.form.acmedns_storage_base_url.placeholder": "请输入 ACME-DNS JSON 帐户数据服务器地址", + "access.form.acmedns_storage_base_url.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/acme-dns/", + "access.form.acmedns_storage_path.label": "ACME-DNS JSON 帐户数据文件", + "access.form.acmedns_storage_path.placeholder": "ACME-DNS JSON 帐户数据文件。每个域的帐户都将注册/保存到此文件,并用于 TXT 更新。", + "access.form.acmedns_storage_path.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/acme-dns/", "access.form.acmehttpreq_endpoint.label": "服务端点", "access.form.acmehttpreq_endpoint.placeholder": "请输入服务端点", "access.form.acmehttpreq_endpoint.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/httpreq/", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 73a3b63c..d9baa7d7 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -3,6 +3,7 @@ "provider.1panel.console": "1Panel - 控制台", "provider.1panel.site": "1Panel - 网站", "provider.acmeca": "ACME 自定义 CA 端点", + "provider.acmedns": "ACME DNS", "provider.acmehttpreq": "ACME 自定义 HTTP 端点", "provider.aliyun": "阿里云", "provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB", From 152bdbfcf509432941d52df0ad6486c43df722c2 Mon Sep 17 00:00:00 2001 From: "Aldrich J. Xing" Date: Fri, 15 Aug 2025 01:49:29 +0800 Subject: [PATCH 3/6] support Provider ACME DNS in Backend. --- go.mod | 1 + go.sum | 2 ++ internal/applicant/providers.go | 16 +++++++++ internal/domain/access.go | 6 ++++ internal/domain/provider.go | 2 ++ .../acme-dns01/providers/acmedns/acmedns.go | 35 +++++++++++++++++++ 6 files changed, 62 insertions(+) create mode 100644 pkg/core/ssl-applicator/acme-dns01/providers/acmedns/acmedns.go diff --git a/go.mod b/go.mod index 712a9904..fb1968eb 100644 --- a/go.mod +++ b/go.mod @@ -123,6 +123,7 @@ require ( github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect github.com/nrdcg/desec v0.10.0 // indirect + github.com/nrdcg/goacmedns v0.2.0 // indirect github.com/nrdcg/mailinabox v0.2.0 // indirect github.com/nrdcg/porkbun v0.4.0 // indirect github.com/peterhellberg/link v1.2.0 // indirect diff --git a/go.sum b/go.sum index 149f9151..d5bb2afb 100644 --- a/go.sum +++ b/go.sum @@ -692,6 +692,8 @@ github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 h1:ouZ2JWDl8IW5k1qu github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3/go.mod h1:ZwadWt7mVhMHMbAQ1w8IhDqtWO3eWqWq72W7trnaiE8= github.com/nrdcg/desec v0.10.0 h1:qrEDiqnsvNU9QE7lXIXi/tIHAfyaFXKxF2/8/52O8uM= github.com/nrdcg/desec v0.10.0/go.mod h1:5+4vyhMRTs49V9CNoODF/HwT8Mwxv9DJ6j+7NekUnBs= +github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0= +github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg= github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk= github.com/nrdcg/mailinabox v0.2.0/go.mod h1:0yxqeYOiGyxAu7Sb94eMxHPIOsPYXAjTeA9ZhePhGnc= github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg= diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 7b07997f..55c4c0fe 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -6,6 +6,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/certimate-go/certimate/internal/domain" + pACMEDNS "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/acmedns" pACMEHttpReq "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/acmehttpreq" pAliyun "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/aliyun" pAliyunESA "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/aliyun-esa" @@ -75,6 +76,21 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi NOTICE: If you add new constant, please keep ASCII order. */ switch options.Provider { + case domain.ACMEDns01ProviderTypeACMEDNS: + { + access := domain.AccessConfigForACMEDNS{} + if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + applicant, err := pACMEDNS.NewChallengeProvider(&pACMEDNS.ChallengeProviderConfig{ + ApiBase: access.ApiBase, + StorageBaseUrl: access.StorageBaseUrl, + StoragePath: access.StoragePath, + }) + return applicant, err + } + case domain.ACMEDns01ProviderTypeACMEHttpReq: { access := domain.AccessConfigForACMEHttpReq{} diff --git a/internal/domain/access.go b/internal/domain/access.go index dd63ada1..6aad4602 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -28,6 +28,12 @@ type AccessConfigForACMECA struct { EabHmacKey string `json:"eabHmacKey,omitempty"` } +type AccessConfigForACMEDNS struct { + ApiBase string `json:"apiBase"` + StorageBaseUrl string `json:"storageBaseUrl,omitempty"` + StoragePath string `json:"storagePath,omitempty"` +} + type AccessConfigForACMEHttpReq struct { Endpoint string `json:"endpoint"` Mode string `json:"mode,omitempty"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index c6e7b969..9372026e 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -11,6 +11,7 @@ type AccessProviderType string const ( AccessProviderType1Panel = AccessProviderType("1panel") AccessProviderTypeACMECA = AccessProviderType("acmeca") + AccessProviderTypeACMEDNS = AccessProviderType("acmedns") AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq") AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai(预留) AccessProviderTypeAliyun = AccessProviderType("aliyun") @@ -121,6 +122,7 @@ ACME DNS-01 提供商常量值。 NOTICE: If you add new constant, please keep ASCII order. */ const ( + ACMEDns01ProviderTypeACMEDNS = ACMEDns01ProviderType(AccessProviderTypeACMEDNS) ACMEDns01ProviderTypeACMEHttpReq = ACMEDns01ProviderType(AccessProviderTypeACMEHttpReq) ACMEDns01ProviderTypeAliyun = ACMEDns01ProviderType(AccessProviderTypeAliyun) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAliyunDNS] ACMEDns01ProviderTypeAliyunDNS = ACMEDns01ProviderType(AccessProviderTypeAliyun + "-dns") diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/acmedns/acmedns.go b/pkg/core/ssl-applicator/acme-dns01/providers/acmedns/acmedns.go new file mode 100644 index 00000000..992766f8 --- /dev/null +++ b/pkg/core/ssl-applicator/acme-dns01/providers/acmedns/acmedns.go @@ -0,0 +1,35 @@ +package acmedns + +import ( + "errors" + "net/url" + + "github.com/go-acme/lego/v4/providers/dns/acmedns" + + "github.com/certimate-go/certimate/pkg/core" +) + +type ChallengeProviderConfig struct { + ApiBase string `json:"apiBase,omitempty"` + StorageBaseUrl string `json:"storageBaseUrl,omitempty"` + StoragePath string `json:"storagePath,omitempty"` +} + +func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger, error) { + if config == nil { + return nil, errors.New("the configuration of the acme challenge provider is nil") + } + + ApiBase, _ := url.Parse(config.ApiBase) + providerConfig := acmedns.NewDefaultConfig() + providerConfig.APIBase = ApiBase.String() + providerConfig.StorageBaseURL = config.StorageBaseUrl + providerConfig.StoragePath = config.StoragePath + + provider, err := acmedns.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} From d7e20a0b838d46697235554f6d3cc5625934785a Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 3 Sep 2025 20:54:37 +0800 Subject: [PATCH 4/6] feat: optimize certificate lifetime options --- internal/certapply/client_certifier.go | 15 +-- internal/domain/workflow.go | 4 +- internal/workflow/engine/executor_bizapply.go | 12 ++- .../designer/forms/BizApplyNodeConfigForm.tsx | 92 +++++++++++++++++-- ui/src/domain/workflow.ts | 2 +- .../i18n/locales/en/nls.workflow.nodes.json | 18 ++-- .../i18n/locales/zh/nls.workflow.nodes.json | 16 ++-- 7 files changed, 123 insertions(+), 36 deletions(-) diff --git a/internal/certapply/client_certifier.go b/internal/certapply/client_certifier.go index 72a60104..30c1574e 100644 --- a/internal/certapply/client_certifier.go +++ b/internal/certapply/client_certifier.go @@ -16,16 +16,15 @@ import ( "github.com/go-acme/lego/v4/challenge/http01" "github.com/go-acme/lego/v4/log" "github.com/samber/lo" - "github.com/xhit/go-str2duration/v2" "github.com/certimate-go/certimate/internal/certapply/applicators" "github.com/certimate-go/certimate/internal/domain" ) type ObtainCertificateRequest struct { - Domains []string - KeyType certcrypto.KeyType - LifeTime string + Domains []string + KeyType certcrypto.KeyType + ValidityTo time.Time // 提供商相关 ChallengeType string @@ -155,15 +154,9 @@ func (c *ACMEClient) ObtainCertificate(request *ObtainCertificateRequest) (*Obta Domains: request.Domains, Bundle: true, Profile: request.ACMEProfile, + NotAfter: request.ValidityTo, ReplacesCertID: lo.If(request.ARIReplacesAcctUrl == c.account.ACMEAcctUrl, request.ARIReplacesCertId).Else(""), } - if request.LifeTime != "" { - lifeTime, err := str2duration.ParseDuration(request.LifeTime) - if err != nil { - return nil, fmt.Errorf("invalid lifetime: %w", err) - } - req.NotAfter = time.Now().Add(lifeTime) - } resp, err := c.client.Certificate.Obtain(req) if err != nil { ariErr := &acme.AlreadyReplacedError{} diff --git a/internal/domain/workflow.go b/internal/domain/workflow.go index 1d540079..f5b1f13b 100644 --- a/internal/domain/workflow.go +++ b/internal/domain/workflow.go @@ -150,7 +150,7 @@ func (c WorkflowNodeConfig) AsBizApply() WorkflowNodeConfigForBizApply { CAProvider: xmaps.GetString(c, "caProvider"), CAProviderAccessId: xmaps.GetString(c, "caProviderAccessId"), CAProviderConfig: xmaps.GetKVMapAny(c, "caProviderConfig"), - LifeTime: xmaps.GetString(c, "lifeTime"), + ValidityLifetime: xmaps.GetString(c, "validityLifetime"), ACMEProfile: xmaps.GetString(c, "acmeProfile"), Nameservers: nameservers, DnsPropagationWait: xmaps.GetInt32(c, "dnsPropagationWait"), @@ -221,7 +221,7 @@ type WorkflowNodeConfigForBizApply struct { CAProviderAccessId string `json:"caProviderAccessId,omitempty"` // CA 提供商授权记录 ID CAProviderConfig map[string]any `json:"caProviderConfig,omitempty"` // CA 提供商额外配置 KeyAlgorithm string `json:"keyAlgorithm,omitempty"` // 证书算法 - LifeTime string `json:"lifeTime,omitempty"` // 证书有效期 + ValidityLifetime string `json:"validityLifetime,omitempty"` // 证书有效期,形如 "30d"、"6h" ACMEProfile string `json:"acmeProfile,omitempty"` // ACME Profiles Extension Nameservers []string `json:"nameservers,omitempty"` // DNS 服务器列表,以半角分号分隔 DnsPropagationWait int32 `json:"dnsPropagationWait,omitempty"` // DNS 传播等待时间,等同于 lego 的 `--dns-propagation-wait` 参数 diff --git a/internal/workflow/engine/executor_bizapply.go b/internal/workflow/engine/executor_bizapply.go index 547e8e4b..a69d09ed 100644 --- a/internal/workflow/engine/executor_bizapply.go +++ b/internal/workflow/engine/executor_bizapply.go @@ -15,6 +15,7 @@ import ( "github.com/go-acme/lego/v4/lego" legolog "github.com/go-acme/lego/v4/log" "github.com/samber/lo" + "github.com/xhit/go-str2duration/v2" "github.com/certimate-go/certimate/internal/app" "github.com/certimate-go/certimate/internal/certapply" @@ -261,8 +262,15 @@ func (ne *bizApplyNodeExecutor) executeObtain(execCtx *NodeExecutionContext, nod DnsPropagationTimeout: nodeCfg.DnsPropagationTimeout, DnsTTL: nodeCfg.DnsTTL, HttpDelayWait: nodeCfg.HttpDelayWait, - LifeTime: nodeCfg.LifeTime, - ACMEProfile: nodeCfg.ACMEProfile, + ValidityTo: lo.If(nodeCfg.ValidityLifetime == "", time.Time{}). + ElseF(func() time.Time { + duration, err := str2duration.ParseDuration(nodeCfg.ValidityLifetime) + if err != nil { + return time.Time{} + } + return time.Now().Add(duration) + }), + ACMEProfile: nodeCfg.ACMEProfile, ARIReplacesAcctUrl: lo.If(lastCertificate == nil, ""). ElseF(func() string { if lastCertificate.ACMERenewed { diff --git a/ui/src/components/workflow/designer/forms/BizApplyNodeConfigForm.tsx b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigForm.tsx index 29772843..423d96f6 100644 --- a/ui/src/components/workflow/designer/forms/BizApplyNodeConfigForm.tsx +++ b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigForm.tsx @@ -4,7 +4,7 @@ import { Link } from "react-router"; import { type FlowNodeEntity, getNodeForm } from "@flowgram.ai/fixed-layout-editor"; import { IconChevronRight, IconCircleMinus, IconPlus } from "@tabler/icons-react"; import { useControllableValue, useMount } from "ahooks"; -import { type AnchorProps, AutoComplete, Button, Divider, Flex, Form, type FormInstance, Input, InputNumber, Select, Switch, Typography } from "antd"; +import { type AnchorProps, AutoComplete, Button, Divider, Flex, Form, type FormInstance, Input, InputNumber, Select, Space, Switch, Typography } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -318,17 +318,19 @@ const BizApplyNodeConfigForm = ({ node, ...props }: BizApplyNodeConfigFormProps)
} + tooltip={} > - + } > @@ -535,6 +537,82 @@ const InternalEmailInput = memo( } ); +const InternalValidityLifetimeInput = memo( + ({ disabled, placeholder, ...props }: { disabled?: boolean; placeholder?: string; value?: string; onChange?: (value: string) => void }) => { + const { t } = useTranslation(); + + const [value, setValue] = useControllableValue(props, { + valuePropName: "value", + defaultValuePropName: "defaultValue", + trigger: "onChange", + }); + + const parseCombinedValue = (val: string): [string | undefined, string | undefined] => { + const match = String(val).match(/^(\d+)([a-zA-Z]+)$/); + if (match) { + return [match[1], match[2]]; + } + + return [undefined, undefined]; + }; + + const [inputValue, setInputValue] = useState(parseCombinedValue(value)[0]); + const [selectValue, setSelectValue] = useState(parseCombinedValue(value)[1] || "d"); + useEffect(() => { + const [v, u] = parseCombinedValue(value); + setInputValue(v); + setSelectValue(u || "d"); + }, [value]); + + const handleInputClear = () => { + setValue(""); + }; + + const handleInputChange = (e: React.ChangeEvent) => { + setInputValue(e.currentTarget.value); + + if (e.currentTarget.value) { + setValue(`${e.currentTarget.value}${selectValue}`); + } else { + setValue(""); + } + }; + + const handleSelectChange = (value: string) => { + setSelectValue(value); + + if (inputValue) { + setValue(`${inputValue}${value}`); + } + }; + + return ( + + +
+ + } + tooltip={} > - - - - } - > - + ); @@ -52,9 +43,8 @@ const AccessConfigFieldsProviderACMEDNS = () => { const getInitialValues = (): Nullish>> => { return { - apiBase: "https://auth.acme-dns.io/", - storageBaseUrl: "", - storagePath: "", + serverUrl: "https://auth.acme-dns.io/", + credentials: "", }; }; @@ -62,15 +52,20 @@ const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) = const { t } = i18n; return z.object({ - apiBase: z.url(t("common.errmsg.url_invalid")), - storageBaseUrl: z + serverUrl: z.url(t("common.errmsg.url_invalid")), + credentials: z .string() - .max(256, t("common.errmsg.string_max", { max: 256 })) - .nullish(), - storagePath: z - .string() - .max(256, t("common.errmsg.string_max", { max: 256 })) - .nullish(), + .max(20480, t("common.errmsg.string_max", { max: 20480 })) + .refine((v) => { + if (!v) return false; + + try { + const obj = JSON.parse(v); + return typeof obj === "object" && !Array.isArray(obj); + } catch { + return false; + } + }, t("access.form.acmedns_credentials.errmsg.json_invalid")), }); }; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 4e2102e3..13bd180d 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -174,7 +174,7 @@ export const accessProvidersMap: Maphttps://go-acme.github.io/lego/dns/acme-dns/", - "access.form.acmedns_storage_base_url.label": "ACME-DNS Credentials URL PATH", - "access.form.acmedns_storage_base_url.placeholder": "Enter the URL path to the ACME-DNS JSON credentials JSON file. Each credentials are stored in a separate JSON file. This file will be used for TXT record updates.", - "access.form.acmedns_storage_base_url.tooltip": "For more information, see https://go-acme.github.io/lego/dns/acme-dns/", - "access.form.acmedns_storage_path.label": "ACME-DNS Credentials Local Path", - "access.form.acmedns_storage_path.placeholder": "Please enter the ACME-DNS JSON Credentials JSON File Path. Each credentials are in a separate JSON file. It will be used for TXT record updates.", - "access.form.acmedns_storage_path.tooltip": "For more information, see https://go-acme.github.io/lego/dns/acme-dns/", + "access.form.acmedns_server_url.label": "ACME-DNS server URL", + "access.form.acmedns_server_url.placeholder": "Please enter ACME-DNS server URL", + "access.form.acmedns_credentials.label": "ACME-DNS credentials", + "access.form.acmedns_credentials.placeholder": "Please enter ACME-DNS credentials", + "access.form.acmedns_credentials.tooltip": "For more information, see https://github.com/joohoi/acme-dns", + "access.form.acmedns_credentials.errmsg.json_invalid": "Please enter a valiod JSON string", "access.form.acmehttpreq_endpoint.label": "Endpoint", "access.form.acmehttpreq_endpoint.placeholder": "Please enter endpoint", "access.form.acmehttpreq_endpoint.tooltip": "For more information, see https://go-acme.github.io/lego/dns/httpreq/", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index eb0b16b6..0607dbbf 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -3,7 +3,7 @@ "provider.1panel.console": "1Panel - Console itself", "provider.1panel.site": "1Panel - Website", "provider.acmeca": "ACME Custom CA Endpoint", - "provider.acmedns": "ACME DNS", + "provider.acmedns": "ACME-DNS", "provider.acmehttpreq": "ACME Custom HTTP Endpoint", "provider.aliyun": "Alibaba Cloud", "provider.aliyun.alb": "Alibaba Cloud - ALB (Application Load Balancer)", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 4d232911..d97447a7 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -58,15 +58,12 @@ "access.form.acmeca_eab_kid.placeholder": "请输入 ACME EAB KID", "access.form.acmeca_eab_hmac_key.label": "ACME EAB HMAC Key(可选)", "access.form.acmeca_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key", - "access.form.acmedns_api_base.label": "ACME-DNS API 地址", - "access.form.acmedns_api_base.placeholder": "请输入 ACME-DNS API 地址", - "access.form.acmedns_api_base.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/acme-dns/", - "access.form.acmedns_storage_base_url.label": "ACME-DNS JSON 帐户数据服务器", - "access.form.acmedns_storage_base_url.placeholder": "请输入 ACME-DNS JSON 帐户数据服务器地址", - "access.form.acmedns_storage_base_url.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/acme-dns/", - "access.form.acmedns_storage_path.label": "ACME-DNS JSON 帐户数据文件", - "access.form.acmedns_storage_path.placeholder": "ACME-DNS JSON 帐户数据文件。每个域的帐户都将注册/保存到此文件,并用于 TXT 更新。", - "access.form.acmedns_storage_path.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/acme-dns/", + "access.form.acmedns_server_url.label": "ACME-DNS 服务地址", + "access.form.acmedns_server_url.placeholder": "请输入 ACME-DNS 服务地址", + "access.form.acmedns_credentials.label": "ACME-DNS 凭证文件", + "access.form.acmedns_credentials.placeholder": "请输入 ACME-DNS 凭证文件", + "access.form.acmedns_credentials.tooltip": "这是什么?请参阅 https://github.com/joohoi/acme-dns", + "access.form.acmedns_credentials.errmsg.json_invalid": "请输入有效的 JSON 格式字符串", "access.form.acmehttpreq_endpoint.label": "服务端点", "access.form.acmehttpreq_endpoint.placeholder": "请输入服务端点", "access.form.acmehttpreq_endpoint.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/httpreq/", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index c4592212..2f1b6429 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -3,7 +3,7 @@ "provider.1panel.console": "1Panel - 面板自身", "provider.1panel.site": "1Panel - 网站", "provider.acmeca": "ACME 自定义 CA 端点", - "provider.acmedns": "ACME DNS", + "provider.acmedns": "ACME-DNS", "provider.acmehttpreq": "ACME 自定义 HTTP 端点", "provider.aliyun": "阿里云", "provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB", From ac7f50901d4aa538e713b9c1cbd3393f157b64f0 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 3 Sep 2025 21:45:40 +0800 Subject: [PATCH 6/6] chore(ui): improve i18n --- .../access/forms/AccessConfigFieldsProvider1Panel.tsx | 1 + .../access/forms/AccessConfigFieldsProviderBaotaPanel.tsx | 1 + .../access/forms/AccessConfigFieldsProviderBaotaWAF.tsx | 1 + .../access/forms/AccessConfigFieldsProviderRatPanel.tsx | 1 + ui/src/i18n/locales/en/nls.access.json | 4 ++++ ui/src/i18n/locales/zh/nls.access.json | 4 ++++ 6 files changed, 12 insertions(+) diff --git a/ui/src/components/access/forms/AccessConfigFieldsProvider1Panel.tsx b/ui/src/components/access/forms/AccessConfigFieldsProvider1Panel.tsx index c2e79e45..6441e14e 100644 --- a/ui/src/components/access/forms/AccessConfigFieldsProvider1Panel.tsx +++ b/ui/src/components/access/forms/AccessConfigFieldsProvider1Panel.tsx @@ -21,6 +21,7 @@ const AccessConfigFormFieldsProvider1Panel = () => { name={[parentNamePath, "serverUrl"]} initialValue={initialValues.serverUrl} label={t("access.form.1panel_server_url.label")} + extra={t("access.form.1panel_server_url.help")} rules={[formRule]} > diff --git a/ui/src/components/access/forms/AccessConfigFieldsProviderBaotaPanel.tsx b/ui/src/components/access/forms/AccessConfigFieldsProviderBaotaPanel.tsx index c5eca4b5..67802dd6 100644 --- a/ui/src/components/access/forms/AccessConfigFieldsProviderBaotaPanel.tsx +++ b/ui/src/components/access/forms/AccessConfigFieldsProviderBaotaPanel.tsx @@ -21,6 +21,7 @@ const AccessConfigFormFieldsProviderBaotaPanel = () => { name={[parentNamePath, "serverUrl"]} initialValue={initialValues.serverUrl} label={t("access.form.baotapanel_server_url.label")} + extra={t("access.form.baotapanel_server_url.help")} rules={[formRule]} > diff --git a/ui/src/components/access/forms/AccessConfigFieldsProviderBaotaWAF.tsx b/ui/src/components/access/forms/AccessConfigFieldsProviderBaotaWAF.tsx index dc4cd422..980534fd 100644 --- a/ui/src/components/access/forms/AccessConfigFieldsProviderBaotaWAF.tsx +++ b/ui/src/components/access/forms/AccessConfigFieldsProviderBaotaWAF.tsx @@ -21,6 +21,7 @@ const AccessConfigFormFieldsProviderBaotaWAF = () => { name={[parentNamePath, "serverUrl"]} initialValue={initialValues.serverUrl} label={t("access.form.baotawaf_server_url.label")} + extra={t("access.form.baotawaf_server_url.help")} rules={[formRule]} > diff --git a/ui/src/components/access/forms/AccessConfigFieldsProviderRatPanel.tsx b/ui/src/components/access/forms/AccessConfigFieldsProviderRatPanel.tsx index 09c4d156..675617b4 100644 --- a/ui/src/components/access/forms/AccessConfigFieldsProviderRatPanel.tsx +++ b/ui/src/components/access/forms/AccessConfigFieldsProviderRatPanel.tsx @@ -21,6 +21,7 @@ const AccessConfigFormFieldsProviderRatPanel = () => { name={[parentNamePath, "serverUrl"]} initialValue={initialValues.serverUrl} label={t("access.form.ratpanel_server_url.label")} + extra={t("access.form.ratpanel_server_url.help")} rules={[formRule]} > diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index c3568bb6..f2ae220d 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -44,6 +44,7 @@ "access.form.provider.search.placeholder": "Search provider ...", "access.form.1panel_server_url.label": "1Panel server URL", "access.form.1panel_server_url.placeholder": "Please enter 1Panel server URL", + "access.form.1panel_server_url.help": "Notes: DO NOT include the security entrance suffix.", "access.form.1panel_api_version.label": "1Panel version", "access.form.1panel_api_version.placeholder": "Please select 1Panel version", "access.form.1panel_api_key.label": "1Panel API key", @@ -122,11 +123,13 @@ "access.form.baishan_api_token.placeholder": "Please enter Baishan Cloud API token", "access.form.baotapanel_server_url.label": "aaPanel server URL", "access.form.baotapanel_server_url.placeholder": "Please enter aaPanel server URL", + "access.form.baotapanel_server_url.help": "Notes: DO NOT include the security entrance suffix.", "access.form.baotapanel_api_key.label": "aaPanel API key", "access.form.baotapanel_api_key.placeholder": "Please enter aaPanel API key", "access.form.baotapanel_api_key.tooltip": "For more information, see https://www.bt.cn/bbs/thread-20376-1-1.html", "access.form.baotawaf_server_url.label": "aaWAF server URL", "access.form.baotawaf_server_url.placeholder": "Please enter aaWAF server URL", + "access.form.baotawaf_server_url.help": "Notes: DO NOT include the security entrance suffix.", "access.form.baotawaf_api_key.label": "aaWAF API key", "access.form.baotawaf_api_key.placeholder": "Please enter aaWAF API key", "access.form.baotawaf_api_key.tooltip": "For more information, see https://github.com/aaPanel/aaWAF/blob/main/API.md", @@ -393,6 +396,7 @@ "access.form.rainyun_api_key.tooltip": "For more information, see https://app.rainyun.com/account/settings/api-key", "access.form.ratpanel_server_url.label": "RatPanel server URL", "access.form.ratpanel_server_url.placeholder": "Please enter RatPanel server URL", + "access.form.ratpanel_server_url.help": "Notes: DO NOT include the security entrance suffix.", "access.form.ratpanel_access_token_id.label": "RatPanel access token ID", "access.form.ratpanel_access_token_id.placeholder": "Please enter RatPanel access token ID", "access.form.ratpanel_access_token_id.tooltip": "For more information, see https://ratpanel.github.io/advanced/api.html", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index d97447a7..9aef0305 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -46,6 +46,7 @@ "access.form.common_allow_insecure_conns.switch.off": "不允许", "access.form.1panel_server_url.label": "1Panel 服务地址", "access.form.1panel_server_url.placeholder": "请输入 1Panel 服务地址", + "access.form.1panel_server_url.help": "提示:请勿包含安全入口后缀。", "access.form.1panel_api_version.label": "1Panel 版本", "access.form.1panel_api_version.placeholder": "请选择 1Panel 版本", "access.form.1panel_api_key.label": "1Panel 接口密钥", @@ -118,11 +119,13 @@ "access.form.baishan_api_token.placeholder": "请输入白山云 API Token", "access.form.baotapanel_server_url.label": "宝塔面板服务地址", "access.form.baotapanel_server_url.placeholder": "请输入宝塔面板服务地址", + "access.form.baotapanel_server_url.help": "提示:请勿包含安全入口后缀。", "access.form.baotapanel_api_key.label": "宝塔面板接口密钥", "access.form.baotapanel_api_key.placeholder": "请输入宝塔面板接口密钥", "access.form.baotapanel_api_key.tooltip": "这是什么?请参阅 https://www.bt.cn/bbs/thread-113890-1-1.html", "access.form.baotawaf_server_url.label": "堡塔云 WAF 服务地址", "access.form.baotawaf_server_url.placeholder": "请输入堡塔云 WAF 服务地址", + "access.form.baotawaf_server_url.help": "提示:请勿包含安全入口后缀。", "access.form.baotawaf_api_key.label": "堡塔云 WAF 接口密钥", "access.form.baotawaf_api_key.placeholder": "请输入 堡塔云 WAF 接口密钥", "access.form.baotawaf_api_key.tooltip": "这是什么?请参阅 https://github.com/aaPanel/aaWAF/blob/main/API.md", @@ -392,6 +395,7 @@ "access.form.rainyun_api_key.tooltip": "这是什么?请参阅 https://app.rainyun.com/account/settings/api-key", "access.form.ratpanel_server_url.label": "耗子面板服务地址", "access.form.ratpanel_server_url.placeholder": "请输入耗子面板服务地址", + "access.form.ratpanel_server_url.help": "提示:请勿包含安全入口后缀。", "access.form.ratpanel_access_token_id.label": "耗子面板 AccessToken ID", "access.form.ratpanel_access_token_id.placeholder": "请输入耗子面板 AccessToken ID", "access.form.ratpanel_access_token_id.tooltip": "这是什么?请参阅 https://ratpanel.github.io/advanced/api.html",