From 72896e052ccd5f8ccb26d98baa336c950cfd4a27 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 19 Feb 2025 23:57:22 +0800 Subject: [PATCH] feat: add jdcloud dns-01 applicant --- README.md | 1 + README_EN.md | 1 + go.mod | 2 + go.sum | 4 + internal/applicant/providers.go | 20 +- internal/domain/access.go | 7 +- internal/domain/provider.go | 5 +- .../baiducloud/internal/lego.go | 6 +- .../lego-providers/jdcloud/internal/lego.go | 229 ++++++++++++++++++ .../lego-providers/jdcloud/jdcloud.go | 47 ++++ ui/public/imgs/providers/jdcloud.svg | 1 + ui/src/components/Version.tsx | 2 +- ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormJDCloudConfig.tsx | 76 ++++++ .../workflow/node/ApplyNodeConfigForm.tsx | 4 + .../ApplyNodeConfigFormJDCloudDNSConfig.tsx | 61 +++++ ui/src/domain/access.ts | 6 + ui/src/domain/provider.ts | 6 + ui/src/i18n/locales/en/nls.access.json | 10 +- ui/src/i18n/locales/en/nls.common.json | 10 +- .../i18n/locales/en/nls.workflow.nodes.json | 2 + ui/src/i18n/locales/zh/nls.access.json | 10 +- ui/src/i18n/locales/zh/nls.provider.json | 2 + .../i18n/locales/zh/nls.workflow.nodes.json | 2 + 24 files changed, 502 insertions(+), 15 deletions(-) create mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/internal/lego.go create mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/jdcloud.go create mode 100644 ui/public/imgs/providers/jdcloud.svg create mode 100644 ui/src/components/access/AccessFormJDCloudConfig.tsx create mode 100644 ui/src/components/workflow/node/ApplyNodeConfigFormJDCloudDNSConfig.tsx diff --git a/README.md b/README.md index f9d4df00..fcc0ad27 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ make local.run | [百度智能云](https://cloud.baidu.com/) | | | [华为云](https://www.huaweicloud.com/) | | | [火山引擎](https://www.volcengine.com/) | | +| [京东云](https://www.jdcloud.com/) | | | [AWS Route53](https://aws.amazon.com/route53/) | | | [Azure](https://azure.microsoft.com/) | | | [CloudFlare](https://www.cloudflare.com/) | | diff --git a/README_EN.md b/README_EN.md index a6d13c26..cae7e0f7 100644 --- a/README_EN.md +++ b/README_EN.md @@ -92,6 +92,7 @@ The following DNS providers are supported: | [Baidu AI Cloud](https://intl.cloud.baidu.com/) | | | [Huawei Cloud](https://www.huaweicloud.com/) | | | [Volcengine](https://www.volcengine.com/) | | +| [JD Cloud](https://www.jdcloud.com/) | | | [AWS Route53](https://aws.amazon.com/route53/) | | | [Azure DNS](https://azure.microsoft.com/) | | | [CloudFlare](https://www.cloudflare.com/) | | diff --git a/go.mod b/go.mod index c07f2a5b..5a1949f5 100644 --- a/go.mod +++ b/go.mod @@ -77,12 +77,14 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.16.0 // indirect github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect + github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/jdcloud-api/jdcloud-sdk-go v1.62.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/go.sum b/go.sum index f127aeb4..1929c808 100644 --- a/go.sum +++ b/go.sum @@ -427,6 +427,8 @@ github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -576,6 +578,8 @@ github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/U github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jdcloud-api/jdcloud-sdk-go v1.62.0 h1:uPfyOSY16mBrhggriDNeySFB4ZkzMMXpNac2P0fbDRw= +github.com/jdcloud-api/jdcloud-sdk-go v1.62.0/go.mod h1:UrKjuULIWLjHFlG6aSPunArE5QX57LftMmStAZJBEX8= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 10466389..d1e80083 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -17,6 +17,7 @@ import ( pGname "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname" pGoDaddy "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/godaddy" pHuaweiCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/huaweicloud" + pJDCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud" pNameDotCom "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namedotcom" pNameSilo "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namesilo" pNS1 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1" @@ -85,7 +86,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeAzureDNS: + case domain.ApplyDNSProviderTypeAzure, domain.ApplyDNSProviderTypeAzureDNS: { access := domain.AccessConfigForAzure{} if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -214,6 +215,23 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } + case domain.ApplyDNSProviderTypeJDCloud, domain.ApplyDNSProviderTypeJDCloudDNS: + { + access := domain.AccessConfigForJDCloud{} + if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + applicant, err := pJDCloud.NewChallengeProvider(&pJDCloud.ChallengeProviderConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + RegionId: maps.GetValueAsString(options.ProviderApplyConfig, "region_id"), + DnsPropagationTimeout: options.DnsPropagationTimeout, + DnsTTL: options.DnsTTL, + }) + return applicant, err + } + case domain.ApplyDNSProviderTypeNameDotCom: { access := domain.AccessConfigForNameDotCom{} diff --git a/internal/domain/access.go b/internal/domain/access.go index eaa3340e..ddd00b1c 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -115,12 +115,17 @@ type AccessConfigForHuaweiCloud struct { SecretAccessKey string `json:"secretAccessKey"` } -type AccessConfigForLocal struct{} +type AccessConfigForJDCloud struct { + AccessKeyId string `json:"accessKeyId"` + AccessKeySecret string `json:"accessKeySecret"` +} type AccessConfigForKubernetes struct { KubeConfig string `json:"kubeConfig,omitempty"` } +type AccessConfigForLocal struct{} + type AccessConfigForNameDotCom struct { Username string `json:"username"` ApiToken string `json:"apiToken"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 08527fdd..5f36117e 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -34,7 +34,7 @@ const ( AccessProviderTypeGoDaddy = AccessProviderType("godaddy") AccessProviderTypeGoEdge = AccessProviderType("goedge") // GoEdge(预留) AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud") - AccessProviderTypeJDCloud = AccessProviderType("jdcloud") // 京东云(预留) + AccessProviderTypeJDCloud = AccessProviderType("jdcloud") AccessProviderTypeKubernetes = AccessProviderType("k8s") AccessProviderTypeLocal = AccessProviderType("local") AccessProviderTypeNameDotCom = AccessProviderType("namedotcom") @@ -68,6 +68,7 @@ const ( ApplyDNSProviderTypeAliyunDNS = ApplyDNSProviderType("aliyun-dns") ApplyDNSProviderTypeAWS = ApplyDNSProviderType("aws") // 兼容旧值,等同于 [ApplyDNSProviderTypeAWSRoute53] ApplyDNSProviderTypeAWSRoute53 = ApplyDNSProviderType("aws-route53") + ApplyDNSProviderTypeAzure = ApplyDNSProviderType("azure") // 兼容旧值,等同于 [ApplyDNSProviderTypeAzure] ApplyDNSProviderTypeAzureDNS = ApplyDNSProviderType("azure-dns") ApplyDNSProviderTypeBaiduCloud = ApplyDNSProviderType("baiducloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeBaiduCloudDNS] ApplyDNSProviderTypeBaiduCloudDNS = ApplyDNSProviderType("baiducloud-dns") @@ -78,6 +79,8 @@ const ( ApplyDNSProviderTypeGoDaddy = ApplyDNSProviderType("godaddy") ApplyDNSProviderTypeHuaweiCloud = ApplyDNSProviderType("huaweicloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeHuaweiCloudDNS] ApplyDNSProviderTypeHuaweiCloudDNS = ApplyDNSProviderType("huaweicloud-dns") + ApplyDNSProviderTypeJDCloud = ApplyDNSProviderType("jdcloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeJDCloudDNS] + ApplyDNSProviderTypeJDCloudDNS = ApplyDNSProviderType("jdcloud-dns") ApplyDNSProviderTypeNameDotCom = ApplyDNSProviderType("namedotcom") ApplyDNSProviderTypeNameSilo = ApplyDNSProviderType("namesilo") ApplyDNSProviderTypeNS1 = ApplyDNSProviderType("ns1") diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/baiducloud/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/baiducloud/internal/lego.go index fd138c6d..558ad5eb 100644 --- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/baiducloud/internal/lego.go +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/baiducloud/internal/lego.go @@ -184,10 +184,10 @@ func (d *DNSProvider) removeDNSRecord(domain, subDomain, value string) error { if record == nil { return nil + } else { + err = d.client.DeleteRecord(domain, record.Id, d.generateClientToken()) + return err } - - err = d.client.DeleteRecord(domain, record.Id, d.generateClientToken()) - return err } func (d *DNSProvider) generateClientToken() string { diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/internal/lego.go new file mode 100644 index 00000000..a85fa79c --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/internal/lego.go @@ -0,0 +1,229 @@ +package lego_jdcloud + +import ( + "errors" + "fmt" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + jdCore "github.com/jdcloud-api/jdcloud-sdk-go/core" + jdDnsApi "github.com/jdcloud-api/jdcloud-sdk-go/services/domainservice/apis" + jdDnsClient "github.com/jdcloud-api/jdcloud-sdk-go/services/domainservice/client" + jdDnsModel "github.com/jdcloud-api/jdcloud-sdk-go/services/domainservice/models" +) + +const ( + envNamespace = "JDCLOUD_" + + EnvAccessKeyID = envNamespace + "ACCESS_KEY_ID" + EnvAccessKeySecret = envNamespace + "ACCESS_KEY_SECRET" + EnvRegionId = envNamespace + "REGION_ID" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +type Config struct { + AccessKeyID string + AccessKeySecret string + RegionId string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int32 + HTTPTimeout time.Duration +} + +type DNSProvider struct { + client *jdDnsClient.DomainserviceClient + config *Config +} + +func NewDefaultConfig() *Config { + return &Config{ + TTL: int32(env.GetOrDefaultInt(EnvTTL, 300)), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + } +} + +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAccessKeyID, EnvAccessKeySecret) + if err != nil { + return nil, fmt.Errorf("jdcloud: %w", err) + } + + config := NewDefaultConfig() + config.AccessKeyID = values[EnvAccessKeyID] + config.AccessKeySecret = values[EnvAccessKeySecret] + config.RegionId = values[EnvRegionId] + + return NewDNSProviderConfig(config) +} + +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("jdcloud: the configuration of the DNS provider is nil") + } + + clientCredentials := jdCore.NewCredentials(config.AccessKeyID, config.AccessKeySecret) + clientConfig := jdCore.NewConfig() + clientConfig.SetTimeout(config.HTTPTimeout) + client := jdDnsClient.NewDomainserviceClient(clientCredentials) + client.SetConfig(clientConfig) + + return &DNSProvider{ + client: client, + config: config, + }, nil +} + +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + zoneName, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("jdcloud: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zoneName) + if err != nil { + return fmt.Errorf("jdcloud: %w", err) + } + + if err := d.addOrUpdateDNSRecord(domain, subDomain, info.Value); err != nil { + return fmt.Errorf("jdcloud: %w", err) + } + + return nil +} + +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + fqdn, value := dns01.GetRecord(domain, keyAuth) + subDomain := dns01.UnFqdn(fqdn) + + if err := d.removeDNSRecord(domain, subDomain, value); err != nil { + return fmt.Errorf("jdcloud: %w", err) + } + + return nil +} + +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +func (d *DNSProvider) getDNSZone(domain string) (*jdDnsModel.DomainInfo, error) { + pageNumber := 1 + pageSize := 100 + for { + request := &jdDnsApi.DescribeDomainsRequest{} + request.RegionId = d.config.RegionId + request.DomainName = &domain + request.PageNumber = pageNumber + request.PageSize = pageSize + + response, err := d.client.DescribeDomains(request) + if err != nil { + return nil, err + } + + for _, item := range response.Result.DataList { + if item.DomainName == domain { + return &item, nil + } + } + + if len(response.Result.DataList) < pageSize { + break + } + + pageNumber++ + } + + return nil, fmt.Errorf("jdcloud: zone %s not found", domain) +} + +func (d *DNSProvider) getDNSZoneAndRecord(domain, subDomain string) (*jdDnsModel.DomainInfo, *jdDnsModel.RRInfo, error) { + zone, err := d.getDNSZone(domain) + if err != nil { + return nil, nil, err + } + + pageNumber := 1 + pageSize := 100 + for { + request := jdDnsApi.NewDescribeResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", &zone.Id)) + request.Search = &subDomain + request.PageNumber = &pageNumber + request.PageSize = &pageSize + + response, err := d.client.DescribeResourceRecord(request) + if err != nil { + return zone, nil, err + } + + for _, record := range response.Result.DataList { + if record.Type == "TXT" && record.HostRecord == subDomain { + return zone, &record, nil + } + } + + if len(response.Result.DataList) < pageSize { + break + } + + pageNumber++ + } + + return nil, nil, nil +} + +func (d *DNSProvider) addOrUpdateDNSRecord(domain, subDomain, value string) error { + zone, record, err := d.getDNSZoneAndRecord(domain, subDomain) + if err != nil { + return err + } + + if record == nil { + request := jdDnsApi.NewCreateResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", &zone.Id), &jdDnsModel.AddRR{ + Type: "TXT", + HostRecord: subDomain, + HostValue: value, + Ttl: int(d.config.TTL), + }) + _, err := d.client.CreateResourceRecord(request) + return err + } else { + request := jdDnsApi.NewModifyResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", &zone.Id), fmt.Sprintf("%d", &record.Id), &jdDnsModel.UpdateRR{ + Type: "TXT", + HostRecord: subDomain, + HostValue: value, + Ttl: int(d.config.TTL), + }) + _, err := d.client.ModifyResourceRecord(request) + return err + } +} + +func (d *DNSProvider) removeDNSRecord(domain, subDomain, value string) error { + zone, record, err := d.getDNSZoneAndRecord(domain, subDomain) + if err != nil { + return err + } + + if record == nil { + return nil + } else { + req := jdDnsApi.NewDeleteResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", &zone.Id), fmt.Sprintf("%d", &record.Id)) + _, err = d.client.DeleteResourceRecord(req) + return err + } +} diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/jdcloud.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/jdcloud.go new file mode 100644 index 00000000..5729d932 --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/jdcloud.go @@ -0,0 +1,47 @@ +package jdcloud + +import ( + "time" + + "github.com/go-acme/lego/v4/challenge" + + internal "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/internal" +) + +type ChallengeProviderConfig struct { + AccessKeyId string `json:"accessKeyId"` + AccessKeySecret string `json:"accessKeySecret"` + RegionId string `json:"regionId"` + DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` + DnsTTL int32 `json:"dnsTTL,omitempty"` +} + +func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) { + if config == nil { + panic("config is nil") + } + + regionId := config.RegionId + if regionId == "" { + // 京东云的 SDK 要求必须传一个区域,实际上 DNS-01 流程里用不到,但不传会报错 + regionId = "cn-north-1" + } + + providerConfig := internal.NewDefaultConfig() + providerConfig.AccessKeyID = config.AccessKeyId + providerConfig.AccessKeySecret = config.AccessKeySecret + providerConfig.RegionId = regionId + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = config.DnsTTL + } + + provider, err := internal.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/ui/public/imgs/providers/jdcloud.svg b/ui/public/imgs/providers/jdcloud.svg new file mode 100644 index 00000000..720dbf4d --- /dev/null +++ b/ui/public/imgs/providers/jdcloud.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/Version.tsx b/ui/src/components/Version.tsx index fcdf1170..3d736271 100644 --- a/ui/src/components/Version.tsx +++ b/ui/src/components/Version.tsx @@ -1,4 +1,4 @@ -import { memo, useEffect, useState } from "react"; +import { memo } from "react"; import { useTranslation } from "react-i18next"; import { ReadOutlined as ReadOutlinedIcon } from "@ant-design/icons"; import { Badge, Divider, Space, Typography } from "antd"; diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index f552e594..44d6ae0c 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -27,6 +27,7 @@ import AccessFormGcoreConfig from "./AccessFormGcoreConfig"; import AccessFormGnameConfig from "./AccessFormGnameConfig"; import AccessFormGoDaddyConfig from "./AccessFormGoDaddyConfig"; import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig"; +import AccessFormJDCloudConfig from "./AccessFormJDCloudConfig"; import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig"; import AccessFormLocalConfig from "./AccessFormLocalConfig"; import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig"; @@ -131,6 +132,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.HUAWEICLOUD: return ; + case ACCESS_PROVIDERS.JDCLOUD: + return ; case ACCESS_PROVIDERS.KUBERNETES: return ; case ACCESS_PROVIDERS.LOCAL: diff --git a/ui/src/components/access/AccessFormJDCloudConfig.tsx b/ui/src/components/access/AccessFormJDCloudConfig.tsx new file mode 100644 index 00000000..7ab6b167 --- /dev/null +++ b/ui/src/components/access/AccessFormJDCloudConfig.tsx @@ -0,0 +1,76 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForJDCloud } from "@/domain/access"; + +type AccessFormJDCloudConfigFieldValues = Nullish; + +export type AccessFormJDCloudConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormJDCloudConfigFieldValues; + onValuesChange?: (values: AccessFormJDCloudConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormJDCloudConfigFieldValues => { + return { + accessKeyId: "", + accessKeySecret: "", + }; +}; + +const AccessFormJDCloudConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange: onValuesChange }: AccessFormJDCloudConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + accessKeyId: z + .string() + .min(1, t("access.form.jdcloud_access_key_id.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + accessKeySecret: z + .string() + .min(1, t("access.form.jdcloud_access_key_secret.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormJDCloudConfig; diff --git a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx index 7b43cad2..c714e197 100644 --- a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx @@ -35,6 +35,7 @@ import { validDomainName, validIPv4Address, validIPv6Address } from "@/utils/val import ApplyNodeConfigFormAWSRoute53Config from "./ApplyNodeConfigFormAWSRoute53Config"; import ApplyNodeConfigFormHuaweiCloudDNSConfig from "./ApplyNodeConfigFormHuaweiCloudDNSConfig"; +import ApplyNodeConfigFormJDCloudDNSConfig from "./ApplyNodeConfigFormJDCloudDNSConfig"; type ApplyNodeConfigFormFieldValues = Partial; @@ -145,6 +146,9 @@ const ApplyNodeConfigForm = forwardRef; + case APPLY_DNS_PROVIDERS.JDCLOUD: + case APPLY_DNS_PROVIDERS.JDCLOUD_DNS: + return ; } }, [disabled, initialValues?.providerConfig, fieldProvider, nestedFormInst, nestedFormName]); diff --git a/ui/src/components/workflow/node/ApplyNodeConfigFormJDCloudDNSConfig.tsx b/ui/src/components/workflow/node/ApplyNodeConfigFormJDCloudDNSConfig.tsx new file mode 100644 index 00000000..f9533972 --- /dev/null +++ b/ui/src/components/workflow/node/ApplyNodeConfigFormJDCloudDNSConfig.tsx @@ -0,0 +1,61 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type ApplyNodeConfigFormJDCloudDNSConfigFieldValues = Nullish<{ + regionId: string; +}>; + +export type ApplyNodeConfigFormJDCloudDNSConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: ApplyNodeConfigFormJDCloudDNSConfigFieldValues; + onValuesChange?: (values: ApplyNodeConfigFormJDCloudDNSConfigFieldValues) => void; +}; + +const initFormModel = (): ApplyNodeConfigFormJDCloudDNSConfigFieldValues => { + return { + regionId: "cn-north-1", + }; +}; + +const ApplyNodeConfigFormJDCloudDNSConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: ApplyNodeConfigFormJDCloudDNSConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + regionId: z + .string({ message: t("workflow_node.apply.form.jdcloud_dns_region_id.placeholder") }) + .nonempty(t("workflow_node.apply.form.jdcloud_dns_region_id.placeholder")) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + +
+ ); +}; + +export default ApplyNodeConfigFormJDCloudDNSConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 0ece4826..c91e647f 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -24,6 +24,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForGname | AccessConfigForGoDaddy | AccessConfigForHuaweiCloud + | AccessConfigForJDCloud | AccessConfigForKubernetes | AccessConfigForLocal | AccessConfigForNameDotCom @@ -133,6 +134,11 @@ export type AccessConfigForHuaweiCloud = { secretAccessKey: string; }; +export type AccessConfigForJDCloud = { + accessKeyId: string; + accessKeySecret: string; +}; + export type AccessConfigForKubernetes = { kubeConfig?: string; }; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index b5bcaca7..39a5424e 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -22,6 +22,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ GODADDY: "godaddy", EDGIO: "edgio", HUAWEICLOUD: "huaweicloud", + JDCLOUD: "jdcloud", KUBERNETES: "k8s", LOCAL: "local", NAMEDOTCOM: "namedotcom", @@ -70,6 +71,7 @@ export const accessProvidersMap: Maphttps://www.alibabacloud.com/help/en/acr/create-and-obtain-an-accesskey-pair", - "access.form.aliyun_access_key_secret.label": "Aliyun AccessKey Secret", - "access.form.aliyun_access_key_secret.placeholder": "Please enter Aliyun AccessKey Secret", + "access.form.aliyun_access_key_secret.label": "Aliyun AccessKeySecret", + "access.form.aliyun_access_key_secret.placeholder": "Please enter Aliyun AccessKeySecret", "access.form.aliyun_access_key_secret.tooltip": "For more information, see https://www.alibabacloud.com/help/en/acr/create-and-obtain-an-accesskey-pair", "access.form.aws_access_key_id.label": "AWS AccessKeyId", "access.form.aws_access_key_id.placeholder": "Please enter AWS AccessKeyId", @@ -133,6 +133,12 @@ "access.form.huaweicloud_secret_access_key.label": "Huawei Cloud SecretAccessKey", "access.form.huaweicloud_secret_access_key.placeholder": "Please enter Huawei Cloud SecretAccessKey", "access.form.huaweicloud_secret_access_key.tooltip": "For more information, see https://support.huaweicloud.com/intl/en-us/usermanual-ca/ca_01_0003.html", + "access.form.jdcloud_access_key_id.label": "JD Cloud AccessKeyId", + "access.form.jdcloud_access_key_id.placeholder": "Please enter JD Cloud AccessKeyId", + "access.form.jdcloud_access_key_id.tooltip": "For more information, see https://docs.jdcloud.com/en/account-management/accesskey-management", + "access.form.jdcloud_access_key_secret.label": "JD Cloud AccessKeySecret", + "access.form.jdcloud_access_key_secret.placeholder": "Please enter JD Cloud AccessKeySecret", + "access.form.jdcloud_access_key_secret.tooltip": "For more information, see https://docs.jdcloud.com/en/account-management/accesskey-management", "access.form.k8s_kubeconfig.label": "KubeConfig", "access.form.k8s_kubeconfig.placeholder": "Please enter KubeConfig file", "access.form.k8s_kubeconfig.upload": "Choose File ...", diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index d5e5508b..eb288ce5 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -42,7 +42,7 @@ "provider.aliyun.cdn": "Alibaba Cloud - CDN (Content Delivery Network)", "provider.aliyun.clb": "Alibaba Cloud - CLB (Classic Load Balancer)", "provider.aliyun.dcdn": "Alibaba Cloud - DCDN (Dynamic Route for Content Delivery Network)", - "provider.aliyun.dns": "Alibaba Cloud - DNS (Domain Name Service)", + "provider.aliyun.dns": "Alibaba Cloud - DNS", "provider.aliyun.esa": "Alibaba Cloud - ESA (Edge Security Acceleration)", "provider.aliyun.live": "Alibaba Cloud - ApsaraVideo Live", "provider.aliyun.nlb": "Alibaba Cloud - NLB (Network Load Balancer)", @@ -69,8 +69,10 @@ "provider.godaddy": "GoDaddy", "provider.huaweicloud": "Huawei Cloud", "provider.huaweicloud.cdn": "Huawei Cloud - CDN (Content Delivery Network)", - "provider.huaweicloud.dns": "Huawei Cloud - DNS (Domain Name Service)", + "provider.huaweicloud.dns": "Huawei Cloud - DNS", "provider.huaweicloud.elb": "Huawei Cloud - ELB (Elastic Load Balance)", + "provider.jdcloud": "JD Cloud", + "provider.jdcloud.dns": "JD Cloud - DNS", "provider.kubernetes": "Kubernetes", "provider.kubernetes.secret": "Kubernetes - Secret", "provider.local": "Local deployment", @@ -88,7 +90,7 @@ "provider.tencentcloud.clb": "Tencent Cloud - CLB (Cloud Load Balancer)", "provider.tencentcloud.cos": "Tencent Cloud - COS (Cloud Object Storage)", "provider.tencentcloud.css": "Tencent Cloud - CSS (Cloud Streaming Service)", - "provider.tencentcloud.dns": "Tencent Cloud - DNS (Domain Name Service)", + "provider.tencentcloud.dns": "Tencent Cloud - DNS", "provider.tencentcloud.ecdn": "Tencent Cloud - ECDN (Enterprise Content Delivery Network)", "provider.tencentcloud.eo": "Tencent Cloud - EdgeOne", "provider.tencentcloud.ssl_deploy": "Tencent Cloud - via SSL Certificate Service Deployment Job", @@ -99,7 +101,7 @@ "provider.volcengine.cdn": "Volcengine - CDN (Content Delivery Network)", "provider.volcengine.clb": "Volcengine - CLB (Cloud Load Balancer)", "provider.volcengine.dcdn": "Volcengine - DCDN (Dynamic Content Delivery Network)", - "provider.volcengine.dns": "Volcengine - DNS (Domain Name Service)", + "provider.volcengine.dns": "Volcengine - DNS", "provider.volcengine.live": "Volcengine - Live", "provider.volcengine.tos": "Volcengine - TOS (Tinder Object Storage)", "provider.webhook": "Webhook", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 6bf12328..d0ae4173 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -46,6 +46,8 @@ "workflow_node.apply.form.huaweicloud_dns_region.label": "Huawei Cloud DNS region", "workflow_node.apply.form.huaweicloud_dns_region.placeholder": "Please enter Huawei Cloud DNS region (e.g. cn-north-1)", "workflow_node.apply.form.huaweicloud_dns_region.tooltip": "For more information, see https://console-intl.huaweicloud.com/apiexplorer/#/endpoint", + "workflow_node.apply.form.jdcloud_dns_region_id.label": "JD Cloud DNS region ID", + "workflow_node.apply.form.jdcloud_dns_region_id.placeholder": "Please enter JD Cloud DNS region ID (e.g. cn-north-1)", "workflow_node.apply.form.advanced_config.label": "Advanced settings", "workflow_node.apply.form.key_algorithm.label": "Certificate key algorithm", "workflow_node.apply.form.key_algorithm.placeholder": "Please select certificate key algorithm", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index ee7fea3a..c35d65d3 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -38,8 +38,8 @@ "access.form.aliyun_access_key_id.label": "阿里云 AccessKeyId", "access.form.aliyun_access_key_id.placeholder": "请输入阿里云 AccessKeyId", "access.form.aliyun_access_key_id.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair", - "access.form.aliyun_access_key_secret.label": "阿里云 AccessKey Secret", - "access.form.aliyun_access_key_secret.placeholder": "请输入阿里云 AccessKey Secret", + "access.form.aliyun_access_key_secret.label": "阿里云 AccessKeySecret", + "access.form.aliyun_access_key_secret.placeholder": "请输入阿里云 AccessKeySecret", "access.form.aliyun_access_key_secret.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair", "access.form.aws_access_key_id.label": "AWS AccessKeyId", "access.form.aws_access_key_id.placeholder": "请输入 AWS AccessKeyId", @@ -133,6 +133,12 @@ "access.form.huaweicloud_secret_access_key.label": "华为云 SecretAccessKey", "access.form.huaweicloud_secret_access_key.placeholder": "请输入华为云 SecretAccessKey", "access.form.huaweicloud_secret_access_key.tooltip": "这是什么?请参阅 https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html", + "access.form.jdcloud_access_key_id.label": "京东云 AccessKeyId", + "access.form.jdcloud_access_key_id.placeholder": "请输入京东云 AccessKeyId", + "access.form.jdcloud_access_key_id.tooltip": "这是什么?请参阅 https://docs.jdcloud.com/cn/account-management/accesskey-management", + "access.form.jdcloud_access_key_secret.label": "京东云 AccessKeySecret", + "access.form.jdcloud_access_key_secret.placeholder": "请输入京东云 AccessKeySecret", + "access.form.jdcloud_access_key_secret.tooltip": "这是什么?请参阅 https://docs.jdcloud.com/cn/account-management/accesskey-management", "access.form.k8s_kubeconfig.label": "KubeConfig", "access.form.k8s_kubeconfig.placeholder": "请选择 KubeConfig 文件", "access.form.k8s_kubeconfig.upload": "选择文件", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index e120b343..958f54a3 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -53,6 +53,8 @@ "provider.huaweicloud.dns": "华为云 - 云解析 DNS", "provider.huaweicloud.elb": "华为云 - 弹性负载均衡 ELB", "provider.huaweicloud.waf": "华为云 - Web 应用防火墙 WAF", + "provider.jdcloud": "京东云", + "provider.jdcloud.dns": "京东云 - 云解析 DNS", "provider.kubernetes": "Kubernetes", "provider.kubernetes.secret": "Kubernetes - Secret", "provider.local": "本地部署", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index d5ac3aca..5860ba08 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -46,6 +46,8 @@ "workflow_node.apply.form.huaweicloud_dns_region.label": "华为云 DNS 服务区域", "workflow_node.apply.form.huaweicloud_dns_region.placeholder": "请输入华为云 DNS 服务区域(例如:cn-north-1)", "workflow_node.apply.form.huaweicloud_dns_region.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/apiexplorer/#/endpoint", + "workflow_node.apply.form.jdcloud_dns_region_id.label": "京东云 DNS 服务地域 ID", + "workflow_node.apply.form.jdcloud_dns_region_id.placeholder": "请输入京东云 DNS 服务地域 ID(例如:cn-north-1)", "workflow_node.apply.form.advanced_config.label": "高级设置", "workflow_node.apply.form.key_algorithm.label": "数字证书算法", "workflow_node.apply.form.key_algorithm.placeholder": "请选择数字证书算法",