feat(provider): new deployment provider: axisnow

This commit is contained in:
Fu Diwei
2026-08-05 23:37:57 +08:00
parent 7b3f4111c6
commit 94eb0b0489
20 changed files with 632 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
package deployers
import (
"fmt"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/pkg/core"
dplyimpl "github.com/certimate-go/certimate/pkg/core/deployer/providers/axisnow"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
func init() {
Registries.MustRegister(domain.DeploymentProviderTypeAxisNow, func(options *ProviderFactoryOptions) (core.Deployer, error) {
credentials := domain.AccessConfigForAxisNow{}
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
provider, err := dplyimpl.NewDeployer(&dplyimpl.DeployerConfig{
ApiToken: credentials.ApiToken,
})
return provider, err
})
}

View File

@@ -87,6 +87,10 @@ type AccessConfigForAWS struct {
SecretAccessKey string `json:"secretAccessKey"`
}
type AccessConfigForAxisNow struct {
ApiToken string `json:"apiToken"`
}
type AccessConfigForAzure struct {
TenantId string `json:"tenantId"`
ClientId string `json:"clientId"`

View File

@@ -25,6 +25,7 @@ const (
AccessProviderTypeAPISIX = AccessProviderType("apisix")
AccessProviderTypeArvanCloud = AccessProviderType("arvancloud")
AccessProviderTypeAWS = AccessProviderType("aws")
AccessProviderTypeAxisNow = AccessProviderType("axisnow")
AccessProviderTypeAzure = AccessProviderType("azure")
AccessProviderTypeBaiduCloud = AccessProviderType("baiducloud")
AccessProviderTypeBaishan = AccessProviderType("baishan")
@@ -342,6 +343,7 @@ const (
DeploymentProviderTypeAWSCloudFront = DeploymentProviderType(AccessProviderTypeAWS + "-cloudfront")
DeploymentProviderTypeAWSIAM = DeploymentProviderType(AccessProviderTypeAWS + "-iam")
DeploymentProviderTypeAWSNLB = DeploymentProviderType(AccessProviderTypeAWS + "-nlb")
DeploymentProviderTypeAxisNow = DeploymentProviderType(AccessProviderTypeAxisNow)
DeploymentProviderTypeAzureKeyVault = DeploymentProviderType(AccessProviderTypeAzure + "-keyvault")
DeploymentProviderTypeBaiduCloudAppBLB = DeploymentProviderType(AccessProviderTypeBaiduCloud + "-appblb")
DeploymentProviderTypeBaiduCloudBLB = DeploymentProviderType(AccessProviderTypeBaiduCloud + "-blb")

View File

@@ -0,0 +1,138 @@
package axisnow
import (
"context"
"fmt"
"log/slog"
"time"
"github.com/samber/lo"
"github.com/certimate-go/certimate/pkg/core"
axisnowsdk "github.com/certimate-go/certimate/pkg/sdk3rd/axisnow"
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
)
type (
Provider = core.Certmgr
UploadResult = core.CertmgrUploadResult
ReplaceResult = core.CertmgrReplaceResult
)
type CertmgrConfig struct {
// AxisNow API 令牌。
ApiToken string `json:"apiToken"`
}
type Certmgr struct {
config *CertmgrConfig
logger *slog.Logger
sdkClient *axisnowsdk.Client
}
var _ Provider = (*Certmgr)(nil)
func NewCertmgr(config *CertmgrConfig) (*Certmgr, error) {
if config == nil {
return nil, fmt.Errorf("the configuration of the certmgr provider is nil")
}
client, err := createSDKClient(config.ApiToken)
if err != nil {
return nil, fmt.Errorf("could not create client: %w", err)
}
return &Certmgr{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (c *Certmgr) SetLogger(logger *slog.Logger) {
if logger == nil {
c.logger = slog.New(slog.DiscardHandler)
} else {
c.logger = logger
}
}
func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*UploadResult, error) {
// 查询证书列表,避免重复上传
// REF: https://developers.axisnow.io/api-reference/main/client/v1/#/operations/certificate-setting-list-certificates
listCertificatesPage := 1
listCertificatesPerPage := 100
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
listCertificatesReq := &axisnowsdk.ListCertificatesRequest{
Page: lo.ToPtr(listCertificatesPage),
PerPage: lo.ToPtr(listCertificatesPerPage),
}
listCertificatesResp, err := c.sdkClient.ListCertificatesWithContext(ctx, listCertificatesReq)
c.logger.Debug("sdk request 'Certificates.ListCertificates'", slog.Any("request", listCertificatesReq), slog.Any("response", listCertificatesResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'Certificates.ListCertificates': %w", err)
} else {
if listCertificatesResp.Results == nil {
break
}
for _, certItem := range listCertificatesResp.Results {
if xcert.EqualCertificatesFromPEM(certPEM, certItem.Certificate) {
// 如果已存在相同证书,直接返回
c.logger.Info("ssl certificate already exists")
return &UploadResult{
CertId: certItem.UUID,
CertName: certItem.Name,
}, nil
}
}
if len(listCertificatesResp.Results) < listCertificatesPerPage ||
(listCertificatesResp.ResultInfo != nil && listCertificatesPage*listCertificatesPerPage >= listCertificatesResp.ResultInfo.TotalCount) {
break
}
listCertificatesPage++
}
}
// 添加证书
// REF: https://developers.axisnow.io/api-reference/main/client/v1/#/operations/certificate-setting-add-certificate
addCertificateReq := &axisnowsdk.AddCertificateRequest{
Type: lo.ToPtr("upload"),
Name: lo.ToPtr(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
Certificate: lo.ToPtr(certPEM),
PrivateKey: lo.ToPtr(privkeyPEM),
}
addCertificateResp, err := c.sdkClient.AddCertificateWithContext(ctx, addCertificateReq)
c.logger.Debug("sdk request 'Certificates.AddCertificate'", slog.Any("request", addCertificateReq), slog.Any("response", addCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'Certificates.AddCertificate': %w", err)
}
return &UploadResult{
CertId: addCertificateResp.Result.UUID,
CertName: addCertificateResp.Result.Name,
}, nil
}
func (c *Certmgr) Replace(ctx context.Context, certIdOrName string, certPEM, privkeyPEM string) (*ReplaceResult, error) {
return nil, core.ErrUnsupported
}
func createSDKClient(apiToken string) (*axisnowsdk.Client, error) {
client, err := axisnowsdk.NewClient(
axisnowsdk.WithApiToken(apiToken),
)
if err != nil {
return nil, err
}
return client, nil
}

View File

@@ -0,0 +1,44 @@
package axisnow_test
import (
"testing"
"github.com/stretchr/testify/require"
impl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/axisnow"
it "github.com/certimate-go/certimate/pkg/core/certmgr/testing"
)
var (
fp = it.Args("AXISNOW_")
fTestCertPath string
fTestKeyPath string
fApiToken string
)
func init() {
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
fp.DefineString(&fApiToken, "APITOKEN")
}
/*
Shell command to run this test:
go test -v ./axisnow_test.go -args \
--AXISNOW_TESTCERTPATH="/path/to/your-test-cert.pem" \
--AXISNOW_TESTKEYPATH="/path/to/your-test-key.pem" \
--AXISNOW_APITOKEN="your-api-token"
*/
func TestProvider(t *testing.T) {
fp.Parse()
t.Run("Upload", func(t *testing.T) {
provider, err := impl.NewCertmgr(&impl.CertmgrConfig{
ApiToken: fApiToken,
})
require.NoError(t, err)
it.TestUpload(t, provider, it.TestUploadArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
})
}

View File

@@ -0,0 +1,69 @@
package axisnow
import (
"context"
"fmt"
"log/slog"
"github.com/certimate-go/certimate/pkg/core"
cmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/axisnow"
)
type (
Provider = core.Deployer
DeployResult = core.DeployerDeployResult
)
type DeployerConfig struct {
// AxisNow API 令牌。
ApiToken string `json:"apiToken"`
}
type Deployer struct {
config *DeployerConfig
logger *slog.Logger
sdkCertmgr core.Certmgr
}
var _ Provider = (*Deployer)(nil)
func NewDeployer(config *DeployerConfig) (*Deployer, error) {
if config == nil {
return nil, fmt.Errorf("the configuration of the deployer provider is nil")
}
pcertmgr, err := cmgrimpl.NewCertmgr(&cmgrimpl.CertmgrConfig{
ApiToken: config.ApiToken,
})
if err != nil {
return nil, fmt.Errorf("could not create certmgr: %w", err)
}
return &Deployer{
config: config,
logger: slog.Default(),
sdkCertmgr: pcertmgr,
}, nil
}
func (d *Deployer) SetLogger(logger *slog.Logger) {
if logger == nil {
d.logger = slog.New(slog.DiscardHandler)
} else {
d.logger = logger
}
d.sdkCertmgr.SetLogger(logger)
}
func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) {
// 上传证书
upres, err := d.sdkCertmgr.Upload(ctx, certPEM, privkeyPEM)
if err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
return &DeployResult{}, nil
}

View File

@@ -0,0 +1,40 @@
package axisnow
import (
"context"
"net/http"
)
type AddCertificateRequest struct {
Name *string `json:"name,omitempty"`
Type *string `json:"type,omitempty"`
Certificate *string `json:"certificate,omitempty"`
PrivateKey *string `json:"private_key,omitempty"`
}
type AddCertificateResponse struct {
sdkResponseBase
Result *Certificate `json:"result,omitempty"`
}
func (c *Client) AddCertificate(req *AddCertificateRequest) (*AddCertificateResponse, error) {
return c.AddCertificateWithContext(context.Background(), req)
}
func (c *Client) AddCertificateWithContext(ctx context.Context, req *AddCertificateRequest) (*AddCertificateResponse, error) {
httpreq, err := c.newRequest(http.MethodPost, "/certificates")
if err != nil {
return nil, err
} else {
httpreq.SetBody(req)
httpreq.SetContext(ctx)
}
result := &AddCertificateResponse{}
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
return result, err
}
return result, nil
}

View File

@@ -0,0 +1,53 @@
package axisnow
import (
"context"
"net/http"
qs "github.com/google/go-querystring/query"
)
type ListCertificatesRequest struct {
Filter *string `json:"filter,omitempty" url:"filter,omitempty"`
Page *int `json:"page,omitempty" url:"page,omitempty"`
PerPage *int `json:"per_page,omitempty" url:"per_page,omitempty"`
}
type ListCertificatesResponse struct {
sdkResponseBase
ResultInfo *struct {
Page int `json:"page"`
PerPage int `json:"per_page"`
Count int `json:"count"`
TotalCount int `json:"total_count"`
SubscribedTotal int `json:"subscribed_total"`
} `json:"result_info,omitempty"`
Results []*Certificate `json:"result,omitempty"`
}
func (c *Client) ListCertificates(req *ListCertificatesRequest) (*ListCertificatesResponse, error) {
return c.ListCertificatesWithContext(context.Background(), req)
}
func (c *Client) ListCertificatesWithContext(ctx context.Context, req *ListCertificatesRequest) (*ListCertificatesResponse, error) {
httpreq, err := c.newRequest(http.MethodGet, "/certificates")
if err != nil {
return nil, err
} else {
values, err := qs.Values(req)
if err != nil {
return nil, err
}
httpreq.SetQueryParamsFromValues(values)
httpreq.SetContext(ctx)
}
result := &ListCertificatesResponse{}
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
return result, err
}
return result, nil
}

View File

@@ -0,0 +1,100 @@
// A simple SDK client for AxisNow.
// API documentation: https://developers.axisnow.io/api-reference/main/client/v1/#/
package axisnow
import (
"encoding/json"
"fmt"
"time"
"github.com/go-resty/resty/v2"
"github.com/certimate-go/certimate/internal/app"
)
type Client struct {
rc *resty.Client
}
func NewClient(optFns ...OptionsFunc) (*Client, error) {
opts := &Options{}
for _, fn := range optFns {
fn(opts)
}
if opts.ApiToken == "" {
return nil, fmt.Errorf("sdkerr: unset apiToken")
}
httper := resty.New().
SetBaseURL("https://api.axisnow.io/client/v1").
SetHeader("Accept", "application/json").
SetHeader("Authorization", "Bearer "+opts.ApiToken).
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent)
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {
c.rc.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.rc.R()
req.Method = method
req.URL = path
// WARN:
// DO NOT CALL `req.SetResult` or `req.SetError` AGAIN! USE `doRequestWithResult` INSTEAD.
return req, nil
}
func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
if req == nil {
return nil, fmt.Errorf("sdkerr: nil request")
}
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 sdkResponse) (*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 (resp: %s)", err, resp.String())
} else {
if err := res.GetAPIError(); err != nil {
return resp, fmt.Errorf("sdkerr: api error: %s", err.Error())
}
}
}
return resp, nil
}

View File

@@ -0,0 +1,29 @@
package axisnow
type Certificate struct {
UUID string `json:"uuid,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
SubjectName []string `json:"subject_name,omitempty"`
Issuer CertificateIssuer `json:"issuer,omitempty"`
Subject CertificateSubject `json:"subject,omitempty"`
IssueTime int64 `json:"issue_time,omitempty"`
ExpireTime int64 `json:"expire_time,omitempty"`
Certificate string `json:"certificate,omitempty"`
ReferencedCount int `json:"referenced_count,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
type CertificateIssuer struct {
Country string `json:"country,omitempty"`
Organization string `json:"organization,omitempty"`
OrganizationUnit string `json:"organization_unit,omitempty"`
CommonName string `json:"common_name,omitempty"`
}
type CertificateSubject struct {
Organization string `json:"organization,omitempty"`
OrganizationUnit string `json:"organization_unit,omitempty"`
CommonName string `json:"common_name,omitempty"`
}

View File

@@ -0,0 +1,13 @@
package axisnow
type Options struct {
ApiToken string
}
type OptionsFunc func(*Options)
func WithApiToken(apiToken string) OptionsFunc {
return func(o *Options) {
o.ApiToken = apiToken
}
}

View File

@@ -0,0 +1,46 @@
package axisnow
import (
"fmt"
"strings"
)
type sdkResponse interface {
GetAPIError() error
GetSuccess() bool
}
type sdkResponseBase struct {
Success bool `json:"success"`
Errors sdkAPIErrors `json:"errors,omitempty"`
}
type sdkAPIError struct {
Code int `json:"code"`
Message string `json:"message"`
}
type sdkAPIErrors []sdkAPIError
func (e sdkAPIErrors) Error() string {
builder := &strings.Builder{}
for _, item := range e {
fmt.Fprintf(builder, "[%d] %s", item.Code, item.Message)
}
return strings.TrimSpace(builder.String())
}
func (r *sdkResponseBase) GetSuccess() bool {
return r.Success
}
func (r *sdkResponseBase) GetAPIError() error {
if len(r.Errors) > 0 {
return r.Errors
}
return nil
}
var _ sdkResponse = (*sdkResponseBase)(nil)

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -14,6 +14,7 @@ import AccessConfigFieldsProviderAliyun from "./AccessConfigFieldsProviderAliyun
import AccessConfigFieldsProviderAPISIX from "./AccessConfigFieldsProviderAPISIX";
import AccessConfigFieldsProviderArvanCloud from "./AccessConfigFieldsProviderArvanCloud";
import AccessConfigFieldsProviderAWS from "./AccessConfigFieldsProviderAWS";
import AccessConfigFieldsProviderAxisNow from "./AccessConfigFieldsProviderAxisNow";
import AccessConfigFieldsProviderAzure from "./AccessConfigFieldsProviderAzure";
import AccessConfigFieldsProviderBaiduCloud from "./AccessConfigFieldsProviderBaiduCloud";
import AccessConfigFieldsProviderBaishan from "./AccessConfigFieldsProviderBaishan";
@@ -141,6 +142,7 @@ const providerComponentMap: Partial<Record<AccessProviderType, React.ComponentTy
[ACCESS_PROVIDERS.APISIX]: AccessConfigFieldsProviderAPISIX,
[ACCESS_PROVIDERS.ARVANCLOUD]: AccessConfigFieldsProviderArvanCloud,
[ACCESS_PROVIDERS.AWS]: AccessConfigFieldsProviderAWS,
[ACCESS_PROVIDERS.AXISNOW]: AccessConfigFieldsProviderAxisNow,
[ACCESS_PROVIDERS.AZURE]: AccessConfigFieldsProviderAzure,
[ACCESS_PROVIDERS.BAIDUCLOUD]: AccessConfigFieldsProviderBaiduCloud,
[ACCESS_PROVIDERS.BAISHAN]: AccessConfigFieldsProviderBaishan,

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 AccessConfigFormFieldsProviderAxisNow = () => {
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, "apiToken"]}
initialValue={initialValues.apiToken}
label={t("access.form.axisnow_api_token.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.axisnow_api_token.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.axisnow_api_token.placeholder")} />
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
apiToken: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
const { t: _ } = i18n;
return z.object({
apiToken: z.string().nonempty(),
});
};
const _default = Object.assign(AccessConfigFormFieldsProviderAxisNow, {
getInitialValues,
getSchema,
});
export default _default;

View File

@@ -29,6 +29,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
APISIX: "apisix",
ARVANCLOUD: "arvancloud",
AWS: "aws",
AXISNOW: "axisnow",
AZURE: "azure",
BAIDUCLOUD: "baiducloud",
BAISHAN: "baishan",
@@ -216,6 +217,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.BAOTAPANELGO, "provider.baotapanelgo", "/imgs/providers/baota.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.BAOTAWAF, "provider.baotawaf", "/imgs/providers/baota.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.DOKPLOY, "provider.dokploy", "/imgs/providers/dokploy.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.AXISNOW, "provider.axisnow", "/imgs/providers/axisnow.png", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.CDNFLY, "provider.cdnfly", "/imgs/providers/cdnfly.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.GOEDGE, "provider.goedge", "/imgs/providers/goedge.png", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.KONG, "provider.kong", "/imgs/providers/kong.svg", [ACCESS_USAGES.HOSTING]],
@@ -636,6 +638,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({
AWS_CLOUDFRONT: `${ACCESS_PROVIDERS.AWS}-cloudfront`,
AWS_IAM: `${ACCESS_PROVIDERS.AWS}-iam`,
AWS_NLB: `${ACCESS_PROVIDERS.AWS}-nlb`,
AXISNOW: `${ACCESS_PROVIDERS.AXISNOW}`,
AZURE_KEYVAULT: `${ACCESS_PROVIDERS.AZURE}-keyvault`,
BAIDUCLOUD_APPBLB: `${ACCESS_PROVIDERS.BAIDUCLOUD}-appblb`,
BAIDUCLOUD_BLB: `${ACCESS_PROVIDERS.BAIDUCLOUD}-blb`,
@@ -928,6 +931,7 @@ export const deploymentProvidersMap: Map<DeploymentProvider["type"] | string, De
[DEPLOYMENT_PROVIDERS.RAINYUN_SSLCENTER, "provider.rainyun_sslcenter_upload", DEPLOYMENT_CATEGORIES.SSL],
[DEPLOYMENT_PROVIDERS.UNICLOUD_WEBHOST, "provider.unicloud_webhost", DEPLOYMENT_CATEGORIES.WEBSITE],
[DEPLOYMENT_PROVIDERS.MOHUA_MVH, "provider.mohua_mvh", DEPLOYMENT_CATEGORIES.WEBSITE],
[DEPLOYMENT_PROVIDERS.AXISNOW, "provider.axisnow", DEPLOYMENT_CATEGORIES.CDN],
[DEPLOYMENT_PROVIDERS.CDNFLY, "provider.cdnfly", DEPLOYMENT_CATEGORIES.CDN],
[DEPLOYMENT_PROVIDERS.FLEXCDN, "provider.flexcdn", DEPLOYMENT_CATEGORIES.CDN],
[DEPLOYMENT_PROVIDERS.GOEDGE, "provider.goedge", DEPLOYMENT_CATEGORIES.CDN],

View File

@@ -243,6 +243,11 @@
"placeholder": "Please enter AWS SecretAccessKey",
"tooltip": "For more information, see <a href=\"https://docs.aws.amazon.com/en_us/IAM/latest/UserGuide/id_credentials_access-keys.html\" target=\"_blank\">https://docs.aws.amazon.com/en_us/IAM/latest/UserGuide/id_credentials_access-keys.html</a>"
},
"axisnow_api_token": {
"label": "AxisNow API token",
"placeholder": "Please enter AxisNow API token",
"tooltip": "For more information, see <a href=\"https://www.axisnow.io/zh/docs/edge/tenant/api-tokens\" target=\"_blank\">https://www.axisnow.io/zh/docs/edge/tenant/api-tokens</a>"
},
"azure_tenant_id": {
"label": "Azure tenant ID",
"placeholder": "Please enter Azure tenant ID",

View File

@@ -69,6 +69,7 @@
"aws_lightsail": "AWS - Lightsail",
"aws_nlb": "AWS - NLB (Network Load Balancer)",
"aws_route53": "AWS - Route53",
"axisnow": "AxisNow",
"azure": "Azure (Microsoft Azure)",
"azure_dns": "Azure - DNS",
"azure_keyvault": "Azure - KeyVault",

View File

@@ -243,6 +243,11 @@
"placeholder": "请输入 AWS SecretAccessKey",
"tooltip": "这是什么?请参阅 <a href=\"https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/id_credentials_access-keys.html\" target=\"_blank\">https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/id_credentials_access-keys.html</a>"
},
"axisnow_api_token": {
"label": "AxisNow API 令牌",
"placeholder": "请输入 AxisNow API 令牌",
"tooltip": "这是什么?请参阅 <a href=\"https://www.axisnow.io/zh/docs/edge/tenant/api-tokens\" target=\"_blank\">https://www.axisnow.io/zh/docs/edge/tenant/api-tokens</a>"
},
"azure_tenant_id": {
"label": "Azure 租户 ID",
"placeholder": "请输入 Azure 租户 ID",

View File

@@ -69,6 +69,7 @@
"aws_lightsail": "AWS - Lightsail",
"aws_nlb": "AWS - 网络负载均衡器 NLB",
"aws_route53": "AWS - Route53",
"axisnow": "AxisNow",
"azure": "AzureMicrosoft Azure",
"azure_dns": "Azure - DNS",
"azure_keyvault": "Azure - KeyVault",