feat(provider): new acme dns-01 provider: dnsexit

This commit is contained in:
Fu Diwei 2025-12-05 10:35:23 +08:00
parent d26b9015cd
commit 7f6041fd3d
27 changed files with 2259 additions and 1805 deletions

View File

@ -0,0 +1,27 @@
package certifiers
import (
"fmt"
"github.com/go-acme/lego/v4/challenge"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/pkg/core/certifier/challengers/dns01/dnsexit"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
func init() {
ACMEDns01Registries.MustRegister(domain.ACMEDns01ProviderTypeDNSExit, func(options *ProviderFactoryOptions) (challenge.Provider, error) {
credentials := domain.AccessConfigForDNSExit{}
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
provider, err := dnsexit.NewChallenger(&dnsexit.ChallengerConfig{
ApiKey: credentials.ApiKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return provider, err
})
}

View File

@ -183,6 +183,10 @@ type AccessConfigForDiscordBot struct {
ChannelId string `json:"channelId,omitempty"`
}
type AccessConfigForDNSExit struct {
ApiKey string `json:"apiKey"`
}
type AccessConfigForDNSLA struct {
ApiId string `json:"apiId"`
ApiSecret string `json:"apiSecret"`

View File

@ -41,6 +41,7 @@ const (
AccessProviderTypeDigitalOcean = AccessProviderType("digitalocean")
AccessProviderTypeDingTalkBot = AccessProviderType("dingtalkbot")
AccessProviderTypeDiscordBot = AccessProviderType("discordbot")
AccessProviderTypeDNSExit = AccessProviderType("dnsexit")
AccessProviderTypeDNSLA = AccessProviderType("dnsla")
AccessProviderTypeDNSMadeEasy = AccessProviderType("dnsmadeeasy")
AccessProviderTypeDogeCloud = AccessProviderType("dogecloud")
@ -172,6 +173,7 @@ const (
ACMEDns01ProviderTypeCTCCCloudSmartDNS = ACMEDns01ProviderType(AccessProviderTypeCTCCCloud + "-smartdns")
ACMEDns01ProviderTypeDeSEC = ACMEDns01ProviderType(AccessProviderTypeDeSEC)
ACMEDns01ProviderTypeDigitalOcean = ACMEDns01ProviderType(AccessProviderTypeDigitalOcean)
ACMEDns01ProviderTypeDNSExit = ACMEDns01ProviderType(AccessProviderTypeDNSExit)
ACMEDns01ProviderTypeDNSLA = ACMEDns01ProviderType(AccessProviderTypeDNSLA)
ACMEDns01ProviderTypeDNSMadeEasy = ACMEDns01ProviderType(AccessProviderTypeDNSMadeEasy)
ACMEDns01ProviderTypeDuckDNS = ACMEDns01ProviderType(AccessProviderTypeDuckDNS)

View File

@ -42,8 +42,8 @@ type Config struct {
}
type DNSProvider struct {
client *ecloudsdkclouddns.Client
config *Config
client *ecloudsdkclouddns.Client
recordIDs map[string]string
recordIDsMu sync.Mutex
@ -88,8 +88,8 @@ func NewDNSProviderConfig(cfg *Config) (*DNSProvider, error) {
})
return &DNSProvider{
client: client,
config: cfg,
client: client,
recordIDs: make(map[string]string),
recordIDsMu: sync.Mutex{},
}, nil

View File

@ -74,7 +74,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client, err := ctyundns.NewClient(config.AccessKeyId, config.SecretAccessKey)
if err != nil {
return nil, err
return nil, fmt.Errorf("ctyun: %w", err)
} else {
client.SetTimeout(config.HTTPTimeout)
}

View File

@ -0,0 +1,37 @@
package dnsexit
import (
"errors"
"time"
"github.com/certimate-go/certimate/pkg/core/certifier"
"github.com/certimate-go/certimate/pkg/core/certifier/challengers/dns01/dnsexit/internal"
)
type ChallengerConfig struct {
ApiKey string `json:"apiKey"`
DnsPropagationTimeout int `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int `json:"dnsTTL,omitempty"`
}
func NewChallenger(config *ChallengerConfig) (certifier.ACMEChallenger, error) {
if config == nil {
return nil, errors.New("the configuration of the acme challenge provider is nil")
}
providerConfig := internal.NewDefaultConfig()
providerConfig.APIKey = config.ApiKey
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
}

View File

@ -0,0 +1,143 @@
package internal
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"
"github.com/samber/lo"
dnsexitsdk "github.com/certimate-go/certimate/pkg/sdk3rd/dnsexit"
)
const (
envNamespace = "DNSEXIT_"
EnvAPIKey = envNamespace + "APIKEY"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
)
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
type Config struct {
APIKey string
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int
HTTPTimeout time.Duration
}
func NewDefaultConfig() *Config {
return &Config{
TTL: env.GetOrDefaultInt(EnvTTL, 0),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
}
}
type DNSProvider struct {
config *Config
client *dnsexitsdk.Client
}
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvAPIKey)
if err != nil {
return nil, fmt.Errorf("dnsexit: %w", err)
}
config := NewDefaultConfig()
config.APIKey = values[EnvAPIKey]
return NewDNSProviderConfig(config)
}
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("dnsexit: the configuration of the DNS provider is nil")
}
client, err := dnsexitsdk.NewClient(config.APIKey)
if err != nil {
return nil, fmt.Errorf("dnsexit: %w", err)
} else {
client.SetTimeout(config.HTTPTimeout)
}
return &DNSProvider{
config: config,
client: client,
}, 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("dnsexit: could not find zone for domain %q: %w", domain, err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("dnsexit: %w", err)
}
// REF: https://dnsexit.com/dns/dns-api/#example-add-new
request := &dnsexitsdk.DnsRecordRequest{
Domain: lo.ToPtr(dns01.UnFqdn(authZone)),
Add: &dnsexitsdk.DnsRecord{
Type: lo.ToPtr("TXT"),
Name: lo.ToPtr(subDomain),
Content: lo.ToPtr(info.Value),
TTL: lo.ToPtr(d.config.TTL),
Overwrite: lo.ToPtr(true),
},
}
if _, err := d.client.DnsRecord(request); err != nil {
return fmt.Errorf("dnsexit: error when create record: %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("dnsexit: could not find zone for domain %q: %w", domain, err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("dnsexit: %w", err)
}
// REF: https://dnsexit.com/dns/dns-api/#delete-a-record
request := &dnsexitsdk.DnsRecordRequest{
Domain: lo.ToPtr(dns01.UnFqdn(authZone)),
Delete: &dnsexitsdk.DnsRecord{
Type: lo.ToPtr("TXT"),
Name: lo.ToPtr(subDomain),
},
}
if _, err := d.client.DnsRecord(request); err != nil {
return fmt.Errorf("dnsexit: error when delete record: %w", err)
}
return nil
}
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}

View File

@ -40,8 +40,8 @@ type Config struct {
}
type DNSProvider struct {
client *dnslasdk.Client
config *Config
client *dnslasdk.Client
recordIDs map[string]string
recordIDsMu sync.Mutex
@ -76,14 +76,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client, err := dnslasdk.NewClient(config.APIId, config.APISecret)
if err != nil {
return nil, err
return nil, fmt.Errorf("dnsla: %w", err)
} else {
client.SetTimeout(config.HTTPTimeout)
}
return &DNSProvider{
client: client,
config: config,
client: client,
recordIDs: make(map[string]string),
recordIDsMu: sync.Mutex{},
}, nil

View File

@ -34,8 +34,8 @@ type Config struct {
}
type DNSProvider struct {
client *dynv6.Provider
config *Config
client *dynv6.Provider
}
func NewDefaultConfig() *Config {
@ -66,8 +66,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client := &dynv6.Provider{Token: config.HTTPToken}
return &DNSProvider{
client: client,
config: config,
client: client,
}, nil
}

View File

@ -39,8 +39,8 @@ type Config struct {
}
type DNSProvider struct {
client *gnamesdk.Client
config *Config
client *gnamesdk.Client
recordIDs map[string]int64
recordIDsMu sync.Mutex
@ -75,14 +75,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client, err := gnamesdk.NewClient(config.AppID, config.AppKey)
if err != nil {
return nil, err
return nil, fmt.Errorf("gname: %w", err)
} else {
client.SetTimeout(config.HTTPTimeout)
}
return &DNSProvider{
client: client,
config: config,
client: client,
recordIDs: make(map[string]int64),
recordIDsMu: sync.Mutex{},
}, nil

View File

@ -41,8 +41,8 @@ type Config struct {
}
type DNSProvider struct {
client *DomainserviceClient
config *Config
client *DomainserviceClient
recordIDs map[string]int
recordIDsMu sync.Mutex
@ -81,8 +81,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client.Config.SetTimeout(config.HTTPTimeout)
return &DNSProvider{
client: client,
config: config,
client: client,
recordIDs: make(map[string]int),
recordIDsMu: sync.Mutex{},
}, nil

View File

@ -39,8 +39,8 @@ type Config struct {
}
type DNSProvider struct {
client *qingcloudsdk.Client
config *Config
client *qingcloudsdk.Client
recordIDs map[string]*int64
recordIDsMu sync.Mutex
@ -75,14 +75,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client, err := qingcloudsdk.NewClient(config.AccessKey, config.AccessSecret)
if err != nil {
return nil, err
return nil, fmt.Errorf("qingcloud: %w", err)
} else {
client.SetTimeout(config.HTTPTimeout)
}
return &DNSProvider{
client: client,
config: config,
client: client,
recordIDs: make(map[string]*int64),
recordIDsMu: sync.Mutex{},
}, nil

View File

@ -41,8 +41,8 @@ type Config struct {
}
type DNSProvider struct {
client *udnr.UDNRClient
config *Config
client *udnr.UDNRClient
}
func NewDefaultConfig() *Config {
@ -81,8 +81,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client := udnr.NewClient(&cfg, &credential)
return &DNSProvider{
client: client,
config: config,
client: client,
}, nil
}

View File

@ -39,8 +39,8 @@ type Config struct {
}
type DNSProvider struct {
client *xinnetsdk.Client
config *Config
client *xinnetsdk.Client
recordIDs map[string]*int64
recordIDsMu sync.Mutex
@ -75,14 +75,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
client, err := xinnetsdk.NewClient(config.AgentID, config.AppSecret)
if err != nil {
return nil, err
return nil, fmt.Errorf("xinnet: %w", err)
} else {
client.SetTimeout(config.HTTPTimeout)
}
return &DNSProvider{
client: client,
config: config,
client: client,
recordIDs: make(map[string]*int64),
recordIDsMu: sync.Mutex{},
}, nil

View File

@ -196,8 +196,9 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
privkey, err := xcert.ParsePrivateKeyFromPEM(privkeyPEM)
if err != nil {
return nil, fmt.Errorf("could not parse private key: %w", err)
return nil, fmt.Errorf("failed to parse private key: %w", err)
}
privkeyAlg, _, _ := xcryptokey.GetPrivateKeyAlgorithm(privkey)
privkeyAlgStr := ""
switch privkeyAlg {

View File

@ -0,0 +1,40 @@
package dnsexit
import (
"context"
"net/http"
)
type DnsRecordRequest struct {
Domain *string `json:"domain,omitempty"`
Add *DnsRecord `json:"add,omitempty"`
Update *DnsRecord `json:"update,omitempty"`
Delete *DnsRecord `json:"delete,omitempty"`
}
type DnsRecordResponse struct {
apiResponseBase
Details []string `json:"details,omitempty"`
}
func (c *Client) DnsRecord(req *DnsRecordRequest) (*DnsRecordResponse, error) {
return c.DnsRecordWithContext(context.Background(), req)
}
func (c *Client) DnsRecordWithContext(ctx context.Context, req *DnsRecordRequest) (*DnsRecordResponse, error) {
httpreq, err := c.newRequest(http.MethodPost, "/dns/")
if err != nil {
return nil, err
} else {
httpreq.SetBody(req)
httpreq.SetContext(ctx)
}
result := &DnsRecordResponse{}
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
return result, err
}
return result, nil
}

View File

@ -0,0 +1,96 @@
package dnsexit
import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/go-resty/resty/v2"
)
type Client struct {
client *resty.Client
}
func NewClient(apiKey string) (*Client, error) {
if apiKey == "" {
return nil, fmt.Errorf("sdkerr: unset apiKey")
}
client := resty.New().
SetBaseURL("https://api.dnsexit.com").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", "certimate").
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
req.Header.Set("apikey", apiKey)
return nil
})
return &Client{client}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {
c.client.SetTimeout(timeout)
return c
}
func (c *Client) newRequest(method string, path string) (*resty.Request, error) {
if method == "" {
return nil, fmt.Errorf("sdkerr: unset method")
}
if path == "" {
return nil, fmt.Errorf("sdkerr: unset path")
}
req := c.client.R()
req.Method = method
req.URL = path
return req, nil
}
func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
if req == nil {
return nil, fmt.Errorf("sdkerr: nil request")
}
// WARN:
// PLEASE DO NOT USE `req.SetResult` or `req.SetError` HERE! USE `doRequestWithResult` INSTEAD.
resp, err := req.Send()
if err != nil {
return resp, fmt.Errorf("sdkerr: failed to send request: %w", err)
} else if resp.IsError() {
return resp, fmt.Errorf("sdkerr: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String())
}
return resp, nil
}
func (c *Client) doRequestWithResult(req *resty.Request, res apiResponse) (*resty.Response, error) {
if req == nil {
return nil, fmt.Errorf("sdkerr: nil request")
}
resp, err := c.doRequest(req)
if err != nil {
if resp != nil {
json.Unmarshal(resp.Body(), &res)
}
return resp, err
}
if len(resp.Body()) != 0 {
if err := json.Unmarshal(resp.Body(), &res); err != nil {
return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w", err)
} else {
if tcode := res.GetCode(); tcode != 0 {
return resp, fmt.Errorf("sdkerr: code='%d', message='%s'", tcode, res.GetMessage())
}
}
}
return resp, nil
}

View File

@ -0,0 +1,38 @@
package dnsexit
type apiResponse interface {
GetCode() int32
GetMessage() string
}
type apiResponseBase struct {
Code *int32 `json:"code,omitempty"`
Message *string `json:"message,omitempty"`
}
func (r *apiResponseBase) GetCode() int32 {
if r.Code == nil {
return 0
}
return *r.Code
}
func (r *apiResponseBase) GetMessage() string {
if r.Message == nil {
return ""
}
return *r.Message
}
var _ apiResponse = (*apiResponseBase)(nil)
type DnsRecord struct {
Type *string `json:"type,omitempty"`
Name *string `json:"name,omitempty"`
Content *string `json:"content,omitempty"`
TTL *int `json:"ttl,omitempty"`
Priority *int `json:"priority,omitempty"`
Overwrite *bool `json:"overwrite,omitempty"`
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -33,6 +33,7 @@ import AccessConfigFieldsProviderDeSEC from "./AccessConfigFieldsProviderDeSEC";
import AccessConfigFieldsProviderDigitalOcean from "./AccessConfigFieldsProviderDigitalOcean";
import AccessConfigFieldsProviderDingTalkBot from "./AccessConfigFieldsProviderDingTalkBot";
import AccessConfigFieldsProviderDiscordBot from "./AccessConfigFieldsProviderDiscordBot";
import AccessConfigFieldsProviderDNSExit from "./AccessConfigFieldsProviderDNSExit";
import AccessConfigFieldsProviderDNSLA from "./AccessConfigFieldsProviderDNSLA";
import AccessConfigFieldsProviderDNSMadeEasy from "./AccessConfigFieldsProviderDNSMadeEasy";
import AccessConfigFieldsProviderDogeCloud from "./AccessConfigFieldsProviderDogeCloud";
@ -137,6 +138,7 @@ const providerComponentMap: Partial<Record<AccessProviderType, React.ComponentTy
[ACCESS_PROVIDERS.DIGITALOCEAN]: AccessConfigFieldsProviderDigitalOcean,
[ACCESS_PROVIDERS.DINGTALKBOT]: AccessConfigFieldsProviderDingTalkBot,
[ACCESS_PROVIDERS.DISCORDBOT]: AccessConfigFieldsProviderDiscordBot,
[ACCESS_PROVIDERS.DNSEXIT]: AccessConfigFieldsProviderDNSExit,
[ACCESS_PROVIDERS.DNSLA]: AccessConfigFieldsProviderDNSLA,
[ACCESS_PROVIDERS.DNSMADEEASY]: AccessConfigFieldsProviderDNSMadeEasy,
[ACCESS_PROVIDERS.DOGECLOUD]: AccessConfigFieldsProviderDogeCloud,

View File

@ -0,0 +1,52 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { useFormNestedFieldsContext } from "./_context";
const AccessConfigFormFieldsProviderDNSExit = () => {
const { i18n, t } = useTranslation();
const { parentNamePath } = useFormNestedFieldsContext();
const formSchema = z.object({
[parentNamePath]: getSchema({ i18n }),
});
const formRule = createSchemaFieldRule(formSchema);
const initialValues = getInitialValues();
return (
<>
<Form.Item
name={[parentNamePath, "apiKey"]}
initialValue={initialValues.apiKey}
label={t("access.form.dnsexit_api_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.dnsexit_api_key.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.dnsexit_api_key.placeholder")} />
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
apiKey: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
const { t } = i18n;
return z.object({
apiKey: z.string().nonempty(t("access.form.dnsexit_api_key.placeholder")),
});
};
const _default = Object.assign(AccessConfigFormFieldsProviderDNSExit, {
getInitialValues,
getSchema,
});
export default _default;

View File

@ -46,6 +46,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
DIGITALOCEAN: "digitalocean",
DINGTALKBOT: "dingtalkbot",
DISCORDBOT: "discordbot",
DNSEXIT: "dnsexit",
DNSLA: "dnsla",
DNSMADEEASY: "dnsmadeeasy",
DOGECLOUD: "dogecloud",
@ -79,6 +80,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
LITESSL: "litessl",
LOCAL: "local",
MATTERMOST: "mattermost",
MOHUA: "mohua",
NAMECHEAP: "namecheap",
NAMEDOTCOM: "namedotcom",
NAMESILO: "namesilo",
@ -115,7 +117,6 @@ export const ACCESS_PROVIDERS = Object.freeze({
WESTCN: "westcn",
XINNET: "xinnet",
ZEROSSL: "zerossl",
MOHUA: "mohua",
} as const);
export type AccessProviderType = (typeof ACCESS_PROVIDERS)[keyof typeof ACCESS_PROVIDERS];
@ -168,6 +169,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.KSYUN, "provider.ksyun", "/imgs/providers/ksyun.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.BYTEPLUS, "provider.byteplus", "/imgs/providers/byteplus.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.UNICLOUD, "provider.unicloud", "/imgs/providers/unicloud.png", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.MOHUA, "provider.mohua", "/imgs/providers/mohua.png", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS["1PANEL"], "provider.1panel", "/imgs/providers/1panel.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.BAOTAPANEL, "provider.baotapanel", "/imgs/providers/baotapanel.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.BAOTAPANELGO, "provider.baotapanelgo", "/imgs/providers/baotapanel.svg", [ACCESS_USAGES.HOSTING]],
@ -182,7 +184,6 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.APISIX, "provider.apisix", "/imgs/providers/apisix.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.KONG, "provider.kong", "/imgs/providers/kong.png", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.PROXMOXVE, "provider.proxmoxve", "/imgs/providers/proxmoxve.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.MOHUA, "provider.mohua", "/imgs/providers/mohua.png", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.AKAMAI, "provider.akamai", "/imgs/providers/akamai.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.ARVANCLOUD, "provider.arvancloud", "/imgs/providers/arvancloud.svg", [ACCESS_USAGES.DNS]],
@ -192,6 +193,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.CONSTELLIX, "provider.constellix", "/imgs/providers/constellix.png", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.DESEC, "provider.desec", "/imgs/providers/desec.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.DIGITALOCEAN, "provider.digitalocean", "/imgs/providers/digitalocean.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.DNSEXIT, "provider.dnsexit", "/imgs/providers/dnsexit.png", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.DNSLA, "provider.dnsla", "/imgs/providers/dnsla.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.DNSMADEEASY, "provider.dnsmadeeasy", "/imgs/providers/dnsmadeeasy.png", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.DUCKDNS, "provider.duckdns", "/imgs/providers/duckdns.png", [ACCESS_USAGES.DNS]],
@ -345,6 +347,7 @@ export const ACME_DNS01_PROVIDERS = Object.freeze({
CTCCCLOUD_SMARTDNS: `${ACCESS_PROVIDERS.CTCCCLOUD}-smartdns`,
DESEC: `${ACCESS_PROVIDERS.DESEC}`,
DIGITALOCEAN: `${ACCESS_PROVIDERS.DIGITALOCEAN}`,
DNSEXIT: `${ACCESS_PROVIDERS.DNSEXIT}`,
DNSLA: `${ACCESS_PROVIDERS.DNSLA}`,
DNSMADEEASY: `${ACCESS_PROVIDERS.DNSMADEEASY}`,
DUCKDNS: `${ACCESS_PROVIDERS.DUCKDNS}`,
@ -422,6 +425,7 @@ export const acmeDns01ProvidersMap: Map<ACMEDns01Provider["type"] | string, ACME
[ACME_DNS01_PROVIDERS.CONSTELLIX, "provider.constellix"],
[ACME_DNS01_PROVIDERS.DESEC, "provider.desec"],
[ACME_DNS01_PROVIDERS.DIGITALOCEAN, "provider.digitalocean"],
[ACME_DNS01_PROVIDERS.DNSEXIT, "provider.dnsexit"],
[ACME_DNS01_PROVIDERS.DNSLA, "provider.dnsla"],
[ACME_DNS01_PROVIDERS.DNSMADEEASY, "provider.dnsmadeeasy"],
[ACME_DNS01_PROVIDERS.DUCKDNS, "provider.duckdns"],
@ -696,7 +700,6 @@ export const deploymentProvidersMap: Map<DeploymentProvider["type"] | string, De
[DEPLOYMENT_PROVIDERS.HUAWEICLOUD_ELB, "provider.huaweicloud.elb", DEPLOYMENT_CATEGORIES.LOADBALANCE],
[DEPLOYMENT_PROVIDERS.HUAWEICLOUD_WAF, "provider.huaweicloud.waf", DEPLOYMENT_CATEGORIES.FIREWALL],
[DEPLOYMENT_PROVIDERS.HUAWEICLOUD_SCM, "provider.huaweicloud.scm_upload", DEPLOYMENT_CATEGORIES.SSL],
[DEPLOYMENT_PROVIDERS.MOHUA_MVH, "provider.mohua.mvh", DEPLOYMENT_CATEGORIES.WEBSITE],
[DEPLOYMENT_PROVIDERS.VOLCENGINE_TOS, "provider.volcengine.tos", DEPLOYMENT_CATEGORIES.STORAGE],
[DEPLOYMENT_PROVIDERS.VOLCENGINE_CDN, "provider.volcengine.cdn", DEPLOYMENT_CATEGORIES.CDN],
[DEPLOYMENT_PROVIDERS.VOLCENGINE_DCDN, "provider.volcengine.dcdn", DEPLOYMENT_CATEGORIES.CDN],
@ -738,6 +741,7 @@ export const deploymentProvidersMap: Map<DeploymentProvider["type"] | string, De
[DEPLOYMENT_PROVIDERS.BUNNY_CDN, "provider.bunny.cdn", DEPLOYMENT_CATEGORIES.CDN],
[DEPLOYMENT_PROVIDERS.CACHEFLY, "provider.cachefly", DEPLOYMENT_CATEGORIES.CDN],
[DEPLOYMENT_PROVIDERS.GCORE_CDN, "provider.gcore.cdn", DEPLOYMENT_CATEGORIES.CDN],
[DEPLOYMENT_PROVIDERS.MOHUA_MVH, "provider.mohua.mvh", DEPLOYMENT_CATEGORIES.WEBSITE],
[DEPLOYMENT_PROVIDERS.NETLIFY_SITE, "provider.netlify.site", DEPLOYMENT_CATEGORIES.WEBSITE],
[DEPLOYMENT_PROVIDERS.CDNFLY, "provider.cdnfly", DEPLOYMENT_CATEGORIES.CDN],
[DEPLOYMENT_PROVIDERS.FLEXCDN, "provider.flexcdn", DEPLOYMENT_CATEGORIES.CDN],

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
{
{
"provider.1panel": "1Panel",
"provider.1panel.console": "1Panel - Console itself",
"provider.1panel.site": "1Panel - Website",
@ -80,6 +80,7 @@
"provider.digitalocean": "DigitalOcean",
"provider.dingtalkbot": "DingTalk Bot",
"provider.discordbot": "Discord Bot",
"provider.dnsexit": "DNSExit",
"provider.dnsla": "DNS.LA",
"provider.dnsmadeeasy": "DNS Made Easy",
"provider.dogecloud.cdn": "Doge Cloud - CDN (Content Delivery Network)",

File diff suppressed because it is too large Load Diff

View File

@ -239,6 +239,9 @@
"access.form.dnsmadeeasy_api_secret.label": "DNS Made Easy API Secret",
"access.form.dnsmadeeasy_api_secret.placeholder": "请输入 DNS Made Easy API Secret",
"access.form.dnsmadeeasy_api_secret.tooltip": "这是什么?请参阅 <a href=\"https://api-docs.dnsmadeeasy.com/#5b98221f-37e9-4845-a349-5e959241b4a5\" target=\"_blank\">https://api-docs.dnsmadeeasy.com/#Authentication</a>",
"access.form.dnsexit_api_key.label": "DNSExit API Key",
"access.form.dnsexit_api_key.placeholder": "请输入 DNSExit API Key",
"access.form.dnsexit_api_key.tooltip": "这是什么?请参阅 <a href=\"https://dnsexit.com/Direct.sv?cmd=userApiKey\" target=\"_blank\">https://dnsexit.com/Direct.sv?cmd=userApiKey</a>",
"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": "这是什么?请参阅 <a href=\"https://www.dns.la/docs/ApiDoc\" target=\"_blank\">https://www.dns.la/docs/ApiDoc</a>",

View File

@ -80,6 +80,7 @@
"provider.digitalocean": "DigitalOcean",
"provider.dingtalkbot": "钉钉群机器人",
"provider.discordbot": "Discord 机器人",
"provider.dnsexit": "DNSExit",
"provider.dnsla": "DNS.LA",
"provider.dnsmadeeasy": "DNS Made Easy",
"provider.dogecloud": "多吉云",