Merge pull request #948 from fudiwei/dev

This commit is contained in:
RHQYZ 2025-09-03 21:48:32 +08:00 committed by GitHub
commit 6e5ee19829
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 337 additions and 110 deletions

8
go.mod
View File

@ -35,6 +35,7 @@ require (
github.com/blinkbean/dingtalk v1.1.3
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.56
github.com/go-acme/lego/v4 v4.25.2
github.com/go-cmd/cmd v1.4.3
github.com/go-lark/lark v1.16.0
github.com/go-resty/resty/v2 v2.16.5
github.com/go-viper/mapstructure/v2 v2.4.0
@ -65,6 +66,7 @@ require (
github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.21
github.com/volcengine/volc-sdk-golang v1.0.219
github.com/volcengine/volcengine-go-sdk v1.1.30
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.41.0
@ -97,7 +99,6 @@ require (
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-acme/alidns-20150109/v4 v4.5.10 // indirect
github.com/go-acme/tencentclouddnspod v1.0.1208 // indirect
github.com/go-cmd/cmd v1.4.3 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
@ -127,6 +128,7 @@ require (
github.com/namedotcom/go/v4 v4.0.2 // indirect
github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea // indirect
github.com/nrdcg/desec v0.11.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
@ -206,7 +208,7 @@ require (
github.com/nrdcg/namesilo v0.2.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/cast v1.9.2 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.9
github.com/tjfoc/gmsm v1.4.1 // indirect
golang.org/x/image v0.29.0 // indirect
@ -217,7 +219,7 @@ require (
golang.org/x/sys v0.35.0 // indirect
golang.org/x/term v0.34.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/time v0.12.0
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.36.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect

7
go.sum
View File

@ -404,9 +404,8 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8Wd
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
@ -691,6 +690,8 @@ github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea h1:OSgRS4kqOs/WuxuF
github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea/go.mod h1:IDRRngAngb2eTEaWgpO0hukQFI/vJId46fT1KErMytA=
github.com/nrdcg/desec v0.11.0 h1:XZVHy07sg12y8FozMp+l7XvzPsdzog0AYXuQMaHBsfs=
github.com/nrdcg/desec v0.11.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=
@ -893,6 +894,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=

View File

@ -0,0 +1,28 @@
package applicators
import (
"fmt"
"github.com/go-acme/lego/v4/challenge"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/acmedns"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
func init() {
if err := ACMEDns01Registries.Register(domain.ACMEDns01ProviderTypeACMEDNS, func(options *ProviderFactoryOptions) (challenge.Provider, error) {
credentials := domain.AccessConfigForACMEDNS{}
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
provider, err := acmedns.NewChallengeProvider(&acmedns.ChallengeProviderConfig{
ServerUrl: credentials.ServerUrl,
Credentials: credentials.Credentials,
})
return provider, err
}); err != nil {
panic(err)
}
}

View File

@ -22,8 +22,9 @@ import (
)
type ObtainCertificateRequest struct {
Domains []string
KeyType certcrypto.KeyType
Domains []string
KeyType certcrypto.KeyType
ValidityTo time.Time
// 提供商相关
ChallengeType string
@ -153,6 +154,7 @@ 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(""),
}
resp, err := c.client.Certificate.Obtain(req)

View File

@ -32,6 +32,11 @@ type AccessConfigForACMECA struct {
Endpoint string `json:"endpoint"`
}
type AccessConfigForACMEDNS struct {
ServerUrl string `json:"serverUrl"`
Credentials string `json:"credentials"`
}
type AccessConfigForACMEHttpReq struct {
Endpoint string `json:"endpoint"`
Mode string `json:"mode,omitempty"`

View File

@ -11,6 +11,7 @@ NOTICE: If you add new constant, please keep ASCII order.
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")

View File

@ -150,6 +150,7 @@ func (c WorkflowNodeConfig) AsBizApply() WorkflowNodeConfigForBizApply {
CAProvider: xmaps.GetString(c, "caProvider"),
CAProviderAccessId: xmaps.GetString(c, "caProviderAccessId"),
CAProviderConfig: xmaps.GetKVMapAny(c, "caProviderConfig"),
ValidityLifetime: xmaps.GetString(c, "validityLifetime"),
ACMEProfile: xmaps.GetString(c, "acmeProfile"),
Nameservers: nameservers,
DnsPropagationWait: xmaps.GetInt32(c, "dnsPropagationWait"),
@ -216,10 +217,11 @@ type WorkflowNodeConfigForBizApply struct {
Provider string `json:"provider"` // 质询提供商
ProviderAccessId string `json:"providerAccessId"` // 质询提供商授权记录 ID
ProviderConfig map[string]any `json:"providerConfig,omitempty"` // 质询提供商额外配置
KeyAlgorithm string `json:"keyAlgorithm,omitempty"` // 证书算法
CAProvider string `json:"caProvider,omitempty"` // CA 提供商(零值时使用全局配置)
CAProviderAccessId string `json:"caProviderAccessId,omitempty"` // CA 提供商授权记录 ID
CAProviderConfig map[string]any `json:"caProviderConfig,omitempty"` // CA 提供商额外配置
KeyAlgorithm string `json:"keyAlgorithm,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` 参数

View File

@ -41,7 +41,7 @@ func (s *sender[TIn, TOut]) SendWithContext(ctx context.Context, params *TIn) (*
aesCryptor := xcrypto.NewAESCryptor(aesKey)
// 准备临时输入文件
tempIn, err := os.CreateTemp("", "certimate_mprocin_*.tmp")
tempIn, err := os.CreateTemp("", "certimate.mprocin_*.tmp")
if err != nil {
return nil, fmt.Errorf("failed to create temp input file: %w", err)
} else {
@ -64,7 +64,7 @@ func (s *sender[TIn, TOut]) SendWithContext(ctx context.Context, params *TIn) (*
defer os.Remove(tempIn.Name())
// 准备临时输出文件
tempOut, err := os.CreateTemp("", "certimate_mprocout_*.tmp")
tempOut, err := os.CreateTemp("", "certimate.mprocout_*.tmp")
if err != nil {
return nil, fmt.Errorf("failed to create temp output file: %w", err)
} else {
@ -73,7 +73,7 @@ func (s *sender[TIn, TOut]) SendWithContext(ctx context.Context, params *TIn) (*
defer os.Remove(tempOut.Name())
// 准备临时错误文件
tempErr, err := os.CreateTemp("", "certimate_mprocerr_*.tmp")
tempErr, err := os.CreateTemp("", "certimate.mprocerr_*.tmp")
if err != nil {
return nil, fmt.Errorf("failed to create temp error file: %w", err)
} else {

View File

@ -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,7 +262,15 @@ func (ne *bizApplyNodeExecutor) executeObtain(execCtx *NodeExecutionContext, nod
DnsPropagationTimeout: nodeCfg.DnsPropagationTimeout,
DnsTTL: nodeCfg.DnsTTL,
HttpDelayWait: nodeCfg.HttpDelayWait,
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 {

View File

@ -0,0 +1,44 @@
package acmedns
import (
"errors"
"fmt"
"os"
"github.com/go-acme/lego/v4/providers/dns/acmedns"
"github.com/certimate-go/certimate/pkg/core"
)
type ChallengeProviderConfig struct {
ServerUrl string `json:"serverUrl"`
Credentials string `json:"credentials"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger, error) {
if config == nil {
return nil, errors.New("the configuration of the acme challenge provider is nil")
}
tempfile, err := os.CreateTemp("", "certimate.acmedns_*.tmp")
if err != nil {
return nil, fmt.Errorf("failed to create temp credentials file: %w", err)
} else {
if _, err := tempfile.Write([]byte(config.Credentials)); err != nil {
return nil, fmt.Errorf("failed to write temp credentials file: %w", err)
}
tempfile.Close()
}
providerConfig := acmedns.NewDefaultConfig()
providerConfig.APIBase = config.ServerUrl
providerConfig.StoragePath = tempfile.Name()
provider, err := acmedns.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -13,6 +13,7 @@ import { FormNestedFieldsContextProvider } from "./forms/_context";
import { useProviderFilterByUsage } from "./forms/_hooks";
import AccessConfigFieldsProvider1Panel from "./forms/AccessConfigFieldsProvider1Panel";
import AccessConfigFieldsProviderACMECA from "./forms/AccessConfigFieldsProviderACMECA";
import AccessConfigFieldsProviderACMEDNS from "./forms/AccessConfigFieldsProviderACMEDNS";
import AccessConfigFieldsProviderACMEHttpReq from "./forms/AccessConfigFieldsProviderACMEHttpReq";
import AccessConfigFieldsProviderAliyun from "./forms/AccessConfigFieldsProviderAliyun";
import AccessConfigFieldsProviderAPISIX from "./forms/AccessConfigFieldsProviderAPISIX";
@ -133,6 +134,9 @@ const AccessForm = ({ className, style, disabled, initialValues, mode, usage, ..
case ACCESS_PROVIDERS.ACMECA: {
return <AccessConfigFieldsProviderACMECA />;
}
case ACCESS_PROVIDERS.ACMEDNS: {
return <AccessConfigFieldsProviderACMEDNS />;
}
case ACCESS_PROVIDERS.ACMEHTTPREQ: {
return <AccessConfigFieldsProviderACMEHttpReq />;
}

View File

@ -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]}
>
<Input placeholder={t("access.form.1panel_server_url.placeholder")} />

View File

@ -0,0 +1,77 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod/v4";
import TextFileInput from "@/components/TextFileInput";
import { useFormNestedFieldsContext } from "./_context";
const AccessConfigFieldsProviderACMEDNS = () => {
const { i18n, t } = useTranslation();
const { parentNamePath } = useFormNestedFieldsContext();
const formSchema = z.object({
[parentNamePath]: getSchema({ i18n }),
});
const formRule = createSchemaFieldRule(formSchema);
const initialValues = getInitialValues();
return (
<>
<Form.Item
name={[parentNamePath, "serverUrl"]}
initialValue={initialValues.serverUrl}
label={t("access.form.acmedns_server_url.label")}
rules={[formRule]}
>
<Input placeholder={t("access.form.acmedns_server_url.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "credentials"]}
initialValue={initialValues.credentials}
label={t("access.form.acmedns_credentials.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.acmedns_credentials.tooltip") }}></span>}
>
<TextFileInput autoSize={{ minRows: 3, maxRows: 10 }} placeholder={t("access.form.acmedns_credentials.placeholder")} />
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
serverUrl: "https://auth.acme-dns.io/",
credentials: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
const { t } = i18n;
return z.object({
serverUrl: z.url(t("common.errmsg.url_invalid")),
credentials: z
.string()
.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")),
});
};
const _default = Object.assign(AccessConfigFieldsProviderACMEDNS, {
getInitialValues,
getSchema,
});
export default _default;

View File

@ -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]}
>
<Input placeholder={t("access.form.baotapanel_server_url.placeholder")} />

View File

@ -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]}
>
<Input placeholder={t("access.form.baotawaf_server_url.placeholder")} />

View File

@ -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]}
>
<Input placeholder={t("access.form.ratpanel_server_url.placeholder")} />

View File

@ -1,90 +0,0 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input, Select, Switch } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { useFormNestedFieldsContext } from "./_context";
const AccessConfigFormFieldsProvider1Panel = () => {
const { i18n, t } = useTranslation();
const { parentNamePath } = useFormNestedFieldsContext();
const formSchema = z.object({
[parentNamePath]: getSchema({ i18n }),
});
const formRule = createSchemaFieldRule(formSchema);
const initialValues = getInitialValues();
return (
<>
<Form.Item
name={[parentNamePath, "serverUrl"]}
initialValue={initialValues.serverUrl}
label={t("access.form.1panel_server_url.label")}
rules={[formRule]}
>
<Input placeholder={t("access.form.1panel_server_url.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "apiVersion"]}
initialValue={initialValues.apiVersion}
label={t("access.form.1panel_api_version.label")}
rules={[formRule]}
>
<Select options={["v1", "v2"].map((s) => ({ label: s, value: s }))} placeholder={t("access.form.1panel_api_version.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "apiKey"]}
initialValue={initialValues.apiKey}
label={t("access.form.1panel_api_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.1panel_api_key.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.1panel_api_key.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "allowInsecureConnections"]}
initialValue={initialValues.allowInsecureConnections}
label={t("access.form.common_allow_insecure_conns.label")}
rules={[formRule]}
>
<Switch
checkedChildren={t("access.form.common_allow_insecure_conns.switch.on")}
unCheckedChildren={t("access.form.common_allow_insecure_conns.switch.off")}
/>
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
serverUrl: "http://<your-host-addr>:20410/",
apiVersion: "v1",
apiKey: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
const { t } = i18n;
return z.object({
serverUrl: z.url(t("common.errmsg.url_invalid")),
apiVersion: z.string().nonempty(t("access.form.1panel_api_version.placeholder")),
apiKey: z
.string()
.min(1, t("access.form.1panel_api_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
allowInsecureConnections: z.boolean().nullish(),
});
};
const _default = Object.assign(AccessConfigFormFieldsProvider1Panel, {
getInitialValues,
getSchema,
});
export default _default;

View File

@ -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";
@ -317,9 +317,20 @@ const BizApplyNodeConfigForm = ({ node, ...props }: BizApplyNodeConfigFormProps)
</Form.Item>
</Form.Item>
<Form.Item
name="validityLifetime"
label={t("workflow_node.apply.form.validity_lifetime.label")}
extra={t("workflow_node.apply.form.validity_lifetime.help")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.apply.form.validity_lifetime.tooltip") }}></span>}
>
<InternalValidityLifetimeInput placeholder={t("workflow_node.apply.form.validity_lifetime.placeholder")} />
</Form.Item>
<Form.Item
name="acmeProfile"
label={t("workflow_node.apply.form.acme_profile.label")}
extra={t("workflow_node.apply.form.acme_profile.help")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.apply.form.acme_profile.tooltip") }}></span>}
>
@ -526,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<string>(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<HTMLInputElement>) => {
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 (
<Space.Compact className="w-full">
<Input
allowClear
disabled={disabled}
placeholder={placeholder}
type="number"
value={inputValue}
onChange={handleInputChange}
onClear={handleInputClear}
/>
<div className="w-24">
<Select
options={["h", "d"].map((s) => ({
key: s,
label: t(`workflow_node.apply.form.validity_lifetime.units.${s}`),
value: s,
}))}
value={selectValue}
onChange={handleSelectChange}
/>
</div>
</Space.Compact>
);
}
);
const getAnchorItems = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }): Required<AnchorProps>["items"] => {
const { t } = i18n;
@ -595,6 +682,13 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
(v) => (v == null || v === "" ? void 0 : Number(v)),
z.number().int(t("workflow_node.apply.form.dns_ttl.placeholder")).gte(1, t("workflow_node.apply.form.dns_ttl.placeholder")).nullish()
),
validityLifetime: z
.string()
.nullish()
.refine((v) => {
if (!v) return true;
return /^\d+[d|h]$/.test(v) && parseInt(v) > 0;
}, t("workflow_node.apply.form.validity_lifetime.placeholder")),
acmeProfile: z.string().nullish(),
disableFollowCNAME: z.boolean().nullish(),
disableARI: z.boolean().nullish(),

View File

@ -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<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.CMCCCLOUD, "provider.cmcccloud", "/imgs/providers/cmcccloud.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.WESTCN, "provider.westcn", "/imgs/providers/westcn.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.POWERDNS, "provider.powerdns", "/imgs/providers/powerdns.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.ACMEDNS, "provider.acmedns", "/imgs/providers/acmedns.png", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.ACMEHTTPREQ, "provider.acmehttpreq", "/imgs/providers/acmehttpreq.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.LETSENCRYPT, "provider.letsencrypt", "/imgs/providers/letsencrypt.svg", [ACCESS_USAGES.CA], "builtin"],
@ -264,6 +266,7 @@ export const caProvidersMap: Map<CAProvider["type"] | string, CAProvider> = 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`,
@ -369,6 +372,7 @@ export const acmeDns01ProvidersMap: Map<ACMEDns01Provider["type"] | string, ACME
[ACME_DNS01_PROVIDERS.UCLOUD_UDNR, "provider.ucloud.udnr"],
[ACME_DNS01_PROVIDERS.WESTCN, "provider.westcn"],
[ACME_DNS01_PROVIDERS.POWERDNS, "provider.powerdns"],
[ACME_DNS01_PROVIDERS.ACMEDNS, "provider.acmedns"],
[ACME_DNS01_PROVIDERS.ACMEHTTPREQ, "provider.acmehttpreq"],
] satisfies Array<[ACMEDns01ProviderType, string]>
).map(([type, name]) => [

View File

@ -102,6 +102,7 @@ export type WorkflowNodeConfigForBizApply = {
caProviderAccessId?: string;
caProviderConfig?: Record<string, unknown>;
keyAlgorithm: string;
validityLifetime?: string;
acmeProfile?: string;
nameservers?: string;
dnsPropagationTimeout?: number;

View File

@ -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",
@ -59,6 +60,12 @@
"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_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 <a href=\"https://github.com/joohoi/acme-dns\" target=\"_blank\">https://github.com/joohoi/acme-dns</a>",
"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 <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
@ -116,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 <a href=\"https://www.bt.cn/bbs/thread-20376-1-1.html\" target=\"_blank\">https://www.bt.cn/bbs/thread-20376-1-1.html</a>",
"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 <a href=\"https://github.com/aaPanel/aaWAF/blob/main/API.md\" target=\"_blank\">https://github.com/aaPanel/aaWAF/blob/main/API.md</a>",
@ -387,6 +396,7 @@
"access.form.rainyun_api_key.tooltip": "For more information, see <a href=\"https://app.rainyun.com/account/settings/api-key\" target=\"_blank\">https://app.rainyun.com/account/settings/api-key</a>",
"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 <a href=\"https://ratpanel.github.io/advanced/api.html\" target=\"_blank\">https://ratpanel.github.io/advanced/api.html</a>",

View File

@ -3,6 +3,7 @@
"provider.1panel.console": "1Panel - Console itself",
"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)",

View File

@ -22,7 +22,7 @@
"workflow_node.apply.help": "Apply for SSL certificate issuance from the certificate authority.",
"workflow_node.apply.default_name": "Application",
"workflow_node.apply.form_anchor.parameters.tab": "Parameters",
"workflow_node.apply.form_anchor.certificate.tab": "CA Config",
"workflow_node.apply.form_anchor.certificate.tab": "Certificate",
"workflow_node.apply.form_anchor.certificate.title": "Certificate settings",
"workflow_node.apply.form_anchor.advanced.tab": "Advanced",
"workflow_node.apply.form_anchor.advanced.title": "Advanced settings",
@ -71,9 +71,16 @@
"workflow_node.apply.form.ca_provider_access.label": "Certificate authority credential",
"workflow_node.apply.form.ca_provider_access.placeholder": "Please select an credential of the certificate authority",
"workflow_node.apply.form.ca_provider_access.button": "Create",
"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. <br><a href=\"https://letsencrypt.org/docs/profiles/\" target=\"_blank\">Click here to learn more</a>.",
"workflow_node.apply.form.validity_lifetime.label": "Certificate validity lifetime (Optional)",
"workflow_node.apply.form.validity_lifetime.placeholder": "Please enter certificate's validity lifetime",
"workflow_node.apply.form.validity_lifetime.help": "Notes: Not all CAs support this feature.",
"workflow_node.apply.form.validity_lifetime.tooltip": "It determines the <em>NotAfter</em> field of the certificate in the ACME protocol. If you don't understand this option, just keep it by default.",
"workflow_node.apply.form.validity_lifetime.units.h": "Hour(s)",
"workflow_node.apply.form.validity_lifetime.units.d": "Day(s)",
"workflow_node.apply.form.acme_profile.label": "Certificate ACME profile (Optional)",
"workflow_node.apply.form.acme_profile.placeholder": "Please enter certificate's ACME profile",
"workflow_node.apply.form.acme_profile.help": "Notes: Not all CAs support this feature.",
"workflow_node.apply.form.acme_profile.tooltip": "It determines the <em>Profile</em> field of the certificate in the ACME protocol. If you don't understand this option, just keep it by default. <br><a href=\"https://letsencrypt.org/docs/profiles/\" target=\"_blank\">Click here to learn more</a>.",
"workflow_node.apply.form.nameservers.label": "DNS recursive nameservers (Optional)",
"workflow_node.apply.form.nameservers.placeholder": "Please enter DNS recursive nameservers (separated by semicolons)",
"workflow_node.apply.form.nameservers.tooltip": "It determines whether to custom DNS recursive nameservers during ACME DNS-01 challenge. If you don't understand this option, just keep it by default. <br><a href=\"https://go-acme.github.io/lego/usage/cli/options/index.html#dns-resolvers-and-challenge-verification\" target=\"_blank\">Click here to learn more</a>.",

View File

@ -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 接口密钥",
@ -58,6 +59,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_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": "这是什么?请参阅 <a href=\"https://github.com/joohoi/acme-dns\" target=\"_blank\">https://github.com/joohoi/acme-dns</a>",
"access.form.acmedns_credentials.errmsg.json_invalid": "请输入有效的 JSON 格式字符串",
"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>",
@ -112,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": "这是什么?请参阅 <a href=\"https://www.bt.cn/bbs/thread-113890-1-1.html\" target=\"_blank\">https://www.bt.cn/bbs/thread-113890-1-1.html</a>",
"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": "这是什么?请参阅 <a href=\"https://github.com/aaPanel/aaWAF/blob/main/API.md\" target=\"_blank\">https://github.com/aaPanel/aaWAF/blob/main/API.md</a>",
@ -386,6 +395,7 @@
"access.form.rainyun_api_key.tooltip": "这是什么?请参阅 <a href=\"https://app.rainyun.com/account/settings/api-key\" target=\"_blank\">https://app.rainyun.com/account/settings/api-key</a>",
"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": "这是什么?请参阅 <a href=\"https://ratpanel.github.io/advanced/api.html\" target=\"_blank\">https://ratpanel.github.io/advanced/api.html</a>",

View File

@ -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",

View File

@ -63,15 +63,22 @@
"workflow_node.apply.form.tencentcloud_eo_zone_id.placeholder": "请输入腾讯云 EdgeOne 站点 ID",
"workflow_node.apply.form.tencentcloud_eo_zone_id.tooltip": "这是什么?请参阅 <a href=\"https://console.cloud.tencent.com/edgeone\" target=\"_blank\">https://console.cloud.tencent.com/edgeone</a>",
"workflow_node.apply.form.key_algorithm.label": "证书算法",
"workflow_node.apply.form.key_algorithm.placeholder": "请选择证书算法",
"workflow_node.apply.form.key_algorithm.placeholder": "请选择证书算法",
"workflow_node.apply.form.ca_provider.label": "证书颁发机构(可选)",
"workflow_node.apply.form.ca_provider.placeholder": "请选择证书颁发机构",
"workflow_node.apply.form.ca_provider.button": "设置",
"workflow_node.apply.form.ca_provider_access.label": "证书颁发机构授权",
"workflow_node.apply.form.ca_provider_access.placeholder": "请选择证书颁发机构授权",
"workflow_node.apply.form.ca_provider_access.button": "新建",
"workflow_node.apply.form.acme_profile.label": "ACME 证书配置(可选)",
"workflow_node.apply.form.acme_profile.placeholder": "请输入 ACME 证书配置",
"workflow_node.apply.form.validity_lifetime.label": "证书有效期(可选)",
"workflow_node.apply.form.validity_lifetime.placeholder": "请输入证书的有效期",
"workflow_node.apply.form.validity_lifetime.help": "注意:并非所有证书颁发机构都支持此特性。",
"workflow_node.apply.form.validity_lifetime.tooltip": "表示证书的有效期。如果你不了解该选项的用途,保持默认即可。",
"workflow_node.apply.form.validity_lifetime.units.h": "小时",
"workflow_node.apply.form.validity_lifetime.units.d": "天",
"workflow_node.apply.form.acme_profile.label": "证书 ACME 配置(可选)",
"workflow_node.apply.form.acme_profile.placeholder": "请输入证书的 ACME 配置",
"workflow_node.apply.form.acme_profile.help": "注意:并非所有证书颁发机构都支持此特性。",
"workflow_node.apply.form.acme_profile.tooltip": "表示证书颁发时使用的 ACME 证书配置。如果你不了解该选项的用途,保持默认即可。<br><a href=\"https://letsencrypt.org/zh-cn/docs/profiles/\" target=\"_blank\">点此了解更多</a>。",
"workflow_node.apply.form.nameservers.label": "DNS 递归服务器(可选)",
"workflow_node.apply.form.nameservers.placeholder": "请输入 DNS 递归服务器(多个值请用半角分号隔开)",