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