mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-18 09:21:56 +08:00
commit
d15a407079
10
.github/ISSUE_TEMPLATE/3-questions.yml
vendored
10
.github/ISSUE_TEMPLATE/3-questions.yml
vendored
@ -1,6 +1,6 @@
|
||||
name: "❓ Questions"
|
||||
description: "遇到了困难需要求助? / Have problem in use and need help?"
|
||||
title: "[Feature] 简要描述你遇到的问题"
|
||||
title: "简要描述你遇到的问题"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@ -19,6 +19,14 @@ body:
|
||||
3. Yes, I've read the [documentation](https://docs.certimate.me/en/) and didn't find any similar.
|
||||
4. Please describe the problem in detail according to the template specification, otherwise the issue will be closed directly.
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: 软件版本 / Release Version
|
||||
description: 请提供 Certimate 的具体版本。 / Please provide the specific version of Certimate.
|
||||
placeholder: (e.g. v1.0.0)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 问题描述 / Description
|
||||
|
3
go.mod
3
go.mod
@ -8,6 +8,7 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.9.0
|
||||
github.com/Edgio/edgio-api v0.0.0-workspace
|
||||
github.com/G-Core/gcorelabscdn-go v1.0.28
|
||||
github.com/alibabacloud-go/alb-20200616/v2 v2.2.8
|
||||
github.com/alibabacloud-go/cas-20200407/v3 v3.0.4
|
||||
@ -211,6 +212,8 @@ require (
|
||||
modernc.org/sqlite v1.36.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/Edgio/edgio-api v0.0.0-workspace => ./internal/pkg/vendors/edgio-sdk/edgio-api@v0.0.0-workspace
|
||||
|
||||
replace gitlab.ecloud.com/ecloud/ecloudsdkcore v1.0.0 => ./internal/pkg/vendors/cmcc-sdk/ecloudsdkcore@v1.0.0
|
||||
|
||||
replace gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1 => ./internal/pkg/vendors/cmcc-sdk/ecloudsdkclouddns@v1.0.1
|
||||
|
2
go.sum
2
go.sum
@ -498,6 +498,8 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
|
@ -49,6 +49,7 @@ import (
|
||||
pLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local"
|
||||
pQiniuCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn"
|
||||
pQiniuPili "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-pili"
|
||||
pRainYunRCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/rainyun-rcdn"
|
||||
pSafeLine "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/safeline"
|
||||
pSSH "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh"
|
||||
pTencentCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cdn"
|
||||
@ -73,6 +74,7 @@ import (
|
||||
pVolcEngineImageX "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-imagex"
|
||||
pVolcEngineLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-live"
|
||||
pVolcEngineTOS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-tos"
|
||||
pWangsuCDNPro "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-cdnpro"
|
||||
pWebhook "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/maputil"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/sliceutil"
|
||||
@ -681,6 +683,27 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) {
|
||||
}
|
||||
}
|
||||
|
||||
case domain.DeployProviderTypeRainYunRCDN:
|
||||
{
|
||||
access := domain.AccessConfigForRainYun{}
|
||||
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
switch options.Provider {
|
||||
case domain.DeployProviderTypeTencentCloudCDN:
|
||||
deployer, err := pRainYunRCDN.NewDeployer(&pRainYunRCDN.DeployerConfig{
|
||||
ApiKey: access.ApiKey,
|
||||
InstanceId: maputil.GetInt32(options.ProviderDeployConfig, "instanceId"),
|
||||
Domain: maputil.GetString(options.ProviderDeployConfig, "domain"),
|
||||
})
|
||||
return deployer, err
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case domain.DeployProviderTypeSafeLine:
|
||||
{
|
||||
access := domain.AccessConfigForSafeLine{}
|
||||
@ -981,6 +1004,30 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) {
|
||||
}
|
||||
}
|
||||
|
||||
case domain.DeployProviderTypeWangsuCDNPro:
|
||||
{
|
||||
access := domain.AccessConfigForWangsu{}
|
||||
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
switch options.Provider {
|
||||
case domain.DeployProviderTypeWangsuCDNPro:
|
||||
deployer, err := pWangsuCDNPro.NewDeployer(&pWangsuCDNPro.DeployerConfig{
|
||||
AccessKeyId: access.AccessKeyId,
|
||||
AccessKeySecret: access.AccessKeySecret,
|
||||
Environment: maputil.GetOrDefaultString(options.ProviderDeployConfig, "environment", "production"),
|
||||
Domain: maputil.GetString(options.ProviderDeployConfig, "domain"),
|
||||
CertificateId: maputil.GetString(options.ProviderDeployConfig, "certificateId"),
|
||||
WebhookId: maputil.GetString(options.ProviderDeployConfig, "webhookId"),
|
||||
})
|
||||
return deployer, err
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case domain.DeployProviderTypeWebhook:
|
||||
{
|
||||
access := domain.AccessConfigForWebhook{}
|
||||
|
@ -228,6 +228,11 @@ type AccessConfigForVolcEngine struct {
|
||||
SecretAccessKey string `json:"secretAccessKey"`
|
||||
}
|
||||
|
||||
type AccessConfigForWangsu struct {
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
AccessKeySecret string `json:"accessKeySecret"`
|
||||
}
|
||||
|
||||
type AccessConfigForWebhook struct {
|
||||
Url string `json:"url"`
|
||||
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
|
||||
|
@ -14,6 +14,8 @@ const (
|
||||
NotifyChannelTypeEmail = NotifyChannelType("email")
|
||||
NotifyChannelTypeGotify = NotifyChannelType("gotify")
|
||||
NotifyChannelTypeLark = NotifyChannelType("lark")
|
||||
NotifyChannelTypeMattermost = NotifyChannelType("mattermost")
|
||||
NotifyChannelTypePushover = NotifyChannelType("pushover")
|
||||
NotifyChannelTypePushPlus = NotifyChannelType("pushplus")
|
||||
NotifyChannelTypeServerChan = NotifyChannelType("serverchan")
|
||||
NotifyChannelTypeTelegram = NotifyChannelType("telegram")
|
||||
|
@ -61,6 +61,7 @@ const (
|
||||
AccessProviderTypeUpyun = AccessProviderType("upyun")
|
||||
AccessProviderTypeVercel = AccessProviderType("vercel")
|
||||
AccessProviderTypeVolcEngine = AccessProviderType("volcengine")
|
||||
AccessProviderTypeWangsu = AccessProviderType("wangsu")
|
||||
AccessProviderTypeWebhook = AccessProviderType("webhook")
|
||||
AccessProviderTypeWestcn = AccessProviderType("westcn")
|
||||
AccessProviderTypeZeroSSL = AccessProviderType("zerossl")
|
||||
@ -186,6 +187,7 @@ const (
|
||||
DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn")
|
||||
DeployProviderTypeQiniuKodo = DeployProviderType("qiniu-kodo")
|
||||
DeployProviderTypeQiniuPili = DeployProviderType("qiniu-pili")
|
||||
DeployProviderTypeRainYunRCDN = DeployProviderType("rainyun-rcdn")
|
||||
DeployProviderTypeSafeLine = DeployProviderType("safeline")
|
||||
DeployProviderTypeSSH = DeployProviderType("ssh")
|
||||
DeployProviderTypeTencentCloudCDN = DeployProviderType("tencentcloud-cdn")
|
||||
@ -211,5 +213,6 @@ const (
|
||||
DeployProviderTypeVolcEngineImageX = DeployProviderType("volcengine-imagex")
|
||||
DeployProviderTypeVolcEngineLive = DeployProviderType("volcengine-live")
|
||||
DeployProviderTypeVolcEngineTOS = DeployProviderType("volcengine-tos")
|
||||
DeployProviderTypeWangsuCDNPro = DeployProviderType("wangsu-cdnpro")
|
||||
DeployProviderTypeWebhook = DeployProviderType("webhook")
|
||||
)
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
|
||||
pGotify "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/gotify"
|
||||
pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark"
|
||||
pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost"
|
||||
pPushover "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushover"
|
||||
pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus"
|
||||
pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan"
|
||||
pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram"
|
||||
@ -59,6 +61,19 @@ func createNotifier(channel domain.NotifyChannelType, channelConfig map[string]a
|
||||
WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"),
|
||||
})
|
||||
|
||||
case domain.NotifyChannelTypeMattermost:
|
||||
return pMattermost.NewNotifier(&pMattermost.NotifierConfig{
|
||||
ServerUrl: maputil.GetString(channelConfig, "serverUrl"),
|
||||
ChannelId: maputil.GetString(channelConfig, "channelId"),
|
||||
Username: maputil.GetString(channelConfig, "username"),
|
||||
Password: maputil.GetString(channelConfig, "password"),
|
||||
})
|
||||
case domain.NotifyChannelTypePushover:
|
||||
return pPushover.NewNotifier(&pPushover.NotifierConfig{
|
||||
Token: maputil.GetString(channelConfig, "token"),
|
||||
User: maputil.GetString(channelConfig, "user"),
|
||||
})
|
||||
|
||||
case domain.NotifyChannelTypePushPlus:
|
||||
return pPushPlus.NewNotifier(&pPushPlus.NotifierConfig{
|
||||
Token: maputil.GetString(channelConfig, "token"),
|
||||
|
@ -4,12 +4,12 @@ import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
edgio "github.com/Edgio/edgio-api/applications/v7"
|
||||
edgiodtos "github.com/Edgio/edgio-api/applications/v7/dtos"
|
||||
xerrors "github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
|
||||
edgsdk "github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7"
|
||||
edgsdkdtos "github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7/dtos"
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
@ -24,7 +24,7 @@ type DeployerConfig struct {
|
||||
type DeployerProvider struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *edgsdk.EdgioClient
|
||||
sdkClient *edgio.EdgioClient
|
||||
}
|
||||
|
||||
var _ deployer.Deployer = (*DeployerProvider)(nil)
|
||||
@ -64,7 +64,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
|
||||
// 上传 TLS 证书
|
||||
// REF: https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts
|
||||
uploadTlsCertReq := edgsdkdtos.UploadTlsCertRequest{
|
||||
uploadTlsCertReq := edgiodtos.UploadTlsCertRequest{
|
||||
EnvironmentID: d.config.EnvironmentId,
|
||||
PrimaryCert: privateCertPem,
|
||||
IntermediateCert: intermediateCertPem,
|
||||
@ -79,7 +79,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
return &deployer.DeployResult{}, nil
|
||||
}
|
||||
|
||||
func createSdkClient(clientId, clientSecret string) (*edgsdk.EdgioClient, error) {
|
||||
client := edgsdk.NewEdgioClient(clientId, clientSecret, "", "")
|
||||
func createSdkClient(clientId, clientSecret string) (*edgio.EdgioClient, error) {
|
||||
client := edgio.NewEdgioClient(clientId, clientSecret, "", "")
|
||||
return client, nil
|
||||
}
|
||||
|
@ -100,9 +100,15 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
SSlEnabled: true,
|
||||
SSLData: int(updateResourceCertId),
|
||||
ProxySSLEnabled: getResourceResp.ProxySSLEnabled,
|
||||
ProxySSLCA: &getResourceResp.ProxySSLCA,
|
||||
ProxySSLData: &getResourceResp.ProxySSLData,
|
||||
Options: getResourceResp.Options,
|
||||
}
|
||||
if getResourceResp.ProxySSLCA != 0 {
|
||||
updateResourceReq.ProxySSLCA = &getResourceResp.ProxySSLCA
|
||||
}
|
||||
if getResourceResp.ProxySSLData != 0 {
|
||||
updateResourceReq.ProxySSLData = &getResourceResp.ProxySSLData
|
||||
}
|
||||
if getResourceResp.Options != nil {
|
||||
updateResourceReq.Options = getResourceResp.Options
|
||||
}
|
||||
updateResourceResp, err := d.sdkClient.Update(context.TODO(), d.config.ResourceId, updateResourceReq)
|
||||
d.logger.Debug("sdk request 'resources.Update'", slog.Int64("resourceId", d.config.ResourceId), slog.Any("request", updateResourceReq), slog.Any("response", updateResourceResp))
|
||||
|
@ -0,0 +1,102 @@
|
||||
package rainyunrcdn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
|
||||
xerrors "github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/rainyun-sslcenter"
|
||||
rainyunsdk "github.com/usual2970/certimate/internal/pkg/vendors/rainyun-sdk"
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
// 雨云 API 密钥。
|
||||
ApiKey string `json:"apiKey"`
|
||||
// RCDN 实例 ID。
|
||||
InstanceId int32 `json:"instanceId"`
|
||||
// 加速域名(支持泛域名)。
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
type DeployerProvider struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *rainyunsdk.Client
|
||||
sslUploader uploader.Uploader
|
||||
}
|
||||
|
||||
var _ deployer.Deployer = (*DeployerProvider)(nil)
|
||||
|
||||
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
|
||||
if config == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
client, err := createSdkClient(config.ApiKey)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||
}
|
||||
|
||||
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
|
||||
ApiKey: config.ApiKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||
}
|
||||
|
||||
return &DeployerProvider{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: client,
|
||||
sslUploader: uploader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
||||
if logger == nil {
|
||||
d.logger = slog.Default()
|
||||
} else {
|
||||
d.logger = logger
|
||||
}
|
||||
d.sslUploader.WithLogger(logger)
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||
// 上传证书到 SSL 证书
|
||||
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||
} else {
|
||||
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
|
||||
}
|
||||
|
||||
// RCDN SSL 绑定域名
|
||||
// REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-184214120
|
||||
certId, _ := strconv.Atoi(upres.CertId)
|
||||
rcdnInstanceSslBindReq := &rainyunsdk.RcdnInstanceSslBindRequest{
|
||||
CertId: int32(certId),
|
||||
Domains: []string{d.config.Domain},
|
||||
}
|
||||
rcdnInstanceSslBindResp, err := d.sdkClient.RcdnInstanceSslBind(d.config.InstanceId, rcdnInstanceSslBindReq)
|
||||
d.logger.Debug("sdk request 'rcdn.InstanceSslBind'", slog.Any("instanceId", d.config.InstanceId), slog.Any("request", rcdnInstanceSslBindReq), slog.Any("response", rcdnInstanceSslBindResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'rcdn.InstanceSslBind'")
|
||||
}
|
||||
|
||||
return &deployer.DeployResult{}, nil
|
||||
}
|
||||
|
||||
func createSdkClient(apiKey string) (*rainyunsdk.Client, error) {
|
||||
if apiKey == "" {
|
||||
return nil, errors.New("invalid rainyun api key")
|
||||
}
|
||||
|
||||
client := rainyunsdk.NewClient(apiKey)
|
||||
return client, nil
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package rainyunrcdn_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/rainyun-rcdn"
|
||||
)
|
||||
|
||||
var (
|
||||
fInputCertPath string
|
||||
fInputKeyPath string
|
||||
fApiKey string
|
||||
fInstanceId int64
|
||||
fDomain string
|
||||
)
|
||||
|
||||
func init() {
|
||||
argsPrefix := "CERTIMATE_DEPLOYER_RAINYUNRCDN_"
|
||||
|
||||
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
|
||||
flag.Int64Var(&fInstanceId, argsPrefix+"INSTANCEID", 0, "")
|
||||
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./ucloud_ucdn_test.go -args \
|
||||
--CERTIMATE_DEPLOYER_RAINYUNRCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||
--CERTIMATE_DEPLOYER_RAINYUNRCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||
--CERTIMATE_DEPLOYER_RAINYUNRCDN_APIKEY="your-api-key" \
|
||||
--CERTIMATE_DEPLOYER_RAINYUNRCDN_INSTANCEID="your-rcdn-instance-id" \
|
||||
--CERTIMATE_DEPLOYER_RAINYUNRCDN_DOMAIN="example.com"
|
||||
*/
|
||||
func TestDeploy(t *testing.T) {
|
||||
flag.Parse()
|
||||
|
||||
t.Run("Deploy", func(t *testing.T) {
|
||||
t.Log(strings.Join([]string{
|
||||
"args:",
|
||||
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
|
||||
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
|
||||
fmt.Sprintf("APIKEY: %v", fApiKey),
|
||||
fmt.Sprintf("INSTANCEID: %v", fInstanceId),
|
||||
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||
}, "\n"))
|
||||
|
||||
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
|
||||
PrivateKey: fApiKey,
|
||||
InstanceId: fInstanceId,
|
||||
Domain: fDomain,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fInputCertData, _ := os.ReadFile(fInputCertPath)
|
||||
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
|
||||
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("ok: %v", res)
|
||||
})
|
||||
}
|
@ -107,7 +107,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
// REF: https://cloud.tencent.com/document/product/400/91667
|
||||
deployCertificateInstanceReq := tcssl.NewDeployCertificateInstanceRequest()
|
||||
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
|
||||
deployCertificateInstanceReq.ResourceType = common.StringPtr("ecdn")
|
||||
deployCertificateInstanceReq.ResourceType = common.StringPtr("cdn")
|
||||
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
|
||||
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(instanceIds)
|
||||
deployCertificateInstanceResp, err := d.sdkClients.SSL.DeployCertificateInstance(deployCertificateInstanceReq)
|
||||
|
@ -182,7 +182,7 @@ func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId str
|
||||
return errors.New("config `listenerId` is required")
|
||||
}
|
||||
|
||||
if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, cloudCertId); err != nil {
|
||||
if err := d.updateListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -178,7 +178,7 @@ func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId str
|
||||
return errors.New("config `listenerId` is required")
|
||||
}
|
||||
|
||||
if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, cloudCertId); err != nil {
|
||||
if err := d.updateListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,276 @@
|
||||
package wangsucdnpro
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
xerrors "github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
|
||||
wangsucdn "github.com/usual2970/certimate/internal/pkg/vendors/wangsu-sdk/cdn"
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
// 网宿云 AccessKeyId。
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
// 网宿云 AccessKeySecret。
|
||||
AccessKeySecret string `json:"accessKeySecret"`
|
||||
// 网宿云环境。
|
||||
Environment string `json:"environment"`
|
||||
// 加速域名(支持泛域名)。
|
||||
Domain string `json:"domain"`
|
||||
// 证书 ID。
|
||||
// 选填。
|
||||
CertificateId string `json:"certificateId,omitempty"`
|
||||
// Webhook ID。
|
||||
// 选填。
|
||||
WebhookId string `json:"webhookId,omitempty"`
|
||||
}
|
||||
|
||||
type DeployerProvider struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *wangsucdn.Client
|
||||
}
|
||||
|
||||
var _ deployer.Deployer = (*DeployerProvider)(nil)
|
||||
|
||||
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
|
||||
if config == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||
}
|
||||
|
||||
return &DeployerProvider{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
||||
if logger == nil {
|
||||
d.logger = slog.Default()
|
||||
} else {
|
||||
d.logger = logger
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||
if d.config.Domain == "" {
|
||||
return nil, errors.New("config `domain` is required")
|
||||
}
|
||||
|
||||
// 解析证书内容
|
||||
certX509, err := certutil.ParseCertificateFromPEM(certPem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 查询已部署加速域名的详情
|
||||
getHostnameDetailResp, err := d.sdkClient.GetHostnameDetail(d.config.Domain)
|
||||
d.logger.Debug("sdk request 'cdn.GetHostnameDetail'", slog.String("hostname", d.config.Domain), slog.Any("response", getHostnameDetailResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetHostnameDetail'")
|
||||
}
|
||||
|
||||
// 生成网宿云证书参数
|
||||
encryptedPrivateKey, err := encryptPrivateKey(privkeyPem, d.config.AccessKeySecret, time.Now().Unix())
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to encrypt private key")
|
||||
}
|
||||
certificateNewVersionInfo := &wangsucdn.CertificateVersion{
|
||||
PrivateKey: tea.String(encryptedPrivateKey),
|
||||
Certificate: tea.String(certPem),
|
||||
IdentificationInfo: &wangsucdn.CertificateVersionIdentificationInfo{
|
||||
CommonName: tea.String(certX509.Subject.CommonName),
|
||||
SubjectAlternativeNames: &certX509.DNSNames,
|
||||
},
|
||||
}
|
||||
|
||||
// 网宿云证书 URL 中包含证书 ID 及版本号
|
||||
// 格式:
|
||||
// http://open.chinanetcenter.com/cdn/certificates/5dca2205f9e9cc0001df7b33
|
||||
// http://open.chinanetcenter.com/cdn/certificates/329f12c1fe6708c23c31e91f/versions/5
|
||||
var wangsuCertUrl string
|
||||
var wangsuCertId, wangsuCertVer string
|
||||
|
||||
// 如果原证书 ID 为空,则创建证书;否则更新证书。
|
||||
timestamp := time.Now().Unix()
|
||||
if d.config.CertificateId == "" {
|
||||
// 创建证书
|
||||
createCertificateReq := &wangsucdn.CreateCertificateRequest{
|
||||
Timestamp: timestamp,
|
||||
Name: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())),
|
||||
AutoRenew: tea.String("Off"),
|
||||
NewVersion: certificateNewVersionInfo,
|
||||
}
|
||||
createCertificateResp, err := d.sdkClient.CreateCertificate(createCertificateReq)
|
||||
d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.CreateCertificate'")
|
||||
}
|
||||
|
||||
wangsuCertUrl = createCertificateResp.CertificateUrl
|
||||
d.logger.Info("ssl certificate uploaded", slog.Any("certUrl", wangsuCertUrl))
|
||||
|
||||
wangsuCertIdMatches := regexp.MustCompile(`/certificates/([a-zA-Z0-9-]+)`).FindStringSubmatch(wangsuCertUrl)
|
||||
if len(wangsuCertIdMatches) > 1 {
|
||||
wangsuCertId = wangsuCertIdMatches[1]
|
||||
}
|
||||
|
||||
wangsuCertVer = "1"
|
||||
} else {
|
||||
// 更新证书
|
||||
updateCertificateReq := &wangsucdn.UpdateCertificateRequest{
|
||||
Timestamp: timestamp,
|
||||
Name: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())),
|
||||
AutoRenew: tea.String("Off"),
|
||||
NewVersion: certificateNewVersionInfo,
|
||||
}
|
||||
updateCertificateResp, err := d.sdkClient.UpdateCertificate(d.config.CertificateId, updateCertificateReq)
|
||||
d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("certificateId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UpdateCertificate'")
|
||||
}
|
||||
|
||||
wangsuCertUrl = updateCertificateResp.CertificateUrl
|
||||
d.logger.Info("ssl certificate uploaded", slog.Any("certUrl", wangsuCertUrl))
|
||||
|
||||
wangsuCertIdMatches := regexp.MustCompile(`/certificates/([a-zA-Z0-9-]+)`).FindStringSubmatch(wangsuCertUrl)
|
||||
if len(wangsuCertIdMatches) > 1 {
|
||||
wangsuCertId = wangsuCertIdMatches[1]
|
||||
}
|
||||
|
||||
wangsuCertVerMatches := regexp.MustCompile(`/versions/(\d+)`).FindStringSubmatch(wangsuCertUrl)
|
||||
if len(wangsuCertVerMatches) > 1 {
|
||||
wangsuCertVer = wangsuCertVerMatches[1]
|
||||
}
|
||||
}
|
||||
|
||||
// 创建部署任务
|
||||
// REF: https://www.wangsu.com/document/api-doc/27034
|
||||
createDeploymentTaskReq := &wangsucdn.CreateDeploymentTaskRequest{
|
||||
Name: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())),
|
||||
Target: tea.String(d.config.Environment),
|
||||
Actions: &[]wangsucdn.DeploymentTaskAction{
|
||||
{
|
||||
Action: tea.String("deploy_cert"),
|
||||
CertificateId: tea.String(wangsuCertId),
|
||||
Version: tea.String(wangsuCertVer),
|
||||
},
|
||||
},
|
||||
}
|
||||
if d.config.WebhookId != "" {
|
||||
createDeploymentTaskReq.Webhook = tea.String(d.config.WebhookId)
|
||||
}
|
||||
createDeploymentTaskResp, err := d.sdkClient.CreateDeploymentTask(createDeploymentTaskReq)
|
||||
d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("request", createDeploymentTaskReq), slog.Any("response", createDeploymentTaskResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.CreateDeploymentTask'")
|
||||
}
|
||||
|
||||
// 循环获取部署任务详细信息,等待任务状态变更
|
||||
// REF: https://www.wangsu.com/document/api-doc/27038
|
||||
var wangsuTaskId string
|
||||
wangsuTaskMatches := regexp.MustCompile(`/deploymentTasks/([a-zA-Z0-9-]+)`).FindStringSubmatch(wangsuCertUrl)
|
||||
if len(wangsuTaskMatches) > 1 {
|
||||
wangsuTaskId = wangsuTaskMatches[1]
|
||||
}
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
getDeploymentTaskDetailResp, err := d.sdkClient.GetDeploymentTaskDetail(wangsuTaskId)
|
||||
d.logger.Debug("sdk request 'cdn.GetDeploymentTaskDetail'", slog.Any("taskId", wangsuTaskId), slog.Any("response", getDeploymentTaskDetailResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDeploymentTaskDetail'")
|
||||
}
|
||||
|
||||
if getDeploymentTaskDetailResp.Status == "failed" {
|
||||
return nil, errors.New("unexpected deployment task status")
|
||||
} else if getDeploymentTaskDetailResp.Status == "succeeded" {
|
||||
break
|
||||
}
|
||||
|
||||
d.logger.Info("waiting for deployment task completion ...")
|
||||
time.Sleep(time.Second * 15)
|
||||
}
|
||||
|
||||
return &deployer.DeployResult{}, nil
|
||||
}
|
||||
|
||||
func createSdkClient(accessKeyId, accessKeySecret string) (*wangsucdn.Client, error) {
|
||||
if accessKeyId == "" {
|
||||
return nil, errors.New("invalid wangsu access key id")
|
||||
}
|
||||
|
||||
if accessKeySecret == "" {
|
||||
return nil, errors.New("invalid wangsu access key secret")
|
||||
}
|
||||
|
||||
return wangsucdn.NewClient(accessKeyId, accessKeySecret), nil
|
||||
}
|
||||
|
||||
func encryptPrivateKey(privkeyPem string, secretKey string, timestamp int64) (string, error) {
|
||||
date := time.Unix(timestamp, 0).UTC()
|
||||
dateStr := date.Format("Mon, 02 Jan 2006 15:04:05 GMT")
|
||||
|
||||
mac := hmac.New(sha256.New, []byte(secretKey))
|
||||
mac.Write([]byte(dateStr))
|
||||
aesivkey := mac.Sum(nil)
|
||||
aesivkeyHex := hex.EncodeToString(aesivkey)
|
||||
|
||||
if len(aesivkeyHex) != 64 {
|
||||
return "", fmt.Errorf("invalid hmac length: %d", len(aesivkeyHex))
|
||||
}
|
||||
ivHex := aesivkeyHex[:32]
|
||||
keyHex := aesivkeyHex[32:64]
|
||||
|
||||
iv, err := hex.DecodeString(ivHex)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decode iv: %w", err)
|
||||
}
|
||||
|
||||
key, err := hex.DecodeString(keyHex)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decode key: %w", err)
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
plainBytes := []byte(privkeyPem)
|
||||
padlen := aes.BlockSize - len(plainBytes)%aes.BlockSize
|
||||
if padlen > 0 {
|
||||
paddata := bytes.Repeat([]byte{byte(padlen)}, padlen)
|
||||
plainBytes = append(plainBytes, paddata...)
|
||||
}
|
||||
|
||||
encBytes := make([]byte, len(plainBytes))
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
mode.CryptBlocks(encBytes, plainBytes)
|
||||
|
||||
return base64.StdEncoding.EncodeToString(encBytes), nil
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package wangsucdnpro_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-cdnpro"
|
||||
)
|
||||
|
||||
var (
|
||||
fInputCertPath string
|
||||
fInputKeyPath string
|
||||
fAccessKeyId string
|
||||
fAccessKeySecret string
|
||||
fEnvironment string
|
||||
fDomain string
|
||||
fCertificateId string
|
||||
fWebhookId string
|
||||
)
|
||||
|
||||
func init() {
|
||||
argsPrefix := "CERTIMATE_DEPLOYER_WANGSUCDNPRO_"
|
||||
|
||||
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
|
||||
flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
|
||||
flag.StringVar(&fEnvironment, argsPrefix+"ENVIRONMENT", "production", "")
|
||||
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||
flag.StringVar(&fCertificateId, argsPrefix+"CERTIFICATEID", "", "")
|
||||
flag.StringVar(&fWebhookId, argsPrefix+"WEBHOOKID", "", "")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./wangsu_cdnpro_test.go -args \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_ACCESSKEYID="your-access-key-id" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_ACCESSKEYSECRET="your-access-key-secret" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_ENVIRONMENT="production" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_DOMAIN="example.com" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_CERTIFICATEID="your-certificate-id"\
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_WEBHOOKID="your-webhook-id"
|
||||
*/
|
||||
func TestDeploy(t *testing.T) {
|
||||
flag.Parse()
|
||||
|
||||
t.Run("Deploy", func(t *testing.T) {
|
||||
t.Log(strings.Join([]string{
|
||||
"args:",
|
||||
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
|
||||
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
|
||||
fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId),
|
||||
fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret),
|
||||
fmt.Sprintf("ENVIRONMENT: %v", fEnvironment),
|
||||
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||
fmt.Sprintf("CERTIFICATEID: %v", fCertificateId),
|
||||
fmt.Sprintf("WEBHOOKID: %v", fWebhookId),
|
||||
}, "\n"))
|
||||
|
||||
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
|
||||
AccessKeyId: fAccessKeyId,
|
||||
AccessKeySecret: fAccessKeySecret,
|
||||
Environment: fEnvironment,
|
||||
Domain: fDomain,
|
||||
CertificateId: fCertificateId,
|
||||
WebhookId: fWebhookId,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fInputCertData, _ := os.ReadFile(fInputCertPath)
|
||||
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
|
||||
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("ok: %v", res)
|
||||
})
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package mattermost
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/nikoksr/notify/service/mattermost"
|
||||
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type NotifierConfig struct {
|
||||
// Mattermost 服务地址。
|
||||
ServerUrl string `json:"serverUrl"`
|
||||
// 频道ID
|
||||
ChannelId string `json:"channelId"`
|
||||
// 用户名
|
||||
Username string `json:"username"`
|
||||
// 密码
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type NotifierProvider struct {
|
||||
config *NotifierConfig
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
var _ notifier.Notifier = (*NotifierProvider)(nil)
|
||||
|
||||
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
|
||||
if config == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
return &NotifierProvider{
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
|
||||
if logger == nil {
|
||||
n.logger = slog.Default()
|
||||
} else {
|
||||
n.logger = logger
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
|
||||
srv := mattermost.New(n.config.ServerUrl)
|
||||
|
||||
if err := srv.LoginWithCredentials(ctx, n.config.Username, n.config.Password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srv.AddReceivers(n.config.ChannelId)
|
||||
|
||||
// 复写消息样式
|
||||
srv.PreSend(func(req *http.Request) error {
|
||||
m := map[string]interface{}{
|
||||
"channel_id": n.config.ChannelId,
|
||||
"props": map[string]interface{}{
|
||||
"attachments": []map[string]interface{}{
|
||||
{
|
||||
"title": subject,
|
||||
"text": message,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if body, err := json.Marshal(m); err != nil {
|
||||
return err
|
||||
} else {
|
||||
req.ContentLength = int64(len(body))
|
||||
req.Body = io.NopCloser(bytes.NewReader(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err = srv.Send(ctx, subject, message); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ¬ifier.NotifyResult{}, nil
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package mattermost_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost"
|
||||
)
|
||||
|
||||
const (
|
||||
mockSubject = "test_subject"
|
||||
mockMessage = "test_message"
|
||||
)
|
||||
|
||||
var (
|
||||
fServerUrl string
|
||||
fChannelId string
|
||||
fUsername string
|
||||
fPassword string
|
||||
)
|
||||
|
||||
func init() {
|
||||
argsPrefix := "CERTIMATE_NOTIFIER_MATTERMOST_"
|
||||
|
||||
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
|
||||
flag.StringVar(&fChannelId, argsPrefix+"CHANNELID", "", "")
|
||||
flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "")
|
||||
flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./mattermost_test.go -args \
|
||||
--CERTIMATE_NOTIFIER_MATTERMOST_SERVERURL="https://example.com/your-server-url" \
|
||||
--CERTIMATE_NOTIFIER_MATTERMOST_CHANNELID="your-chanel-id" \
|
||||
--CERTIMATE_NOTIFIER_MATTERMOST_USERNAME="your-username" \
|
||||
--CERTIMATE_NOTIFIER_MATTERMOST_PASSWORD="your-password"
|
||||
*/
|
||||
func TestNotify(t *testing.T) {
|
||||
flag.Parse()
|
||||
|
||||
t.Run("Notify", func(t *testing.T) {
|
||||
t.Log(strings.Join([]string{
|
||||
"args:",
|
||||
fmt.Sprintf("SERVERURL: %v", fServerUrl),
|
||||
fmt.Sprintf("CHANNELID: %v", fChannelId),
|
||||
fmt.Sprintf("USERNAME: %v", fUsername),
|
||||
fmt.Sprintf("PASSWORD: %v", fPassword),
|
||||
}, "\n"))
|
||||
|
||||
notifier, err := provider.NewNotifier(&provider.NotifierConfig{
|
||||
ServerUrl: fServerUrl,
|
||||
ChannelId: fChannelId,
|
||||
Username: fUsername,
|
||||
Password: fPassword,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("ok: %v", res)
|
||||
})
|
||||
}
|
102
internal/pkg/core/notifier/providers/pushover/pushover.go
Normal file
102
internal/pkg/core/notifier/providers/pushover/pushover.go
Normal file
@ -0,0 +1,102 @@
|
||||
package pushover
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||
)
|
||||
|
||||
type NotifierConfig struct {
|
||||
Token string `json:"token"` // 应用 API Token
|
||||
User string `json:"user"` // 用户/分组 Key
|
||||
}
|
||||
|
||||
type NotifierProvider struct {
|
||||
config *NotifierConfig
|
||||
logger *slog.Logger
|
||||
// 未来将移除
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
var _ notifier.Notifier = (*NotifierProvider)(nil)
|
||||
|
||||
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
|
||||
if config == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
return &NotifierProvider{
|
||||
config: config,
|
||||
httpClient: http.DefaultClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
|
||||
if logger == nil {
|
||||
n.logger = slog.Default()
|
||||
} else {
|
||||
n.logger = logger
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Notify 发送通知
|
||||
// 参考文档:https://pushover.net/api
|
||||
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
|
||||
// 请求体
|
||||
reqBody := &struct {
|
||||
Token string `json:"token"`
|
||||
User string `json:"user"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Token: n.config.Token,
|
||||
User: n.config.User,
|
||||
Title: subject,
|
||||
Message: message,
|
||||
}
|
||||
|
||||
// Make request
|
||||
body, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "encode message body")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
"https://api.pushover.net/1/messages.json",
|
||||
bytes.NewReader(body),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create new request")
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
// Send request to pushover service
|
||||
resp, err := n.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "send request to pushover server")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
result, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "read response")
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("pushover returned status code %d: %s", resp.StatusCode, string(result))
|
||||
}
|
||||
|
||||
return ¬ifier.NotifyResult{}, nil
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package pushover_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushover"
|
||||
)
|
||||
|
||||
const (
|
||||
mockSubject = "test_subject"
|
||||
mockMessage = "test_message"
|
||||
)
|
||||
|
||||
var (
|
||||
fToken string
|
||||
fUser string
|
||||
)
|
||||
|
||||
func init() {
|
||||
argsPrefix := "CERTIMATE_NOTIFIER_PUSHOVER_"
|
||||
flag.StringVar(&fToken, argsPrefix+"TOKEN", "", "")
|
||||
flag.StringVar(&fUser, argsPrefix+"USER", "", "")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./pushover_test.go -args \
|
||||
--CERTIMATE_NOTIFIER_PUSHOVER_TOKEN="your-pushover-token" \
|
||||
--CERTIMATE_NOTIFIER_PUSHOVER_USER="your-pushover-user" \
|
||||
*/
|
||||
func TestNotify(t *testing.T) {
|
||||
flag.Parse()
|
||||
|
||||
t.Run("Notify", func(t *testing.T) {
|
||||
t.Log(strings.Join([]string{
|
||||
"args:",
|
||||
fmt.Sprintf("TOKEN: %v", fToken),
|
||||
}, "\n"))
|
||||
|
||||
notifier, err := provider.NewNotifier(&provider.NotifierConfig{
|
||||
Token: fToken,
|
||||
User: fUser,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("ok: %v", res)
|
||||
})
|
||||
}
|
@ -58,7 +58,7 @@ func (u *UploaderProvider) WithLogger(logger *slog.Logger) uploader.Uploader {
|
||||
|
||||
func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||
// 遍历证书列表,避免重复上传
|
||||
if res, err := u.getExistCert(ctx, certPem, privkeyPem); err != nil {
|
||||
if res, err := u.getCertIfExists(ctx, certPem, privkeyPem); err != nil {
|
||||
return nil, err
|
||||
} else if res != nil {
|
||||
u.logger.Info("ssl certificate already exists")
|
||||
@ -82,7 +82,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
|
||||
}
|
||||
|
||||
// 遍历证书列表,获取刚刚上传证书 ID
|
||||
if res, err := u.getExistCert(ctx, certPem, privkeyPem); err != nil {
|
||||
if res, err := u.getCertIfExists(ctx, certPem, privkeyPem); err != nil {
|
||||
return nil, err
|
||||
} else if res == nil {
|
||||
return nil, fmt.Errorf("no ssl certificate found, may be upload failed (code: %d, message: %s)", uploadWebsiteSSLResp.GetCode(), uploadWebsiteSSLResp.GetMessage())
|
||||
@ -91,7 +91,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UploaderProvider) getExistCert(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||
func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||
searchWebsiteSSLPageNumber := int32(1)
|
||||
searchWebsiteSSLPageSize := int32(100)
|
||||
for {
|
||||
|
@ -0,0 +1,169 @@
|
||||
package rainyunsslcenter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
xerrors "github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
|
||||
rainyunsdk "github.com/usual2970/certimate/internal/pkg/vendors/rainyun-sdk"
|
||||
)
|
||||
|
||||
type UploaderConfig struct {
|
||||
// 雨云 API 密钥。
|
||||
ApiKey string `json:"ApiKey"`
|
||||
}
|
||||
|
||||
type UploaderProvider struct {
|
||||
config *UploaderConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *rainyunsdk.Client
|
||||
}
|
||||
|
||||
var _ uploader.Uploader = (*UploaderProvider)(nil)
|
||||
|
||||
func NewUploader(config *UploaderConfig) (*UploaderProvider, error) {
|
||||
if config == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
client, err := createSdkClient(config.ApiKey)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||
}
|
||||
|
||||
return &UploaderProvider{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *UploaderProvider) WithLogger(logger *slog.Logger) uploader.Uploader {
|
||||
if logger == nil {
|
||||
u.logger = slog.Default()
|
||||
} else {
|
||||
u.logger = logger
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||
if res, err := u.getCertIfExists(ctx, certPem); err != nil {
|
||||
return nil, err
|
||||
} else if res != nil {
|
||||
u.logger.Info("ssl certificate already exists")
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SSL 证书上传
|
||||
// REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-69943046
|
||||
sslCenterCreateReq := &rainyunsdk.SslCenterCreateRequest{
|
||||
Cert: certPem,
|
||||
Key: privkeyPem,
|
||||
}
|
||||
sslCenterCreateResp, err := u.sdkClient.SslCenterCreate(sslCenterCreateReq)
|
||||
u.logger.Debug("sdk request 'sslcenter.Create'", slog.Any("request", sslCenterCreateReq), slog.Any("response", sslCenterCreateResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'sslcenter.Create'")
|
||||
}
|
||||
|
||||
if res, err := u.getCertIfExists(ctx, certPem); err != nil {
|
||||
return nil, err
|
||||
} else if res == nil {
|
||||
return nil, errors.New("rainyun sslcenter: no certificate found")
|
||||
} else {
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPem string) (res *uploader.UploadResult, err error) {
|
||||
// 解析证书内容
|
||||
certX509, err := certutil.ParseCertificateFromPEM(certPem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 遍历 SSL 证书列表,避免重复上传
|
||||
// REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-69943046
|
||||
// REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-69943048
|
||||
sslCenterListPage := int32(1)
|
||||
sslCenterListPerPage := int32(100)
|
||||
for {
|
||||
sslCenterListReq := &rainyunsdk.SslCenterListRequest{
|
||||
Filters: &rainyunsdk.SslCenterListFilters{
|
||||
Domain: &certX509.Subject.CommonName,
|
||||
},
|
||||
Page: &sslCenterListPage,
|
||||
PerPage: &sslCenterListPerPage,
|
||||
}
|
||||
sslCenterListResp, err := u.sdkClient.SslCenterList(sslCenterListReq)
|
||||
u.logger.Debug("sdk request 'sslcenter.List'", slog.Any("request", sslCenterListReq), slog.Any("response", sslCenterListResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'sslcenter.List'")
|
||||
}
|
||||
|
||||
if sslCenterListResp.Data != nil && sslCenterListResp.Data.Records != nil {
|
||||
for _, sslItem := range sslCenterListResp.Data.Records {
|
||||
// 先对比证书的多域名
|
||||
if sslItem.Domain != strings.Join(certX509.DNSNames, ", ") {
|
||||
continue
|
||||
}
|
||||
|
||||
// 再对比证书的有效期
|
||||
if sslItem.StartDate != certX509.NotBefore.Unix() || sslItem.ExpireDate != certX509.NotAfter.Unix() {
|
||||
continue
|
||||
}
|
||||
|
||||
// 最后对比证书内容
|
||||
sslCenterGetResp, err := u.sdkClient.SslCenterGet(sslItem.ID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'sslcenter.Get'")
|
||||
}
|
||||
|
||||
var isSameCert bool
|
||||
if sslCenterGetResp.Data != nil {
|
||||
if sslCenterGetResp.Data.Cert == certPem {
|
||||
isSameCert = true
|
||||
} else {
|
||||
oldCertX509, err := certutil.ParseCertificateFromPEM(sslCenterGetResp.Data.Cert)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
isSameCert = certutil.EqualCertificate(certX509, oldCertX509)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已存在相同证书,直接返回
|
||||
if isSameCert {
|
||||
return &uploader.UploadResult{
|
||||
CertId: fmt.Sprintf("%d", sslItem.ID),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sslCenterListResp.Data == nil || len(sslCenterListResp.Data.Records) < int(sslCenterListPerPage) {
|
||||
break
|
||||
} else {
|
||||
sslCenterListPage++
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func createSdkClient(apiKey string) (*rainyunsdk.Client, error) {
|
||||
if apiKey == "" {
|
||||
return nil, errors.New("invalid rainyun api key")
|
||||
}
|
||||
|
||||
client := rainyunsdk.NewClient(apiKey)
|
||||
return client, nil
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package rainyunsslcenter_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
provider "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/rainyun-sslcenter"
|
||||
)
|
||||
|
||||
var (
|
||||
fInputCertPath string
|
||||
fInputKeyPath string
|
||||
fApiKey string
|
||||
)
|
||||
|
||||
func init() {
|
||||
argsPrefix := "CERTIMATE_UPLOADER_RAINYUNSSLCENTER_"
|
||||
|
||||
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./rainyun_sslcenter_test.go -args \
|
||||
--CERTIMATE_UPLOADER_RAINYUNSSLCENTER_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||
--CERTIMATE_UPLOADER_RAINYUNSSLCENTER_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||
--CERTIMATE_UPLOADER_RAINYUNSSLCENTER_APIKEY="your-api-key"
|
||||
*/
|
||||
func TestDeploy(t *testing.T) {
|
||||
flag.Parse()
|
||||
|
||||
t.Run("Deploy", func(t *testing.T) {
|
||||
t.Log(strings.Join([]string{
|
||||
"args:",
|
||||
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
|
||||
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
|
||||
fmt.Sprintf("APIKEY: %v", fApiKey),
|
||||
}, "\n"))
|
||||
|
||||
uploader, err := provider.NewUploader(&provider.UploaderConfig{
|
||||
ApiKey: fApiKey,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fInputCertData, _ := os.ReadFile(fInputCertPath)
|
||||
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
|
||||
res, err := uploader.Upload(context.Background(), string(fInputCertData), string(fInputKeyData))
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
sres, _ := json.Marshal(res)
|
||||
t.Logf("ok: %s", string(sres))
|
||||
})
|
||||
}
|
@ -89,10 +89,10 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
|
||||
u.logger.Debug("sdk request 'ussl.UploadNormalCertificate'", slog.Any("request", uploadNormalCertificateReq), slog.Any("response", uploadNormalCertificateResp))
|
||||
if err != nil {
|
||||
if uploadNormalCertificateResp != nil && uploadNormalCertificateResp.GetRetCode() == 80035 {
|
||||
if res, err := u.getExistCert(ctx, certPem); err != nil {
|
||||
if res, err := u.getCertIfExists(ctx, certPem); err != nil {
|
||||
return nil, err
|
||||
} else if res == nil {
|
||||
return nil, errors.New("no certificate found")
|
||||
return nil, errors.New("ucloud ssl: no certificate found")
|
||||
} else {
|
||||
u.logger.Info("ssl certificate already exists")
|
||||
return res, nil
|
||||
@ -112,7 +112,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *UploaderProvider) getExistCert(ctx context.Context, certPem string) (res *uploader.UploadResult, err error) {
|
||||
func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPem string) (res *uploader.UploadResult, err error) {
|
||||
// 解析证书内容
|
||||
certX509, err := certutil.ParseCertificateFromPEM(certPem)
|
||||
if err != nil {
|
||||
|
@ -74,6 +74,18 @@ func GetOrDefaultInt32(dict map[string]any, key string, defaultValue int32) int3
|
||||
}
|
||||
}
|
||||
|
||||
if result, ok := value.(int64); ok {
|
||||
if result != 0 {
|
||||
return int32(result)
|
||||
}
|
||||
}
|
||||
|
||||
if result, ok := value.(int); ok {
|
||||
if result != 0 {
|
||||
return int32(result)
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容字符串类型的值
|
||||
if str, ok := value.(string); ok {
|
||||
if result, err := strconv.ParseInt(str, 10, 32); err == nil {
|
||||
@ -126,6 +138,12 @@ func GetOrDefaultInt64(dict map[string]any, key string, defaultValue int64) int6
|
||||
}
|
||||
}
|
||||
|
||||
if result, ok := value.(int); ok {
|
||||
if result != 0 {
|
||||
return int64(result)
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容字符串类型的值
|
||||
if str, ok := value.(string); ok {
|
||||
if result, err := strconv.ParseInt(str, 10, 64); err == nil {
|
||||
|
2
internal/pkg/vendors/1panel-sdk/client.go
vendored
2
internal/pkg/vendors/1panel-sdk/client.go
vendored
@ -79,7 +79,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("1panel api error: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("1panel api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body())
|
||||
return resp, fmt.Errorf("1panel api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
2
internal/pkg/vendors/baishan-sdk/client.go
vendored
2
internal/pkg/vendors/baishan-sdk/client.go
vendored
@ -75,7 +75,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("baishan api error: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("baishan api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body())
|
||||
return resp, fmt.Errorf("baishan api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
2
internal/pkg/vendors/btpanel-sdk/client.go
vendored
2
internal/pkg/vendors/btpanel-sdk/client.go
vendored
@ -86,7 +86,7 @@ func (c *Client) sendRequest(path string, params interface{}) (*resty.Response,
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("baota api error: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("baota api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body())
|
||||
return resp, fmt.Errorf("baota api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
2
internal/pkg/vendors/cachefly-sdk/client.go
vendored
2
internal/pkg/vendors/cachefly-sdk/client.go
vendored
@ -59,7 +59,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("cachefly api error: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("cachefly api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body())
|
||||
return resp, fmt.Errorf("cachefly api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
7
internal/pkg/vendors/cdnfly-sdk/api.go
vendored
7
internal/pkg/vendors/cdnfly-sdk/api.go
vendored
@ -3,17 +3,18 @@ package cdnflysdk
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func (c *Client) GetSite(req *GetSiteRequest) (*GetSiteResponse, error) {
|
||||
resp := &GetSiteResponse{}
|
||||
err := c.sendRequestWithResult(http.MethodGet, fmt.Sprintf("/v1/sites/%s", req.Id), req, resp)
|
||||
err := c.sendRequestWithResult(http.MethodGet, fmt.Sprintf("/v1/sites/%s", url.PathEscape(req.Id)), req, resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c *Client) UpdateSite(req *UpdateSiteRequest) (*UpdateSiteResponse, error) {
|
||||
resp := &UpdateSiteResponse{}
|
||||
err := c.sendRequestWithResult(http.MethodPut, fmt.Sprintf("/v1/sites/%s", req.Id), req, resp)
|
||||
err := c.sendRequestWithResult(http.MethodPut, fmt.Sprintf("/v1/sites/%s", url.PathEscape(req.Id)), req, resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
@ -25,6 +26,6 @@ func (c *Client) CreateCertificate(req *CreateCertificateRequest) (*CreateCertif
|
||||
|
||||
func (c *Client) UpdateCertificate(req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) {
|
||||
resp := &UpdateCertificateResponse{}
|
||||
err := c.sendRequestWithResult(http.MethodPut, fmt.Sprintf("/v1/certs/%s", req.Id), req, resp)
|
||||
err := c.sendRequestWithResult(http.MethodPut, fmt.Sprintf("/v1/certs/%s", url.PathEscape(req.Id)), req, resp)
|
||||
return resp, err
|
||||
}
|
||||
|
2
internal/pkg/vendors/cdnfly-sdk/client.go
vendored
2
internal/pkg/vendors/cdnfly-sdk/client.go
vendored
@ -65,7 +65,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("cdnfly api error: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("cdnfly api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body())
|
||||
return resp, fmt.Errorf("cdnfly api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
2
internal/pkg/vendors/dnsla-sdk/client.go
vendored
2
internal/pkg/vendors/dnsla-sdk/client.go
vendored
@ -60,7 +60,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("dnsla api error: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("dnsla api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body())
|
||||
return resp, fmt.Errorf("dnsla api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
@ -6,9 +6,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Edgio/edgio-api/applications/v7/dtos"
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7/dtos"
|
||||
)
|
||||
|
||||
// AccessTokenResponse represents the response from the token endpoint.
|
@ -3,7 +3,7 @@ package edgio_api
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7/dtos"
|
||||
"github.com/Edgio/edgio-api/applications/v7/dtos"
|
||||
)
|
||||
|
||||
type EdgioClientInterface interface {
|
3
internal/pkg/vendors/edgio-sdk/edgio-api@v0.0.0-workspace/go.mod
vendored
Normal file
3
internal/pkg/vendors/edgio-sdk/edgio-api@v0.0.0-workspace/go.mod
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
module github.com/Edgio/edgio-api
|
||||
|
||||
go 1.23.0
|
2
internal/pkg/vendors/gname-sdk/client.go
vendored
2
internal/pkg/vendors/gname-sdk/client.go
vendored
@ -82,7 +82,7 @@ func (c *Client) sendRequest(path string, params interface{}) (*resty.Response,
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("gname api error: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("gname api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body())
|
||||
return resp, fmt.Errorf("gname api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
30
internal/pkg/vendors/rainyun-sdk/api.go
vendored
Normal file
30
internal/pkg/vendors/rainyun-sdk/api.go
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
package rainyunsdk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (c *Client) SslCenterList(req *SslCenterListRequest) (*SslCenterListResponse, error) {
|
||||
resp := &SslCenterListResponse{}
|
||||
err := c.sendRequestWithResult(http.MethodGet, "/product/sslcenter", req, resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c *Client) SslCenterGet(id int32) (*SslCenterGetResponse, error) {
|
||||
resp := &SslCenterGetResponse{}
|
||||
err := c.sendRequestWithResult(http.MethodGet, fmt.Sprintf("/product/sslcenter/%d", id), nil, resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c *Client) SslCenterCreate(req *SslCenterCreateRequest) (*SslCenterCreateResponse, error) {
|
||||
resp := &SslCenterCreateResponse{}
|
||||
err := c.sendRequestWithResult(http.MethodPost, "/product/sslcenter/", req, resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c *Client) RcdnInstanceSslBind(id int32, req *RcdnInstanceSslBindRequest) (*RcdnInstanceSslBindResponse, error) {
|
||||
resp := &RcdnInstanceSslBindResponse{}
|
||||
err := c.sendRequestWithResult(http.MethodPost, fmt.Sprintf("/product/rcdn/instance/%d/ssl_bind", id), req, resp)
|
||||
return resp, err
|
||||
}
|
74
internal/pkg/vendors/rainyun-sdk/client.go
vendored
Normal file
74
internal/pkg/vendors/rainyun-sdk/client.go
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
package rainyunsdk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
apiKey string
|
||||
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewClient(apiKey string) *Client {
|
||||
client := resty.New()
|
||||
|
||||
return &Client{
|
||||
apiKey: apiKey,
|
||||
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().SetHeader("x-api-key", c.apiKey)
|
||||
req.Method = method
|
||||
req.URL = "https://api.v2.rainyun.com" + path
|
||||
if strings.EqualFold(method, http.MethodGet) {
|
||||
if params != nil {
|
||||
jsonb, _ := json.Marshal(params)
|
||||
req = req.SetQueryParam("options", string(jsonb))
|
||||
}
|
||||
} else {
|
||||
req = req.
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(params)
|
||||
}
|
||||
|
||||
resp, err := req.Send()
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("rainyun api error: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("rainyun api error: unexpected status code: %d, resp: %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 {
|
||||
if resp != nil {
|
||||
json.Unmarshal(resp.Body(), &result)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
||||
return fmt.Errorf("rainyun api error: failed to parse response: %w", err)
|
||||
} else if errcode := result.GetCode(); errcode/100 != 2 {
|
||||
return fmt.Errorf("rainyun api error: %d - %s", errcode, result.GetMessage())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
83
internal/pkg/vendors/rainyun-sdk/models.go
vendored
Normal file
83
internal/pkg/vendors/rainyun-sdk/models.go
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
package rainyunsdk
|
||||
|
||||
type BaseResponse interface {
|
||||
GetCode() int32
|
||||
GetMessage() string
|
||||
}
|
||||
|
||||
type baseResponse struct {
|
||||
Code *int32 `json:"code,omitempty"`
|
||||
Message *string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (r *baseResponse) GetCode() int32 {
|
||||
if r.Code != nil {
|
||||
return *r.Code
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *baseResponse) GetMessage() string {
|
||||
if r.Message != nil {
|
||||
return *r.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type SslCenterListFilters struct {
|
||||
Domain *string `json:"Domain,omitempty"`
|
||||
}
|
||||
|
||||
type SslCenterListRequest struct {
|
||||
Filters *SslCenterListFilters `json:"columnFilters,omitempty"`
|
||||
Sort []*string `json:"sort,omitempty"`
|
||||
Page *int32 `json:"page,omitempty"`
|
||||
PerPage *int32 `json:"perPage,omitempty"`
|
||||
}
|
||||
|
||||
type SslCenterListResponse struct {
|
||||
baseResponse
|
||||
Data *struct {
|
||||
TotalRecords int32 `json:"TotalRecords"`
|
||||
Records []*struct {
|
||||
ID int32 `json:"ID"`
|
||||
UID int32 `json:"UID"`
|
||||
Domain string `json:"Domain"`
|
||||
Issuer string `json:"Issuer"`
|
||||
StartDate int64 `json:"StartDate"`
|
||||
ExpireDate int64 `json:"ExpDate"`
|
||||
UploadTime int64 `json:"UploadTime"`
|
||||
} `json:"Records"`
|
||||
} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type SslCenterGetResponse struct {
|
||||
baseResponse
|
||||
Data *struct {
|
||||
Cert string `json:"Cert"`
|
||||
Key string `json:"Key"`
|
||||
Domain string `json:"DomainName"`
|
||||
Issuer string `json:"Issuer"`
|
||||
StartDate int64 `json:"StartDate"`
|
||||
ExpireDate int64 `json:"ExpDate"`
|
||||
RemainDays int32 `json:"RemainDays"`
|
||||
} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type SslCenterCreateRequest struct {
|
||||
Cert string `json:"cert"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type SslCenterCreateResponse struct {
|
||||
baseResponse
|
||||
}
|
||||
|
||||
type RcdnInstanceSslBindRequest struct {
|
||||
CertId int32 `json:"cert_id"`
|
||||
Domains []string `json:"domains"`
|
||||
}
|
||||
|
||||
type RcdnInstanceSslBindResponse struct {
|
||||
baseResponse
|
||||
}
|
2
internal/pkg/vendors/safeline-sdk/client.go
vendored
2
internal/pkg/vendors/safeline-sdk/client.go
vendored
@ -47,7 +47,7 @@ func (c *Client) sendRequest(path string, params interface{}) (*resty.Response,
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("safeline api error: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("safeline api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body())
|
||||
return resp, fmt.Errorf("safeline api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
@ -64,7 +64,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("upyun api error: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("upyun api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body())
|
||||
return resp, fmt.Errorf("upyun api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
58
internal/pkg/vendors/wangsu-sdk/cdn/api.go
vendored
Normal file
58
internal/pkg/vendors/wangsu-sdk/cdn/api.go
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
func (c *Client) CreateCertificate(req *CreateCertificateRequest) (*CreateCertificateResponse, error) {
|
||||
resp := &CreateCertificateResponse{}
|
||||
r, err := c.client.SendRequestWithResult(http.MethodPost, "/cdn/certificates", req, resp, func(r *resty.Request) {
|
||||
r.SetHeader("x-cnc-timestamp", fmt.Sprintf("%d", req.Timestamp))
|
||||
})
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
resp.CertificateUrl = r.Header().Get("Location")
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c *Client) UpdateCertificate(certificateId string, req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) {
|
||||
resp := &UpdateCertificateResponse{}
|
||||
r, err := c.client.SendRequestWithResult(http.MethodPatch, fmt.Sprintf("/cdn/certificates/%s", url.PathEscape(certificateId)), req, resp, func(r *resty.Request) {
|
||||
r.SetHeader("x-cnc-timestamp", fmt.Sprintf("%d", req.Timestamp))
|
||||
})
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
resp.CertificateUrl = r.Header().Get("Location")
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c *Client) GetHostnameDetail(hostname string) (*GetHostnameDetailResponse, error) {
|
||||
resp := &GetHostnameDetailResponse{}
|
||||
_, err := c.client.SendRequestWithResult(http.MethodGet, fmt.Sprintf("/cdn/hostnames/%s", url.PathEscape(hostname)), nil, resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c *Client) CreateDeploymentTask(req *CreateDeploymentTaskRequest) (*CreateDeploymentTaskResponse, error) {
|
||||
resp := &CreateDeploymentTaskResponse{}
|
||||
r, err := c.client.SendRequestWithResult(http.MethodPost, "/cdn/deploymentTasks", req, resp)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
resp.DeploymentTaskUrl = r.Header().Get("Location")
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c *Client) GetDeploymentTaskDetail(deploymentTaskId string) (*GetDeploymentTaskDetailResponse, error) {
|
||||
resp := &GetDeploymentTaskDetailResponse{}
|
||||
_, err := c.client.SendRequestWithResult(http.MethodGet, fmt.Sprintf("/cdn/deploymentTasks/%s", deploymentTaskId), nil, resp)
|
||||
return resp, err
|
||||
}
|
20
internal/pkg/vendors/wangsu-sdk/cdn/client.go
vendored
Normal file
20
internal/pkg/vendors/wangsu-sdk/cdn/client.go
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/vendors/wangsu-sdk/openapi"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
client *openapi.Client
|
||||
}
|
||||
|
||||
func NewClient(accessKey, secretKey string) *Client {
|
||||
return &Client{client: openapi.NewClient(accessKey, secretKey)}
|
||||
}
|
||||
|
||||
func (c *Client) WithTimeout(timeout time.Duration) *Client {
|
||||
c.client.WithTimeout(timeout)
|
||||
return c
|
||||
}
|
107
internal/pkg/vendors/wangsu-sdk/cdn/models.go
vendored
Normal file
107
internal/pkg/vendors/wangsu-sdk/cdn/models.go
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"github.com/usual2970/certimate/internal/pkg/vendors/wangsu-sdk/openapi"
|
||||
)
|
||||
|
||||
type baseResponse struct {
|
||||
RequestId *string `json:"-"`
|
||||
Code *string `json:"code,omitempty"`
|
||||
Message *string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
var _ openapi.Result = (*baseResponse)(nil)
|
||||
|
||||
func (r *baseResponse) SetRequestId(requestId string) {
|
||||
r.RequestId = &requestId
|
||||
}
|
||||
|
||||
type CertificateVersion struct {
|
||||
Comments *string `json:"comments,omitempty"`
|
||||
PrivateKey *string `json:"privateKey,omitempty"`
|
||||
Certificate *string `json:"certificate,omitempty"`
|
||||
ChainCert *string `json:"chainCert,omitempty"`
|
||||
IdentificationInfo *CertificateVersionIdentificationInfo `json:"identificationInfo,omitempty"`
|
||||
}
|
||||
|
||||
type CertificateVersionIdentificationInfo struct {
|
||||
Country *string `json:"country,omitempty"`
|
||||
State *string `json:"state,omitempty"`
|
||||
City *string `json:"city,omitempty"`
|
||||
Company *string `json:"company,omitempty"`
|
||||
Department *string `json:"department,omitempty"`
|
||||
CommonName *string `json:"commonName,omitempty" required:"true"`
|
||||
Email *string `json:"email,omitempty"`
|
||||
SubjectAlternativeNames *[]string `json:"subjectAlternativeNames,omitempty" required:"true"`
|
||||
}
|
||||
|
||||
type CreateCertificateRequest struct {
|
||||
Timestamp int64 `json:"-"`
|
||||
Name *string `json:"name,omitempty" required:"true"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
AutoRenew *string `json:"autoRenew,omitempty"`
|
||||
ForceRenew *bool `json:"forceRenew,omitempty"`
|
||||
NewVersion *CertificateVersion `json:"newVersion,omitempty" required:"true"`
|
||||
}
|
||||
|
||||
type CreateCertificateResponse struct {
|
||||
baseResponse
|
||||
CertificateUrl string `json:"-"`
|
||||
}
|
||||
|
||||
type UpdateCertificateRequest struct {
|
||||
Timestamp int64 `json:"-"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
AutoRenew *string `json:"autoRenew,omitempty"`
|
||||
ForceRenew *bool `json:"forceRenew,omitempty"`
|
||||
NewVersion *CertificateVersion `json:"newVersion,omitempty" required:"true"`
|
||||
}
|
||||
|
||||
type UpdateCertificateResponse struct {
|
||||
baseResponse
|
||||
CertificateUrl string `json:"-"`
|
||||
}
|
||||
|
||||
type HostnameProperty struct {
|
||||
PropertyId string `json:"propertyId"`
|
||||
Version int32 `json:"version"`
|
||||
CertificateId *string `json:"certificateId,omitempty"`
|
||||
}
|
||||
|
||||
type GetHostnameDetailResponse struct {
|
||||
baseResponse
|
||||
Hostname string `json:"hostname"`
|
||||
PropertyInProduction *HostnameProperty `json:"propertyInProduction,omitempty"`
|
||||
PropertyInStaging *HostnameProperty `json:"propertyInStaging,omitempty"`
|
||||
}
|
||||
|
||||
type DeploymentTaskAction struct {
|
||||
Action *string `json:"action,omitempty" required:"true"`
|
||||
PropertyId *string `json:"propertyId,omitempty"`
|
||||
CertificateId *string `json:"certificateId,omitempty"`
|
||||
Version *string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
type CreateDeploymentTaskRequest struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Target *string `json:"target,omitempty" required:"true"`
|
||||
Actions *[]DeploymentTaskAction `json:"actions,omitempty" required:"true"`
|
||||
Webhook *string `json:"webhook,omitempty"`
|
||||
}
|
||||
|
||||
type CreateDeploymentTaskResponse struct {
|
||||
baseResponse
|
||||
DeploymentTaskUrl string `json:"-"`
|
||||
}
|
||||
|
||||
type GetDeploymentTaskDetailResponse struct {
|
||||
baseResponse
|
||||
Target string `json:"target"`
|
||||
Actions []DeploymentTaskAction `json:"actions"`
|
||||
Status string `json:"status"`
|
||||
StatusDetails string `json:"statusDetails"`
|
||||
SubmissionTime string `json:"submissionTime"`
|
||||
FinishTime string `json:"finishTime"`
|
||||
ApiRequestId string `json:"apiRequestId"`
|
||||
}
|
187
internal/pkg/vendors/wangsu-sdk/openapi/client.go
vendored
Normal file
187
internal/pkg/vendors/wangsu-sdk/openapi/client.go
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
accessKey string
|
||||
secretKey string
|
||||
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
type Result interface {
|
||||
SetRequestId(requestId string)
|
||||
}
|
||||
|
||||
func NewClient(accessKey, secretKey string) *Client {
|
||||
client := resty.New().
|
||||
SetBaseURL("https://open.chinanetcenter.com").
|
||||
SetHeader("Host", "open.chinanetcenter.com").
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
|
||||
// Step 1: Get request method
|
||||
method := req.Method
|
||||
method = strings.ToUpper(method)
|
||||
|
||||
// Step 2: Get request path
|
||||
path := "/"
|
||||
if req.URL != nil {
|
||||
path = req.URL.Path
|
||||
}
|
||||
|
||||
// Step 3: Get unencoded query string
|
||||
queryString := ""
|
||||
if method != http.MethodPost && req.URL != nil {
|
||||
queryString = req.URL.RawQuery
|
||||
|
||||
s, err := url.QueryUnescape(queryString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryString = s
|
||||
}
|
||||
|
||||
// Step 4: Get canonical headers & signed headers
|
||||
canonicalHeaders := "" +
|
||||
"content-type:" + strings.TrimSpace(strings.ToLower(req.Header.Get("Content-Type"))) + "\n" +
|
||||
"host:" + strings.TrimSpace(strings.ToLower(req.Header.Get("Host"))) + "\n"
|
||||
signedHeaders := "content-type;host"
|
||||
|
||||
// Step 5: Get request payload
|
||||
payload := ""
|
||||
if method != http.MethodGet && 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
|
||||
}
|
||||
|
||||
payload = string(payloadb)
|
||||
}
|
||||
hashedPayload := sha256.Sum256([]byte(payload))
|
||||
hashedPayloadHex := strings.ToLower(hex.EncodeToString(hashedPayload[:]))
|
||||
|
||||
// Step 6: Get timestamp
|
||||
var reqtime time.Time
|
||||
timestampString := req.Header.Get("x-cnc-timestamp")
|
||||
if timestampString == "" {
|
||||
reqtime = time.Now().UTC()
|
||||
timestampString = fmt.Sprintf("%d", reqtime.Unix())
|
||||
} else {
|
||||
timestamp, err := strconv.ParseInt(timestampString, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reqtime = time.Unix(timestamp, 0).UTC()
|
||||
}
|
||||
|
||||
// Step 7: Get canonical request string
|
||||
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", method, path, queryString, canonicalHeaders, signedHeaders, hashedPayloadHex)
|
||||
hashedCanonicalRequest := sha256.Sum256([]byte(canonicalRequest))
|
||||
hashedCanonicalRequestHex := strings.ToLower(hex.EncodeToString(hashedCanonicalRequest[:]))
|
||||
|
||||
// Step 8: String to sign
|
||||
const SignAlgorithmHeader = "CNC-HMAC-SHA256"
|
||||
stringToSign := fmt.Sprintf("%s\n%s\n%s", SignAlgorithmHeader, timestampString, hashedCanonicalRequestHex)
|
||||
hmac := hmac.New(sha256.New, []byte(secretKey))
|
||||
hmac.Write([]byte(stringToSign))
|
||||
sign := hmac.Sum(nil)
|
||||
signHex := strings.ToLower(hex.EncodeToString(sign))
|
||||
|
||||
// Step 9: Add headers to request
|
||||
req.Header.Set("x-cnc-accessKey", accessKey)
|
||||
req.Header.Set("x-cnc-timestamp", timestampString)
|
||||
req.Header.Set("x-cnc-auth-method", "AKSK")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("%s Credential=%s, SignedHeaders=%s, Signature=%s", SignAlgorithmHeader, accessKey, signedHeaders, signHex))
|
||||
req.Header.Set("Date", reqtime.Format("Mon, 02 Jan 2006 15:04:05 GMT"))
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return &Client{
|
||||
accessKey: accessKey,
|
||||
secretKey: secretKey,
|
||||
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{}, configureReq ...func(req *resty.Request)) (*resty.Response, error) {
|
||||
req := c.client.R()
|
||||
req.Method = method
|
||||
req.URL = path
|
||||
if strings.EqualFold(method, http.MethodGet) {
|
||||
qs := make(map[string]string)
|
||||
if params != nil {
|
||||
temp := make(map[string]any)
|
||||
jsonb, _ := json.Marshal(params)
|
||||
json.Unmarshal(jsonb, &temp)
|
||||
for k, v := range temp {
|
||||
if v != nil {
|
||||
qs[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
req = req.SetQueryParams(qs)
|
||||
} else {
|
||||
req = req.SetBody(params)
|
||||
}
|
||||
|
||||
for _, fn := range configureReq {
|
||||
fn(req)
|
||||
}
|
||||
|
||||
resp, err := req.Send()
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("wangsu api error: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("wangsu api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) SendRequestWithResult(method string, path string, params interface{}, result Result, configureReq ...func(req *resty.Request)) (*resty.Response, error) {
|
||||
resp, err := c.sendRequest(method, path, params, configureReq...)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
json.Unmarshal(resp.Body(), &result)
|
||||
result.SetRequestId(resp.Header().Get("x-cnc-request-id"))
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
||||
return resp, fmt.Errorf("wangsu api error: failed to parse response: %w", err)
|
||||
}
|
||||
|
||||
result.SetRequestId(resp.Header().Get("x-cnc-request-id"))
|
||||
return resp, nil
|
||||
}
|
@ -258,15 +258,15 @@ func init() {
|
||||
}
|
||||
|
||||
type dWorkflowNode struct {
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Config map[string]any `json:"config"`
|
||||
Inputs map[string]any `json:"inputs"`
|
||||
Outputs map[string]any `json:"outputs"`
|
||||
Next *dWorkflowNode `json:"next,omitempty"`
|
||||
Branches []dWorkflowNode `json:"branches,omitempty"`
|
||||
Validated bool `json:"validated"`
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Config map[string]any `json:"config"`
|
||||
Inputs []map[string]any `json:"inputs"`
|
||||
Outputs []map[string]any `json:"outputs"`
|
||||
Next *dWorkflowNode `json:"next,omitempty"`
|
||||
Branches []dWorkflowNode `json:"branches,omitempty"`
|
||||
Validated bool `json:"validated"`
|
||||
}
|
||||
|
||||
for _, workflowRun := range workflowRuns {
|
||||
|
91
migrations/1744192800_upgrade.go
Normal file
91
migrations/1744192800_upgrade.go
Normal file
@ -0,0 +1,91 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("4yzbv8urny5ja1e")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update field
|
||||
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
|
||||
"hidden": false,
|
||||
"id": "hwy7m03o",
|
||||
"maxSelect": 1,
|
||||
"name": "provider",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "select",
|
||||
"values": [
|
||||
"1panel",
|
||||
"acmehttpreq",
|
||||
"akamai",
|
||||
"aliyun",
|
||||
"aws",
|
||||
"azure",
|
||||
"baiducloud",
|
||||
"baishan",
|
||||
"baotapanel",
|
||||
"byteplus",
|
||||
"buypass",
|
||||
"cachefly",
|
||||
"cdnfly",
|
||||
"cloudflare",
|
||||
"cloudns",
|
||||
"cmcccloud",
|
||||
"ctcccloud",
|
||||
"cucccloud",
|
||||
"desec",
|
||||
"dnsla",
|
||||
"dogecloud",
|
||||
"dynv6",
|
||||
"edgio",
|
||||
"fastly",
|
||||
"gname",
|
||||
"gcore",
|
||||
"godaddy",
|
||||
"goedge",
|
||||
"googletrustservices",
|
||||
"huaweicloud",
|
||||
"jdcloud",
|
||||
"k8s",
|
||||
"letsencrypt",
|
||||
"letsencryptstaging",
|
||||
"local",
|
||||
"namecheap",
|
||||
"namedotcom",
|
||||
"namesilo",
|
||||
"ns1",
|
||||
"porkbun",
|
||||
"powerdns",
|
||||
"qiniu",
|
||||
"qingcloud",
|
||||
"rainyun",
|
||||
"safeline",
|
||||
"ssh",
|
||||
"sslcom",
|
||||
"tencentcloud",
|
||||
"ucloud",
|
||||
"upyun",
|
||||
"vercel",
|
||||
"volcengine",
|
||||
"wangsu",
|
||||
"webhook",
|
||||
"westcn",
|
||||
"zerossl"
|
||||
]
|
||||
}`)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Save(collection)
|
||||
}, func(app core.App) error {
|
||||
return nil
|
||||
})
|
||||
}
|
1
ui/public/imgs/providers/wangsu.svg
Normal file
1
ui/public/imgs/providers/wangsu.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" x="0" y="0" width="200" height="200" viewBox="0 0 340 100"><g><path d="M290,61.5c-4.1,4.6-29.3,36.7-56.9,26.7c-3.6-1.3-7.2-3.3-10.9-6.2c3.8,0.2,7.5,0,10.9-0.3 c5.3-0.5,9.8-1.5,12.4-2.9c6.1-3,4.5-13.2-12.4-16.2c-0.4-0.1-0.9-0.2-1.3-0.2c-11.3-1.7-19.5,1.7-23.7,5.3 c-0.1,0-0.3-0.1-0.4-0.2c-2.6-1.1-5.2-2.2-7.8-3.2c20-0.9,23.4-14.1,4.8-26.4c-7.5-5-16.3-9.4-25.6-13.4 c-10.7-4.5-22.3-8.5-33.7-11.7c-7.9-2.2-15.7-4.1-23.1-5.7C109.9,4.6,98.7,2.8,90,2C71.1,0.3,58.1,1.5,50.8,5.8 C47.7,7.6,46,10,46,13v0.3c0,1.6,0.5,3.3,1.4,5.2c3,6.3,11.1,13.5,24.1,21.5c11.5,7.1,28.8,15.2,43.5,20.3 c0.1,0.8,0.2,1.6,0.6,2.6c0.9,2.4,3.1,5.2,6.7,8.3c0.5,0.5,1.1,1,1.8,1.5c5.7,4.5,12.7,8.7,21.1,12.2l0.2,0.1 c10.9,4.6,22.6,8.2,33.7,10.8c16.7,3.8,32.2,5.4,42.6,4.9c0.5-0.2,0.7-0.4,0.4-0.6c-0.1-0.1-0.2-0.2-0.4-0.2 c-5.7-0.4-12.9-1.2-21.7-2.3c-7.3-1-14.3-2.3-21-3.8c-12.1-2.7-23.1-6.2-33-10.5c-0.2-0.1-0.4-0.2-0.7-0.3 c-6.7-2.9-10.5-5.7-11.4-8.2c-1.2-3.4,1.9-5.2,5-6c2,0.7,4.1,1.3,6.4,1.9c10.2,2.9,22.2,5.5,33.7,7.4c11.1,1.9,21.7,3.2,29.5,3.6 c7.6,6.7,16.1,10.2,24.6,11.3c15.2,1.8,30.4-4.1,41.1-13.8c7.3-6.6,13.2-13,16.3-17L290,61.5z M145.3,30.3 c8.5,0.3,17.4,1.3,26.7,3.1c2.5,0.5,4.8,1,7,1.5c6.7,1.6,11.5,3.3,14.5,5.2c3.4,2.1,4.1,3.7,2.1,4.9c-1.5,0.9-4.6,1.5-9.4,2 c-2.4,0.2-4.8,0.4-7.2,0.4c-2.4,0.1-4.9,0-7.4,0c-4.9-0.2-9.8-0.9-14.8-1.8c-4.9-1-8.3-2-10-3c-1.3-0.8-1.6-1.4-1-1.9 c0.4-0.2,1.1-0.4,2.3-0.5c0.9-0.1,1.5-0.4,2.1-0.7c1.1-0.7,1.4-1.6,0.8-2.9c-0.7-1.3-2-2.6-4-3.8c-0.5-0.3-1.1-0.6-1.7-1 c-0.7-0.4-1.6-0.8-2.6-1.3C143.6,30.3,144.4,30.3,145.3,30.3z M145.3,51c-6-0.5-11.4-0.5-16.2,0.1c-2.7,0.3-5,0.7-6.9,1.3 c-1.3,0.4-2.5,0.9-3.4,1.4c-0.3-0.1-0.6-0.2-1-0.4c-12.8-4.5-26.4-14.1-19.6-18.1c4.8-2.8,12.8-4.4,24-4.8c2-0.1,4.1-0.1,6.3-0.1 c-1.7,5.1,2.7,10.8,12.9,17.1c1.3,0.8,2.6,1.6,3.8,2.3c0.9,0.5,1.7,1,2.6,1.4C147,51.1,146.1,51.1,145.3,51z M206.7,79.4 c-8.5-0.8-18.3-2.9-27.7-5.2c-10.6-2.7-20.7-5.7-27.7-7.9c6.4-1.5,14.8-2.2,25.6-2c0.7,0,1.4,0,2.1,0c10.8,0.4,20,1.9,27.8,4.6 C204.6,71.5,203.8,74.9,206.7,79.4z M233.1,66.9c0.4,0,0.7,0,1.1,0.1c3.6,0.4,6.7,0.9,9.8,3.1c3.5,2.4,3.5,4.4,2.1,6.5 c-1.3,2.1-7.1,2.9-13,3.2c-4.6,0.2-9.3,0.1-12.2,0C216.1,74.7,221.9,66.4,233.1,66.9z" fill="#005BAC"/></g></svg>
|
After Width: | Height: | Size: 2.2 KiB |
@ -51,6 +51,7 @@ import AccessFormUCloudConfig from "./AccessFormUCloudConfig";
|
||||
import AccessFormUpyunConfig from "./AccessFormUpyunConfig";
|
||||
import AccessFormVercelConfig from "./AccessFormVercelConfig";
|
||||
import AccessFormVolcEngineConfig from "./AccessFormVolcEngineConfig";
|
||||
import AccessFormWangsuConfig from "./AccessFormWangsuConfig";
|
||||
import AccessFormWebhookConfig from "./AccessFormWebhookConfig";
|
||||
import AccessFormWestcnConfig from "./AccessFormWestcnConfig";
|
||||
import AccessFormZeroSSLConfig from "./AccessFormZeroSSLConfig";
|
||||
@ -229,6 +230,8 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
return <AccessFormVercelConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.VOLCENGINE:
|
||||
return <AccessFormVolcEngineConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.WANGSU:
|
||||
return <AccessFormWangsuConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.WEBHOOK:
|
||||
return <AccessFormWebhookConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.WESTCN:
|
||||
|
76
ui/src/components/access/AccessFormWangsuConfig.tsx
Normal file
76
ui/src/components/access/AccessFormWangsuConfig.tsx
Normal file
@ -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 AccessConfigForWangsu } from "@/domain/access";
|
||||
|
||||
type AccessFormWangsuConfigFieldValues = Nullish<AccessConfigForWangsu>;
|
||||
|
||||
export type AccessFormWangsuConfigProps = {
|
||||
form: FormInstance;
|
||||
formName: string;
|
||||
disabled?: boolean;
|
||||
initialValues?: AccessFormWangsuConfigFieldValues;
|
||||
onValuesChange?: (values: AccessFormWangsuConfigFieldValues) => void;
|
||||
};
|
||||
|
||||
const initFormModel = (): AccessFormWangsuConfigFieldValues => {
|
||||
return {
|
||||
accessKeyId: "",
|
||||
accessKeySecret: "",
|
||||
};
|
||||
};
|
||||
|
||||
const AccessFormWangsuConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange: onValuesChange }: AccessFormWangsuConfigProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
accessKeyId: z
|
||||
.string()
|
||||
.min(1, t("access.form.wangsu_access_key_id.placeholder"))
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim(),
|
||||
accessKeySecret: z
|
||||
.string()
|
||||
.min(1, t("access.form.wangsu_access_key_secret.placeholder"))
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim(),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
|
||||
onValuesChange?.(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={formInst}
|
||||
disabled={disabled}
|
||||
initialValues={initialValues ?? initFormModel()}
|
||||
layout="vertical"
|
||||
name={formName}
|
||||
onValuesChange={handleFormChange}
|
||||
>
|
||||
<Form.Item
|
||||
name="accessKeyId"
|
||||
label={t("access.form.wangsu_access_key_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.wangsu_access_key_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.wangsu_access_key_id.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="accessKeySecret"
|
||||
label={t("access.form.wangsu_access_key_secret.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.wangsu_access_key_secret.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.wangsu_access_key_secret.placeholder")} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessFormWangsuConfig;
|
@ -9,6 +9,8 @@ import NotifyChannelEditFormDingTalkFields from "./NotifyChannelEditFormDingTalk
|
||||
import NotifyChannelEditFormEmailFields from "./NotifyChannelEditFormEmailFields";
|
||||
import NotifyChannelEditFormGotifyFields from "./NotifyChannelEditFormGotifyFields.tsx";
|
||||
import NotifyChannelEditFormLarkFields from "./NotifyChannelEditFormLarkFields";
|
||||
import NotifyChannelEditFormMattermostFields from "./NotifyChannelEditFormMattermostFields.tsx";
|
||||
import NotifyChannelEditFormPushoverFields from "./NotifyChannelEditFormPushoverFields";
|
||||
import NotifyChannelEditFormPushPlusFields from "./NotifyChannelEditFormPushPlusFields";
|
||||
import NotifyChannelEditFormServerChanFields from "./NotifyChannelEditFormServerChanFields";
|
||||
import NotifyChannelEditFormTelegramFields from "./NotifyChannelEditFormTelegramFields";
|
||||
@ -54,6 +56,10 @@ const NotifyChannelEditForm = forwardRef<NotifyChannelEditFormInstance, NotifyCh
|
||||
return <NotifyChannelEditFormGotifyFields />;
|
||||
case NOTIFY_CHANNELS.LARK:
|
||||
return <NotifyChannelEditFormLarkFields />;
|
||||
case NOTIFY_CHANNELS.MATTERMOST:
|
||||
return <NotifyChannelEditFormMattermostFields />;
|
||||
case NOTIFY_CHANNELS.PUSHOVER:
|
||||
return <NotifyChannelEditFormPushoverFields />;
|
||||
case NOTIFY_CHANNELS.PUSHPLUS:
|
||||
return <NotifyChannelEditFormPushPlusFields />;
|
||||
case NOTIFY_CHANNELS.SERVERCHAN:
|
||||
|
@ -0,0 +1,64 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
const NotifyChannelEditFormMattermostFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
serverUrl: z
|
||||
.string({ message: t("settings.notification.channel.form.mattermost_server_url.placeholder") })
|
||||
.url(t("common.errmsg.url_invalid")),
|
||||
channelId: z
|
||||
.string({ message: t("settings.notification.channel.form.mattermost_channel_id.placeholder") })
|
||||
.nonempty(t("settings.notification.channel.form.mattermost_channel_id.placeholder")),
|
||||
username: z
|
||||
.string({ message: t("settings.notification.channel.form.mattermost_username.placeholder") })
|
||||
.nonempty(t("settings.notification.channel.form.mattermost_username.placeholder")),
|
||||
password: z
|
||||
.string({ message: t("settings.notification.channel.form.mattermost_password.placeholder") })
|
||||
.nonempty(t("settings.notification.channel.form.mattermost_password.placeholder")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="serverUrl"
|
||||
label={t("settings.notification.channel.form.mattermost_server_url.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.notification.channel.form.mattermost_server_url.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("settings.notification.channel.form.mattermost_server_url.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="channelId"
|
||||
label={t("settings.notification.channel.form.mattermost_channel_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.notification.channel.form.mattermost_channel_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("settings.notification.channel.form.mattermost_channel_id.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="username"
|
||||
label={t("settings.notification.channel.form.mattermost_username.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Input placeholder={t("settings.notification.channel.form.mattermost_username.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="password"
|
||||
label={t("settings.notification.channel.form.mattermost_password.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Input.Password placeholder={t("settings.notification.channel.form.mattermost_password.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotifyChannelEditFormMattermostFields;
|
@ -0,0 +1,41 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
const NotifyChannelEditFormPushoverFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
token: z
|
||||
.string({ message: t("settings.notification.channel.form.pushover_token.placeholder") })
|
||||
.nonempty(t("settings.notification.channel.form.pushover_token.placeholder")),
|
||||
user: z
|
||||
.string({ message: t("settings.notification.channel.form.pushover_user.placeholder") })
|
||||
.nonempty(t("settings.notification.channel.form.pushover_user.placeholder")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="token"
|
||||
label={t("settings.notification.channel.form.pushover_token.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.notification.channel.form.pushover_token.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("settings.notification.channel.form.pushover_token.placeholder")} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="user"
|
||||
label={t("settings.notification.channel.form.pushover_user.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.notification.channel.form.pushover_user.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("settings.notification.channel.form.pushover_user.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotifyChannelEditFormPushoverFields;
|
@ -50,26 +50,27 @@ const AccessProviderSelect = ({ filter, showOptionTags, ...props }: AccessProvid
|
||||
<div className="flex max-w-full items-center justify-between gap-4 overflow-hidden">
|
||||
<Space className="max-w-full grow truncate" size={4}>
|
||||
<Avatar src={provider.icon} size="small" />
|
||||
<Typography.Text className="leading-loose" type={provider.builtin ? "secondary" : undefined} delete={provider.builtin ? true : undefined} ellipsis>
|
||||
<Typography.Text className="leading-loose" type={provider.builtin ? "secondary" : undefined} ellipsis>
|
||||
{t(provider.name)}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
{showOptionTags && (
|
||||
<div>
|
||||
<Show when={showOptionTagForDNS && provider.usages.includes(ACCESS_USAGES.DNS)}>
|
||||
<Tag color="peru">{t("access.props.provider.usage.dns")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForHosting && provider.usages.includes(ACCESS_USAGES.HOSTING)}>
|
||||
<Tag color="royalblue">{t("access.props.provider.usage.hosting")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForCA && provider.usages.includes(ACCESS_USAGES.CA)}>
|
||||
<Tag color="crimson">{t("access.props.provider.usage.ca")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForNotification && provider.usages.includes(ACCESS_USAGES.NOTIFICATION)}>
|
||||
<Tag color="mediumaquamarine">{t("access.props.provider.usage.notification")}</Tag>
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Show when={provider.builtin}>
|
||||
<Tag color="grey">{t("access.props.provider.builtin")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForDNS && provider.usages.includes(ACCESS_USAGES.DNS)}>
|
||||
<Tag color="peru">{t("access.props.provider.usage.dns")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForHosting && provider.usages.includes(ACCESS_USAGES.HOSTING)}>
|
||||
<Tag color="royalblue">{t("access.props.provider.usage.hosting")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForCA && provider.usages.includes(ACCESS_USAGES.CA)}>
|
||||
<Tag color="crimson">{t("access.props.provider.usage.ca")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForNotification && provider.usages.includes(ACCESS_USAGES.NOTIFICATION)}>
|
||||
<Tag color="mediumaquamarine">{t("access.props.provider.usage.notification")}</Tag>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
SyncOutlined as SyncOutlinedIcon,
|
||||
} from "@ant-design/icons";
|
||||
import { useRequest } from "ahooks";
|
||||
import { Button, Empty, Modal, Space, Table, type TableProps, Tag, Tooltip, notification } from "antd";
|
||||
import { Alert, Button, Empty, Modal, Space, Table, type TableProps, Tag, Tooltip, notification } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
@ -284,6 +284,8 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
||||
{NotificationContextHolder}
|
||||
|
||||
<div className={className} style={style}>
|
||||
<Alert className="mb-4" type="warning" message={<span dangerouslySetInnerHTML={{ __html: t("workflow_run.table.alert") }}></span>} />
|
||||
|
||||
<Table<WorkflowRunModel>
|
||||
columns={tableColumns}
|
||||
dataSource={tableData}
|
||||
|
@ -56,6 +56,7 @@ import DeployNodeConfigFormLocalConfig from "./DeployNodeConfigFormLocalConfig";
|
||||
import DeployNodeConfigFormQiniuCDNConfig from "./DeployNodeConfigFormQiniuCDNConfig";
|
||||
import DeployNodeConfigFormQiniuKodoConfig from "./DeployNodeConfigFormQiniuKodoConfig";
|
||||
import DeployNodeConfigFormQiniuPiliConfig from "./DeployNodeConfigFormQiniuPiliConfig";
|
||||
import DeployNodeConfigFormRainYunRCDNConfig from "./DeployNodeConfigFormRainYunRCDNConfig";
|
||||
import DeployNodeConfigFormSafeLineConfig from "./DeployNodeConfigFormSafeLineConfig";
|
||||
import DeployNodeConfigFormSSHConfig from "./DeployNodeConfigFormSSHConfig.tsx";
|
||||
import DeployNodeConfigFormTencentCloudCDNConfig from "./DeployNodeConfigFormTencentCloudCDNConfig.tsx";
|
||||
@ -80,6 +81,7 @@ import DeployNodeConfigFormVolcEngineDCDNConfig from "./DeployNodeConfigFormVolc
|
||||
import DeployNodeConfigFormVolcEngineImageXConfig from "./DeployNodeConfigFormVolcEngineImageXConfig.tsx";
|
||||
import DeployNodeConfigFormVolcEngineLiveConfig from "./DeployNodeConfigFormVolcEngineLiveConfig.tsx";
|
||||
import DeployNodeConfigFormVolcEngineTOSConfig from "./DeployNodeConfigFormVolcEngineTOSConfig.tsx";
|
||||
import DeployNodeConfigFormWangsuCDNProConfig from "./DeployNodeConfigFormWangsuCDNProConfig.tsx";
|
||||
import DeployNodeConfigFormWebhookConfig from "./DeployNodeConfigFormWebhookConfig.tsx";
|
||||
|
||||
type DeployNodeConfigFormFieldValues = Partial<WorkflowNodeConfigForDeploy>;
|
||||
@ -251,6 +253,8 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
return <DeployNodeConfigFormQiniuKodoConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.QINIU_PILI:
|
||||
return <DeployNodeConfigFormQiniuPiliConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.RAINYUN_RCDN:
|
||||
return <DeployNodeConfigFormRainYunRCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.SAFELINE:
|
||||
return <DeployNodeConfigFormSafeLineConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.SSH:
|
||||
@ -299,6 +303,8 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
return <DeployNodeConfigFormVolcEngineLiveConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.VOLCENGINE_TOS:
|
||||
return <DeployNodeConfigFormVolcEngineTOSConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.WANGSU_CDNPRO:
|
||||
return <DeployNodeConfigFormWangsuCDNProConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.WEBHOOK:
|
||||
return <DeployNodeConfigFormWebhookConfig {...nestedFormProps} />;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
type DeployNodeConfigFormGcoreCDNConfigFieldValues = Nullish<{
|
||||
resourceId?: string | number;
|
||||
resourceId: string | number;
|
||||
}>;
|
||||
|
||||
export type DeployNodeConfigFormGcoreCDNConfigProps = {
|
||||
@ -27,7 +27,7 @@ const DeployNodeConfigFormGcoreCDNConfig = ({ form: formInst, formName, disabled
|
||||
const formSchema = z.object({
|
||||
resourceId: z.union([z.string(), z.number()]).refine((v) => {
|
||||
return /^\d+$/.test(v + "") && +v > 0;
|
||||
}, t("workflow_node.deploy.form.gcore_cdn_certificate_id.placeholder")),
|
||||
}, t("workflow_node.deploy.form.gcore_cdn_resource_id.placeholder")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
|
@ -0,0 +1,80 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, type FormInstance, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
type DeployNodeConfigFormRainYunRCDNConfigFieldValues = Nullish<{
|
||||
instanceId: string | number;
|
||||
domain: string;
|
||||
}>;
|
||||
|
||||
export type DeployNodeConfigFormRainYunRCDNConfigProps = {
|
||||
form: FormInstance;
|
||||
formName: string;
|
||||
disabled?: boolean;
|
||||
initialValues?: DeployNodeConfigFormRainYunRCDNConfigFieldValues;
|
||||
onValuesChange?: (values: DeployNodeConfigFormRainYunRCDNConfigFieldValues) => void;
|
||||
};
|
||||
|
||||
const initFormModel = (): DeployNodeConfigFormRainYunRCDNConfigFieldValues => {
|
||||
return {
|
||||
instanceId: "",
|
||||
};
|
||||
};
|
||||
|
||||
const DeployNodeConfigFormRainYunRCDNConfig = ({
|
||||
form: formInst,
|
||||
formName,
|
||||
disabled,
|
||||
initialValues,
|
||||
onValuesChange,
|
||||
}: DeployNodeConfigFormRainYunRCDNConfigProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
instanceId: z.union([z.string(), z.number()]).refine((v) => {
|
||||
return /^\d+$/.test(v + "") && +v > 0;
|
||||
}, t("workflow_node.deploy.form.rainyun_rcdn_instance_id.placeholder")),
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.rainyun_rcdn_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
|
||||
onValuesChange?.(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={formInst}
|
||||
disabled={disabled}
|
||||
initialValues={initialValues ?? initFormModel()}
|
||||
layout="vertical"
|
||||
name={formName}
|
||||
onValuesChange={handleFormChange}
|
||||
>
|
||||
<Form.Item
|
||||
name="instanceId"
|
||||
label={t("workflow_node.deploy.form.rainyun_rcdn_instance_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.rainyun_rcdn_instance_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input type="number" placeholder={t("workflow_node.deploy.form.rainyun_rcdn_instance_id.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.rainyun_rcdn_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.rainyun_rcdn_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.rainyun_rcdn_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeConfigFormRainYunRCDNConfig;
|
@ -113,7 +113,7 @@ const DeployNodeConfigFormVolcEngineALBConfig = ({
|
||||
|
||||
<Show when={fieldResourceType === RESOURCE_TYPE_LOADBALANCER}>
|
||||
<Form.Item
|
||||
name="listenerId"
|
||||
name="loadbalancerId"
|
||||
label={t("workflow_node.deploy.form.volcengine_alb_loadbalancer_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.volcengine_alb_loadbalancer_id.tooltip") }}></span>}
|
||||
|
@ -104,7 +104,7 @@ const DeployNodeConfigFormVolcEngineCLBConfig = ({
|
||||
|
||||
<Show when={fieldResourceType === RESOURCE_TYPE_LOADBALANCER}>
|
||||
<Form.Item
|
||||
name="listenerId"
|
||||
name="loadbalancerId"
|
||||
label={t("workflow_node.deploy.form.volcengine_clb_loadbalancer_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.volcengine_clb_loadbalancer_id.tooltip") }}></span>}
|
||||
|
@ -0,0 +1,107 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, type FormInstance, Input, Select } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
type DeployNodeConfigFormBaishanCDNConfigFieldValues = Nullish<{
|
||||
environment: string;
|
||||
domain: string;
|
||||
certificateId?: string;
|
||||
webhookId?: string;
|
||||
}>;
|
||||
|
||||
export type DeployNodeConfigFormBaishanCDNConfigProps = {
|
||||
form: FormInstance;
|
||||
formName: string;
|
||||
disabled?: boolean;
|
||||
initialValues?: DeployNodeConfigFormBaishanCDNConfigFieldValues;
|
||||
onValuesChange?: (values: DeployNodeConfigFormBaishanCDNConfigFieldValues) => void;
|
||||
};
|
||||
|
||||
const ENVIRONMENT_PRODUCTION = "production" as const;
|
||||
const ENVIRONMENT_STAGING = "stating" as const;
|
||||
|
||||
const initFormModel = (): DeployNodeConfigFormBaishanCDNConfigFieldValues => {
|
||||
return {
|
||||
environment: ENVIRONMENT_PRODUCTION,
|
||||
};
|
||||
};
|
||||
|
||||
const DeployNodeConfigFormBaishanCDNConfig = ({
|
||||
form: formInst,
|
||||
formName,
|
||||
disabled,
|
||||
initialValues,
|
||||
onValuesChange,
|
||||
}: DeployNodeConfigFormBaishanCDNConfigProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
resourceType: z.union([z.literal(ENVIRONMENT_PRODUCTION), z.literal(ENVIRONMENT_STAGING)], {
|
||||
message: t("workflow_node.deploy.form.wangsu_cdnpro_environment.placeholder"),
|
||||
}),
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.wangsu_cdnpro_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")),
|
||||
certificateId: z.string().nullish(),
|
||||
webhookId: z.string().nullish(),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
|
||||
onValuesChange?.(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={formInst}
|
||||
disabled={disabled}
|
||||
initialValues={initialValues ?? initFormModel()}
|
||||
layout="vertical"
|
||||
name={formName}
|
||||
onValuesChange={handleFormChange}
|
||||
>
|
||||
<Form.Item name="environment" label={t("workflow_node.deploy.form.wangsu_cdnpro_environment.label")} rules={[formRule]}>
|
||||
<Select placeholder={t("workflow_node.deploy.form.wangsu_cdnpro_environment.placeholder")}>
|
||||
<Select.Option key={ENVIRONMENT_PRODUCTION} value={ENVIRONMENT_PRODUCTION}>
|
||||
{t("workflow_node.deploy.form.wangsu_cdnpro_environment.option.production.label")}
|
||||
</Select.Option>
|
||||
<Select.Option key={ENVIRONMENT_STAGING} value={ENVIRONMENT_STAGING}>
|
||||
{t("workflow_node.deploy.form.wangsu_cdnpro_environment.option.staging.label")}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.wangsu_cdnpro_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.wangsu_cdnpro_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.wangsu_cdnpro_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="certificateId"
|
||||
label={t("workflow_node.deploy.form.wangsu_cdnpro_certificate_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.wangsu_cdnpro_certificate_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.wangsu_cdnpro_certificate_id.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="webhookId"
|
||||
label={t("workflow_node.deploy.form.wangsu_cdnpro_webhook_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.wangsu_cdnpro_webhook_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.wangsu_cdnpro_webhook_id.placeholder")} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeConfigFormBaishanCDNConfig;
|
@ -47,6 +47,7 @@ export interface AccessModel extends BaseModel {
|
||||
| AccessConfigForUpyun
|
||||
| AccessConfigForVercel
|
||||
| AccessConfigForVolcEngine
|
||||
| AccessConfigForWangsu
|
||||
| AccessConfigForWebhook
|
||||
| AccessConfigForWestcn
|
||||
| AccessConfigForZeroSSL
|
||||
@ -268,6 +269,11 @@ export type AccessConfigForVolcEngine = {
|
||||
secretAccessKey: string;
|
||||
};
|
||||
|
||||
export type AccessConfigForWangsu = {
|
||||
accessKeyId: string;
|
||||
accessKeySecret: string;
|
||||
};
|
||||
|
||||
export type AccessConfigForWebhook = {
|
||||
url: string;
|
||||
allowInsecureConnections?: boolean;
|
||||
|
@ -50,6 +50,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
|
||||
UPYUN: "upyun",
|
||||
VERCEL: "vercel",
|
||||
VOLCENGINE: "volcengine",
|
||||
WANGSU: "wangsu",
|
||||
WEBHOOK: "webhook",
|
||||
WESTCN: "westcn",
|
||||
ZEROSSL: "zerossl",
|
||||
@ -94,10 +95,12 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
|
||||
[ACCESS_PROVIDERS.AWS, "provider.aws", "/imgs/providers/aws.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.AZURE, "provider.azure", "/imgs/providers/azure.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.GCORE, "provider.gcore", "/imgs/providers/gcore.png", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.RAINYUN, "provider.rainyun", "/imgs/providers/rainyun.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||
|
||||
[ACCESS_PROVIDERS.QINIU, "provider.qiniu", "/imgs/providers/qiniu.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.UPYUN, "provider.upyun", "/imgs/providers/upyun.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.BAISHAN, "provider.baishan", "/imgs/providers/baishan.png", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.WANGSU, "provider.wangsu", "/imgs/providers/wangsu.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.DOGECLOUD, "provider.dogecloud", "/imgs/providers/dogecloud.png", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.BYTEPLUS, "provider.byteplus", "/imgs/providers/byteplus.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.UCLOUD, "provider.ucloud", "/imgs/providers/ucloud.svg", [ACCESS_USAGES.HOSTING]],
|
||||
@ -122,7 +125,6 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
|
||||
[ACCESS_PROVIDERS.PORKBUN, "provider.porkbun", "/imgs/providers/porkbun.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.VERCEL, "provider.vercel", "/imgs/providers/vercel.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.CMCCCLOUD, "provider.cmcccloud", "/imgs/providers/cmcccloud.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.RAINYUN, "provider.rainyun", "/imgs/providers/rainyun.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.WESTCN, "provider.westcn", "/imgs/providers/westcn.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.POWERDNS, "provider.powerdns", "/imgs/providers/powerdns.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.ACMEHTTPREQ, "provider.acmehttpreq", "/imgs/providers/acmehttpreq.svg", [ACCESS_USAGES.DNS]],
|
||||
@ -344,6 +346,7 @@ export const DEPLOY_PROVIDERS = Object.freeze({
|
||||
QINIU_CDN: `${ACCESS_PROVIDERS.QINIU}-cdn`,
|
||||
QINIU_KODO: `${ACCESS_PROVIDERS.QINIU}-kodo`,
|
||||
QINIU_PILI: `${ACCESS_PROVIDERS.QINIU}-pili`,
|
||||
RAINYUN_RCDN: `${ACCESS_PROVIDERS.RAINYUN}-rcdn`,
|
||||
SAFELINE: `${ACCESS_PROVIDERS.SAFELINE}`,
|
||||
SSH: `${ACCESS_PROVIDERS.SSH}`,
|
||||
TENCENTCLOUD_CDN: `${ACCESS_PROVIDERS.TENCENTCLOUD}-cdn`,
|
||||
@ -369,6 +372,7 @@ export const DEPLOY_PROVIDERS = Object.freeze({
|
||||
VOLCENGINE_IMAGEX: `${ACCESS_PROVIDERS.VOLCENGINE}-imagex`,
|
||||
VOLCENGINE_LIVE: `${ACCESS_PROVIDERS.VOLCENGINE}-live`,
|
||||
VOLCENGINE_TOS: `${ACCESS_PROVIDERS.VOLCENGINE}-tos`,
|
||||
WANGSU_CDNPRO: `${ACCESS_PROVIDERS.WANGSU}-cdnpro`,
|
||||
WEBHOOK: `${ACCESS_PROVIDERS.WEBHOOK}`,
|
||||
} as const);
|
||||
|
||||
@ -418,7 +422,7 @@ export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProv
|
||||
[DEPLOY_PROVIDERS.ALIYUN_LIVE, "provider.aliyun.live", DEPLOY_CATEGORIES.AV],
|
||||
[DEPLOY_PROVIDERS.ALIYUN_VOD, "provider.aliyun.vod", DEPLOY_CATEGORIES.AV],
|
||||
[DEPLOY_PROVIDERS.ALIYUN_FC, "provider.aliyun.fc", DEPLOY_CATEGORIES.SERVERLESS],
|
||||
[DEPLOY_PROVIDERS.ALIYUN_CAS, "provider.aliyun.cas", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.ALIYUN_CAS, "provider.aliyun.cas_upload", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.ALIYUN_CAS_DEPLOY, "provider.aliyun.cas_deploy", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.TENCENTCLOUD_COS, "provider.tencentcloud.cos", DEPLOY_CATEGORIES.STORAGE],
|
||||
[DEPLOY_PROVIDERS.TENCENTCLOUD_CDN, "provider.tencentcloud.cdn", DEPLOY_CATEGORIES.CDN],
|
||||
@ -429,16 +433,16 @@ export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProv
|
||||
[DEPLOY_PROVIDERS.TENCENTCLOUD_CSS, "provider.tencentcloud.css", DEPLOY_CATEGORIES.AV],
|
||||
[DEPLOY_PROVIDERS.TENCENTCLOUD_VOD, "provider.tencentcloud.vod", DEPLOY_CATEGORIES.AV],
|
||||
[DEPLOY_PROVIDERS.TENCENTCLOUD_SCF, "provider.tencentcloud.scf", DEPLOY_CATEGORIES.SERVERLESS],
|
||||
[DEPLOY_PROVIDERS.TENCENTCLOUD_SSL, "provider.tencentcloud.ssl", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.TENCENTCLOUD_SSL, "provider.tencentcloud.ssl_upload", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.TENCENTCLOUD_SSL_DEPLOY, "provider.tencentcloud.ssl_deploy", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.BAIDUCLOUD_CDN, "provider.baiducloud.cdn", DEPLOY_CATEGORIES.CDN],
|
||||
[DEPLOY_PROVIDERS.BAIDUCLOUD_BLB, "provider.baiducloud.blb", DEPLOY_CATEGORIES.LOADBALANCE],
|
||||
[DEPLOY_PROVIDERS.BAIDUCLOUD_APPBLB, "provider.baiducloud.appblb", DEPLOY_CATEGORIES.LOADBALANCE],
|
||||
[DEPLOY_PROVIDERS.BAIDUCLOUD_CERT, "provider.baiducloud.cert", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.BAIDUCLOUD_CERT, "provider.baiducloud.cert_upload", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.HUAWEICLOUD_CDN, "provider.huaweicloud.cdn", DEPLOY_CATEGORIES.CDN],
|
||||
[DEPLOY_PROVIDERS.HUAWEICLOUD_ELB, "provider.huaweicloud.elb", DEPLOY_CATEGORIES.LOADBALANCE],
|
||||
[DEPLOY_PROVIDERS.HUAWEICLOUD_WAF, "provider.huaweicloud.waf", DEPLOY_CATEGORIES.FIREWALL],
|
||||
[DEPLOY_PROVIDERS.HUAWEICLOUD_SCM, "provider.huaweicloud.scm", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.HUAWEICLOUD_SCM, "provider.huaweicloud.scm_upload", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.VOLCENGINE_TOS, "provider.volcengine.tos", DEPLOY_CATEGORIES.STORAGE],
|
||||
[DEPLOY_PROVIDERS.VOLCENGINE_CDN, "provider.volcengine.cdn", DEPLOY_CATEGORIES.CDN],
|
||||
[DEPLOY_PROVIDERS.VOLCENGINE_DCDN, "provider.volcengine.dcdn", DEPLOY_CATEGORIES.CDN],
|
||||
@ -446,7 +450,7 @@ export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProv
|
||||
[DEPLOY_PROVIDERS.VOLCENGINE_ALB, "provider.volcengine.alb", DEPLOY_CATEGORIES.LOADBALANCE],
|
||||
[DEPLOY_PROVIDERS.VOLCENGINE_IMAGEX, "provider.volcengine.imagex", DEPLOY_CATEGORIES.STORAGE],
|
||||
[DEPLOY_PROVIDERS.VOLCENGINE_LIVE, "provider.volcengine.live", DEPLOY_CATEGORIES.AV],
|
||||
[DEPLOY_PROVIDERS.VOLCENGINE_CERTCENTER, "provider.volcengine.certcenter", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.VOLCENGINE_CERTCENTER, "provider.volcengine.certcenter_upload", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.JDCLOUD_ALB, "provider.jdcloud.alb", DEPLOY_CATEGORIES.LOADBALANCE],
|
||||
[DEPLOY_PROVIDERS.JDCLOUD_CDN, "provider.jdcloud.cdn", DEPLOY_CATEGORIES.CDN],
|
||||
[DEPLOY_PROVIDERS.JDCLOUD_LIVE, "provider.jdcloud.live", DEPLOY_CATEGORIES.AV],
|
||||
@ -457,10 +461,12 @@ export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProv
|
||||
[DEPLOY_PROVIDERS.UPYUN_FILE, "provider.upyun.file", DEPLOY_CATEGORIES.STORAGE],
|
||||
[DEPLOY_PROVIDERS.UPYUN_CDN, "provider.upyun.cdn", DEPLOY_CATEGORIES.CDN],
|
||||
[DEPLOY_PROVIDERS.BAISHAN_CDN, "provider.baishan.cdn", DEPLOY_CATEGORIES.CDN],
|
||||
[DEPLOY_PROVIDERS.WANGSU_CDNPRO, "provider.wangsu.cdnpro", DEPLOY_CATEGORIES.CDN],
|
||||
[DEPLOY_PROVIDERS.DOGECLOUD_CDN, "provider.dogecloud.cdn", DEPLOY_CATEGORIES.CDN],
|
||||
[DEPLOY_PROVIDERS.BYTEPLUS_CDN, "provider.byteplus.cdn", DEPLOY_CATEGORIES.CDN],
|
||||
[DEPLOY_PROVIDERS.UCLOUD_US3, "provider.ucloud.us3", DEPLOY_CATEGORIES.STORAGE],
|
||||
[DEPLOY_PROVIDERS.UCLOUD_UCDN, "provider.ucloud.ucdn", DEPLOY_CATEGORIES.CDN],
|
||||
[DEPLOY_PROVIDERS.RAINYUN_RCDN, "provider.rainyun.rcdn", DEPLOY_CATEGORIES.CDN],
|
||||
[DEPLOY_PROVIDERS.AWS_CLOUDFRONT, "provider.aws.cloudfront", DEPLOY_CATEGORIES.CDN],
|
||||
[DEPLOY_PROVIDERS.AWS_ACM, "provider.aws.acm", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.AZURE_KEYVAULT, "provider.azure.keyvault", DEPLOY_CATEGORIES.OTHER],
|
||||
|
@ -44,6 +44,8 @@ export const NOTIFY_CHANNELS = Object.freeze({
|
||||
EMAIL: "email",
|
||||
GOTIFY: "gotify",
|
||||
LARK: "lark",
|
||||
MATTERMOST: "mattermost",
|
||||
PUSHOVER: "pushover",
|
||||
PUSHPLUS: "pushplus",
|
||||
SERVERCHAN: "serverchan",
|
||||
TELEGRAM: "telegram",
|
||||
@ -64,6 +66,8 @@ export type NotifyChannelsSettingsContent = {
|
||||
[NOTIFY_CHANNELS.EMAIL]?: EmailNotifyChannelConfig;
|
||||
[NOTIFY_CHANNELS.GOTIFY]?: GotifyNotifyChannelConfig;
|
||||
[NOTIFY_CHANNELS.LARK]?: LarkNotifyChannelConfig;
|
||||
[NOTIFY_CHANNELS.MATTERMOST]?: MattermostNotifyChannelConfig;
|
||||
[NOTIFY_CHANNELS.PUSHOVER]?: PushoverNotifyChannelConfig;
|
||||
[NOTIFY_CHANNELS.PUSHPLUS]?: PushPlusNotifyChannelConfig;
|
||||
[NOTIFY_CHANNELS.SERVERCHAN]?: ServerChanNotifyChannelConfig;
|
||||
[NOTIFY_CHANNELS.TELEGRAM]?: TelegramNotifyChannelConfig;
|
||||
@ -106,6 +110,20 @@ export type LarkNotifyChannelConfig = {
|
||||
enabled?: boolean;
|
||||
};
|
||||
|
||||
export type MattermostNotifyChannelConfig = {
|
||||
serverUrl: string;
|
||||
channel: string;
|
||||
username: string;
|
||||
password: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export type PushoverNotifyChannelConfig = {
|
||||
token: string;
|
||||
user: string;
|
||||
enabled?: boolean;
|
||||
};
|
||||
|
||||
export type PushPlusNotifyChannelConfig = {
|
||||
token: string;
|
||||
enabled?: boolean;
|
||||
@ -143,6 +161,8 @@ export const notifyChannelsMap: Map<NotifyChannel["type"], NotifyChannel> = new
|
||||
[NOTIFY_CHANNELS.DINGTALK, "common.notifier.dingtalk"],
|
||||
[NOTIFY_CHANNELS.GOTIFY, "common.notifier.gotify"],
|
||||
[NOTIFY_CHANNELS.LARK, "common.notifier.lark"],
|
||||
[NOTIFY_CHANNELS.MATTERMOST, "common.notifier.mattermost"],
|
||||
[NOTIFY_CHANNELS.PUSHOVER, "common.notifier.pushover"],
|
||||
[NOTIFY_CHANNELS.PUSHPLUS, "common.notifier.pushplus"],
|
||||
[NOTIFY_CHANNELS.WECOM, "common.notifier.wecom"],
|
||||
[NOTIFY_CHANNELS.TELEGRAM, "common.notifier.telegram"],
|
||||
|
@ -1 +1 @@
|
||||
export const version = "v0.3.7";
|
||||
export const version = "v0.3.8";
|
||||
|
@ -17,6 +17,7 @@
|
||||
"access.props.provider.usage.hosting": "Hosting provider",
|
||||
"access.props.provider.usage.ca": "Certificate authority",
|
||||
"access.props.provider.usage.notification": "Notification channel",
|
||||
"access.props.provider.builtin": "Built-in",
|
||||
"access.props.range.both_dns_hosting": "Provider",
|
||||
"access.props.range.ca_only": "Certificate authority",
|
||||
"access.props.range.notify_only": "Notification channel",
|
||||
@ -237,7 +238,7 @@
|
||||
"access.form.qiniu_secret_key.tooltip": "For more information, see <a href=\"https://portal.qiniu.com/\" target=\"_blank\">https://portal.qiniu.com/</a>",
|
||||
"access.form.rainyun_api_key.label": "Rain Yun API key",
|
||||
"access.form.rainyun_api_key.placeholder": "Please enter Rain Yun API key",
|
||||
"access.form.rainyun_api_key.tooltip": "For more information, see <a href=\"https://www.rainyun.com/docs/account/racc/setting#api%E5%AF%86%E9%92%A5\" target=\"_blank\">https://www.rainyun.com/docs/account/racc/setting</a>",
|
||||
"access.form.rainyun_api_key.tooltip": "For more information, see <a href=\"https://app.rainyun.com/account/settings/api-key\" target=\"_blank\">https://app.rainyun.com/account/settings/api-key</a>",
|
||||
"access.form.safeline_api_url.label": "SafeLine URL",
|
||||
"access.form.safeline_api_url.placeholder": "Please enter SafeLine URL",
|
||||
"access.form.safeline_api_url.tooltip": "For more information, see <a href=\"https://docs.waf.chaitin.com/en/tutorials/install#use-web-ui\" target=\"_blank\">https://docs.waf.chaitin.com/en/tutorials/install</a>",
|
||||
@ -297,6 +298,12 @@
|
||||
"access.form.volcengine_secret_access_key.label": "VolcEngine SecretAccessKey",
|
||||
"access.form.volcengine_secret_access_key.placeholder": "Please enter VolcEngine SecretAccessKey",
|
||||
"access.form.volcengine_secret_access_key.tooltip": "For more information, see <a href=\"https://www.volcengine.com/docs/6291/216571\" target=\"_blank\">https://www.volcengine.com/docs/6291/216571</a>",
|
||||
"access.form.wangsu_access_key_id.label": "Wangsu Cloud AccessKeyId",
|
||||
"access.form.wangsu_access_key_id.placeholder": "Please enter Wangsu Cloud AccessKeyId",
|
||||
"access.form.wangsu_access_key_id.tooltip": "For more information, see <a href=\"https://en.wangsu.com/document/account-manage/15775\" target=\"_blank\">https://en.wangsu.com/document/account-manage/15775</a>",
|
||||
"access.form.wangsu_access_key_secret.label": "Wangsu Cloud AccessKeySecret",
|
||||
"access.form.wangsu_access_key_secret.placeholder": "Please enter Wangsu Cloud AccessKeySecret",
|
||||
"access.form.wangsu_access_key_secret.tooltip": "For more information, see <a href=\"https://en.wangsu.com/document/account-manage/15775\" target=\"_blank\">https://en.wangsu.com/document/account-manage/15775</a>",
|
||||
"access.form.webhook_url.label": "Webhook URL",
|
||||
"access.form.webhook_url.placeholder": "Please enter Webhook URL",
|
||||
"access.form.webhook_allow_insecure_conns.label": "Insecure SSL/TLS connections",
|
||||
|
@ -41,6 +41,8 @@
|
||||
"common.notifier.email": "Email",
|
||||
"common.notifier.gotify": "Gotify",
|
||||
"common.notifier.lark": "Lark",
|
||||
"common.notifier.mattermost": "Mattermost",
|
||||
"common.notifier.pushover": "Pushover",
|
||||
"common.notifier.pushplus": "PushPlus",
|
||||
"common.notifier.serverchan": "ServerChan",
|
||||
"common.notifier.telegram": "Telegram",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"provider.acmehttpreq": "Http Request (ACME Proxy)",
|
||||
"provider.aliyun": "Alibaba Cloud",
|
||||
"provider.aliyun.alb": "Alibaba Cloud - ALB (Application Load Balancer)",
|
||||
"provider.aliyun.cas": "Alibaba Cloud - Upload to CAS (Certificate Management Service)",
|
||||
"provider.aliyun.cas_upload": "Alibaba Cloud - Upload to CAS (Certificate Management Service)",
|
||||
"provider.aliyun.cas_deploy": "Alibaba Cloud - Deploy via CAS (Certificate Management Service)",
|
||||
"provider.aliyun.cdn": "Alibaba Cloud - CDN (Content Delivery Network)",
|
||||
"provider.aliyun.clb": "Alibaba Cloud - CLB (Classic Load Balancer)",
|
||||
@ -31,7 +31,7 @@
|
||||
"provider.baiducloud.appblb": "Baidu Cloud - AppBLB (Application Baidu Load Balancer)",
|
||||
"provider.baiducloud.blb": "Baidu Cloud - BLB (Baidu Load Balancer)",
|
||||
"provider.baiducloud.cdn": "Baidu Cloud - CDN (Content Delivery Network)",
|
||||
"provider.baiducloud.cert": "Baidu Cloud - Upload to SSL Certificate Service",
|
||||
"provider.baiducloud.cert_upload": "Baidu Cloud - Upload to SSL Certificate Service",
|
||||
"provider.baiducloud.dns": "Baidu Cloud - DNS (Domain Name Service)",
|
||||
"provider.baishan": "Baishan",
|
||||
"provider.baishan.cdn": "Baishan - CDN (Content Delivery Network)",
|
||||
@ -67,7 +67,7 @@
|
||||
"provider.huaweicloud.cdn": "Huawei Cloud - CDN (Content Delivery Network)",
|
||||
"provider.huaweicloud.dns": "Huawei Cloud - DNS (Domain Name Service)",
|
||||
"provider.huaweicloud.elb": "Huawei Cloud - ELB (Elastic Load Balance)",
|
||||
"provider.huaweicloud.scm": "Huawei Cloud - Upload to SCM (SSL Certificate Manager)",
|
||||
"provider.huaweicloud.scm_upload": "Huawei Cloud - Upload to SCM (SSL Certificate Manager)",
|
||||
"provider.huaweicloud.waf": "Huawei Cloud - WAF (Web Application Firewall)",
|
||||
"provider.jdcloud": "JD Cloud",
|
||||
"provider.jdcloud.alb": "JD Cloud - ALB (Application Load Balancer)",
|
||||
@ -91,6 +91,7 @@
|
||||
"provider.qiniu.kodo": "Qiniu - Kodo",
|
||||
"provider.qiniu.pili": "Qiniu - Pili",
|
||||
"provider.rainyun": "Rain Yun",
|
||||
"provider.rainyun.rcdn": "Rain Yun - RCDN (Rain Content Delivery Network)",
|
||||
"provider.safeline": "SafeLine",
|
||||
"provider.ssh": "SSH deployment",
|
||||
"provider.sslcom": "SSL.com",
|
||||
@ -103,7 +104,7 @@
|
||||
"provider.tencentcloud.ecdn": "Tencent Cloud - ECDN (Enterprise Content Delivery Network)",
|
||||
"provider.tencentcloud.eo": "Tencent Cloud - EdgeOne",
|
||||
"provider.tencentcloud.scf": "Tencent Cloud - SCF (Serverless Cloud Function)",
|
||||
"provider.tencentcloud.ssl": "Tencent Cloud - Upload to SSL Certificate Service",
|
||||
"provider.tencentcloud.ssl_upload": "Tencent Cloud - Upload to SSL Certificate Service",
|
||||
"provider.tencentcloud.ssl_deploy": "Tencent Cloud - Deploy via SSL Certificate Service",
|
||||
"provider.tencentcloud.vod": "Tencent Cloud - VOD (Video on Demand)",
|
||||
"provider.tencentcloud.waf": "Tencent Cloud - WAF (Web Application Firewall)",
|
||||
@ -112,18 +113,20 @@
|
||||
"provider.ucloud.us3": "UCloud - US3 (UCloud Object-based Storage)",
|
||||
"provider.upyun": "UPYUN",
|
||||
"provider.upyun.cdn": "UPYUN - CDN (Content Delivery Network)",
|
||||
"provider.upyun.file": "UPYUN - File Storage",
|
||||
"provider.upyun.file": "UPYUN - USS (Storage Service)",
|
||||
"provider.vercel": "Vercel",
|
||||
"provider.volcengine": "Volcengine",
|
||||
"provider.volcengine.alb": "Volcengine - ALB (Application Load Balancer)",
|
||||
"provider.volcengine.cdn": "Volcengine - CDN (Content Delivery Network)",
|
||||
"provider.volcengine.certcenter": "Volcengine - Upload to Certificate Center",
|
||||
"provider.volcengine.certcenter_upload": "Volcengine - Upload to Certificate Center",
|
||||
"provider.volcengine.clb": "Volcengine - CLB (Cloud Load Balancer)",
|
||||
"provider.volcengine.dcdn": "Volcengine - DCDN (Dynamic Content Delivery Network)",
|
||||
"provider.volcengine.dns": "Volcengine - DNS (Domain Name Service)",
|
||||
"provider.volcengine.imagex": "Volcengine - ImageX",
|
||||
"provider.volcengine.live": "Volcengine - Live",
|
||||
"provider.volcengine.tos": "Volcengine - TOS (Tinder Object Storage)",
|
||||
"provider.wangsu": "Wangsu Cloud",
|
||||
"provider.wangsu.cdnpro": "Wangsu Cloud - CDN Pro",
|
||||
"provider.webhook": "Webhook",
|
||||
"provider.westcn": "West.cn",
|
||||
"provider.zerossl": "ZeroSSL",
|
||||
|
@ -53,21 +53,38 @@
|
||||
"settings.notification.channel.form.email_sender_address.placeholder": "Please enter sender email address",
|
||||
"settings.notification.channel.form.email_receiver_address.label": "Receiver email address",
|
||||
"settings.notification.channel.form.email_receiver_address.placeholder": "Please enter receiver email address",
|
||||
"settings.notification.channel.form.gotify_url.placeholder": "Please enter Service URL",
|
||||
"settings.notification.channel.form.gotify_url.label": "Service URL",
|
||||
"settings.notification.channel.form.gotify_url.tooltip": "Example: <b>https://gotify.exmaple.com</b>, the protocol needs to be included but the trailing '/' should not be included.<br>For more information, see <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a>",
|
||||
"settings.notification.channel.form.gotify_token.placeholder": "Please enter Application Token",
|
||||
"settings.notification.channel.form.gotify_token.label": "Application Token",
|
||||
"settings.notification.channel.form.gotify_url.placeholder": "Please enter Service URL",
|
||||
"settings.notification.channel.form.gotify_url.tooltip": "For more information, see <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a><br><br>Example: <b>https://gotify.exmaple.com</b>, the trailing '/' should not be included.",
|
||||
"settings.notification.channel.form.gotify_token.label": "Application token",
|
||||
"settings.notification.channel.form.gotify_token.placeholder": "Please enter Application token",
|
||||
"settings.notification.channel.form.gotify_token.tooltip": "For more information, see <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a>",
|
||||
"settings.notification.channel.form.gotify_priority.placeholder": "Please enter message priority",
|
||||
"settings.notification.channel.form.gotify_priority.label": "Message Priority",
|
||||
"settings.notification.channel.form.gotify_priority.tooltip": "Message Priority, you can set it to 1 as default.<br>For more information, see <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a><br><a href=\"https://github.com/gotify/android/issues/18#issuecomment-437403888\" target=\"_blank\">https://github.com/gotify/android/issues/18#issuecomment-437403888</a>",
|
||||
"settings.notification.channel.form.gotify_priority.label": "Message priority",
|
||||
"settings.notification.channel.form.gotify_priority.tooltip": "For more information, see <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a>, <a href=\"https://github.com/gotify/android/issues/18#issuecomment-437403888\" target=\"_blank\">https://github.com/gotify/android/issues/18#issuecomment-437403888</a>",
|
||||
"settings.notification.channel.form.gotify_priority.error.gte0": "Message Priority must be greater than or equal to 0.",
|
||||
"settings.notification.channel.form.lark_webhook_url.label": "Webhook URL",
|
||||
"settings.notification.channel.form.lark_webhook_url.placeholder": "Please enter Webhook URL",
|
||||
"settings.notification.channel.form.lark_webhook_url.tooltip": "For more information, see <a href=\"https://www.feishu.cn/hc/en-US/articles/807992406756\" target=\"_blank\">https://www.feishu.cn/hc/en-US/articles/807992406756</a>",
|
||||
"settings.notification.channel.form.mattermost_server_url.label": "Service URL",
|
||||
"settings.notification.channel.form.mattermost_server_url.placeholder": "Please enter service URL",
|
||||
"settings.notification.channel.form.mattermost_server_url.tooltip": "Example: <b>https://exmaple.com</b>, the protocol needs to be included but the trailing '/' should not be included.",
|
||||
"settings.notification.channel.form.mattermost_channel_id.label": "Channel ID",
|
||||
"settings.notification.channel.form.mattermost_channel_id.placeholder": "Please enter channel ID",
|
||||
"settings.notification.channel.form.mattermost_channel_id.tooltip": "How to get the channel ID? Select the target channel from the left sidebar, click on the channel name at the top, and choose ”Channel Details.” You can directly see the channel ID on the pop-up page.",
|
||||
"settings.notification.channel.form.mattermost_username.label": "Username",
|
||||
"settings.notification.channel.form.mattermost_username.placeholder": "Please enter username",
|
||||
"settings.notification.channel.form.mattermost_password.label": "Password",
|
||||
"settings.notification.channel.form.mattermost_password.placeholder": "Please enter password",
|
||||
"settings.notification.channel.form.pushover_token.placeholder": "Please enter Application API Token",
|
||||
"settings.notification.channel.form.pushover_token.label": "Application API Token",
|
||||
"settings.notification.channel.form.pushover_token.tooltip": "For more information, see <a href=\"https://pushover.net/api#registration\" target=\"_blank\">https://pushover.net/api#registration</a>",
|
||||
"settings.notification.channel.form.pushover_user.placeholder": "Please enter User/Group Key",
|
||||
"settings.notification.channel.form.pushover_user.label": "User/Group Key",
|
||||
"settings.notification.channel.form.pushover_user.tooltip": "For more information, see <a href=\"https://pushover.net/api#identifiers\" target=\"_blank\">https://pushover.net/api#identifiers</a>",
|
||||
"settings.notification.channel.form.pushplus_token.placeholder": "Please enter Token",
|
||||
"settings.notification.channel.form.pushplus_token.label": "Token",
|
||||
"settings.notification.channel.form.pushplus_token.placeholder": "Please enter token",
|
||||
"settings.notification.channel.form.pushplus_token.tooltip": "For more information, see <a href=\"https://www.pushplus.plus/push1.html\" target=\"_blank\">https://www.pushplus.plus/push1.html</a>",
|
||||
"settings.notification.channel.form.serverchan_url.label": "Server URL",
|
||||
"settings.notification.channel.form.serverchan_url.placeholder": "Please enter ServerChan server URL (e.g. https://sctapi.ftqq.com/*****.send)",
|
||||
|
@ -95,7 +95,7 @@
|
||||
"workflow_node.deploy.form.provider.placeholder": "Please select deploy target",
|
||||
"workflow_node.deploy.form.provider_access.label": "Host provider authorization",
|
||||
"workflow_node.deploy.form.provider_access.placeholder": "Please select an authorization of host provider",
|
||||
"workflow_node.deploy.form.provider_access.tooltip": "Used to deploy certificates.",
|
||||
"workflow_node.deploy.form.provider_access.tooltip": "Used to invoke API during deployment.",
|
||||
"workflow_node.deploy.form.provider_access.button": "Create",
|
||||
"workflow_node.deploy.form.provider_access.guide_for_local": "Tips: If you are running Certimate in Docker, the \"Local\" refers to the container rather than the host.",
|
||||
"workflow_node.deploy.form.certificate.label": "Certificate",
|
||||
@ -269,11 +269,11 @@
|
||||
"workflow_node.deploy.form.baiducloud_cdn_domain.label": "Baidu Cloud CDN domain",
|
||||
"workflow_node.deploy.form.baiducloud_cdn_domain.placeholder": "Please enter Baidu Cloud CDN domain name",
|
||||
"workflow_node.deploy.form.baiducloud_cdn_domain.tooltip": "For more information, see <a href=\"https://console.bce.baidu.com/cdn\" target=\"_blank\">https://console.bce.baidu.com/cdn</a>",
|
||||
"workflow_node.deploy.form.baishan_cdn_domain.label": "Baishan CDN domain",
|
||||
"workflow_node.deploy.form.baishan_cdn_domain.placeholder": "Please enter Baishan CDN domain name",
|
||||
"workflow_node.deploy.form.baishan_cdn_domain.label": "Baishan Cloud CDN domain",
|
||||
"workflow_node.deploy.form.baishan_cdn_domain.placeholder": "Please enter Baishan Cloud CDN domain name",
|
||||
"workflow_node.deploy.form.baishan_cdn_domain.tooltip": "For more information, see <a href=\"https://cdnx.console.baishan.com\" target=\"_blank\">https://cdnx.console.baishan.com</a>",
|
||||
"workflow_node.deploy.form.baishan_cdn_certificate_id.label": "Baishan CDN certificate ID (Optional",
|
||||
"workflow_node.deploy.form.baishan_cdn_certificate_id.placeholder": "Please enter Baishan CDN certificate ID",
|
||||
"workflow_node.deploy.form.baishan_cdn_certificate_id.label": "Baishan Cloud CDN certificate ID (Optional)",
|
||||
"workflow_node.deploy.form.baishan_cdn_certificate_id.placeholder": "Please enter Baishan Cloud CDN certificate ID",
|
||||
"workflow_node.deploy.form.baishan_cdn_certificate_id.tooltip": "For more information, see <a href=\"https://cdnx.console.baishan.com/#/cdn/cert\" target=\"_blank\">https://cdnx.console.baishan.com/#/cdn/cert</a>",
|
||||
"workflow_node.deploy.form.baotapanel_console_auto_restart.label": "Auto restart after deployment",
|
||||
"workflow_node.deploy.form.baotapanel_site_type.label": "aaPanel site type",
|
||||
@ -436,6 +436,12 @@
|
||||
"workflow_node.deploy.form.qiniu_pili_domain.label": "Qiniu Pili streaming domain",
|
||||
"workflow_node.deploy.form.qiniu_pili_domain.placeholder": "Please enter Qiniu Pili streaming domain name",
|
||||
"workflow_node.deploy.form.qiniu_pili_domain.tooltip": "For more information, see <a href=\"hhttps://portal.qiniu.com/hub\" target=\"_blank\">https://portal.qiniu.com/hub</a>",
|
||||
"workflow_node.deploy.form.rainyun_rcdn_instance_id.label": "Rain Yun RCDN instance ID",
|
||||
"workflow_node.deploy.form.rainyun_rcdn_instance_id.placeholder": "Please enter Rain Yun RCDN instance ID",
|
||||
"workflow_node.deploy.form.rainyun_rcdn_instance_id.tooltip": "For more information, see <a href=\"https://app.rainyun.com/apps/rcdn/list\" target=\"_blank\">https://app.rainyun.com/apps/rcdn/list</a>",
|
||||
"workflow_node.deploy.form.rainyun_rcdn_domain.label": "Rain Yun RCDN domain",
|
||||
"workflow_node.deploy.form.rainyun_rcdn_domain.placeholder": "Please enter Rain Yun RCDN domain name",
|
||||
"workflow_node.deploy.form.rainyun_rcdn_domain.tooltip": "For more information, see <a href=\"https://app.rainyun.com/apps/rcdn/list\" target=\"_blank\">https://app.rainyun.com/apps/rcdn/list</a>",
|
||||
"workflow_node.deploy.form.safeline_resource_type.label": "Resource type",
|
||||
"workflow_node.deploy.form.safeline_resource_type.placeholder": "Please select resource type",
|
||||
"workflow_node.deploy.form.safeline_resource_type.option.certificate.label": "Certificate",
|
||||
@ -633,6 +639,19 @@
|
||||
"workflow_node.deploy.form.volcengine_tos_domain.label": "VolcEngine TOS domain",
|
||||
"workflow_node.deploy.form.volcengine_tos_domain.placeholder": "Please enter VolcEngine TOS domain name",
|
||||
"workflow_node.deploy.form.volcengine_tos_domain.tooltip": "For more information, see <a href=\"https://console.volcengine.com/tos\" target=\"_blank\">https://console.volcengine.com/tos</a>",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_environment.label": "Wangsu Cloud environment",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_environment.placeholder": "Please select Wangsu Cloud environment",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_environment.option.production.label": "Production environment",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_environment.option.staging.label": "Staging environment",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_domain.label": "Wangsu Cloud CDN domain",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_domain.placeholder": "Please enter Wangsu Cloud CDN domain name",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_domain.tooltip": "For more information, see <a href=\"https://cdnpro.console.wangsu.com/v2/index/#/properties\" target=\"_blank\">https://cdnpro.console.wangsu.com/v2/index/#/properties</a>",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_certificate_id.label": "Wangsu Cloud CDN certificate ID (Optional)",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_certificate_id.placeholder": "Please enter Wangsu Cloud CDN certificate ID",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_certificate_id.tooltip": "For more information, see <a href=\"https://cdnpro.console.wangsu.com/v2/index/#/certificate\" target=\"_blank\">https://cdnpro.console.wangsu.com/v2/index/#/certificate</a>",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_webhook_id.label": "Wangsu Cloud CDN Webhook ID (Optional)",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_webhook_id.placeholder": "Please enter Wangsu Cloud CDN Webhook ID",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_webhook_id.tooltip": "For more information, see <a href=\"https://cdnpro.console.wangsu.com/v2/index/#/certificate\" target=\"_blank\">https://cdnpro.console.wangsu.com/v2/index/#/certificate</a>",
|
||||
"workflow_node.deploy.form.webhook_data.label": "Webhook data (JSON format)",
|
||||
"workflow_node.deploy.form.webhook_data.placeholder": "Please enter Webhook data",
|
||||
"workflow_node.deploy.form.webhook_data.guide": "Tips: The Webhook data should be a key-value pair in JSON format. The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL. <br><br>Supported variables: <br><strong>${DOMAIN}</strong>: The primary domain of the certificate (<i>CommonName</i>).<br><strong>${DOMAINS}</strong>: The domain list of the certificate (<i>SubjectAltNames</i>).<br><strong>${CERTIFICATE}</strong>: The PEM format content of the certificate file.<br><strong>${PRIVATE_KEY}</strong>: The PEM format content of the private key file.",
|
||||
|
@ -5,6 +5,8 @@
|
||||
"workflow_run.action.delete": "Delete run",
|
||||
"workflow_run.action.delete.confirm": "Are you sure to delete this run?",
|
||||
|
||||
"workflow_run.table.alert": "Attention: The workflow run contains the execution results of each node. Deleting it may trigger re-application or re-deployment of certificates due to the inability to find the previous execution result. Please do not delete unless necessary. It is recommended to keep it for at least 180 days.",
|
||||
|
||||
"workflow_run.props.id": "ID",
|
||||
"workflow_run.props.status": "Status",
|
||||
"workflow_run.props.status.pending": "Pending",
|
||||
|
@ -17,6 +17,7 @@
|
||||
"access.props.provider.usage.hosting": "主机提供商",
|
||||
"access.props.provider.usage.ca": "证书颁发机构",
|
||||
"access.props.provider.usage.notification": "通知渠道",
|
||||
"access.props.provider.builtin": "内置",
|
||||
"access.props.range.both_dns_hosting": "提供商",
|
||||
"access.props.range.ca_only": "证书颁发机构",
|
||||
"access.props.range.notify_only": "通知渠道",
|
||||
@ -231,7 +232,7 @@
|
||||
"access.form.qiniu_secret_key.tooltip": "这是什么?请参阅 <a href=\"https://portal.qiniu.com/\" target=\"_blank\">https://portal.qiniu.com/</a>",
|
||||
"access.form.rainyun_api_key.label": "雨云 API 密钥",
|
||||
"access.form.rainyun_api_key.placeholder": "请输入雨云 API 密钥",
|
||||
"access.form.rainyun_api_key.tooltip": "这是什么?请参阅 <a href=\"https://www.rainyun.com/docs/account/racc/setting#api%E5%AF%86%E9%92%A5\" target=\"_blank\">https://www.rainyun.com/docs/account/racc/setting</a>",
|
||||
"access.form.rainyun_api_key.tooltip": "这是什么?请参阅 <a href=\"https://app.rainyun.com/account/settings/api-key\" target=\"_blank\">https://app.rainyun.com/account/settings/api-key</a>",
|
||||
"access.form.safeline_api_url.label": "雷池 URL",
|
||||
"access.form.safeline_api_url.placeholder": "请输入雷池 URL",
|
||||
"access.form.safeline_api_url.tooltip": "这是什么?请参阅 <a href=\"https://docs.waf-ce.chaitin.cn/zh/%E4%B8%8A%E6%89%8B%E6%8C%87%E5%8D%97/%E5%AE%89%E8%A3%85%E9%9B%B7%E6%B1%A0#%E8%AE%BF%E9%97%AE%E9%9B%B7%E6%B1%A0%E6%8E%A7%E5%88%B6%E5%8F%B0\" target=\"_blank\">https://docs.waf-ce.chaitin.cn/zh/上手指南/安装雷池</a>",
|
||||
@ -297,6 +298,12 @@
|
||||
"access.form.volcengine_secret_access_key.label": "火山引擎 SecretAccessKey",
|
||||
"access.form.volcengine_secret_access_key.placeholder": "请输入火山引擎 SecretAccessKey",
|
||||
"access.form.volcengine_secret_access_key.tooltip": "这是什么?请参阅 <a href=\"https://www.volcengine.com/docs/6291/216571\" target=\"_blank\">https://www.volcengine.com/docs/6291/216571</a>",
|
||||
"access.form.wangsu_access_key_id.label": "网宿云 AccessKeyId",
|
||||
"access.form.wangsu_access_key_id.placeholder": "请输入网宿科技 AccessKeyId",
|
||||
"access.form.wangsu_access_key_id.tooltip": "这是什么?请参阅 <a href=\"https://www.wangsu.com/document/account-manage/15775\" target=\"_blank\">https://www.wangsu.com/document/account-manage/15775</a>",
|
||||
"access.form.wangsu_access_key_secret.label": "网宿科技 AccessKeySecret",
|
||||
"access.form.wangsu_access_key_secret.placeholder": "请输入网宿科技 AccessKeySecret",
|
||||
"access.form.wangsu_access_key_secret.tooltip": "这是什么?请参阅 <a href=\"https://www.wangsu.com/document/account-manage/15775\" target=\"_blank\">https://www.wangsu.com/document/account-manage/15775</a>",
|
||||
"access.form.webhook_url.label": "Webhook 回调地址",
|
||||
"access.form.webhook_url.placeholder": "请输入 Webhook 回调地址",
|
||||
"access.form.webhook_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误",
|
||||
|
@ -41,6 +41,8 @@
|
||||
"common.notifier.email": "邮件",
|
||||
"common.notifier.gotify": "Gotify",
|
||||
"common.notifier.lark": "飞书",
|
||||
"common.notifier.mattermost": "Mattermost",
|
||||
"common.notifier.pushover": "Pushover",
|
||||
"common.notifier.pushplus": "PushPlus推送加",
|
||||
"common.notifier.serverchan": "Server 酱",
|
||||
"common.notifier.telegram": "Telegram",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"provider.acmehttpreq": "Http Request (ACME Proxy)",
|
||||
"provider.aliyun": "阿里云",
|
||||
"provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB",
|
||||
"provider.aliyun.cas": "阿里云 - 上传到数字证书管理服务 CAS",
|
||||
"provider.aliyun.cas_upload": "阿里云 - 上传到数字证书管理服务 CAS",
|
||||
"provider.aliyun.cas_deploy": "阿里云 - 通过数字证书管理服务 CAS 创建部署任务",
|
||||
"provider.aliyun.cdn": "阿里云 - 内容分发网络 CDN",
|
||||
"provider.aliyun.clb": "阿里云 - 传统型负载均衡 CLB",
|
||||
@ -31,7 +31,7 @@
|
||||
"provider.baiducloud.appblb": "百度智能云 - 应用型负载均衡 BLB",
|
||||
"provider.baiducloud.blb": "百度智能云 - 普通型负载均衡 BLB",
|
||||
"provider.baiducloud.cdn": "百度智能云 - 内容分发网络 CDN",
|
||||
"provider.baiducloud.cert": "百度智能云 - 上传到 SSL 证书服务",
|
||||
"provider.baiducloud.cert_upload": "百度智能云 - 上传到 SSL 证书服务",
|
||||
"provider.baiducloud.dns": "百度智能云 - 智能云解析 DNS",
|
||||
"provider.baishan": "白山云",
|
||||
"provider.baishan.cdn": "白山云 - 内容分发网络 CDN",
|
||||
@ -67,7 +67,7 @@
|
||||
"provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN",
|
||||
"provider.huaweicloud.dns": "华为云 - 云解析 DNS",
|
||||
"provider.huaweicloud.elb": "华为云 - 弹性负载均衡 ELB",
|
||||
"provider.huaweicloud.scm": "华为云 - 上传到云证书管理服务 SCM",
|
||||
"provider.huaweicloud.scm_upload": "华为云 - 上传到云证书管理服务 SCM",
|
||||
"provider.huaweicloud.waf": "华为云 - Web 应用防火墙 WAF",
|
||||
"provider.jdcloud": "京东云",
|
||||
"provider.jdcloud.alb": "京东云 - 应用负载均衡 ALB",
|
||||
@ -91,6 +91,7 @@
|
||||
"provider.qiniu.kodo": "七牛云 - 对象存储 Kodo",
|
||||
"provider.qiniu.pili": "七牛云 - 视频直播 Pili",
|
||||
"provider.rainyun": "雨云",
|
||||
"provider.rainyun.rcdn": "雨云 - 雨盾 CDN",
|
||||
"provider.safeline": "雷池",
|
||||
"provider.ssh": "SSH 部署",
|
||||
"provider.sslcom": "SSL.com",
|
||||
@ -103,7 +104,7 @@
|
||||
"provider.tencentcloud.ecdn": "腾讯云 - 全站加速网络 ECDN",
|
||||
"provider.tencentcloud.eo": "腾讯云 - 边缘安全加速平台 EdgeOne",
|
||||
"provider.tencentcloud.scf": "腾讯云 - 云函数 SCF",
|
||||
"provider.tencentcloud.ssl": "腾讯云 - 上传到 SSL 证书服务",
|
||||
"provider.tencentcloud.ssl_upload": "腾讯云 - 上传到 SSL 证书服务",
|
||||
"provider.tencentcloud.ssl_deploy": "腾讯云 - 通过 SSL 证书服务创建部署任务",
|
||||
"provider.tencentcloud.vod": "腾讯云 - 云点播 VOD",
|
||||
"provider.tencentcloud.waf": "腾讯云 - Web 应用防火墙 WAF",
|
||||
@ -112,18 +113,20 @@
|
||||
"provider.ucloud.us3": "优刻得 - 对象存储 US3",
|
||||
"provider.upyun": "又拍云",
|
||||
"provider.upyun.cdn": "又拍云 - 云分发 CDN",
|
||||
"provider.upyun.file": "又拍云 - 云存储",
|
||||
"provider.upyun.file": "又拍云 - 云存储 USS",
|
||||
"provider.vercel": "Vercel",
|
||||
"provider.volcengine": "火山引擎",
|
||||
"provider.volcengine.alb": "火山引擎 - 应用型负载均衡 ALB",
|
||||
"provider.volcengine.cdn": "火山引擎 - 内容分发网络 CDN",
|
||||
"provider.volcengine.certcenter": "火山引擎 - 上传到证书中心",
|
||||
"provider.volcengine.certcenter_upload": "火山引擎 - 上传到证书中心",
|
||||
"provider.volcengine.clb": "火山引擎 - 负载均衡 CLB",
|
||||
"provider.volcengine.dcdn": "火山引擎 - 全站加速 DCDN",
|
||||
"provider.volcengine.dns": "火山引擎 - 云解析 DNS",
|
||||
"provider.volcengine.imagex": "火山引擎 - 图片服务 ImageX",
|
||||
"provider.volcengine.live": "火山引擎 - 视频直播 Live",
|
||||
"provider.volcengine.tos": "火山引擎 - 对象存储 TOS",
|
||||
"provider.wangsu": "网宿云",
|
||||
"provider.wangsu.cdnpro": "网宿云 - CDN Pro",
|
||||
"provider.webhook": "Webhook",
|
||||
"provider.westcn": "西部数码",
|
||||
"provider.zerossl": "ZeroSSL",
|
||||
|
@ -53,22 +53,39 @@
|
||||
"settings.notification.channel.form.email_sender_address.placeholder": "请输入发送邮箱地址",
|
||||
"settings.notification.channel.form.email_receiver_address.label": "接收邮箱地址",
|
||||
"settings.notification.channel.form.email_receiver_address.placeholder": "请输入接收邮箱地址",
|
||||
"settings.notification.channel.form.gotify_url.placeholder": "请输入服务地址",
|
||||
"settings.notification.channel.form.gotify_url.label": "服务地址",
|
||||
"settings.notification.channel.form.gotify_url.tooltip": "示例: <b>https://gotify.exmaple.com</>,需要包含协议但不要包含末尾的'/'。<br>请参阅 <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a>",
|
||||
"settings.notification.channel.form.gotify_token.placeholder": "请输入应用Token",
|
||||
"settings.notification.channel.form.gotify_token.label": "应用Token",
|
||||
"settings.notification.channel.form.gotify_token.tooltip": "应用Token。<br>请参阅 <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a>",
|
||||
"settings.notification.channel.form.gotify_url.placeholder": "请输入服务地址",
|
||||
"settings.notification.channel.form.gotify_url.tooltip": "这是什么?请参阅 <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a><br><br>示例: <b>https://gotify.exmaple.com</b>,不要包含末尾的'/'。",
|
||||
"settings.notification.channel.form.gotify_token.label": "应用 Token",
|
||||
"settings.notification.channel.form.gotify_token.placeholder": "请输入应用 Token",
|
||||
"settings.notification.channel.form.gotify_token.tooltip": "这是什么?请参阅 <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a>",
|
||||
"settings.notification.channel.form.gotify_priority.label": "消息优先级(可选)",
|
||||
"settings.notification.channel.form.gotify_priority.placeholder": "请输入消息优先级",
|
||||
"settings.notification.channel.form.gotify_priority.label": "消息优先级",
|
||||
"settings.notification.channel.form.gotify_priority.tooltip": "消息优先级, 可以设置为1作为默认值。<br>请参阅 <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a><br><a href=\"https://github.com/gotify/android/issues/18#issuecomment-437403888\" target=\"_blank\">https://github.com/gotify/android/issues/18#issuecomment-437403888</a>",
|
||||
"settings.notification.channel.form.gotify_priority.error.gte0": "消息优先级需要大于等于0",
|
||||
"settings.notification.channel.form.gotify_priority.tooltip": "这是什么?请参阅 <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a>、<a href=\"https://github.com/gotify/android/issues/18#issuecomment-437403888\" target=\"_blank\">https://github.com/gotify/android/issues/18#issuecomment-437403888</a>",
|
||||
"settings.notification.channel.form.gotify_priority.error.gte0": "消息优先级需要大于等于 0",
|
||||
"settings.notification.channel.form.lark_webhook_url.label": "机器人 Webhook 地址",
|
||||
"settings.notification.channel.form.lark_webhook_url.placeholder": "请输入机器人 Webhook 地址",
|
||||
"settings.notification.channel.form.lark_webhook_url.tooltip": "这是什么?请参阅 <a href=\"https://www.feishu.cn/hc/zh-CN/articles/807992406756\" target=\"_blank\">https://www.feishu.cn/hc/zh-CN/articles/807992406756</a>",
|
||||
"settings.notification.channel.form.mattermost_server_url.label": "服务地址",
|
||||
"settings.notification.channel.form.mattermost_server_url.placeholder": "请输入服务地址",
|
||||
"settings.notification.channel.form.mattermost_server_url.tooltip": "示例: <b>https://exmaple.com</b>,需要包含协议但不要包含末尾的'/'",
|
||||
"settings.notification.channel.form.mattermost_channel_id.label": "频道ID",
|
||||
"settings.notification.channel.form.mattermost_channel_id.placeholder": "请输入频道ID",
|
||||
"settings.notification.channel.form.mattermost_channel_id.tooltip": "频道ID怎么获取?从左侧边栏中选择目标频道,点击顶部的频道名称,选择“频道详情”,即可在弹出页面中直接看到频道ID",
|
||||
"settings.notification.channel.form.mattermost_username.label": "用户名",
|
||||
"settings.notification.channel.form.mattermost_username.placeholder": "请输入用户名",
|
||||
"settings.notification.channel.form.mattermost_password.label": "密码",
|
||||
"settings.notification.channel.form.mattermost_password.placeholder": "请输入密码",
|
||||
"settings.notification.channel.form.pushover_token.placeholder": "请输入应用 API Token",
|
||||
"settings.notification.channel.form.pushover_token.label": "应用 API Token",
|
||||
"settings.notification.channel.form.pushover_token.tooltip": "这是什么?请参阅 <a href=\"https://pushover.net/api#registration\" target=\"_blank\">https://pushover.net/api#registration</a>",
|
||||
"settings.notification.channel.form.pushover_user.placeholder": "请输入用户/分组 Key",
|
||||
"settings.notification.channel.form.pushover_user.label": "用户/分组 Key",
|
||||
"settings.notification.channel.form.pushover_user.tooltip": "这是什么?请参阅 <a href=\"https://pushover.net/api#identifiers\" target=\"_blank\">https://pushover.net/api#identifiers</a>",
|
||||
"settings.notification.channel.form.pushplus_token.placeholder": "请输入Token",
|
||||
"settings.notification.channel.form.pushplus_token.label": "Token",
|
||||
"settings.notification.channel.form.pushplus_token.tooltip": "请参阅 <a href=\"https://www.pushplus.plus/push1.html\" target=\"_blank\">https://www.pushplus.plus/push1.html</a>",
|
||||
"settings.notification.channel.form.pushplus_token.placeholder": "请输入 Token",
|
||||
"settings.notification.channel.form.pushplus_token.tooltip": "这是什么?请参阅 <a href=\"https://www.pushplus.plus/push1.html\" target=\"_blank\">https://www.pushplus.plus/push1.html</a>",
|
||||
"settings.notification.channel.form.serverchan_url.label": "服务器地址",
|
||||
"settings.notification.channel.form.serverchan_url.placeholder": "请输入服务器地址(形如: https://sctapi.ftqq.com/*****.send)",
|
||||
"settings.notification.channel.form.serverchan_url.tooltip": "这是什么?请参阅 <a href=\"https://sct.ftqq.com/forward\" target=\"_blank\">https://sct.ftqq.com/forward</a>",
|
||||
|
@ -94,7 +94,7 @@
|
||||
"workflow_node.deploy.form.provider.placeholder": "请选择部署目标",
|
||||
"workflow_node.deploy.form.provider_access.label": "主机提供商授权",
|
||||
"workflow_node.deploy.form.provider_access.placeholder": "请选择主机提供商授权",
|
||||
"workflow_node.deploy.form.provider_access.tooltip": "用于部署证书,注意与申请阶段所需的 DNS 提供商相区分。",
|
||||
"workflow_node.deploy.form.provider_access.tooltip": "用于部署证书时调用相关 API,注意与申请阶段所需的 DNS 提供商相区分。",
|
||||
"workflow_node.deploy.form.provider_access.button": "新建",
|
||||
"workflow_node.deploy.form.provider_access.guide_for_local": "小贴士:如果你正在使用 Docker 运行 Certimate,“本地”指的是容器内而非宿主机。",
|
||||
"workflow_node.deploy.form.certificate.label": "待部署证书",
|
||||
@ -435,6 +435,12 @@
|
||||
"workflow_node.deploy.form.qiniu_pili_domain.label": "七牛云视频直播流域名",
|
||||
"workflow_node.deploy.form.qiniu_pili_domain.placeholder": "请输入七牛云视频直播流域名",
|
||||
"workflow_node.deploy.form.qiniu_pili_domain.tooltip": "这是什么?请参阅 <a href=\"hhttps://portal.qiniu.com/hub\" target=\"_blank\">https://portal.qiniu.com/hub</a>",
|
||||
"workflow_node.deploy.form.rainyun_rcdn_instance_id.label": "雨云 RCDN 实例 ID",
|
||||
"workflow_node.deploy.form.rainyun_rcdn_instance_id.placeholder": "请输入雨云 RCDN 实例 ID",
|
||||
"workflow_node.deploy.form.rainyun_rcdn_instance_id.tooltip": "这是什么?请参阅 <a href=\"https://app.rainyun.com/apps/rcdn/list\" target=\"_blank\">https://app.rainyun.com/apps/rcdn/list</a>",
|
||||
"workflow_node.deploy.form.rainyun_rcdn_domain.label": "雨云 RCDN 加速域名",
|
||||
"workflow_node.deploy.form.rainyun_rcdn_domain.placeholder": "请输入雨云 RCDN 加速域名(支持泛域名)",
|
||||
"workflow_node.deploy.form.rainyun_rcdn_domain.tooltip": "这是什么?请参阅 <a href=\"https://app.rainyun.com/apps/rcdn/list\" target=\"_blank\">https://app.rainyun.com/apps/rcdn/list</a>",
|
||||
"workflow_node.deploy.form.safeline_resource_type.label": "证书替换方式",
|
||||
"workflow_node.deploy.form.safeline_resource_type.placeholder": "请选择证书替换方式",
|
||||
"workflow_node.deploy.form.safeline_resource_type.option.certificate.label": "替换指定证书",
|
||||
@ -632,6 +638,19 @@
|
||||
"workflow_node.deploy.form.volcengine_tos_domain.label": "火山引擎 TOS 自定义域名",
|
||||
"workflow_node.deploy.form.volcengine_tos_domain.placeholder": "请输入火山引擎 TOS 自定义域名",
|
||||
"workflow_node.deploy.form.volcengine_tos_domain.tooltip": "这是什么?请参阅 see <a href=\"https://console.volcengine.com/tos\" target=\"_blank\">https://console.volcengine.com/tos</a>",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_environment.label": "网宿云环境",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_environment.placeholder": "请选择网宿云环境",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_environment.option.production.label": "生产环境",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_environment.option.staging.label": "演练环境",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_domain.label": "网宿云 CDN Pro 加速域名",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_domain.placeholder": "请输入网宿云 CDN Pro 加速域名(支持泛域名)",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_domain.tooltip": "这是什么?请参阅 <a href=\"https://cdnpro.console.wangsu.com/v2/index/#/properties\" target=\"_blank\">https://cdnpro.console.wangsu.com/v2/index/#/properties</a>",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_certificate_id.label": "网宿云 CDN Pro 原证书 ID(可选)",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_certificate_id.placeholder": "请输入网宿云 CDN Pro 原证书 ID",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_certificate_id.tooltip": "这是什么?请参阅 <a href=\"https://cdnpro.console.wangsu.com/v2/index/#/certificate\" target=\"_blank\">https://cdnpro.console.wangsu.com/v2/index/#/certificate</a><br><br>不填写时,将上传新证书;否则,将替换原证书。",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_webhook_id.label": "网宿云 CDN Pro 部署任务 Webhook ID(可选)",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_webhook_id.placeholder": "请输入网宿云 CDN Pro 部署任务 Webhook ID",
|
||||
"workflow_node.deploy.form.wangsu_cdnpro_webhook_id.tooltip": "这是什么?请参阅 <a href=\"https://cdnpro.console.wangsu.com/v2/index/#/certificate\" target=\"_blank\">https://cdnpro.console.wangsu.com/v2/index/#/certificate</a>",
|
||||
"workflow_node.deploy.form.webhook_data.label": "Webhook 回调数据(JSON 格式)",
|
||||
"workflow_node.deploy.form.webhook_data.placeholder": "请输入 Webhook 回调数据",
|
||||
"workflow_node.deploy.form.webhook_data.guide": "小贴士:回调数据是一个 JSON 格式的键值对。其中值支持模板变量,将在被发送到指定的 Webhook URL 时被替换为实际值;其他内容将保持原样。<br><br>支持的变量:<br><strong>${DOMAIN}</strong>:证书的主域名(即 <i>CommonName</i>)<br><strong>${DOMAINS}</strong>:证书的多域名列表(即 <i>SubjectAltNames</i>)<br><strong>${CERTIFICATE}</strong>:证书文件 PEM 格式内容<br><strong>${PRIVATE_KEY}</strong>:私钥文件 PEM 格式内容",
|
||||
@ -639,7 +658,7 @@
|
||||
"workflow_node.deploy.form.webhook_data_preset.button": "使用预设模板",
|
||||
"workflow_node.deploy.form.strategy_config.label": "执行策略",
|
||||
"workflow_node.deploy.form.skip_on_last_succeeded.label": "重复部署",
|
||||
"workflow_node.deploy.form.skip_on_last_succeeded.prefix": "当上次部署相同证书已成功时",
|
||||
"workflow_node.deploy.form.skip_on_last_succeeded.prefix": "当上次部署相同证书成功时,",
|
||||
"workflow_node.deploy.form.skip_on_last_succeeded.suffix": "重新部署。",
|
||||
"workflow_node.deploy.form.skip_on_last_succeeded.switch.on": "跳过",
|
||||
"workflow_node.deploy.form.skip_on_last_succeeded.switch.off": "不跳过",
|
||||
|
@ -5,6 +5,8 @@
|
||||
"workflow_run.action.delete": "删除执行",
|
||||
"workflow_run.action.delete.confirm": "确定要删除此执行吗?请注意此操作仅清除日志历史,但不会影响签发的证书。",
|
||||
|
||||
"workflow_run.table.alert": "注意:执行记录中包含工作流各节点的执行结果,删除后可能导致因找不到前次执行结果而触发重新申请或部署证书。如无必要请勿提前删除,建议保留至少 180 天。",
|
||||
|
||||
"workflow_run.props.id": "ID",
|
||||
"workflow_run.props.status": "状态",
|
||||
"workflow_run.props.status.pending": "等待执行",
|
||||
|
@ -285,7 +285,7 @@ const Dashboard = () => {
|
||||
}}
|
||||
pagination={false}
|
||||
rowKey={(record) => record.id}
|
||||
scroll={{ x: "max(100%, 960px)" }}
|
||||
scroll={{ x: "max(100%, 720px)" }}
|
||||
size="small"
|
||||
/>
|
||||
</Card>
|
||||
@ -310,7 +310,7 @@ const StatisticCard = ({
|
||||
onClick?: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<Card className="size-full overflow-hidden" hoverable loading={loading} variant="borderless" onClick={onClick}>
|
||||
<Card className="size-full overflow-hidden" hoverable loading={loading} bordered={false} onClick={onClick}>
|
||||
<Space size="middle">
|
||||
{icon}
|
||||
<Statistic
|
||||
|
Loading…
x
Reference in New Issue
Block a user