From f81fa2eb63f55217c109e20aa48785d8f26add19 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 21 Feb 2025 17:21:39 +0800 Subject: [PATCH] feat: add dns.la dns-01 applicant --- README.md | 3 +- README_EN.md | 3 +- internal/applicant/providers.go | 19 +- internal/domain/access.go | 5 + internal/domain/provider.go | 3 +- .../acme-dns-01/lego-providers/dnsla/dnsla.go | 39 +++ .../lego-providers/dnsla/internal/lego.go | 240 ++++++++++++++++++ .../lego-providers/gname/internal/lego.go | 2 +- .../lego-providers/jdcloud/internal/lego.go | 8 +- internal/pkg/vendors/baishan-sdk/client.go | 3 +- internal/pkg/vendors/cachefly-sdk/client.go | 3 +- internal/pkg/vendors/dnsla-sdk/api.go | 52 ++++ internal/pkg/vendors/dnsla-sdk/client.go | 80 ++++++ internal/pkg/vendors/dnsla-sdk/models.go | 125 +++++++++ ui/public/imgs/providers/dnsla.svg | 1 + ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormDNSLAConfig.tsx | 76 ++++++ ui/src/domain/access.ts | 6 + ui/src/domain/provider.ts | 6 +- ui/src/i18n/locales/en/nls.access.json | 6 + ui/src/i18n/locales/zh/nls.access.json | 6 + 21 files changed, 677 insertions(+), 12 deletions(-) create mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla/dnsla.go create mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla/internal/lego.go create mode 100644 internal/pkg/vendors/dnsla-sdk/api.go create mode 100644 internal/pkg/vendors/dnsla-sdk/client.go create mode 100644 internal/pkg/vendors/dnsla-sdk/models.go create mode 100644 ui/public/imgs/providers/dnsla.svg create mode 100644 ui/src/components/access/AccessFormDNSLAConfig.tsx diff --git a/README.md b/README.md index 1bcf2034..0899200e 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,8 @@ make local.run | [AWS Route53](https://aws.amazon.com/route53/) | | | [Azure](https://azure.microsoft.com/) | | | [CloudFlare](https://www.cloudflare.com/) | | -| [ClouDNS](https://www.cloudns.net//) | | +| [ClouDNS](https://www.cloudns.net/) | | +| [DNS.LA](https://www.dns.la/) | | | [Gcore](https://gcore.com/) | | | [GNAME](https://www.gname.com/) | | | [GoDaddy](https://www.godaddy.com/) | | diff --git a/README_EN.md b/README_EN.md index 2e850330..b92fc82a 100644 --- a/README_EN.md +++ b/README_EN.md @@ -96,7 +96,8 @@ The following DNS providers are supported: | [AWS Route53](https://aws.amazon.com/route53/) | | | [Azure DNS](https://azure.microsoft.com/) | | | [CloudFlare](https://www.cloudflare.com/) | | -| [ClouDNS](https://www.cloudns.net//) | | +| [ClouDNS](https://www.cloudns.net/) | | +| [DNS.LA](https://www.dns.la/) | | | [Gcore](https://gcore.com/) | | | [GNAME](https://www.gname.com/) | | | [GoDaddy](https://www.godaddy.com/) | | diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index ef013490..cc8a3cfd 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -14,6 +14,7 @@ import ( pCloudflare "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudflare" pClouDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns" pCMCCCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud" + pDNSLA "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla" pGcore "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gcore" 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" @@ -169,6 +170,22 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } + case domain.ApplyDNSProviderTypeDNSLA: + { + access := domain.AccessConfigForDNSLA{} + if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + applicant, err := pDNSLA.NewChallengeProvider(&pDNSLA.ChallengeProviderConfig{ + ApiId: access.ApiId, + ApiSecret: access.ApiSecret, + DnsPropagationTimeout: options.DnsPropagationTimeout, + DnsTTL: options.DnsTTL, + }) + return applicant, err + } + case domain.ApplyDNSProviderTypeGcore: { access := domain.AccessConfigForGcore{} @@ -259,7 +276,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { applicant, err := pNamecheap.NewChallengeProvider(&pNamecheap.ChallengeProviderConfig{ Username: access.Username, - ApiKey: access.ApiKey, + ApiKey: access.ApiKey, DnsPropagationTimeout: options.DnsPropagationTimeout, DnsTTL: options.DnsTTL, }) diff --git a/internal/domain/access.go b/internal/domain/access.go index 4b693522..25df3210 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -91,6 +91,11 @@ type AccessConfigForCMCCCloud struct { AccessKeySecret string `json:"accessKeySecret"` } +type AccessConfigForDNSLA struct { + ApiId string `json:"apiId"` + ApiSecret string `json:"apiSecret"` +} + type AccessConfigForDogeCloud struct { AccessKey string `json:"accessKey"` SecretKey string `json:"secretKey"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index f0949e03..950abc08 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -26,7 +26,7 @@ const ( AccessProviderTypeCMCCCloud = AccessProviderType("cmcccloud") AccessProviderTypeCTCCCloud = AccessProviderType("ctcccloud") // 联通云(预留) AccessProviderTypeCUCCCloud = AccessProviderType("cucccloud") // 天翼云(预留) - AccessProviderTypeDNSLA = AccessProviderType("dnsla") // DNS.LA(预留) + AccessProviderTypeDNSLA = AccessProviderType("dnsla") AccessProviderTypeDogeCloud = AccessProviderType("dogecloud") AccessProviderTypeEdgio = AccessProviderType("edgio") AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留) @@ -77,6 +77,7 @@ const ( ApplyDNSProviderTypeCloudflare = ApplyDNSProviderType("cloudflare") ApplyDNSProviderTypeClouDNS = ApplyDNSProviderType("cloudns") ApplyDNSProviderTypeCMCCCloud = ApplyDNSProviderType("cmcccloud") + ApplyDNSProviderTypeDNSLA = ApplyDNSProviderType("dnsla") ApplyDNSProviderTypeGcore = ApplyDNSProviderType("gcore") ApplyDNSProviderTypeGname = ApplyDNSProviderType("gname") ApplyDNSProviderTypeGoDaddy = ApplyDNSProviderType("godaddy") diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla/dnsla.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla/dnsla.go new file mode 100644 index 00000000..5b0bd977 --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla/dnsla.go @@ -0,0 +1,39 @@ +package dnsla + +import ( + "time" + + "github.com/go-acme/lego/v4/challenge" + + internal "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla/internal" +) + +type ChallengeProviderConfig struct { + ApiId string `json:"apiId"` + ApiSecret string `json:"apiSecret"` + DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` + DnsTTL int32 `json:"dnsTTL,omitempty"` +} + +func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) { + if config == nil { + panic("config is nil") + } + + providerConfig := internal.NewDefaultConfig() + providerConfig.APIId = config.ApiId + providerConfig.APISecret = config.ApiSecret + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = int(config.DnsTTL) + } + + provider, err := internal.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla/internal/lego.go new file mode 100644 index 00000000..1b9603bd --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla/internal/lego.go @@ -0,0 +1,240 @@ +package lego_dnsla + +import ( + "errors" + "fmt" + "strings" + "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" + + dnslasdk "github.com/usual2970/certimate/internal/pkg/vendors/dnsla-sdk" +) + +const ( + envNamespace = "DNSLA_" + + EnvAPIId = envNamespace + "API_ID" + EnvAPISecret = envNamespace + "API_KEY" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +type Config struct { + APIId string + APISecret string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPTimeout time.Duration +} + +type DNSProvider struct { + client *dnslasdk.Client + config *Config +} + +func NewDefaultConfig() *Config { + return &Config{ + TTL: 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(EnvAPIId, EnvAPISecret) + if err != nil { + return nil, fmt.Errorf("dnsla: %w", err) + } + + config := NewDefaultConfig() + config.APIId = values[EnvAPIId] + config.APISecret = values[EnvAPISecret] + + return NewDNSProviderConfig(config) +} + +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("dnsla: the configuration of the DNS provider is nil") + } + + client := dnslasdk.NewClient(config.APIId, config.APISecret). + WithTimeout(config.HTTPTimeout) + + return &DNSProvider{ + client: client, + config: config, + }, nil +} + +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("dnsla: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("dnsla: %w", err) + } + + if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil { + return fmt.Errorf("dnsla: %w", err) + } + + return nil +} + +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("dnsla: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("dnsla: %w", err) + } + + if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); err != nil { + return fmt.Errorf("dnsla: %w", err) + } + + return nil +} + +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +func (d *DNSProvider) getDNSZone(zoneName string) (*dnslasdk.DomainInfo, error) { + pageIndex := 1 + pageSize := 100 + for { + request := &dnslasdk.ListDomainsRequest{ + PageIndex: int32(pageIndex), + PageSize: int32(pageSize), + } + response, err := d.client.ListDomains(request) + if err != nil { + return nil, err + } + + if response.Data != nil { + for _, item := range response.Data.Results { + if strings.TrimRight(item.Domain, ".") == zoneName || strings.TrimRight(item.DisplayDomain, ".") == zoneName { + return item, nil + } + } + } + + if response.Data == nil || len(response.Data.Results) < pageSize { + break + } + + pageIndex++ + } + + return nil, fmt.Errorf("dnsla: zone %s not found", zoneName) +} + +func (d *DNSProvider) getDNSZoneAndRecord(zoneName, subDomain string) (*dnslasdk.DomainInfo, *dnslasdk.RecordInfo, error) { + zone, err := d.getDNSZone(zoneName) + if err != nil { + return nil, nil, err + } + + pageIndex := 1 + pageSize := 100 + for { + request := &dnslasdk.ListRecordsRequest{ + DomainId: zone.Id, + Host: &subDomain, + PageIndex: int32(pageIndex), + PageSize: int32(pageSize), + } + response, err := d.client.ListRecords(request) + if err != nil { + return zone, nil, err + } + + if response.Data != nil { + for _, record := range response.Data.Results { + if record.Type == 16 && (record.Host == subDomain || record.DisplayHost == subDomain) { + return zone, record, nil + } + } + } + + if response.Data == nil || len(response.Data.Results) < pageSize { + break + } + + pageIndex++ + } + + return zone, nil, nil +} + +func (d *DNSProvider) addOrUpdateDNSRecord(zoneName, subDomain, value string) error { + zone, record, err := d.getDNSZoneAndRecord(zoneName, subDomain) + if err != nil { + return err + } + + if record == nil { + request := &dnslasdk.CreateRecordRequest{ + DomainId: zone.Id, + Type: 16, + Host: subDomain, + Data: value, + Ttl: int32(d.config.TTL), + } + _, err := d.client.CreateRecord(request) + return err + } else { + reqType := int32(16) + reqTtl := int32(d.config.TTL) + request := &dnslasdk.UpdateRecordRequest{ + Id: record.Id, + Type: &reqType, + Host: &subDomain, + Data: &value, + Ttl: &reqTtl, + } + _, err := d.client.UpdateRecord(request) + return err + } +} + +func (d *DNSProvider) removeDNSRecord(zoneName, subDomain string) error { + _, record, err := d.getDNSZoneAndRecord(zoneName, subDomain) + if err != nil { + return err + } + + if record == nil { + return nil + } else { + request := &dnslasdk.DeleteRecordRequest{ + Id: record.Id, + } + _, err = d.client.DeleteRecord(request) + return err + } +} diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal/lego.go index 17e9162f..3d0f2e54 100644 --- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal/lego.go +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal/lego.go @@ -43,7 +43,7 @@ type DNSProvider struct { func NewDefaultConfig() *Config { return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), + TTL: env.GetOrDefaultInt(EnvTTL, 300), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), 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 index bdbe5235..68d81f7e 100644 --- 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 @@ -130,12 +130,12 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -func (d *DNSProvider) getDNSZone(domain string) (*jdDnsModel.DomainInfo, error) { +func (d *DNSProvider) getDNSZone(zoneName string) (*jdDnsModel.DomainInfo, error) { pageNumber := 1 pageSize := 10 for { request := jdDnsApi.NewDescribeDomainsRequest(d.config.RegionId, pageNumber, pageSize) - request.SetDomainName(domain) + request.SetDomainName(zoneName) response, err := d.client.DescribeDomains(request) if err != nil { @@ -143,7 +143,7 @@ func (d *DNSProvider) getDNSZone(domain string) (*jdDnsModel.DomainInfo, error) } for _, item := range response.Result.DataList { - if item.DomainName == domain { + if item.DomainName == zoneName { return &item, nil } } @@ -155,7 +155,7 @@ func (d *DNSProvider) getDNSZone(domain string) (*jdDnsModel.DomainInfo, error) pageNumber++ } - return nil, fmt.Errorf("jdcloud: zone %s not found", domain) + return nil, fmt.Errorf("jdcloud: zone %s not found", zoneName) } func (d *DNSProvider) getDNSZoneAndRecord(zoneName, subDomain string) (*jdDnsModel.DomainInfo, *jdDnsModel.RRInfo, error) { diff --git a/internal/pkg/vendors/baishan-sdk/client.go b/internal/pkg/vendors/baishan-sdk/client.go index ff66af5b..e52e6af9 100644 --- a/internal/pkg/vendors/baishan-sdk/client.go +++ b/internal/pkg/vendors/baishan-sdk/client.go @@ -12,7 +12,8 @@ import ( type Client struct { apiToken string - client *resty.Client + + client *resty.Client } func NewClient(apiToken string) *Client { diff --git a/internal/pkg/vendors/cachefly-sdk/client.go b/internal/pkg/vendors/cachefly-sdk/client.go index e48e2010..d74c3698 100644 --- a/internal/pkg/vendors/cachefly-sdk/client.go +++ b/internal/pkg/vendors/cachefly-sdk/client.go @@ -12,7 +12,8 @@ import ( type Client struct { apiToken string - client *resty.Client + + client *resty.Client } func NewClient(apiToken string) *Client { diff --git a/internal/pkg/vendors/dnsla-sdk/api.go b/internal/pkg/vendors/dnsla-sdk/api.go new file mode 100644 index 00000000..df8e6026 --- /dev/null +++ b/internal/pkg/vendors/dnsla-sdk/api.go @@ -0,0 +1,52 @@ +package dnslasdk + +import ( + "fmt" + "net/http" + "net/url" +) + +func (c *Client) ListDomains(req *ListDomainsRequest) (*ListDomainsResponse, error) { + resp := ListDomainsResponse{} + err := c.sendRequestWithResult(http.MethodGet, "/domainList", req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (c *Client) ListRecords(req *ListRecordsRequest) (*ListRecordsResponse, error) { + resp := ListRecordsResponse{} + err := c.sendRequestWithResult(http.MethodGet, "/recordList", req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (c *Client) CreateRecord(req *CreateRecordRequest) (*CreateRecordResponse, error) { + resp := CreateRecordResponse{} + err := c.sendRequestWithResult(http.MethodPost, "/record", req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (c *Client) UpdateRecord(req *UpdateRecordRequest) (*UpdateRecordResponse, error) { + resp := UpdateRecordResponse{} + err := c.sendRequestWithResult(http.MethodPut, "/record", req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (c *Client) DeleteRecord(req *DeleteRecordRequest) (*DeleteRecordResponse, error) { + resp := DeleteRecordResponse{} + err := c.sendRequestWithResult(http.MethodDelete, fmt.Sprintf("/record?id=%s", url.QueryEscape(req.Id)), req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} diff --git a/internal/pkg/vendors/dnsla-sdk/client.go b/internal/pkg/vendors/dnsla-sdk/client.go new file mode 100644 index 00000000..936b634d --- /dev/null +++ b/internal/pkg/vendors/dnsla-sdk/client.go @@ -0,0 +1,80 @@ +package dnslasdk + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + apiId string + apiSecret string + + client *resty.Client +} + +func NewClient(apiId, apiSecret string) *Client { + client := resty.New() + + return &Client{ + apiId: apiId, + apiSecret: apiSecret, + client: client, + } +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.SetTimeout(timeout) + return c +} + +func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { + req := c.client.R().SetBasicAuth(c.apiId, c.apiSecret) + req.Method = method + req.URL = "https://api.dns.la/api" + path + if strings.EqualFold(method, http.MethodGet) { + qs := make(map[string]string) + if params != nil { + temp := make(map[string]any) + jsonData, _ := json.Marshal(params) + json.Unmarshal(jsonData, &temp) + for k, v := range temp { + qs[k] = fmt.Sprintf("%v", v) + } + } + + req = req.SetQueryParams(qs) + } else { + req = req. + SetHeader("Content-Type", "application/json"). + SetBody(params) + } + + resp, err := req.Send() + if err != nil { + return nil, fmt.Errorf("dnsla api error: failed to send request: %w", err) + } else if resp.IsError() { + return nil, fmt.Errorf("dnsla api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) + } + + return resp, nil +} + +func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result BaseResponse) error { + resp, err := c.sendRequest(method, path, params) + if err != nil { + return err + } + + if err := json.Unmarshal(resp.Body(), &result); err != nil { + return fmt.Errorf("dnsla api error: failed to parse response: %w", err) + } else if errcode := result.GetCode(); errcode/100 != 2 { + return fmt.Errorf("dnsla api error: %d - %s", errcode, result.GetMessage()) + } + + return nil +} diff --git a/internal/pkg/vendors/dnsla-sdk/models.go b/internal/pkg/vendors/dnsla-sdk/models.go new file mode 100644 index 00000000..85fe7978 --- /dev/null +++ b/internal/pkg/vendors/dnsla-sdk/models.go @@ -0,0 +1,125 @@ +package dnslasdk + +type BaseResponse interface { + GetCode() int + GetMessage() string +} + +type baseResponse struct { + Code int `json:"code"` + Message string `json:"message"` +} + +func (r *baseResponse) GetCode() int { + return r.Code +} + +func (r *baseResponse) GetMessage() string { + return r.Message +} + +type DomainInfo struct { + Id string `json:"id"` + GroupId string `json:"groupId"` + GroupName string `json:"groupName"` + Domain string `json:"domain"` + DisplayDomain string `json:"displayDomain"` + CreatedAt int64 `json:"createdAt"` + UpdatedAt int64 `json:"updatedAt"` +} + +type RecordInfo struct { + Id string `json:"id"` + DomainId string `json:"domainId"` + GroupId string `json:"groupId"` + GroupName string `json:"groupName"` + LineId string `json:"lineId"` + LineCode string `json:"lineCode"` + LineName string `json:"lineName"` + Type int32 `json:"type"` + Host string `json:"host"` + DisplayHost string `json:"displayHost"` + Data string `json:"data"` + DisplayData string `json:"displayData"` + Ttl int32 `json:"ttl"` + Weight int32 `json:"weight"` + Preference int32 `json:"preference"` + CreatedAt int64 `json:"createdAt"` + UpdatedAt int64 `json:"updatedAt"` +} + +type ListDomainsRequest struct { + PageIndex int32 `json:"pageIndex"` + PageSize int32 `json:"pageSize"` + GroupId *string `json:"groupId,omitempty"` +} + +type ListDomainsResponse struct { + baseResponse + Data *struct { + Total int32 `json:"total"` + Results []*DomainInfo `json:"results"` + } `json:"data,omitempty"` +} + +type ListRecordsRequest struct { + PageIndex int32 `json:"pageIndex"` + PageSize int32 `json:"pageSize"` + DomainId string `json:"domainId"` + GroupId *string `json:"groupId,omitempty"` + LineId *string `json:"lineId,omitempty"` + Type *int32 `json:"type,omitempty"` + Host *string `json:"host,omitempty"` + Data *string `json:"data,omitempty"` +} + +type ListRecordsResponse struct { + baseResponse + Data *struct { + Total int32 `json:"total"` + Results []*RecordInfo `json:"results"` + } `json:"data,omitempty"` +} + +type CreateRecordRequest struct { + DomainId string `json:"domainId"` + GroupId *string `json:"groupId,omitempty"` + LineId *string `json:"lineId,omitempty"` + Type int32 `json:"type"` + Host string `json:"host"` + Data string `json:"data"` + Ttl int32 `json:"ttl"` + Weight *int32 `json:"weight,omitempty"` + Preference *int32 `json:"preference,omitempty"` +} + +type CreateRecordResponse struct { + baseResponse + Data *struct { + Id string `json:"id"` + } `json:"data,omitempty"` +} + +type UpdateRecordRequest struct { + Id string `json:"id"` + GroupId *string `json:"groupId,omitempty"` + LineId *string `json:"lineId,omitempty"` + Type *int32 `json:"type,omitempty"` + Host *string `json:"host,omitempty"` + Data *string `json:"data,omitempty"` + Ttl *int32 `json:"ttl,omitempty"` + Weight *int32 `json:"weight,omitempty"` + Preference *int32 `json:"preference,omitempty"` +} + +type UpdateRecordResponse struct { + baseResponse +} + +type DeleteRecordRequest struct { + Id string `json:"-"` +} + +type DeleteRecordResponse struct { + baseResponse +} diff --git a/ui/public/imgs/providers/dnsla.svg b/ui/public/imgs/providers/dnsla.svg new file mode 100644 index 00000000..7908049e --- /dev/null +++ b/ui/public/imgs/providers/dnsla.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 681d80f1..47c6d8c6 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -22,6 +22,7 @@ import AccessFormCdnflyConfig from "./AccessFormCdnflyConfig"; import AccessFormCloudflareConfig from "./AccessFormCloudflareConfig"; import AccessFormClouDNSConfig from "./AccessFormClouDNSConfig"; import AccessFormCMCCCloudConfig from "./AccessFormCMCCCloudConfig"; +import AccessFormDNSLAConfig from "./AccessFormDNSLAConfig"; import AccessFormDogeCloudConfig from "./AccessFormDogeCloudConfig"; import AccessFormEdgioConfig from "./AccessFormEdgioConfig"; import AccessFormGcoreConfig from "./AccessFormGcoreConfig"; @@ -124,6 +125,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.CMCCCLOUD: return ; + case ACCESS_PROVIDERS.DNSLA: + return ; case ACCESS_PROVIDERS.DOGECLOUD: return ; case ACCESS_PROVIDERS.GCORE: diff --git a/ui/src/components/access/AccessFormDNSLAConfig.tsx b/ui/src/components/access/AccessFormDNSLAConfig.tsx new file mode 100644 index 00000000..df8403ea --- /dev/null +++ b/ui/src/components/access/AccessFormDNSLAConfig.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 AccessConfigForDNSLA } from "@/domain/access"; + +type AccessFormDNSLAConfigFieldValues = Nullish; + +export type AccessFormDNSLAConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormDNSLAConfigFieldValues; + onValuesChange?: (values: AccessFormDNSLAConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormDNSLAConfigFieldValues => { + return { + apiId: "", + apiSecret: "", + }; +}; + +const AccessFormDNSLAConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange: onValuesChange }: AccessFormDNSLAConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiId: z + .string() + .min(1, t("access.form.dnsla_api_id.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + apiSecret: z + .string() + .min(1, t("access.form.dnsla_api_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 AccessFormDNSLAConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index a1e14d86..d32960ca 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -19,6 +19,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForCloudflare | AccessConfigForClouDNS | AccessConfigForCMCCCloud + | AccessConfigForDNSLA | AccessConfigForDogeCloud | AccessConfigForEdgio | AccessConfigForGcore @@ -112,6 +113,11 @@ export type AccessConfigForCMCCCloud = { accessKeySecret: string; }; +export type AccessConfigForDNSLA = { + apiId: string; + apiSecret: string; +}; + export type AccessConfigForDogeCloud = { accessKey: string; secretKey: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 3a806b88..0841f39d 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -17,6 +17,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ CLOUDFLARE: "cloudflare", CLOUDNS: "cloudns", CMCCCLOUD: "cmcccloud", + DNSLA: "dnsla", DOGECLOUD: "dogecloud", GCORE: "gcore", GNAME: "gname", @@ -91,6 +92,7 @@ export const accessProvidersMap: Maphttps://ecloud.10086.cn/op-help-center/doc/article/49739", + "access.form.dnsla_api_id.label": "DNS.LA API ID", + "access.form.dnsla_api_id.placeholder": "Please enter DNS.LA API ID", + "access.form.dnsla_api_id.tooltip": "For more information, see https://www.dns.la/docs/ApiDoc", + "access.form.dnsla_api_secret.label": "DNS.LA API secret", + "access.form.dnsla_api_secret.placeholder": "Please enter DNS.LA API secret", + "access.form.dnsla_api_secret.tooltip": "For more information, see https://www.dns.la/docs/ApiDoc", "access.form.dogecloud_access_key.label": "Doge Cloud AccessKey", "access.form.dogecloud_access_key.placeholder": "Please enter Doge Cloud AccessKey", "access.form.dogecloud_access_key.tooltip": "For more information, see https://console.dogecloud.com/", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index cb9befa4..64b034d5 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -106,6 +106,12 @@ "access.form.cmcccloud_access_key_secret.label": "移动云 AccessKeySecret", "access.form.cmcccloud_access_key_secret.placeholder": "请输入移动云 AccessKeySecret", "access.form.cmcccloud_access_key_secret.tooltip": "这是什么?请参阅 https://ecloud.10086.cn/op-help-center/doc/article/49739", + "access.form.dnsla_api_id.label": "DNS.LA API ID", + "access.form.dnsla_api_id.placeholder": "请输入 DNS.LA API ID", + "access.form.dnsla_api_id.tooltip": "这是什么?请参阅 https://www.dns.la/docs/ApiDoc", + "access.form.dnsla_api_secret.label": "DNS.LA API 密钥", + "access.form.dnsla_api_secret.placeholder": "请输入 DNS.LA API 密钥", + "access.form.dnsla_api_secret.tooltip": "这是什么?请参阅 https://www.dns.la/docs/ApiDoc", "access.form.dogecloud_access_key.label": "多吉云 AccessKey", "access.form.dogecloud_access_key.placeholder": "请输入多吉云 AccessKey", "access.form.dogecloud_access_key.tooltip": "这是什么?请参阅 https://console.dogecloud.com/",