Merge branch 'main' into main

This commit is contained in:
Yoan.liu 2025-04-13 08:24:16 +08:00 committed by GitHub
commit 5b4c3bb668
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
78 changed files with 2210 additions and 106 deletions

3
go.mod
View File

@ -71,6 +71,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
github.com/Edgio/edgio-api v0.0.0-workspace // indirect
github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7 // indirect github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7 // indirect
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect
github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
@ -211,6 +212,8 @@ require (
modernc.org/sqlite v1.36.1 // indirect 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/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 replace gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1 => ./internal/pkg/vendors/cmcc-sdk/ecloudsdkclouddns@v1.0.1

View File

@ -49,6 +49,7 @@ import (
pLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local" pLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local"
pQiniuCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn" pQiniuCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn"
pQiniuPili "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-pili" 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" pSafeLine "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/safeline"
pSSH "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh" pSSH "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh"
pTencentCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cdn" 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" pVolcEngineImageX "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-imagex"
pVolcEngineLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-live" pVolcEngineLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-live"
pVolcEngineTOS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-tos" 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" 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/maputil"
"github.com/usual2970/certimate/internal/pkg/utils/sliceutil" "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: case domain.DeployProviderTypeSafeLine:
{ {
access := domain.AccessConfigForSafeLine{} 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: case domain.DeployProviderTypeWebhook:
{ {
access := domain.AccessConfigForWebhook{} access := domain.AccessConfigForWebhook{}

View File

@ -228,6 +228,11 @@ type AccessConfigForVolcEngine struct {
SecretAccessKey string `json:"secretAccessKey"` SecretAccessKey string `json:"secretAccessKey"`
} }
type AccessConfigForWangsu struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
}
type AccessConfigForWebhook struct { type AccessConfigForWebhook struct {
Url string `json:"url"` Url string `json:"url"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`

View File

@ -15,6 +15,7 @@ const (
NotifyChannelTypeGotify = NotifyChannelType("gotify") NotifyChannelTypeGotify = NotifyChannelType("gotify")
NotifyChannelTypeLark = NotifyChannelType("lark") NotifyChannelTypeLark = NotifyChannelType("lark")
NotifyChannelTypeMattermost = NotifyChannelType("mattermost") NotifyChannelTypeMattermost = NotifyChannelType("mattermost")
NotifyChannelTypePushover = NotifyChannelType("pushover")
NotifyChannelTypePushPlus = NotifyChannelType("pushplus") NotifyChannelTypePushPlus = NotifyChannelType("pushplus")
NotifyChannelTypeServerChan = NotifyChannelType("serverchan") NotifyChannelTypeServerChan = NotifyChannelType("serverchan")
NotifyChannelTypeTelegram = NotifyChannelType("telegram") NotifyChannelTypeTelegram = NotifyChannelType("telegram")

View File

@ -61,6 +61,7 @@ const (
AccessProviderTypeUpyun = AccessProviderType("upyun") AccessProviderTypeUpyun = AccessProviderType("upyun")
AccessProviderTypeVercel = AccessProviderType("vercel") AccessProviderTypeVercel = AccessProviderType("vercel")
AccessProviderTypeVolcEngine = AccessProviderType("volcengine") AccessProviderTypeVolcEngine = AccessProviderType("volcengine")
AccessProviderTypeWangsu = AccessProviderType("wangsu")
AccessProviderTypeWebhook = AccessProviderType("webhook") AccessProviderTypeWebhook = AccessProviderType("webhook")
AccessProviderTypeWestcn = AccessProviderType("westcn") AccessProviderTypeWestcn = AccessProviderType("westcn")
AccessProviderTypeZeroSSL = AccessProviderType("zerossl") AccessProviderTypeZeroSSL = AccessProviderType("zerossl")
@ -186,6 +187,7 @@ const (
DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn") DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn")
DeployProviderTypeQiniuKodo = DeployProviderType("qiniu-kodo") DeployProviderTypeQiniuKodo = DeployProviderType("qiniu-kodo")
DeployProviderTypeQiniuPili = DeployProviderType("qiniu-pili") DeployProviderTypeQiniuPili = DeployProviderType("qiniu-pili")
DeployProviderTypeRainYunRCDN = DeployProviderType("rainyun-rcdn")
DeployProviderTypeSafeLine = DeployProviderType("safeline") DeployProviderTypeSafeLine = DeployProviderType("safeline")
DeployProviderTypeSSH = DeployProviderType("ssh") DeployProviderTypeSSH = DeployProviderType("ssh")
DeployProviderTypeTencentCloudCDN = DeployProviderType("tencentcloud-cdn") DeployProviderTypeTencentCloudCDN = DeployProviderType("tencentcloud-cdn")
@ -211,5 +213,6 @@ const (
DeployProviderTypeVolcEngineImageX = DeployProviderType("volcengine-imagex") DeployProviderTypeVolcEngineImageX = DeployProviderType("volcengine-imagex")
DeployProviderTypeVolcEngineLive = DeployProviderType("volcengine-live") DeployProviderTypeVolcEngineLive = DeployProviderType("volcengine-live")
DeployProviderTypeVolcEngineTOS = DeployProviderType("volcengine-tos") DeployProviderTypeVolcEngineTOS = DeployProviderType("volcengine-tos")
DeployProviderTypeWangsuCDNPro = DeployProviderType("wangsu-cdnpro")
DeployProviderTypeWebhook = DeployProviderType("webhook") DeployProviderTypeWebhook = DeployProviderType("webhook")
) )

View File

@ -11,6 +11,7 @@ import (
pGotify "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/gotify" pGotify "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/gotify"
pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark" pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark"
pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost" 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" pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus"
pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan" pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan"
pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram"
@ -67,6 +68,11 @@ func createNotifier(channel domain.NotifyChannelType, channelConfig map[string]a
Username: maputil.GetString(channelConfig, "username"), Username: maputil.GetString(channelConfig, "username"),
Password: maputil.GetString(channelConfig, "password"), 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: case domain.NotifyChannelTypePushPlus:
return pPushPlus.NewNotifier(&pPushPlus.NotifierConfig{ return pPushPlus.NewNotifier(&pPushPlus.NotifierConfig{

View File

@ -4,12 +4,12 @@ import (
"context" "context"
"log/slog" "log/slog"
edgio "github.com/Edgio/edgio-api/applications/v7"
edgiodtos "github.com/Edgio/edgio-api/applications/v7/dtos"
xerrors "github.com/pkg/errors" xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer" "github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/utils/certutil" "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 { type DeployerConfig struct {
@ -24,7 +24,7 @@ type DeployerConfig struct {
type DeployerProvider struct { type DeployerProvider struct {
config *DeployerConfig config *DeployerConfig
logger *slog.Logger logger *slog.Logger
sdkClient *edgsdk.EdgioClient sdkClient *edgio.EdgioClient
} }
var _ deployer.Deployer = (*DeployerProvider)(nil) var _ deployer.Deployer = (*DeployerProvider)(nil)
@ -64,7 +64,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
// 上传 TLS 证书 // 上传 TLS 证书
// REF: https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts // REF: https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts
uploadTlsCertReq := edgsdkdtos.UploadTlsCertRequest{ uploadTlsCertReq := edgiodtos.UploadTlsCertRequest{
EnvironmentID: d.config.EnvironmentId, EnvironmentID: d.config.EnvironmentId,
PrimaryCert: privateCertPem, PrimaryCert: privateCertPem,
IntermediateCert: intermediateCertPem, IntermediateCert: intermediateCertPem,
@ -79,7 +79,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
return &deployer.DeployResult{}, nil return &deployer.DeployResult{}, nil
} }
func createSdkClient(clientId, clientSecret string) (*edgsdk.EdgioClient, error) { func createSdkClient(clientId, clientSecret string) (*edgio.EdgioClient, error) {
client := edgsdk.NewEdgioClient(clientId, clientSecret, "", "") client := edgio.NewEdgioClient(clientId, clientSecret, "", "")
return client, nil return client, nil
} }

View File

@ -100,9 +100,15 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
SSlEnabled: true, SSlEnabled: true,
SSLData: int(updateResourceCertId), SSLData: int(updateResourceCertId),
ProxySSLEnabled: getResourceResp.ProxySSLEnabled, ProxySSLEnabled: getResourceResp.ProxySSLEnabled,
ProxySSLCA: &getResourceResp.ProxySSLCA, }
ProxySSLData: &getResourceResp.ProxySSLData, if getResourceResp.ProxySSLCA != 0 {
Options: getResourceResp.Options, 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) 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)) d.logger.Debug("sdk request 'resources.Update'", slog.Int64("resourceId", d.config.ResourceId), slog.Any("request", updateResourceReq), slog.Any("response", updateResourceResp))

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -182,7 +182,7 @@ func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId str
return errors.New("config `listenerId` is required") 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 return err
} }

View File

@ -178,7 +178,7 @@ func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId str
return errors.New("config `listenerId` is required") 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 return err
} }

View File

@ -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
}

View File

@ -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)
})
}

View 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 &notifier.NotifyResult{}, nil
}

View File

@ -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)
})
}

View File

@ -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) { 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 return nil, err
} else if res != nil { } else if res != nil {
u.logger.Info("ssl certificate already exists") u.logger.Info("ssl certificate already exists")
@ -82,7 +82,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
} }
// 遍历证书列表,获取刚刚上传证书 ID // 遍历证书列表,获取刚刚上传证书 ID
if res, err := u.getExistCert(ctx, certPem, privkeyPem); err != nil { if res, err := u.getCertIfExists(ctx, certPem, privkeyPem); err != nil {
return nil, err return nil, err
} else if res == nil { } else if res == nil {
return nil, fmt.Errorf("no ssl certificate found, may be upload failed (code: %d, message: %s)", uploadWebsiteSSLResp.GetCode(), uploadWebsiteSSLResp.GetMessage()) 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) searchWebsiteSSLPageNumber := int32(1)
searchWebsiteSSLPageSize := int32(100) searchWebsiteSSLPageSize := int32(100)
for { for {

View File

@ -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
}

View File

@ -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))
})
}

View File

@ -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)) u.logger.Debug("sdk request 'ussl.UploadNormalCertificate'", slog.Any("request", uploadNormalCertificateReq), slog.Any("response", uploadNormalCertificateResp))
if err != nil { if err != nil {
if uploadNormalCertificateResp != nil && uploadNormalCertificateResp.GetRetCode() == 80035 { 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 return nil, err
} else if res == nil { } else if res == nil {
return nil, errors.New("no certificate found") return nil, errors.New("ucloud ssl: no certificate found")
} else { } else {
u.logger.Info("ssl certificate already exists") u.logger.Info("ssl certificate already exists")
return res, nil return res, nil
@ -112,7 +112,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
}, nil }, 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) certX509, err := certutil.ParseCertificateFromPEM(certPem)
if err != nil { if err != nil {

View File

@ -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 str, ok := value.(string); ok {
if result, err := strconv.ParseInt(str, 10, 32); err == nil { 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 str, ok := value.(string); ok {
if result, err := strconv.ParseInt(str, 10, 64); err == nil { if result, err := strconv.ParseInt(str, 10, 64); err == nil {

View File

@ -79,7 +79,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
if err != nil { if err != nil {
return resp, fmt.Errorf("1panel api error: failed to send request: %w", err) return resp, fmt.Errorf("1panel api error: failed to send request: %w", err)
} else if resp.IsError() { } 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 return resp, nil

View File

@ -75,7 +75,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
if err != nil { if err != nil {
return resp, fmt.Errorf("baishan api error: failed to send request: %w", err) return resp, fmt.Errorf("baishan api error: failed to send request: %w", err)
} else if resp.IsError() { } 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 return resp, nil

View File

@ -86,7 +86,7 @@ func (c *Client) sendRequest(path string, params interface{}) (*resty.Response,
if err != nil { if err != nil {
return resp, fmt.Errorf("baota api error: failed to send request: %w", err) return resp, fmt.Errorf("baota api error: failed to send request: %w", err)
} else if resp.IsError() { } 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 return resp, nil

View File

@ -59,7 +59,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
if err != nil { if err != nil {
return resp, fmt.Errorf("cachefly api error: failed to send request: %w", err) return resp, fmt.Errorf("cachefly api error: failed to send request: %w", err)
} else if resp.IsError() { } 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 return resp, nil

View File

@ -3,17 +3,18 @@ package cdnflysdk
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url"
) )
func (c *Client) GetSite(req *GetSiteRequest) (*GetSiteResponse, error) { func (c *Client) GetSite(req *GetSiteRequest) (*GetSiteResponse, error) {
resp := &GetSiteResponse{} 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 return resp, err
} }
func (c *Client) UpdateSite(req *UpdateSiteRequest) (*UpdateSiteResponse, error) { func (c *Client) UpdateSite(req *UpdateSiteRequest) (*UpdateSiteResponse, error) {
resp := &UpdateSiteResponse{} 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 return resp, err
} }
@ -25,6 +26,6 @@ func (c *Client) CreateCertificate(req *CreateCertificateRequest) (*CreateCertif
func (c *Client) UpdateCertificate(req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) { func (c *Client) UpdateCertificate(req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) {
resp := &UpdateCertificateResponse{} 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 return resp, err
} }

View File

@ -65,7 +65,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
if err != nil { if err != nil {
return resp, fmt.Errorf("cdnfly api error: failed to send request: %w", err) return resp, fmt.Errorf("cdnfly api error: failed to send request: %w", err)
} else if resp.IsError() { } 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 return resp, nil

View File

@ -60,7 +60,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
if err != nil { if err != nil {
return resp, fmt.Errorf("dnsla api error: failed to send request: %w", err) return resp, fmt.Errorf("dnsla api error: failed to send request: %w", err)
} else if resp.IsError() { } 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 return resp, nil

View File

@ -6,9 +6,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/Edgio/edgio-api/applications/v7/dtos"
"github.com/go-resty/resty/v2" "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. // AccessTokenResponse represents the response from the token endpoint.

View File

@ -3,7 +3,7 @@ package edgio_api
import ( import (
"context" "context"
"github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7/dtos" "github.com/Edgio/edgio-api/applications/v7/dtos"
) )
type EdgioClientInterface interface { type EdgioClientInterface interface {

View File

@ -0,0 +1,3 @@
module github.com/Edgio/edgio-api
go 1.23.0

View File

@ -82,7 +82,7 @@ func (c *Client) sendRequest(path string, params interface{}) (*resty.Response,
if err != nil { if err != nil {
return resp, fmt.Errorf("gname api error: failed to send request: %w", err) return resp, fmt.Errorf("gname api error: failed to send request: %w", err)
} else if resp.IsError() { } 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 return resp, nil

30
internal/pkg/vendors/rainyun-sdk/api.go vendored Normal file
View 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
}

View 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
}

View 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
}

View File

@ -47,7 +47,7 @@ func (c *Client) sendRequest(path string, params interface{}) (*resty.Response,
if err != nil { if err != nil {
return resp, fmt.Errorf("safeline api error: failed to send request: %w", err) return resp, fmt.Errorf("safeline api error: failed to send request: %w", err)
} else if resp.IsError() { } 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 return resp, nil

View File

@ -64,7 +64,7 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r
if err != nil { if err != nil {
return resp, fmt.Errorf("upyun api error: failed to send request: %w", err) return resp, fmt.Errorf("upyun api error: failed to send request: %w", err)
} else if resp.IsError() { } 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 return resp, nil

View 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", url.PathEscape(hostname)), nil, resp)
return resp, err
}

View 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
}

View 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"`
}

View 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
}

View File

@ -262,8 +262,8 @@ func init() {
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Config map[string]any `json:"config"` Config map[string]any `json:"config"`
Inputs map[string]any `json:"inputs"` Inputs []map[string]any `json:"inputs"`
Outputs map[string]any `json:"outputs"` Outputs []map[string]any `json:"outputs"`
Next *dWorkflowNode `json:"next,omitempty"` Next *dWorkflowNode `json:"next,omitempty"`
Branches []dWorkflowNode `json:"branches,omitempty"` Branches []dWorkflowNode `json:"branches,omitempty"`
Validated bool `json:"validated"` Validated bool `json:"validated"`

View 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
})
}

View 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

View File

@ -51,6 +51,7 @@ import AccessFormUCloudConfig from "./AccessFormUCloudConfig";
import AccessFormUpyunConfig from "./AccessFormUpyunConfig"; import AccessFormUpyunConfig from "./AccessFormUpyunConfig";
import AccessFormVercelConfig from "./AccessFormVercelConfig"; import AccessFormVercelConfig from "./AccessFormVercelConfig";
import AccessFormVolcEngineConfig from "./AccessFormVolcEngineConfig"; import AccessFormVolcEngineConfig from "./AccessFormVolcEngineConfig";
import AccessFormWangsuConfig from "./AccessFormWangsuConfig";
import AccessFormWebhookConfig from "./AccessFormWebhookConfig"; import AccessFormWebhookConfig from "./AccessFormWebhookConfig";
import AccessFormWestcnConfig from "./AccessFormWestcnConfig"; import AccessFormWestcnConfig from "./AccessFormWestcnConfig";
import AccessFormZeroSSLConfig from "./AccessFormZeroSSLConfig"; import AccessFormZeroSSLConfig from "./AccessFormZeroSSLConfig";
@ -229,6 +230,8 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
return <AccessFormVercelConfig {...nestedFormProps} />; return <AccessFormVercelConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.VOLCENGINE: case ACCESS_PROVIDERS.VOLCENGINE:
return <AccessFormVolcEngineConfig {...nestedFormProps} />; return <AccessFormVolcEngineConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.WANGSU:
return <AccessFormWangsuConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.WEBHOOK: case ACCESS_PROVIDERS.WEBHOOK:
return <AccessFormWebhookConfig {...nestedFormProps} />; return <AccessFormWebhookConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.WESTCN: case ACCESS_PROVIDERS.WESTCN:

View 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;

View File

@ -10,6 +10,7 @@ import NotifyChannelEditFormEmailFields from "./NotifyChannelEditFormEmailFields
import NotifyChannelEditFormGotifyFields from "./NotifyChannelEditFormGotifyFields.tsx"; import NotifyChannelEditFormGotifyFields from "./NotifyChannelEditFormGotifyFields.tsx";
import NotifyChannelEditFormLarkFields from "./NotifyChannelEditFormLarkFields"; import NotifyChannelEditFormLarkFields from "./NotifyChannelEditFormLarkFields";
import NotifyChannelEditFormMattermostFields from "./NotifyChannelEditFormMattermostFields.tsx"; import NotifyChannelEditFormMattermostFields from "./NotifyChannelEditFormMattermostFields.tsx";
import NotifyChannelEditFormPushoverFields from "./NotifyChannelEditFormPushoverFields";
import NotifyChannelEditFormPushPlusFields from "./NotifyChannelEditFormPushPlusFields"; import NotifyChannelEditFormPushPlusFields from "./NotifyChannelEditFormPushPlusFields";
import NotifyChannelEditFormServerChanFields from "./NotifyChannelEditFormServerChanFields"; import NotifyChannelEditFormServerChanFields from "./NotifyChannelEditFormServerChanFields";
import NotifyChannelEditFormTelegramFields from "./NotifyChannelEditFormTelegramFields"; import NotifyChannelEditFormTelegramFields from "./NotifyChannelEditFormTelegramFields";
@ -57,6 +58,8 @@ const NotifyChannelEditForm = forwardRef<NotifyChannelEditFormInstance, NotifyCh
return <NotifyChannelEditFormLarkFields />; return <NotifyChannelEditFormLarkFields />;
case NOTIFY_CHANNELS.MATTERMOST: case NOTIFY_CHANNELS.MATTERMOST:
return <NotifyChannelEditFormMattermostFields />; return <NotifyChannelEditFormMattermostFields />;
case NOTIFY_CHANNELS.PUSHOVER:
return <NotifyChannelEditFormPushoverFields />;
case NOTIFY_CHANNELS.PUSHPLUS: case NOTIFY_CHANNELS.PUSHPLUS:
return <NotifyChannelEditFormPushPlusFields />; return <NotifyChannelEditFormPushPlusFields />;
case NOTIFY_CHANNELS.SERVERCHAN: case NOTIFY_CHANNELS.SERVERCHAN:

View File

@ -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;

View File

@ -50,12 +50,14 @@ const AccessProviderSelect = ({ filter, showOptionTags, ...props }: AccessProvid
<div className="flex max-w-full items-center justify-between gap-4 overflow-hidden"> <div className="flex max-w-full items-center justify-between gap-4 overflow-hidden">
<Space className="max-w-full grow truncate" size={4}> <Space className="max-w-full grow truncate" size={4}>
<Avatar src={provider.icon} size="small" /> <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)} {t(provider.name)}
</Typography.Text> </Typography.Text>
</Space> </Space>
{showOptionTags && (
<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)}> <Show when={showOptionTagForDNS && provider.usages.includes(ACCESS_USAGES.DNS)}>
<Tag color="peru">{t("access.props.provider.usage.dns")}</Tag> <Tag color="peru">{t("access.props.provider.usage.dns")}</Tag>
</Show> </Show>
@ -69,7 +71,6 @@ const AccessProviderSelect = ({ filter, showOptionTags, ...props }: AccessProvid
<Tag color="mediumaquamarine">{t("access.props.provider.usage.notification")}</Tag> <Tag color="mediumaquamarine">{t("access.props.provider.usage.notification")}</Tag>
</Show> </Show>
</div> </div>
)}
</div> </div>
); );
}; };

View File

@ -11,7 +11,7 @@ import {
SyncOutlined as SyncOutlinedIcon, SyncOutlined as SyncOutlinedIcon,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useRequest } from "ahooks"; 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 dayjs from "dayjs";
import { ClientResponseError } from "pocketbase"; import { ClientResponseError } from "pocketbase";
@ -284,6 +284,8 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
{NotificationContextHolder} {NotificationContextHolder}
<div className={className} style={style}> <div className={className} style={style}>
<Alert className="mb-4" type="warning" message={<span dangerouslySetInnerHTML={{ __html: t("workflow_run.table.alert") }}></span>} />
<Table<WorkflowRunModel> <Table<WorkflowRunModel>
columns={tableColumns} columns={tableColumns}
dataSource={tableData} dataSource={tableData}

View File

@ -56,6 +56,7 @@ import DeployNodeConfigFormLocalConfig from "./DeployNodeConfigFormLocalConfig";
import DeployNodeConfigFormQiniuCDNConfig from "./DeployNodeConfigFormQiniuCDNConfig"; import DeployNodeConfigFormQiniuCDNConfig from "./DeployNodeConfigFormQiniuCDNConfig";
import DeployNodeConfigFormQiniuKodoConfig from "./DeployNodeConfigFormQiniuKodoConfig"; import DeployNodeConfigFormQiniuKodoConfig from "./DeployNodeConfigFormQiniuKodoConfig";
import DeployNodeConfigFormQiniuPiliConfig from "./DeployNodeConfigFormQiniuPiliConfig"; import DeployNodeConfigFormQiniuPiliConfig from "./DeployNodeConfigFormQiniuPiliConfig";
import DeployNodeConfigFormRainYunRCDNConfig from "./DeployNodeConfigFormRainYunRCDNConfig";
import DeployNodeConfigFormSafeLineConfig from "./DeployNodeConfigFormSafeLineConfig"; import DeployNodeConfigFormSafeLineConfig from "./DeployNodeConfigFormSafeLineConfig";
import DeployNodeConfigFormSSHConfig from "./DeployNodeConfigFormSSHConfig.tsx"; import DeployNodeConfigFormSSHConfig from "./DeployNodeConfigFormSSHConfig.tsx";
import DeployNodeConfigFormTencentCloudCDNConfig from "./DeployNodeConfigFormTencentCloudCDNConfig.tsx"; import DeployNodeConfigFormTencentCloudCDNConfig from "./DeployNodeConfigFormTencentCloudCDNConfig.tsx";
@ -80,6 +81,7 @@ import DeployNodeConfigFormVolcEngineDCDNConfig from "./DeployNodeConfigFormVolc
import DeployNodeConfigFormVolcEngineImageXConfig from "./DeployNodeConfigFormVolcEngineImageXConfig.tsx"; import DeployNodeConfigFormVolcEngineImageXConfig from "./DeployNodeConfigFormVolcEngineImageXConfig.tsx";
import DeployNodeConfigFormVolcEngineLiveConfig from "./DeployNodeConfigFormVolcEngineLiveConfig.tsx"; import DeployNodeConfigFormVolcEngineLiveConfig from "./DeployNodeConfigFormVolcEngineLiveConfig.tsx";
import DeployNodeConfigFormVolcEngineTOSConfig from "./DeployNodeConfigFormVolcEngineTOSConfig.tsx"; import DeployNodeConfigFormVolcEngineTOSConfig from "./DeployNodeConfigFormVolcEngineTOSConfig.tsx";
import DeployNodeConfigFormWangsuCDNProConfig from "./DeployNodeConfigFormWangsuCDNProConfig.tsx";
import DeployNodeConfigFormWebhookConfig from "./DeployNodeConfigFormWebhookConfig.tsx"; import DeployNodeConfigFormWebhookConfig from "./DeployNodeConfigFormWebhookConfig.tsx";
type DeployNodeConfigFormFieldValues = Partial<WorkflowNodeConfigForDeploy>; type DeployNodeConfigFormFieldValues = Partial<WorkflowNodeConfigForDeploy>;
@ -251,6 +253,8 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
return <DeployNodeConfigFormQiniuKodoConfig {...nestedFormProps} />; return <DeployNodeConfigFormQiniuKodoConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.QINIU_PILI: case DEPLOY_PROVIDERS.QINIU_PILI:
return <DeployNodeConfigFormQiniuPiliConfig {...nestedFormProps} />; return <DeployNodeConfigFormQiniuPiliConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.RAINYUN_RCDN:
return <DeployNodeConfigFormRainYunRCDNConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.SAFELINE: case DEPLOY_PROVIDERS.SAFELINE:
return <DeployNodeConfigFormSafeLineConfig {...nestedFormProps} />; return <DeployNodeConfigFormSafeLineConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.SSH: case DEPLOY_PROVIDERS.SSH:
@ -299,6 +303,8 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
return <DeployNodeConfigFormVolcEngineLiveConfig {...nestedFormProps} />; return <DeployNodeConfigFormVolcEngineLiveConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.VOLCENGINE_TOS: case DEPLOY_PROVIDERS.VOLCENGINE_TOS:
return <DeployNodeConfigFormVolcEngineTOSConfig {...nestedFormProps} />; return <DeployNodeConfigFormVolcEngineTOSConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.WANGSU_CDNPRO:
return <DeployNodeConfigFormWangsuCDNProConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.WEBHOOK: case DEPLOY_PROVIDERS.WEBHOOK:
return <DeployNodeConfigFormWebhookConfig {...nestedFormProps} />; return <DeployNodeConfigFormWebhookConfig {...nestedFormProps} />;
} }

View File

@ -4,7 +4,7 @@ import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod"; import { z } from "zod";
type DeployNodeConfigFormGcoreCDNConfigFieldValues = Nullish<{ type DeployNodeConfigFormGcoreCDNConfigFieldValues = Nullish<{
resourceId?: string | number; resourceId: string | number;
}>; }>;
export type DeployNodeConfigFormGcoreCDNConfigProps = { export type DeployNodeConfigFormGcoreCDNConfigProps = {
@ -27,7 +27,7 @@ const DeployNodeConfigFormGcoreCDNConfig = ({ form: formInst, formName, disabled
const formSchema = z.object({ const formSchema = z.object({
resourceId: z.union([z.string(), z.number()]).refine((v) => { resourceId: z.union([z.string(), z.number()]).refine((v) => {
return /^\d+$/.test(v + "") && +v > 0; 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); const formRule = createSchemaFieldRule(formSchema);

View File

@ -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;

View File

@ -113,7 +113,7 @@ const DeployNodeConfigFormVolcEngineALBConfig = ({
<Show when={fieldResourceType === RESOURCE_TYPE_LOADBALANCER}> <Show when={fieldResourceType === RESOURCE_TYPE_LOADBALANCER}>
<Form.Item <Form.Item
name="listenerId" name="loadbalancerId"
label={t("workflow_node.deploy.form.volcengine_alb_loadbalancer_id.label")} label={t("workflow_node.deploy.form.volcengine_alb_loadbalancer_id.label")}
rules={[formRule]} rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.volcengine_alb_loadbalancer_id.tooltip") }}></span>} tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.volcengine_alb_loadbalancer_id.tooltip") }}></span>}

View File

@ -104,7 +104,7 @@ const DeployNodeConfigFormVolcEngineCLBConfig = ({
<Show when={fieldResourceType === RESOURCE_TYPE_LOADBALANCER}> <Show when={fieldResourceType === RESOURCE_TYPE_LOADBALANCER}>
<Form.Item <Form.Item
name="listenerId" name="loadbalancerId"
label={t("workflow_node.deploy.form.volcengine_clb_loadbalancer_id.label")} label={t("workflow_node.deploy.form.volcengine_clb_loadbalancer_id.label")}
rules={[formRule]} rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.volcengine_clb_loadbalancer_id.tooltip") }}></span>} tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.volcengine_clb_loadbalancer_id.tooltip") }}></span>}

View File

@ -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;

View File

@ -47,6 +47,7 @@ export interface AccessModel extends BaseModel {
| AccessConfigForUpyun | AccessConfigForUpyun
| AccessConfigForVercel | AccessConfigForVercel
| AccessConfigForVolcEngine | AccessConfigForVolcEngine
| AccessConfigForWangsu
| AccessConfigForWebhook | AccessConfigForWebhook
| AccessConfigForWestcn | AccessConfigForWestcn
| AccessConfigForZeroSSL | AccessConfigForZeroSSL
@ -268,6 +269,11 @@ export type AccessConfigForVolcEngine = {
secretAccessKey: string; secretAccessKey: string;
}; };
export type AccessConfigForWangsu = {
accessKeyId: string;
accessKeySecret: string;
};
export type AccessConfigForWebhook = { export type AccessConfigForWebhook = {
url: string; url: string;
allowInsecureConnections?: boolean; allowInsecureConnections?: boolean;

View File

@ -50,6 +50,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
UPYUN: "upyun", UPYUN: "upyun",
VERCEL: "vercel", VERCEL: "vercel",
VOLCENGINE: "volcengine", VOLCENGINE: "volcengine",
WANGSU: "wangsu",
WEBHOOK: "webhook", WEBHOOK: "webhook",
WESTCN: "westcn", WESTCN: "westcn",
ZEROSSL: "zerossl", 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.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.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.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.QINIU, "provider.qiniu", "/imgs/providers/qiniu.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.UPYUN, "provider.upyun", "/imgs/providers/upyun.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.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.DOGECLOUD, "provider.dogecloud", "/imgs/providers/dogecloud.png", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.BYTEPLUS, "provider.byteplus", "/imgs/providers/byteplus.svg", [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]], [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.PORKBUN, "provider.porkbun", "/imgs/providers/porkbun.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.VERCEL, "provider.vercel", "/imgs/providers/vercel.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.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.WESTCN, "provider.westcn", "/imgs/providers/westcn.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.POWERDNS, "provider.powerdns", "/imgs/providers/powerdns.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]], [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_CDN: `${ACCESS_PROVIDERS.QINIU}-cdn`,
QINIU_KODO: `${ACCESS_PROVIDERS.QINIU}-kodo`, QINIU_KODO: `${ACCESS_PROVIDERS.QINIU}-kodo`,
QINIU_PILI: `${ACCESS_PROVIDERS.QINIU}-pili`, QINIU_PILI: `${ACCESS_PROVIDERS.QINIU}-pili`,
RAINYUN_RCDN: `${ACCESS_PROVIDERS.RAINYUN}-rcdn`,
SAFELINE: `${ACCESS_PROVIDERS.SAFELINE}`, SAFELINE: `${ACCESS_PROVIDERS.SAFELINE}`,
SSH: `${ACCESS_PROVIDERS.SSH}`, SSH: `${ACCESS_PROVIDERS.SSH}`,
TENCENTCLOUD_CDN: `${ACCESS_PROVIDERS.TENCENTCLOUD}-cdn`, TENCENTCLOUD_CDN: `${ACCESS_PROVIDERS.TENCENTCLOUD}-cdn`,
@ -369,6 +372,7 @@ export const DEPLOY_PROVIDERS = Object.freeze({
VOLCENGINE_IMAGEX: `${ACCESS_PROVIDERS.VOLCENGINE}-imagex`, VOLCENGINE_IMAGEX: `${ACCESS_PROVIDERS.VOLCENGINE}-imagex`,
VOLCENGINE_LIVE: `${ACCESS_PROVIDERS.VOLCENGINE}-live`, VOLCENGINE_LIVE: `${ACCESS_PROVIDERS.VOLCENGINE}-live`,
VOLCENGINE_TOS: `${ACCESS_PROVIDERS.VOLCENGINE}-tos`, VOLCENGINE_TOS: `${ACCESS_PROVIDERS.VOLCENGINE}-tos`,
WANGSU_CDNPRO: `${ACCESS_PROVIDERS.WANGSU}-cdnpro`,
WEBHOOK: `${ACCESS_PROVIDERS.WEBHOOK}`, WEBHOOK: `${ACCESS_PROVIDERS.WEBHOOK}`,
} as const); } 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_LIVE, "provider.aliyun.live", DEPLOY_CATEGORIES.AV],
[DEPLOY_PROVIDERS.ALIYUN_VOD, "provider.aliyun.vod", 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_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.ALIYUN_CAS_DEPLOY, "provider.aliyun.cas_deploy", DEPLOY_CATEGORIES.OTHER],
[DEPLOY_PROVIDERS.TENCENTCLOUD_COS, "provider.tencentcloud.cos", DEPLOY_CATEGORIES.STORAGE], [DEPLOY_PROVIDERS.TENCENTCLOUD_COS, "provider.tencentcloud.cos", DEPLOY_CATEGORIES.STORAGE],
[DEPLOY_PROVIDERS.TENCENTCLOUD_CDN, "provider.tencentcloud.cdn", DEPLOY_CATEGORIES.CDN], [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_CSS, "provider.tencentcloud.css", DEPLOY_CATEGORIES.AV],
[DEPLOY_PROVIDERS.TENCENTCLOUD_VOD, "provider.tencentcloud.vod", 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_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.TENCENTCLOUD_SSL_DEPLOY, "provider.tencentcloud.ssl_deploy", DEPLOY_CATEGORIES.OTHER],
[DEPLOY_PROVIDERS.BAIDUCLOUD_CDN, "provider.baiducloud.cdn", DEPLOY_CATEGORIES.CDN], [DEPLOY_PROVIDERS.BAIDUCLOUD_CDN, "provider.baiducloud.cdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.BAIDUCLOUD_BLB, "provider.baiducloud.blb", DEPLOY_CATEGORIES.LOADBALANCE], [DEPLOY_PROVIDERS.BAIDUCLOUD_BLB, "provider.baiducloud.blb", DEPLOY_CATEGORIES.LOADBALANCE],
[DEPLOY_PROVIDERS.BAIDUCLOUD_APPBLB, "provider.baiducloud.appblb", 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_CDN, "provider.huaweicloud.cdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.HUAWEICLOUD_ELB, "provider.huaweicloud.elb", DEPLOY_CATEGORIES.LOADBALANCE], [DEPLOY_PROVIDERS.HUAWEICLOUD_ELB, "provider.huaweicloud.elb", DEPLOY_CATEGORIES.LOADBALANCE],
[DEPLOY_PROVIDERS.HUAWEICLOUD_WAF, "provider.huaweicloud.waf", DEPLOY_CATEGORIES.FIREWALL], [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_TOS, "provider.volcengine.tos", DEPLOY_CATEGORIES.STORAGE],
[DEPLOY_PROVIDERS.VOLCENGINE_CDN, "provider.volcengine.cdn", DEPLOY_CATEGORIES.CDN], [DEPLOY_PROVIDERS.VOLCENGINE_CDN, "provider.volcengine.cdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.VOLCENGINE_DCDN, "provider.volcengine.dcdn", 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_ALB, "provider.volcengine.alb", DEPLOY_CATEGORIES.LOADBALANCE],
[DEPLOY_PROVIDERS.VOLCENGINE_IMAGEX, "provider.volcengine.imagex", DEPLOY_CATEGORIES.STORAGE], [DEPLOY_PROVIDERS.VOLCENGINE_IMAGEX, "provider.volcengine.imagex", DEPLOY_CATEGORIES.STORAGE],
[DEPLOY_PROVIDERS.VOLCENGINE_LIVE, "provider.volcengine.live", DEPLOY_CATEGORIES.AV], [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_ALB, "provider.jdcloud.alb", DEPLOY_CATEGORIES.LOADBALANCE],
[DEPLOY_PROVIDERS.JDCLOUD_CDN, "provider.jdcloud.cdn", DEPLOY_CATEGORIES.CDN], [DEPLOY_PROVIDERS.JDCLOUD_CDN, "provider.jdcloud.cdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.JDCLOUD_LIVE, "provider.jdcloud.live", DEPLOY_CATEGORIES.AV], [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_FILE, "provider.upyun.file", DEPLOY_CATEGORIES.STORAGE],
[DEPLOY_PROVIDERS.UPYUN_CDN, "provider.upyun.cdn", DEPLOY_CATEGORIES.CDN], [DEPLOY_PROVIDERS.UPYUN_CDN, "provider.upyun.cdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.BAISHAN_CDN, "provider.baishan.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.DOGECLOUD_CDN, "provider.dogecloud.cdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.BYTEPLUS_CDN, "provider.byteplus.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_US3, "provider.ucloud.us3", DEPLOY_CATEGORIES.STORAGE],
[DEPLOY_PROVIDERS.UCLOUD_UCDN, "provider.ucloud.ucdn", DEPLOY_CATEGORIES.CDN], [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_CLOUDFRONT, "provider.aws.cloudfront", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.AWS_ACM, "provider.aws.acm", DEPLOY_CATEGORIES.OTHER], [DEPLOY_PROVIDERS.AWS_ACM, "provider.aws.acm", DEPLOY_CATEGORIES.OTHER],
[DEPLOY_PROVIDERS.AZURE_KEYVAULT, "provider.azure.keyvault", DEPLOY_CATEGORIES.OTHER], [DEPLOY_PROVIDERS.AZURE_KEYVAULT, "provider.azure.keyvault", DEPLOY_CATEGORIES.OTHER],

View File

@ -45,6 +45,7 @@ export const NOTIFY_CHANNELS = Object.freeze({
GOTIFY: "gotify", GOTIFY: "gotify",
LARK: "lark", LARK: "lark",
MATTERMOST: "mattermost", MATTERMOST: "mattermost",
PUSHOVER: "pushover",
PUSHPLUS: "pushplus", PUSHPLUS: "pushplus",
SERVERCHAN: "serverchan", SERVERCHAN: "serverchan",
TELEGRAM: "telegram", TELEGRAM: "telegram",
@ -66,6 +67,7 @@ export type NotifyChannelsSettingsContent = {
[NOTIFY_CHANNELS.GOTIFY]?: GotifyNotifyChannelConfig; [NOTIFY_CHANNELS.GOTIFY]?: GotifyNotifyChannelConfig;
[NOTIFY_CHANNELS.LARK]?: LarkNotifyChannelConfig; [NOTIFY_CHANNELS.LARK]?: LarkNotifyChannelConfig;
[NOTIFY_CHANNELS.MATTERMOST]?: MattermostNotifyChannelConfig; [NOTIFY_CHANNELS.MATTERMOST]?: MattermostNotifyChannelConfig;
[NOTIFY_CHANNELS.PUSHOVER]?: PushoverNotifyChannelConfig;
[NOTIFY_CHANNELS.PUSHPLUS]?: PushPlusNotifyChannelConfig; [NOTIFY_CHANNELS.PUSHPLUS]?: PushPlusNotifyChannelConfig;
[NOTIFY_CHANNELS.SERVERCHAN]?: ServerChanNotifyChannelConfig; [NOTIFY_CHANNELS.SERVERCHAN]?: ServerChanNotifyChannelConfig;
[NOTIFY_CHANNELS.TELEGRAM]?: TelegramNotifyChannelConfig; [NOTIFY_CHANNELS.TELEGRAM]?: TelegramNotifyChannelConfig;
@ -116,6 +118,12 @@ export type MattermostNotifyChannelConfig = {
enabled?: boolean; enabled?: boolean;
} }
export type PushoverNotifyChannelConfig = {
token: string;
user: string;
enabled?: boolean;
};
export type PushPlusNotifyChannelConfig = { export type PushPlusNotifyChannelConfig = {
token: string; token: string;
enabled?: boolean; enabled?: boolean;
@ -154,6 +162,7 @@ export const notifyChannelsMap: Map<NotifyChannel["type"], NotifyChannel> = new
[NOTIFY_CHANNELS.GOTIFY, "common.notifier.gotify"], [NOTIFY_CHANNELS.GOTIFY, "common.notifier.gotify"],
[NOTIFY_CHANNELS.LARK, "common.notifier.lark"], [NOTIFY_CHANNELS.LARK, "common.notifier.lark"],
[NOTIFY_CHANNELS.MATTERMOST, "common.notifier.mattermost"], [NOTIFY_CHANNELS.MATTERMOST, "common.notifier.mattermost"],
[NOTIFY_CHANNELS.PUSHOVER, "common.notifier.pushover"],
[NOTIFY_CHANNELS.PUSHPLUS, "common.notifier.pushplus"], [NOTIFY_CHANNELS.PUSHPLUS, "common.notifier.pushplus"],
[NOTIFY_CHANNELS.WECOM, "common.notifier.wecom"], [NOTIFY_CHANNELS.WECOM, "common.notifier.wecom"],
[NOTIFY_CHANNELS.TELEGRAM, "common.notifier.telegram"], [NOTIFY_CHANNELS.TELEGRAM, "common.notifier.telegram"],

View File

@ -17,6 +17,7 @@
"access.props.provider.usage.hosting": "Hosting provider", "access.props.provider.usage.hosting": "Hosting provider",
"access.props.provider.usage.ca": "Certificate authority", "access.props.provider.usage.ca": "Certificate authority",
"access.props.provider.usage.notification": "Notification channel", "access.props.provider.usage.notification": "Notification channel",
"access.props.provider.builtin": "Built-in",
"access.props.range.both_dns_hosting": "Provider", "access.props.range.both_dns_hosting": "Provider",
"access.props.range.ca_only": "Certificate authority", "access.props.range.ca_only": "Certificate authority",
"access.props.range.notify_only": "Notification channel", "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.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.label": "Rain Yun API key",
"access.form.rainyun_api_key.placeholder": "Please enter 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.label": "SafeLine URL",
"access.form.safeline_api_url.placeholder": "Please enter 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>", "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.label": "VolcEngine SecretAccessKey",
"access.form.volcengine_secret_access_key.placeholder": "Please enter 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.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.label": "Webhook URL",
"access.form.webhook_url.placeholder": "Please enter Webhook URL", "access.form.webhook_url.placeholder": "Please enter Webhook URL",
"access.form.webhook_allow_insecure_conns.label": "Insecure SSL/TLS connections", "access.form.webhook_allow_insecure_conns.label": "Insecure SSL/TLS connections",

View File

@ -42,6 +42,7 @@
"common.notifier.gotify": "Gotify", "common.notifier.gotify": "Gotify",
"common.notifier.lark": "Lark", "common.notifier.lark": "Lark",
"common.notifier.mattermost": "Mattermost", "common.notifier.mattermost": "Mattermost",
"common.notifier.pushover": "Pushover",
"common.notifier.pushplus": "PushPlus", "common.notifier.pushplus": "PushPlus",
"common.notifier.serverchan": "ServerChan", "common.notifier.serverchan": "ServerChan",
"common.notifier.telegram": "Telegram", "common.notifier.telegram": "Telegram",

View File

@ -5,7 +5,7 @@
"provider.acmehttpreq": "Http Request (ACME Proxy)", "provider.acmehttpreq": "Http Request (ACME Proxy)",
"provider.aliyun": "Alibaba Cloud", "provider.aliyun": "Alibaba Cloud",
"provider.aliyun.alb": "Alibaba Cloud - ALB (Application Load Balancer)", "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.cas_deploy": "Alibaba Cloud - Deploy via CAS (Certificate Management Service)",
"provider.aliyun.cdn": "Alibaba Cloud - CDN (Content Delivery Network)", "provider.aliyun.cdn": "Alibaba Cloud - CDN (Content Delivery Network)",
"provider.aliyun.clb": "Alibaba Cloud - CLB (Classic Load Balancer)", "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.appblb": "Baidu Cloud - AppBLB (Application Baidu Load Balancer)",
"provider.baiducloud.blb": "Baidu Cloud - BLB (Baidu Load Balancer)", "provider.baiducloud.blb": "Baidu Cloud - BLB (Baidu Load Balancer)",
"provider.baiducloud.cdn": "Baidu Cloud - CDN (Content Delivery Network)", "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.baiducloud.dns": "Baidu Cloud - DNS (Domain Name Service)",
"provider.baishan": "Baishan", "provider.baishan": "Baishan",
"provider.baishan.cdn": "Baishan - CDN (Content Delivery Network)", "provider.baishan.cdn": "Baishan - CDN (Content Delivery Network)",
@ -67,7 +67,7 @@
"provider.huaweicloud.cdn": "Huawei Cloud - CDN (Content Delivery Network)", "provider.huaweicloud.cdn": "Huawei Cloud - CDN (Content Delivery Network)",
"provider.huaweicloud.dns": "Huawei Cloud - DNS (Domain Name Service)", "provider.huaweicloud.dns": "Huawei Cloud - DNS (Domain Name Service)",
"provider.huaweicloud.elb": "Huawei Cloud - ELB (Elastic Load Balance)", "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.huaweicloud.waf": "Huawei Cloud - WAF (Web Application Firewall)",
"provider.jdcloud": "JD Cloud", "provider.jdcloud": "JD Cloud",
"provider.jdcloud.alb": "JD Cloud - ALB (Application Load Balancer)", "provider.jdcloud.alb": "JD Cloud - ALB (Application Load Balancer)",
@ -91,6 +91,7 @@
"provider.qiniu.kodo": "Qiniu - Kodo", "provider.qiniu.kodo": "Qiniu - Kodo",
"provider.qiniu.pili": "Qiniu - Pili", "provider.qiniu.pili": "Qiniu - Pili",
"provider.rainyun": "Rain Yun", "provider.rainyun": "Rain Yun",
"provider.rainyun.rcdn": "Rain Yun - RCDN (Rain Content Delivery Network)",
"provider.safeline": "SafeLine", "provider.safeline": "SafeLine",
"provider.ssh": "SSH deployment", "provider.ssh": "SSH deployment",
"provider.sslcom": "SSL.com", "provider.sslcom": "SSL.com",
@ -103,7 +104,7 @@
"provider.tencentcloud.ecdn": "Tencent Cloud - ECDN (Enterprise Content Delivery Network)", "provider.tencentcloud.ecdn": "Tencent Cloud - ECDN (Enterprise Content Delivery Network)",
"provider.tencentcloud.eo": "Tencent Cloud - EdgeOne", "provider.tencentcloud.eo": "Tencent Cloud - EdgeOne",
"provider.tencentcloud.scf": "Tencent Cloud - SCF (Serverless Cloud Function)", "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.ssl_deploy": "Tencent Cloud - Deploy via SSL Certificate Service",
"provider.tencentcloud.vod": "Tencent Cloud - VOD (Video on Demand)", "provider.tencentcloud.vod": "Tencent Cloud - VOD (Video on Demand)",
"provider.tencentcloud.waf": "Tencent Cloud - WAF (Web Application Firewall)", "provider.tencentcloud.waf": "Tencent Cloud - WAF (Web Application Firewall)",
@ -112,18 +113,20 @@
"provider.ucloud.us3": "UCloud - US3 (UCloud Object-based Storage)", "provider.ucloud.us3": "UCloud - US3 (UCloud Object-based Storage)",
"provider.upyun": "UPYUN", "provider.upyun": "UPYUN",
"provider.upyun.cdn": "UPYUN - CDN (Content Delivery Network)", "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.vercel": "Vercel",
"provider.volcengine": "Volcengine", "provider.volcengine": "Volcengine",
"provider.volcengine.alb": "Volcengine - ALB (Application Load Balancer)", "provider.volcengine.alb": "Volcengine - ALB (Application Load Balancer)",
"provider.volcengine.cdn": "Volcengine - CDN (Content Delivery Network)", "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.clb": "Volcengine - CLB (Cloud Load Balancer)",
"provider.volcengine.dcdn": "Volcengine - DCDN (Dynamic Content Delivery Network)", "provider.volcengine.dcdn": "Volcengine - DCDN (Dynamic Content Delivery Network)",
"provider.volcengine.dns": "Volcengine - DNS (Domain Name Service)", "provider.volcengine.dns": "Volcengine - DNS (Domain Name Service)",
"provider.volcengine.imagex": "Volcengine - ImageX", "provider.volcengine.imagex": "Volcengine - ImageX",
"provider.volcengine.live": "Volcengine - Live", "provider.volcengine.live": "Volcengine - Live",
"provider.volcengine.tos": "Volcengine - TOS (Tinder Object Storage)", "provider.volcengine.tos": "Volcengine - TOS (Tinder Object Storage)",
"provider.wangsu": "Wangsu Cloud",
"provider.wangsu.cdnpro": "Wangsu Cloud - CDN Pro",
"provider.webhook": "Webhook", "provider.webhook": "Webhook",
"provider.westcn": "West.cn", "provider.westcn": "West.cn",
"provider.zerossl": "ZeroSSL", "provider.zerossl": "ZeroSSL",

View File

@ -53,15 +53,15 @@
"settings.notification.channel.form.email_sender_address.placeholder": "Please enter sender email address", "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.label": "Receiver email address",
"settings.notification.channel.form.email_receiver_address.placeholder": "Please enter 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.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_url.placeholder": "Please enter Service URL",
"settings.notification.channel.form.gotify_token.placeholder": "Please enter Application Token", "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.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_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.placeholder": "Please enter message priority",
"settings.notification.channel.form.gotify_priority.label": "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.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.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.label": "Webhook URL",
"settings.notification.channel.form.lark_webhook_url.placeholder": "Please enter Webhook URL", "settings.notification.channel.form.lark_webhook_url.placeholder": "Please enter Webhook URL",
@ -76,8 +76,15 @@
"settings.notification.channel.form.mattermost_username.placeholder": "Please enter 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.label": "Password",
"settings.notification.channel.form.mattermost_password.placeholder": "Please enter 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.placeholder": "Please enter Token",
"settings.notification.channel.form.pushplus_token.label": "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.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.label": "Server URL",
"settings.notification.channel.form.serverchan_url.placeholder": "Please enter ServerChan server URL (e.g. https://sctapi.ftqq.com/*****.send)", "settings.notification.channel.form.serverchan_url.placeholder": "Please enter ServerChan server URL (e.g. https://sctapi.ftqq.com/*****.send)",

View File

@ -95,7 +95,7 @@
"workflow_node.deploy.form.provider.placeholder": "Please select deploy target", "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.label": "Host provider authorization",
"workflow_node.deploy.form.provider_access.placeholder": "Please select an authorization of host provider", "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.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.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", "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.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.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.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.label": "Baishan Cloud CDN domain",
"workflow_node.deploy.form.baishan_cdn_domain.placeholder": "Please enter Baishan CDN domain name", "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_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.label": "Baishan Cloud 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.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.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_console_auto_restart.label": "Auto restart after deployment",
"workflow_node.deploy.form.baotapanel_site_type.label": "aaPanel site type", "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.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.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.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.label": "Resource type",
"workflow_node.deploy.form.safeline_resource_type.placeholder": "Please select 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", "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.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.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.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.label": "Webhook data (JSON format)",
"workflow_node.deploy.form.webhook_data.placeholder": "Please enter Webhook data", "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.", "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.",

View File

@ -5,6 +5,8 @@
"workflow_run.action.delete": "Delete run", "workflow_run.action.delete": "Delete run",
"workflow_run.action.delete.confirm": "Are you sure to delete this 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.id": "ID",
"workflow_run.props.status": "Status", "workflow_run.props.status": "Status",
"workflow_run.props.status.pending": "Pending", "workflow_run.props.status.pending": "Pending",

View File

@ -17,6 +17,7 @@
"access.props.provider.usage.hosting": "主机提供商", "access.props.provider.usage.hosting": "主机提供商",
"access.props.provider.usage.ca": "证书颁发机构", "access.props.provider.usage.ca": "证书颁发机构",
"access.props.provider.usage.notification": "通知渠道", "access.props.provider.usage.notification": "通知渠道",
"access.props.provider.builtin": "内置",
"access.props.range.both_dns_hosting": "提供商", "access.props.range.both_dns_hosting": "提供商",
"access.props.range.ca_only": "证书颁发机构", "access.props.range.ca_only": "证书颁发机构",
"access.props.range.notify_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.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.label": "雨云 API 密钥",
"access.form.rainyun_api_key.placeholder": "请输入雨云 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.label": "雷池 URL",
"access.form.safeline_api_url.placeholder": "请输入雷池 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>", "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.label": "火山引擎 SecretAccessKey",
"access.form.volcengine_secret_access_key.placeholder": "请输入火山引擎 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.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.label": "Webhook 回调地址",
"access.form.webhook_url.placeholder": "请输入 Webhook 回调地址", "access.form.webhook_url.placeholder": "请输入 Webhook 回调地址",
"access.form.webhook_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", "access.form.webhook_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误",

View File

@ -42,6 +42,7 @@
"common.notifier.gotify": "Gotify", "common.notifier.gotify": "Gotify",
"common.notifier.lark": "飞书", "common.notifier.lark": "飞书",
"common.notifier.mattermost": "Mattermost", "common.notifier.mattermost": "Mattermost",
"common.notifier.pushover": "Pushover",
"common.notifier.pushplus": "PushPlus推送加", "common.notifier.pushplus": "PushPlus推送加",
"common.notifier.serverchan": "Server 酱", "common.notifier.serverchan": "Server 酱",
"common.notifier.telegram": "Telegram", "common.notifier.telegram": "Telegram",

View File

@ -5,7 +5,7 @@
"provider.acmehttpreq": "Http Request (ACME Proxy)", "provider.acmehttpreq": "Http Request (ACME Proxy)",
"provider.aliyun": "阿里云", "provider.aliyun": "阿里云",
"provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB", "provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB",
"provider.aliyun.cas": "阿里云 - 上传到数字证书管理服务 CAS", "provider.aliyun.cas_upload": "阿里云 - 上传到数字证书管理服务 CAS",
"provider.aliyun.cas_deploy": "阿里云 - 通过数字证书管理服务 CAS 创建部署任务", "provider.aliyun.cas_deploy": "阿里云 - 通过数字证书管理服务 CAS 创建部署任务",
"provider.aliyun.cdn": "阿里云 - 内容分发网络 CDN", "provider.aliyun.cdn": "阿里云 - 内容分发网络 CDN",
"provider.aliyun.clb": "阿里云 - 传统型负载均衡 CLB", "provider.aliyun.clb": "阿里云 - 传统型负载均衡 CLB",
@ -31,7 +31,7 @@
"provider.baiducloud.appblb": "百度智能云 - 应用型负载均衡 BLB", "provider.baiducloud.appblb": "百度智能云 - 应用型负载均衡 BLB",
"provider.baiducloud.blb": "百度智能云 - 普通型负载均衡 BLB", "provider.baiducloud.blb": "百度智能云 - 普通型负载均衡 BLB",
"provider.baiducloud.cdn": "百度智能云 - 内容分发网络 CDN", "provider.baiducloud.cdn": "百度智能云 - 内容分发网络 CDN",
"provider.baiducloud.cert": "百度智能云 - 上传到 SSL 证书服务", "provider.baiducloud.cert_upload": "百度智能云 - 上传到 SSL 证书服务",
"provider.baiducloud.dns": "百度智能云 - 智能云解析 DNS", "provider.baiducloud.dns": "百度智能云 - 智能云解析 DNS",
"provider.baishan": "白山云", "provider.baishan": "白山云",
"provider.baishan.cdn": "白山云 - 内容分发网络 CDN", "provider.baishan.cdn": "白山云 - 内容分发网络 CDN",
@ -67,7 +67,7 @@
"provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN", "provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN",
"provider.huaweicloud.dns": "华为云 - 云解析 DNS", "provider.huaweicloud.dns": "华为云 - 云解析 DNS",
"provider.huaweicloud.elb": "华为云 - 弹性负载均衡 ELB", "provider.huaweicloud.elb": "华为云 - 弹性负载均衡 ELB",
"provider.huaweicloud.scm": "华为云 - 上传到云证书管理服务 SCM", "provider.huaweicloud.scm_upload": "华为云 - 上传到云证书管理服务 SCM",
"provider.huaweicloud.waf": "华为云 - Web 应用防火墙 WAF", "provider.huaweicloud.waf": "华为云 - Web 应用防火墙 WAF",
"provider.jdcloud": "京东云", "provider.jdcloud": "京东云",
"provider.jdcloud.alb": "京东云 - 应用负载均衡 ALB", "provider.jdcloud.alb": "京东云 - 应用负载均衡 ALB",
@ -91,6 +91,7 @@
"provider.qiniu.kodo": "七牛云 - 对象存储 Kodo", "provider.qiniu.kodo": "七牛云 - 对象存储 Kodo",
"provider.qiniu.pili": "七牛云 - 视频直播 Pili", "provider.qiniu.pili": "七牛云 - 视频直播 Pili",
"provider.rainyun": "雨云", "provider.rainyun": "雨云",
"provider.rainyun.rcdn": "雨云 - 雨盾 CDN",
"provider.safeline": "雷池", "provider.safeline": "雷池",
"provider.ssh": "SSH 部署", "provider.ssh": "SSH 部署",
"provider.sslcom": "SSL.com", "provider.sslcom": "SSL.com",
@ -103,7 +104,7 @@
"provider.tencentcloud.ecdn": "腾讯云 - 全站加速网络 ECDN", "provider.tencentcloud.ecdn": "腾讯云 - 全站加速网络 ECDN",
"provider.tencentcloud.eo": "腾讯云 - 边缘安全加速平台 EdgeOne", "provider.tencentcloud.eo": "腾讯云 - 边缘安全加速平台 EdgeOne",
"provider.tencentcloud.scf": "腾讯云 - 云函数 SCF", "provider.tencentcloud.scf": "腾讯云 - 云函数 SCF",
"provider.tencentcloud.ssl": "腾讯云 - 上传到 SSL 证书服务", "provider.tencentcloud.ssl_upload": "腾讯云 - 上传到 SSL 证书服务",
"provider.tencentcloud.ssl_deploy": "腾讯云 - 通过 SSL 证书服务创建部署任务", "provider.tencentcloud.ssl_deploy": "腾讯云 - 通过 SSL 证书服务创建部署任务",
"provider.tencentcloud.vod": "腾讯云 - 云点播 VOD", "provider.tencentcloud.vod": "腾讯云 - 云点播 VOD",
"provider.tencentcloud.waf": "腾讯云 - Web 应用防火墙 WAF", "provider.tencentcloud.waf": "腾讯云 - Web 应用防火墙 WAF",
@ -112,18 +113,20 @@
"provider.ucloud.us3": "优刻得 - 对象存储 US3", "provider.ucloud.us3": "优刻得 - 对象存储 US3",
"provider.upyun": "又拍云", "provider.upyun": "又拍云",
"provider.upyun.cdn": "又拍云 - 云分发 CDN", "provider.upyun.cdn": "又拍云 - 云分发 CDN",
"provider.upyun.file": "又拍云 - 云存储", "provider.upyun.file": "又拍云 - 云存储 USS",
"provider.vercel": "Vercel", "provider.vercel": "Vercel",
"provider.volcengine": "火山引擎", "provider.volcengine": "火山引擎",
"provider.volcengine.alb": "火山引擎 - 应用型负载均衡 ALB", "provider.volcengine.alb": "火山引擎 - 应用型负载均衡 ALB",
"provider.volcengine.cdn": "火山引擎 - 内容分发网络 CDN", "provider.volcengine.cdn": "火山引擎 - 内容分发网络 CDN",
"provider.volcengine.certcenter": "火山引擎 - 上传到证书中心", "provider.volcengine.certcenter_upload": "火山引擎 - 上传到证书中心",
"provider.volcengine.clb": "火山引擎 - 负载均衡 CLB", "provider.volcengine.clb": "火山引擎 - 负载均衡 CLB",
"provider.volcengine.dcdn": "火山引擎 - 全站加速 DCDN", "provider.volcengine.dcdn": "火山引擎 - 全站加速 DCDN",
"provider.volcengine.dns": "火山引擎 - 云解析 DNS", "provider.volcengine.dns": "火山引擎 - 云解析 DNS",
"provider.volcengine.imagex": "火山引擎 - 图片服务 ImageX", "provider.volcengine.imagex": "火山引擎 - 图片服务 ImageX",
"provider.volcengine.live": "火山引擎 - 视频直播 Live", "provider.volcengine.live": "火山引擎 - 视频直播 Live",
"provider.volcengine.tos": "火山引擎 - 对象存储 TOS", "provider.volcengine.tos": "火山引擎 - 对象存储 TOS",
"provider.wangsu": "网宿云",
"provider.wangsu.cdnpro": "网宿云 - CDN Pro",
"provider.webhook": "Webhook", "provider.webhook": "Webhook",
"provider.westcn": "西部数码", "provider.westcn": "西部数码",
"provider.zerossl": "ZeroSSL", "provider.zerossl": "ZeroSSL",

View File

@ -53,15 +53,15 @@
"settings.notification.channel.form.email_sender_address.placeholder": "请输入发送邮箱地址", "settings.notification.channel.form.email_sender_address.placeholder": "请输入发送邮箱地址",
"settings.notification.channel.form.email_receiver_address.label": "接收邮箱地址", "settings.notification.channel.form.email_receiver_address.label": "接收邮箱地址",
"settings.notification.channel.form.email_receiver_address.placeholder": "请输入接收邮箱地址", "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.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_url.placeholder": "请输入服务地址",
"settings.notification.channel.form.gotify_token.placeholder": "请输入应用Token", "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.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_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.placeholder": "请输入消息优先级",
"settings.notification.channel.form.gotify_priority.label": "消息优先级", "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.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.error.gte0": "消息优先级需要大于等于 0",
"settings.notification.channel.form.lark_webhook_url.label": "机器人 Webhook 地址", "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.placeholder": "请输入机器人 Webhook 地址",
@ -76,9 +76,16 @@
"settings.notification.channel.form.mattermost_username.placeholder": "请输入用户名", "settings.notification.channel.form.mattermost_username.placeholder": "请输入用户名",
"settings.notification.channel.form.mattermost_password.label": "密码", "settings.notification.channel.form.mattermost_password.label": "密码",
"settings.notification.channel.form.mattermost_password.placeholder": "请输入密码", "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.placeholder": "请输入Token",
"settings.notification.channel.form.pushplus_token.label": "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.label": "服务器地址",
"settings.notification.channel.form.serverchan_url.placeholder": "请输入服务器地址(形如: https://sctapi.ftqq.com/*****.send", "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>", "settings.notification.channel.form.serverchan_url.tooltip": "这是什么?请参阅 <a href=\"https://sct.ftqq.com/forward\" target=\"_blank\">https://sct.ftqq.com/forward</a>",

View File

@ -94,7 +94,7 @@
"workflow_node.deploy.form.provider.placeholder": "请选择部署目标", "workflow_node.deploy.form.provider.placeholder": "请选择部署目标",
"workflow_node.deploy.form.provider_access.label": "主机提供商授权", "workflow_node.deploy.form.provider_access.label": "主机提供商授权",
"workflow_node.deploy.form.provider_access.placeholder": "请选择主机提供商授权", "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.button": "新建",
"workflow_node.deploy.form.provider_access.guide_for_local": "小贴士:如果你正在使用 Docker 运行 Certimate“本地”指的是容器内而非宿主机。", "workflow_node.deploy.form.provider_access.guide_for_local": "小贴士:如果你正在使用 Docker 运行 Certimate“本地”指的是容器内而非宿主机。",
"workflow_node.deploy.form.certificate.label": "待部署证书", "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.label": "七牛云视频直播流域名",
"workflow_node.deploy.form.qiniu_pili_domain.placeholder": "请输入七牛云视频直播流域名", "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.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.label": "证书替换方式",
"workflow_node.deploy.form.safeline_resource_type.placeholder": "请选择证书替换方式", "workflow_node.deploy.form.safeline_resource_type.placeholder": "请选择证书替换方式",
"workflow_node.deploy.form.safeline_resource_type.option.certificate.label": "替换指定证书", "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.label": "火山引擎 TOS 自定义域名",
"workflow_node.deploy.form.volcengine_tos_domain.placeholder": "请输入火山引擎 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.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.label": "Webhook 回调数据JSON 格式)",
"workflow_node.deploy.form.webhook_data.placeholder": "请输入 Webhook 回调数据", "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 格式内容", "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.webhook_data_preset.button": "使用预设模板",
"workflow_node.deploy.form.strategy_config.label": "执行策略", "workflow_node.deploy.form.strategy_config.label": "执行策略",
"workflow_node.deploy.form.skip_on_last_succeeded.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.suffix": "重新部署。",
"workflow_node.deploy.form.skip_on_last_succeeded.switch.on": "跳过", "workflow_node.deploy.form.skip_on_last_succeeded.switch.on": "跳过",
"workflow_node.deploy.form.skip_on_last_succeeded.switch.off": "不跳过", "workflow_node.deploy.form.skip_on_last_succeeded.switch.off": "不跳过",

View File

@ -5,6 +5,8 @@
"workflow_run.action.delete": "删除执行", "workflow_run.action.delete": "删除执行",
"workflow_run.action.delete.confirm": "确定要删除此执行吗?请注意此操作仅清除日志历史,但不会影响签发的证书。", "workflow_run.action.delete.confirm": "确定要删除此执行吗?请注意此操作仅清除日志历史,但不会影响签发的证书。",
"workflow_run.table.alert": "注意:执行记录中包含工作流各节点的执行结果,删除后可能导致因找不到前次执行结果而触发重新申请或部署证书。如无必要请勿提前删除,建议保留至少 180 天。",
"workflow_run.props.id": "ID", "workflow_run.props.id": "ID",
"workflow_run.props.status": "状态", "workflow_run.props.status": "状态",
"workflow_run.props.status.pending": "等待执行", "workflow_run.props.status.pending": "等待执行",

View File

@ -285,7 +285,7 @@ const Dashboard = () => {
}} }}
pagination={false} pagination={false}
rowKey={(record) => record.id} rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }} scroll={{ x: "max(100%, 720px)" }}
size="small" size="small"
/> />
</Card> </Card>