mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-24 23:10:13 +08:00
feat(provider): new acme dns-01 provider: xinnet
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
package applicators
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
xinnet "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/xinnet"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
ACMEDns01Registries.MustRegister(domain.ACMEDns01ProviderTypeXinnet, func(options *ProviderFactoryOptions) (challenge.Provider, error) {
|
||||
credentials := domain.AccessConfigForXinnet{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := xinnet.NewChallengeProvider(&xinnet.ChallengeProviderConfig{
|
||||
AgentId: credentials.AgentId,
|
||||
ApiPassword: credentials.ApiPassword,
|
||||
DnsPropagationTimeout: options.DnsPropagationTimeout,
|
||||
DnsTTL: options.DnsTTL,
|
||||
})
|
||||
return provider, err
|
||||
})
|
||||
}
|
||||
@@ -528,6 +528,11 @@ type AccessConfigForWestcn struct {
|
||||
ApiPassword string `json:"apiPassword"`
|
||||
}
|
||||
|
||||
type AccessConfigForXinnet struct {
|
||||
AgentId string `json:"agentId"`
|
||||
ApiPassword string `json:"apiPassword"`
|
||||
}
|
||||
|
||||
type AccessConfigForZeroSSL struct {
|
||||
AccessConfigForACMEExternalAccountBinding
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@ const (
|
||||
AccessProviderTypeWebhook = AccessProviderType("webhook")
|
||||
AccessProviderTypeWeComBot = AccessProviderType("wecombot")
|
||||
AccessProviderTypeWestcn = AccessProviderType("westcn")
|
||||
AccessProviderTypeXinnet = AccessProviderType("xinnet")
|
||||
AccessProviderTypeZeroSSL = AccessProviderType("zerossl")
|
||||
)
|
||||
|
||||
@@ -209,6 +210,7 @@ const (
|
||||
ACMEDns01ProviderTypeVolcEngineDNS = ACMEDns01ProviderType(AccessProviderTypeVolcEngine + "-dns")
|
||||
ACMEDns01ProviderTypeVultr = ACMEDns01ProviderType(AccessProviderTypeVultr)
|
||||
ACMEDns01ProviderTypeWestcn = ACMEDns01ProviderType(AccessProviderTypeWestcn)
|
||||
ACMEDns01ProviderTypeXinnet = ACMEDns01ProviderType(AccessProviderTypeXinnet)
|
||||
)
|
||||
|
||||
type ACMEHttp01ProviderType string
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package xinnet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
"github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/xinnet/internal"
|
||||
)
|
||||
|
||||
type ChallengeProviderConfig struct {
|
||||
AgentId string `json:"agentId"`
|
||||
ApiPassword string `json:"apiPassword"`
|
||||
DnsPropagationTimeout int `json:"dnsPropagationTimeout,omitempty"`
|
||||
DnsTTL int `json:"dnsTTL,omitempty"`
|
||||
}
|
||||
|
||||
func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("the configuration of the acme challenge provider is nil")
|
||||
}
|
||||
|
||||
providerConfig := internal.NewDefaultConfig()
|
||||
providerConfig.AgentID = config.AgentId
|
||||
providerConfig.AppSecret = config.ApiPassword
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"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"
|
||||
|
||||
xinnetsdk "github.com/certimate-go/certimate/pkg/sdk3rd/xinnet"
|
||||
)
|
||||
|
||||
const (
|
||||
envNamespace = "DNSLA_"
|
||||
|
||||
EnvAgentId = envNamespace + "AGENT_ID"
|
||||
EnvAppSecret = envNamespace + "APP_SECRET"
|
||||
|
||||
EnvTTL = envNamespace + "TTL"
|
||||
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
||||
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
|
||||
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
|
||||
)
|
||||
|
||||
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
|
||||
|
||||
type Config struct {
|
||||
AgentID string
|
||||
AppSecret string
|
||||
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPTimeout time.Duration
|
||||
}
|
||||
|
||||
type DNSProvider struct {
|
||||
client *xinnetsdk.Client
|
||||
config *Config
|
||||
|
||||
recordIDs map[string]*int64
|
||||
recordIDsMu sync.Mutex
|
||||
}
|
||||
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, 600),
|
||||
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(EnvAgentId, EnvAppSecret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("xinnet: %w", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.AgentID = values[EnvAgentId]
|
||||
config.AppSecret = values[EnvAppSecret]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("xinnet: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := xinnetsdk.NewClient(config.AgentID, config.AppSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
client.SetTimeout(config.HTTPTimeout)
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
client: client,
|
||||
config: config,
|
||||
recordIDs: make(map[string]*int64),
|
||||
recordIDsMu: sync.Mutex{},
|
||||
}, 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("xinnet: could not find zone for domain %q: %w", domain, err)
|
||||
}
|
||||
|
||||
// REF: https://apidoc.xin.cn/doc-7283900
|
||||
xinnetDnsCreateReq := &xinnetsdk.DnsCreateRequest{
|
||||
DomainName: lo.ToPtr(dns01.UnFqdn(authZone)),
|
||||
RecordName: lo.ToPtr(dns01.UnFqdn(info.EffectiveFQDN)),
|
||||
Type: lo.ToPtr("TXT"),
|
||||
Value: lo.ToPtr(info.Value),
|
||||
Line: lo.ToPtr("默认"),
|
||||
Ttl: lo.ToPtr(int32(d.config.TTL)),
|
||||
}
|
||||
xinnetDnsCreateResp, err := d.client.DnsCreate(xinnetDnsCreateReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("xinnet: error when create record: %w", err)
|
||||
}
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
d.recordIDs[token] = xinnetDnsCreateResp.Data
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
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("xinnet: could not find zone for domain %q: %w", domain, err)
|
||||
}
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
recordID, ok := d.recordIDs[token]
|
||||
d.recordIDsMu.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("xinnet: unknown record ID for '%s'", info.EffectiveFQDN)
|
||||
}
|
||||
|
||||
// REF: https://apidoc.xin.cn/doc-7283901
|
||||
xinnetDnsDeleteReq := &xinnetsdk.DnsDeleteRequest{
|
||||
DomainName: lo.ToPtr(dns01.UnFqdn(authZone)),
|
||||
RecordId: recordID,
|
||||
}
|
||||
if _, err := d.client.DnsDelete(xinnetDnsDeleteReq); err != nil {
|
||||
return fmt.Errorf("xinnet: error when delete record: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package xinnet
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type DnsCreateRequest struct {
|
||||
DomainName *string `json:"domainName,omitempty"`
|
||||
RecordName *string `json:"recordName,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
Value *string `json:"value,omitempty"`
|
||||
Line *string `json:"line,omitempty"`
|
||||
Ttl *int32 `json:"ttl,omitempty"`
|
||||
Mx *int32 `json:"mx,omitempty"`
|
||||
Status *int32 `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
type DnsCreateResponse struct {
|
||||
apiResponseBase
|
||||
Data *int64 `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) DnsCreate(req *DnsCreateRequest) (*DnsCreateResponse, error) {
|
||||
return c.DnsCreateWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) DnsCreateWithContext(ctx context.Context, req *DnsCreateRequest) (*DnsCreateResponse, error) {
|
||||
httpreq, err := c.newRequest("/dns/create/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetBody(req)
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &DnsCreateResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package xinnet
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type DnsDeleteRequest struct {
|
||||
DomainName *string `json:"domainName,omitempty"`
|
||||
RecordId *int64 `json:"recordId,omitempty"`
|
||||
}
|
||||
|
||||
type DnsDeleteResponse struct {
|
||||
apiResponseBase
|
||||
}
|
||||
|
||||
func (c *Client) DnsDelete(req *DnsDeleteRequest) (*DnsDeleteResponse, error) {
|
||||
return c.DnsDeleteWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) DnsDeleteWithContext(ctx context.Context, req *DnsDeleteRequest) (*DnsDeleteResponse, error) {
|
||||
httpreq, err := c.newRequest("/dns/delete/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetBody(req)
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &DnsDeleteResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package xinnet
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewClient(agentId, appSecret string) (*Client, error) {
|
||||
if agentId == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset agentId")
|
||||
}
|
||||
if appSecret == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset appSecret")
|
||||
}
|
||||
|
||||
client := resty.New().
|
||||
SetBaseURL("https://apiv2.xinnet.com/api").
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("User-Agent", "certimate").
|
||||
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
|
||||
// 生成时间戳
|
||||
timestamp := time.Now().UTC().Format("20060102T150405Z")
|
||||
|
||||
// 获取请求路径,注意结尾必须是 "/"
|
||||
urlPath := "/"
|
||||
if req.URL != nil {
|
||||
urlPath = req.URL.Path
|
||||
|
||||
if !strings.HasSuffix(urlPath, "/") {
|
||||
urlPath += "/"
|
||||
}
|
||||
}
|
||||
|
||||
// 获取请求方法
|
||||
requestMethod := req.Method
|
||||
|
||||
// 获取请求体
|
||||
requestBody := ""
|
||||
if req.Body != nil {
|
||||
reader, err := req.GetBody()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
|
||||
payloadb, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
requestBody = string(payloadb)
|
||||
}
|
||||
|
||||
// 计算签名
|
||||
algorithm := "HMAC-SHA256"
|
||||
stringToSign := algorithm + "\n" +
|
||||
timestamp + "\n" +
|
||||
requestMethod + "\n" +
|
||||
urlPath + "\n" +
|
||||
requestBody
|
||||
key := []byte(appSecret)
|
||||
h := hmac.New(sha256.New, key)
|
||||
h.Write([]byte(stringToSign))
|
||||
signature := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("timestamp", timestamp)
|
||||
req.Header.Set("authorization", fmt.Sprintf("%s Access=%s, Signature=%s", algorithm, agentId, signature))
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return &Client{client}, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetTimeout(timeout time.Duration) *Client {
|
||||
c.client.SetTimeout(timeout)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(path string) (*resty.Request, error) {
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset path")
|
||||
}
|
||||
|
||||
req := c.client.R()
|
||||
req.Method = http.MethodPost
|
||||
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='%s', msg='%s'", tcode, res.GetMessage())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package xinnet
|
||||
|
||||
type apiResponse interface {
|
||||
GetCode() string
|
||||
GetMessage() string
|
||||
GetRequestId() string
|
||||
}
|
||||
|
||||
type apiResponseBase struct {
|
||||
Code *string `json:"code,omitempty"`
|
||||
Message *string `json:"message,omitempty"`
|
||||
RequestId *string `json:"requestId,omitempty"`
|
||||
}
|
||||
|
||||
func (r *apiResponseBase) GetCode() string {
|
||||
if r.Code == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return *r.Code
|
||||
}
|
||||
|
||||
func (r *apiResponseBase) GetMessage() string {
|
||||
if r.Message == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return *r.Message
|
||||
}
|
||||
|
||||
func (r *apiResponseBase) GetRequestId() string {
|
||||
if r.RequestId == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return *r.RequestId
|
||||
}
|
||||
|
||||
var _ apiResponse = (*apiResponseBase)(nil)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
@@ -95,6 +95,7 @@ import AccessConfigFieldsProviderWangsu from "./AccessConfigFieldsProviderWangsu
|
||||
import AccessConfigFieldsProviderWebhook from "./AccessConfigFieldsProviderWebhook";
|
||||
import AccessConfigFieldsProviderWeComBot from "./AccessConfigFieldsProviderWeComBot";
|
||||
import AccessConfigFieldsProviderWestcn from "./AccessConfigFieldsProviderWestcn";
|
||||
import AccessConfigFieldsProviderXinnet from "./AccessConfigFieldsProviderXinnet";
|
||||
import AccessConfigFieldsProviderZeroSSL from "./AccessConfigFieldsProviderZeroSSL";
|
||||
|
||||
const providerComponentMap: Partial<Record<AccessProviderType, React.ComponentType<any>>> = {
|
||||
@@ -195,6 +196,7 @@ const providerComponentMap: Partial<Record<AccessProviderType, React.ComponentTy
|
||||
[ACCESS_PROVIDERS.WEBHOOK]: AccessConfigFieldsProviderWebhook,
|
||||
[ACCESS_PROVIDERS.WECOMBOT]: AccessConfigFieldsProviderWeComBot,
|
||||
[ACCESS_PROVIDERS.WESTCN]: AccessConfigFieldsProviderWestcn,
|
||||
[ACCESS_PROVIDERS.XINNET]: AccessConfigFieldsProviderXinnet,
|
||||
[ACCESS_PROVIDERS.ZEROSSL]: AccessConfigFieldsProviderZeroSSL,
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Tips from "@/components/Tips";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const AccessConfigFormFieldsProvider35cn = () => {
|
||||
@@ -17,13 +19,7 @@ const AccessConfigFormFieldsProvider35cn = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "username"]}
|
||||
initialValue={initialValues.username}
|
||||
label={t("access.form.35cn_username.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.35cn_username.tooltip") }}></span>}
|
||||
>
|
||||
<Form.Item name={[parentNamePath, "username"]} initialValue={initialValues.username} label={t("access.form.35cn_username.label")} rules={[formRule]}>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.35cn_username.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
@@ -32,10 +28,13 @@ const AccessConfigFormFieldsProvider35cn = () => {
|
||||
initialValue={initialValues.apiPassword}
|
||||
label={t("access.form.35cn_api_password.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.35cn_api_password.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.35cn_api_password.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("access.form.35cn_agent.guide") }}></span>} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Tips from "@/components/Tips";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const AccessConfigFormFieldsProviderWestcn = () => {
|
||||
@@ -17,13 +19,7 @@ const AccessConfigFormFieldsProviderWestcn = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "username"]}
|
||||
initialValue={initialValues.username}
|
||||
label={t("access.form.westcn_username.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.westcn_username.tooltip") }}></span>}
|
||||
>
|
||||
<Form.Item name={[parentNamePath, "username"]} initialValue={initialValues.username} label={t("access.form.westcn_username.label")} rules={[formRule]}>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.westcn_username.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
@@ -32,10 +28,13 @@ const AccessConfigFormFieldsProviderWestcn = () => {
|
||||
initialValue={initialValues.apiPassword}
|
||||
label={t("access.form.westcn_api_password.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.westcn_api_password.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.westcn_api_password.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("access.form.westcn_agent.guide") }}></span>} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { getI18n, useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Tips from "@/components/Tips";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const AccessConfigFormFieldsProviderXinnet = () => {
|
||||
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, "agentId"]} initialValue={initialValues.agentId} label={t("access.form.xinnet_agent_id.label")} rules={[formRule]}>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.xinnet_agent_id.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "apiPassword"]}
|
||||
initialValue={initialValues.apiPassword}
|
||||
label={t("access.form.xinnet_api_password.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.xinnet_api_password.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("access.form.xinnet_agent.guide") }}></span>} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {
|
||||
agentId: "",
|
||||
apiPassword: "",
|
||||
};
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
|
||||
const { t } = i18n;
|
||||
|
||||
return z.object({
|
||||
agentId: z.string().nonempty(t("access.form.xinnet_agent_id.placeholder")),
|
||||
apiPassword: z.string().nonempty(t("access.form.xinnet_api_password.placeholder")),
|
||||
});
|
||||
};
|
||||
|
||||
const _default = Object.assign(AccessConfigFormFieldsProviderXinnet, {
|
||||
getInitialValues,
|
||||
getSchema,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
@@ -111,6 +111,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
|
||||
WEBHOOK: "webhook",
|
||||
WECOMBOT: "wecombot",
|
||||
WESTCN: "westcn",
|
||||
XINNET: "xinnet",
|
||||
ZEROSSL: "zerossl",
|
||||
} as const);
|
||||
|
||||
@@ -214,6 +215,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
|
||||
[ACCESS_PROVIDERS.CMCCCLOUD, "provider.cmcccloud", "/imgs/providers/cmcccloud.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.WESTCN, "provider.westcn", "/imgs/providers/westcn.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS["35CN"], "provider.35cn", "/imgs/providers/35cn.png", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.XINNET, "provider.xinnet", "/imgs/providers/xinnet.png", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.POWERDNS, "provider.powerdns", "/imgs/providers/powerdns.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.TECHNITIUMDNS, "provider.technitiumdns", "/imgs/providers/technitiumdns.png", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.RFC2136, "provider.rfc2136", "/imgs/providers/rfc.png", [ACCESS_USAGES.DNS]],
|
||||
@@ -376,6 +378,7 @@ export const ACME_DNS01_PROVIDERS = Object.freeze({
|
||||
VOLCENGINE_DNS: `${ACCESS_PROVIDERS.VOLCENGINE}-dns`,
|
||||
VULTR: `${ACCESS_PROVIDERS.VULTR}`,
|
||||
WESTCN: `${ACCESS_PROVIDERS.WESTCN}`,
|
||||
XINNET: `${ACCESS_PROVIDERS.XINNET}`,
|
||||
} as const);
|
||||
|
||||
export type ACMEDns01ProviderType = (typeof ACME_DNS01_PROVIDERS)[keyof typeof ACME_DNS01_PROVIDERS];
|
||||
@@ -440,6 +443,7 @@ export const acmeDns01ProvidersMap: Map<ACMEDns01Provider["type"] | string, ACME
|
||||
[ACME_DNS01_PROVIDERS.UCLOUD_UDNR, "provider.ucloud.udnr"],
|
||||
[ACME_DNS01_PROVIDERS.WESTCN, "provider.westcn"],
|
||||
[ACME_DNS01_PROVIDERS["35CN"], "provider.35cn"],
|
||||
[ACME_DNS01_PROVIDERS.XINNET, "provider.xinnet"],
|
||||
[ACME_DNS01_PROVIDERS.POWERDNS, "provider.powerdns"],
|
||||
[ACME_DNS01_PROVIDERS.TECHNITIUMDNS, "provider.technitiumdns"],
|
||||
[ACME_DNS01_PROVIDERS.RFC2136, "provider.rfc2136"],
|
||||
|
||||
@@ -60,10 +60,9 @@
|
||||
"access.form.1panel_api_key.tooltip": "For more information, see <a href=\"https://docs.1panel.pro/dev_manual/api_manual/\" target=\"_blank\">https://docs.1panel.pro/dev_manual/api_manual/</a>",
|
||||
"access.form.35cn_username.label": "35.cn agent username",
|
||||
"access.form.35cn_username.placeholder": "Please enter West.cn agent username",
|
||||
"access.form.35cn_username.tooltip": "For more information, see <a href=\"https://console-docs.apipost.cn/preview/ab2c3103b22855ba/fac91d1e43fafb69?target_id=fa930623-3109-489d-9835-75fdfba07fbb\" target=\"_blank\">https://www.35.com/agent/mode-api.asp</a>",
|
||||
"access.form.35cn_api_password.label": "35.cn agent API password",
|
||||
"access.form.35cn_api_password.placeholder": "Please enter West.cn agent API password",
|
||||
"access.form.35cn_api_password.tooltip": "For more information, see <a href=\"https://console-docs.apipost.cn/preview/ab2c3103b22855ba/fac91d1e43fafb69?target_id=fa930623-3109-489d-9835-75fdfba07fbb\" target=\"_blank\">https://www.35.com/agent/mode-api.asp</a>",
|
||||
"access.form.35cn_agent.guide": "West.cn API only supports calls from agents. Learn more about this: <br><a href=\"https://console-docs.apipost.cn/preview/ab2c3103b22855ba/fac91d1e43fafb69?target_id=fa930623-3109-489d-9835-75fdfba07fbb\" target=\"_blank\">https://www.35.com/agent/mode-api.asp</a>",
|
||||
"access.form.acmeca_endpoint.label": "Endpoint",
|
||||
"access.form.acmeca_endpoint.placeholder": "Please enter endpoint",
|
||||
"access.form.acmeca_endpoint.tooltip": "For more information, see <a href=\"https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.1\" target=\"_blank\">https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.1</a>",
|
||||
@@ -632,12 +631,16 @@
|
||||
"access.form.webhook_preset_data.option.common.label": "General template",
|
||||
"access.form.wecombot_webhook_url.label": "WeCom bot Webhook URL",
|
||||
"access.form.wecombot_webhook_url.placeholder": "Please enter WeCom bot Webhook URL",
|
||||
"access.form.wecombot_webhook_url.tooltip": "For more information, see <a href=\"https://open.work.weixin.qq.com/help2/pc/18401#%E5%85%AD%E3%80%81%E7%BE%A4%E6%9C%BA%E5%99%A8%E4%BA%BAWebhook%E5%9C%B0%E5%9D%80\" target=\"_blank\">https://open.work.weixin.qq.com/help2/pc/18401</a>",
|
||||
"access.form.wecombot_webhook_url.tooltip": "For more information, see <a href=\"https://www.west.cn/CustomerCenter/doc/apiv2.html#12u3001u8eabu4efdu9a8cu8bc10a3ca20id3d12u3001u8eabu4efdu9a8cu8bc13e203ca3e\" target=\"_blank\">https://www.west.cn/CustomerCenter/doc/apiv2.html</a>",
|
||||
"access.form.westcn_username.label": "West.cn agent username",
|
||||
"access.form.westcn_username.placeholder": "Please enter West.cn agent username",
|
||||
"access.form.westcn_username.tooltip": "For more information, see <a href=\"https://www.west.cn/CustomerCenter/doc/apiv2.html#12u3001u8eabu4efdu9a8cu8bc10a3ca20id3d12u3001u8eabu4efdu9a8cu8bc13e203ca3e\" target=\"_blank\">https://www.west.cn/CustomerCenter/doc/apiv2.html</a>",
|
||||
"access.form.westcn_api_password.label": "West.cn agent API password",
|
||||
"access.form.westcn_api_password.placeholder": "Please enter West.cn agent API password",
|
||||
"access.form.westcn_api_password.tooltip": "For more information, see <a href=\"https://www.west.cn/CustomerCenter/doc/apiv2.html#12u3001u8eabu4efdu9a8cu8bc10a3ca20id3d12u3001u8eabu4efdu9a8cu8bc13e203ca3e\" target=\"_blank\">https://www.west.cn/CustomerCenter/doc/apiv2.html</a>",
|
||||
"access.form.westcn_agent.guide": "West.cn API only supports calls from agents. Learn more about this: <br><a href=\"https://www.west.cn/CustomerCenter/doc/apiv2.html#12u3001u8eabu4efdu9a8cu8bc10a3ca20id3d12u3001u8eabu4efdu9a8cu8bc13e203ca3e\" target=\"_blank\">https://www.west.cn/CustomerCenter/doc/apiv2.html</a>",
|
||||
"access.form.xinnet_agent_id.label": "Xinnet agent ID",
|
||||
"access.form.xinnet_agent_id.placeholder": "Please enter Xinnet agent ID",
|
||||
"access.form.xinnet_api_password.label": "Xinnet agent API password",
|
||||
"access.form.xinnet_api_password.placeholder": "Please enter Xinnet agent API password",
|
||||
"access.form.xinnet_agent.guide": "Xinnet API only supports calls from agents. Learn more about this: <br><a href=\"https://apidoc.xin.cn/doc-7283837\" target=\"_blank\">https://apidoc.xin.cn/doc-7283837</a>",
|
||||
"access.form.zerossl_eab.guide": "Learn more about using EAB key in ZeroSSL: <br><a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>"
|
||||
}
|
||||
|
||||
@@ -200,6 +200,7 @@
|
||||
"provider.webhook": "Webhook",
|
||||
"provider.wecombot": "WeCom Bot",
|
||||
"provider.westcn": "West.cn",
|
||||
"provider.xinnet": "Xinnet",
|
||||
"provider.zerossl": "ZeroSSL",
|
||||
|
||||
"provider.category.all": "All",
|
||||
|
||||
@@ -59,10 +59,9 @@
|
||||
"access.form.1panel_api_key.tooltip": "这是什么?请参阅 <a href=\"https://1panel.cn/docs/dev_manual/api_manual/\" target=\"_blank\">https://1panel.cn/docs/dev_manual/api_manual/</a>",
|
||||
"access.form.35cn_username.label": "三五互联代理商用户名",
|
||||
"access.form.35cn_username.placeholder": "请输入三五互联代理商用户名",
|
||||
"access.form.35cn_username.tooltip": "这是什么?请参阅 <a href=\"https://console-docs.apipost.cn/preview/ab2c3103b22855ba/fac91d1e43fafb69?target_id=fa930623-3109-489d-9835-75fdfba07fbb\" target=\"_blank\">https://www.35.com/agent/mode-api.asp</a>",
|
||||
"access.form.35cn_api_password.label": "三五互联代理商 API 密码",
|
||||
"access.form.35cn_api_password.placeholder": "请输入三五互联代理商 API 密码",
|
||||
"access.form.35cn_api_password.tooltip": "这是什么?请参阅 <a href=\"https://console-docs.apipost.cn/preview/ab2c3103b22855ba/fac91d1e43fafb69?target_id=fa930623-3109-489d-9835-75fdfba07fbb\" target=\"_blank\">https://www.35.com/agent/mode-api.asp</a>",
|
||||
"access.form.35cn_agent.guide": "三五互联 API 仅支持代理商调用。点击下方链接了解更多:<br><a href=\"https://console-docs.apipost.cn/preview/ab2c3103b22855ba/fac91d1e43fafb69?target_id=fa930623-3109-489d-9835-75fdfba07fbb\" target=\"_blank\">https://www.35.com/agent/mode-api.asp</a>",
|
||||
"access.form.acmeca_endpoint.label": "服务端点",
|
||||
"access.form.acmeca_endpoint.placeholder": "请输入服务端点",
|
||||
"access.form.acmeca_endpoint.tooltip": "这是什么?请参阅 <a href=\"https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.1\" target=\"_blank\">https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.1</a>",
|
||||
@@ -634,9 +633,13 @@
|
||||
"access.form.wecombot_webhook_url.tooltip": "这是什么?请参阅 <a href=\"https://open.work.weixin.qq.com/help2/pc/18401#%E5%85%AD%E3%80%81%E7%BE%A4%E6%9C%BA%E5%99%A8%E4%BA%BAWebhook%E5%9C%B0%E5%9D%80\" target=\"_blank\">https://open.work.weixin.qq.com/help2/pc/18401</a>",
|
||||
"access.form.westcn_username.label": "西部数码代理商用户名",
|
||||
"access.form.westcn_username.placeholder": "请输入西部数码代理商用户名",
|
||||
"access.form.westcn_username.tooltip": "这是什么?请参阅 <a href=\"https://www.west.cn/CustomerCenter/doc/apiv2.html#12u3001u8eabu4efdu9a8cu8bc10a3ca20id3d12u3001u8eabu4efdu9a8cu8bc13e203ca3e\" target=\"_blank\">https://www.west.cn/CustomerCenter/doc/apiv2.html</a>",
|
||||
"access.form.westcn_api_password.label": "西部数码代理商 API 密码",
|
||||
"access.form.westcn_api_password.placeholder": "请输入西部数码代理商 API 密码",
|
||||
"access.form.westcn_api_password.tooltip": "这是什么?请参阅 <a href=\"https://www.west.cn/CustomerCenter/doc/apiv2.html#12u3001u8eabu4efdu9a8cu8bc10a3ca20id3d12u3001u8eabu4efdu9a8cu8bc13e203ca3e\" target=\"_blank\">https://www.west.cn/CustomerCenter/doc/apiv2.html</a>",
|
||||
"access.form.westcn_agent.guide": "西部数码 API 仅支持代理商调用。点击下方链接了解更多:<br><a href=\"https://www.west.cn/CustomerCenter/doc/apiv2.html#12u3001u8eabu4efdu9a8cu8bc10a3ca20id3d12u3001u8eabu4efdu9a8cu8bc13e203ca3e\" target=\"_blank\">https://www.west.cn/CustomerCenter/doc/apiv2.html</a>",
|
||||
"access.form.xinnet_agent_id.label": "新网数码代理商编号",
|
||||
"access.form.xinnet_agent_id.placeholder": "请输入新网数码代理商编号",
|
||||
"access.form.xinnet_api_password.label": "新网数码代理商 API 密码",
|
||||
"access.form.xinnet_api_password.placeholder": "请输入新网数码代理商 API 密码",
|
||||
"access.form.xinnet_agent.guide": "新网数码 API 仅支持代理商调用。点击下方链接了解更多:<br><a href=\"https://apidoc.xin.cn/doc-7283837\" target=\"_blank\">https://apidoc.xin.cn/doc-7283837</a>",
|
||||
"access.form.zerossl_eab.guide": "点击下方链接了解如何获取 ZeroSSL EAB:<br><a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>"
|
||||
}
|
||||
|
||||
@@ -200,7 +200,9 @@
|
||||
"provider.webhook": "Webhook",
|
||||
"provider.wecombot": "企业微信群机器人",
|
||||
"provider.westcn": "西部数码",
|
||||
"provider.xinnet": "新网数码",
|
||||
"provider.zerossl": "ZeroSSL",
|
||||
|
||||
"provider.category.all": "全部",
|
||||
"provider.category.cdn": "CDN",
|
||||
"provider.category.storage": "文件存储",
|
||||
|
||||
Reference in New Issue
Block a user