Merge pull request #954 from fudiwei/dev

This commit is contained in:
RHQYZ 2025-09-06 00:26:31 +08:00 committed by GitHub
commit 44596652d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 743 additions and 56 deletions

1
go.mod
View File

@ -140,6 +140,7 @@ require (
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/vultr/govultr/v3 v3.21.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.mongodb.org/mongo-driver v1.17.2 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect

2
go.sum
View File

@ -887,6 +887,8 @@ github.com/volcengine/volc-sdk-golang v1.0.219 h1:IqMCdpJ6uuqS2ZZQYUVHKVd+2H1au0
github.com/volcengine/volc-sdk-golang v1.0.219/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM=
github.com/volcengine/volcengine-go-sdk v1.1.30 h1:D85LL8euXgxwsZzwQ0azT/hannEsiKxKY4h/7/HU764=
github.com/volcengine/volcengine-go-sdk v1.1.30/go.mod h1:EyKoi6t6eZxoPNGr2GdFCZti2Skd7MO3eUzx7TtSvNo=
github.com/vultr/govultr/v3 v3.21.1 h1:0cnA8fXiqayPGbAlNHaW+5oCQjpDNkkAm3Nt3LOHplM=
github.com/vultr/govultr/v3 v3.21.1/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=

View File

@ -0,0 +1,29 @@
package applicators
import (
"fmt"
"github.com/go-acme/lego/v4/challenge"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/vultr"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
func init() {
if err := ACMEDns01Registries.Register(domain.ACMEDns01ProviderTypeVultr, func(options *ProviderFactoryOptions) (challenge.Provider, error) {
credentials := domain.AccessConfigForVultr{}
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
provider, err := vultr.NewChallengeProvider(&vultr.ChallengeProviderConfig{
ApiKey: credentials.ApiKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return provider, err
}); err != nil {
panic(err)
}
}

View File

@ -15,6 +15,7 @@ import (
var acmeDirUrls = map[string]string{
string(domain.CAProviderTypeLetsEncrypt): "https://acme-v02.api.letsencrypt.org/directory",
string(domain.CAProviderTypeLetsEncryptStaging): "https://acme-staging-v02.api.letsencrypt.org/directory",
string(domain.CAProviderTypeActalisSSL): "https://acme-api.actalis.com/acme/directory",
string(domain.CAProviderTypeBuypass): "https://api.buypass.com/acme/directory",
string(domain.CAProviderTypeGoogleTrustServices): "https://dv.acme-v02.api.pki.goog/directory",
string(domain.CAProviderTypeSSLCom): "https://acme.ssl.com/sslcom-dv-rsa",

View File

@ -20,6 +20,7 @@ func init() {
AccessKeyId: credentials.AccessKeyId,
AccessKeySecret: credentials.AccessKeySecret,
ResourceGroupId: credentials.ResourceGroupId,
Region: xmaps.GetString(options.ProviderExtendedConfig, "region"),
Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"),
})
return provider, err

View File

@ -20,6 +20,7 @@ func init() {
AccessKeyId: credentials.AccessKeyId,
AccessKeySecret: credentials.AccessKeySecret,
ResourceGroupId: credentials.ResourceGroupId,
Region: xmaps.GetString(options.ProviderExtendedConfig, "region"),
Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"),
})
return provider, err

View File

@ -2,6 +2,7 @@ package deployers
import (
"fmt"
"strings"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/pkg/core"
@ -16,6 +17,50 @@ func init() {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
parseKeyValueMap := func(s string) (map[string]string, error) {
result := make(map[string]string)
lines := strings.Split(s, "\n")
for i, line := range lines {
if strings.TrimSpace(line) == "" {
continue
}
pos := strings.Index(line, ":")
if pos == -1 {
return nil, fmt.Errorf("invalid line format at line %d", i+1)
}
key := strings.TrimSpace(line[:pos])
value := strings.TrimSpace(line[pos+1:])
if key == "" {
return nil, fmt.Errorf("invalid key at line %d", i+1)
}
result[key] = value
}
return result, nil
}
secretAnnotations := make(map[string]string)
if secretAnnotationsString := xmaps.GetString(options.ProviderExtendedConfig, "secretAnnotations"); secretAnnotationsString != "" {
temp, err := parseKeyValueMap(secretAnnotationsString)
if err != nil {
return nil, fmt.Errorf("failed to parse kubernetes secret annotations: %w", err)
}
secretAnnotations = temp
}
secretLabels := make(map[string]string)
if secretLabelsString := xmaps.GetString(options.ProviderExtendedConfig, "secretLabels"); secretLabelsString != "" {
temp, err := parseKeyValueMap(secretLabelsString)
if err != nil {
return nil, fmt.Errorf("failed to parse kubernetes secret labels: %w", err)
}
secretLabels = temp
}
provider, err := k8ssecret.NewSSLDeployerProvider(&k8ssecret.SSLDeployerProviderConfig{
KubeConfig: credentials.KubeConfig,
Namespace: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "namespace", "default"),
@ -23,6 +68,8 @@ func init() {
SecretType: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "secretType", "kubernetes.io/tls"),
SecretDataKeyForCrt: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForCrt", "tls.crt"),
SecretDataKeyForKey: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForKey", "tls.key"),
SecretAnnotations: secretAnnotations,
SecretLabels: secretLabels,
})
return provider, err
}); err != nil {

View File

@ -43,6 +43,7 @@ func init() {
WebhookData: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", credentials.DataString),
Method: credentials.Method,
Headers: mergedHeaders,
Timeout: xmaps.GetInt32(options.ProviderExtendedConfig, "timeout"),
AllowInsecureConnections: credentials.AllowInsecureConnections,
})
return provider, err

View File

@ -44,6 +44,10 @@ type AccessConfigForACMEHttpReq struct {
Password string `json:"password,omitempty"`
}
type AccessConfigForActalisSSL struct {
AccessConfigForACMEExternalAccountBinding
}
type AccessConfigForAliyun struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
@ -402,6 +406,10 @@ type AccessConfigForVolcEngine struct {
SecretAccessKey string `json:"secretAccessKey"`
}
type AccessConfigForVultr struct {
ApiKey string `json:"apiKey"`
}
type AccessConfigForWangsu struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`

View File

@ -13,6 +13,7 @@ const (
AccessProviderTypeACMECA = AccessProviderType("acmeca")
AccessProviderTypeACMEDNS = AccessProviderType("acmedns")
AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq")
AccessProviderTypeActalisSSL = AccessProviderType("actalisssl")
AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai预留
AccessProviderTypeAliyun = AccessProviderType("aliyun")
AccessProviderTypeAPISIX = AccessProviderType("apisix")
@ -86,6 +87,7 @@ const (
AccessProviderTypeUpyun = AccessProviderType("upyun")
AccessProviderTypeVercel = AccessProviderType("vercel")
AccessProviderTypeVolcEngine = AccessProviderType("volcengine")
AccessProviderTypeVultr = AccessProviderType("vultr")
AccessProviderTypeWangsu = AccessProviderType("wangsu")
AccessProviderTypeWebhook = AccessProviderType("webhook")
AccessProviderTypeWeComBot = AccessProviderType("wecombot")
@ -104,6 +106,7 @@ NOTICE: If you add new constant, please keep ASCII order.
*/
const (
CAProviderTypeACMECA = CAProviderType(AccessProviderTypeACMECA)
CAProviderTypeActalisSSL = CAProviderType(AccessProviderTypeActalisSSL)
CAProviderTypeBuypass = CAProviderType(AccessProviderTypeBuypass)
CAProviderTypeGoogleTrustServices = CAProviderType(AccessProviderTypeGoogleTrustServices)
CAProviderTypeLetsEncrypt = CAProviderType(AccessProviderTypeLetsEncrypt)
@ -171,6 +174,7 @@ const (
ACMEDns01ProviderTypeVercel = ACMEDns01ProviderType(AccessProviderTypeVercel)
ACMEDns01ProviderTypeVolcEngine = ACMEDns01ProviderType(AccessProviderTypeVolcEngine) // 兼容旧值,等同于 [ACMEDns01ProviderTypeVolcEngineDNS]
ACMEDns01ProviderTypeVolcEngineDNS = ACMEDns01ProviderType(AccessProviderTypeVolcEngine + "-dns")
ACMEDns01ProviderTypeVultr = ACMEDns01ProviderType(AccessProviderTypeVultr)
ACMEDns01ProviderTypeWestcn = ACMEDns01ProviderType(AccessProviderTypeWestcn)
)

View File

@ -43,6 +43,7 @@ func init() {
WebhookData: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", credentials.DataString),
Method: credentials.Method,
Headers: mergedHeaders,
Timeout: xmaps.GetInt32(options.ProviderExtendedConfig, "timeout"),
AllowInsecureConnections: credentials.AllowInsecureConnections,
})
return provider, err

View File

@ -111,7 +111,9 @@ func (s *WorkflowService) StartRun(ctx context.Context, req *dtos.WorkflowStartR
workflowRun = resp
}
s.dispatcher.Start(ctx, workflowRun.Id)
if err := s.dispatcher.Start(ctx, workflowRun.Id); err != nil {
return nil, err
}
return &dtos.WorkflowStartRunResp{RunId: workflowRun.Id}, nil
}
@ -131,7 +133,9 @@ func (s *WorkflowService) CancelRun(ctx context.Context, req *dtos.WorkflowCance
return nil, errors.New("workflow run is not pending or processing")
}
s.dispatcher.Cancel(ctx, workflowRun.Id)
if err := s.dispatcher.Cancel(ctx, workflowRun.Id); err != nil {
return nil, err
}
return &dtos.WorkflowCancelRunResp{}, nil
}

View File

@ -27,6 +27,9 @@ type NotifierProviderConfig struct {
Method string `json:"method,omitempty"`
// 请求标头。
Headers map[string]string `json:"headers,omitempty"`
// 请求超时(单位:秒)。
// 零值时默认值 30。
Timeout int32 `json:"timeout,omitempty"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
@ -48,6 +51,9 @@ func NewNotifierProvider(config *NotifierProviderConfig) (*NotifierProvider, err
SetTimeout(30 * time.Second).
SetRetryCount(3).
SetRetryWaitTime(5 * time.Second)
if config.Timeout > 0 {
client.SetTimeout(time.Duration(config.Timeout) * time.Second)
}
if config.AllowInsecureConnections {
client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
}

View File

@ -0,0 +1,38 @@
package vultr
import (
"errors"
"time"
"github.com/go-acme/lego/v4/providers/dns/vultr"
"github.com/certimate-go/certimate/pkg/core"
)
type ChallengeProviderConfig struct {
ApiKey string `json:"apiKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger, error) {
if config == nil {
return nil, errors.New("the configuration of the acme challenge provider is nil")
}
providerConfig := vultr.NewDefaultConfig()
providerConfig.APIKey = config.ApiKey
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := vultr.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@ -5,13 +5,16 @@ import (
"errors"
"fmt"
"log/slog"
"strconv"
"strings"
"time"
alicdn "github.com/alibabacloud-go/cdn-20180510/v5/client"
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
"github.com/alibabacloud-go/tea/tea"
"github.com/samber/lo"
"github.com/certimate-go/certimate/pkg/core"
sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas"
)
type SSLDeployerProviderConfig struct {
@ -21,14 +24,17 @@ type SSLDeployerProviderConfig struct {
AccessKeySecret string `json:"accessKeySecret"`
// 阿里云资源组 ID。
ResourceGroupId string `json:"resourceGroupId,omitempty"`
// 阿里云地域。
Region string `json:"region"`
// 加速域名(支持泛域名)。
Domain string `json:"domain"`
}
type SSLDeployerProvider struct {
config *SSLDeployerProviderConfig
logger *slog.Logger
sdkClient *alicdn.Client
config *SSLDeployerProviderConfig
logger *slog.Logger
sdkClient *alicdn.Client
sslManager core.SSLManager
}
var _ core.SSLDeployer = (*SSLDeployerProvider)(nil)
@ -43,10 +49,23 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
sslmgr, err := sslmgrsp.NewSSLManagerProvider(&sslmgrsp.SSLManagerProviderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
ResourceGroupId: config.ResourceGroupId,
Region: lo.
If(config.Region == "" || strings.HasPrefix(config.Region, "cn-"), "cn-hangzhou").
Else("ap-southeast-1"),
})
if err != nil {
return nil, fmt.Errorf("could not create ssl manager: %w", err)
}
return &SSLDeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
config: config,
logger: slog.Default(),
sdkClient: client,
sslManager: sslmgr,
}, nil
}
@ -59,18 +78,32 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) {
}
func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) {
if d.config.Domain == "" {
return nil, errors.New("config `domain` is required")
}
// 上传证书
upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM)
if err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// "*.example.com" → ".example.com",适配阿里云 CDN 要求的泛域名格式
domain := strings.TrimPrefix(d.config.Domain, "*")
// 设置 CDN 域名域名证书
// REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-setcdndomainsslcertificate
certId, _ := strconv.ParseInt(upres.CertId, 10, 64)
setCdnDomainSSLCertificateReq := &alicdn.SetCdnDomainSSLCertificateRequest{
DomainName: tea.String(domain),
CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
CertType: tea.String("upload"),
DomainName: tea.String(domain),
CertType: tea.String("cas"),
CertId: tea.Int64(int64(certId)),
CertRegion: lo.
If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")).
Else(tea.String("ap-southeast-1")),
SSLProtocol: tea.String("on"),
SSLPub: tea.String(certPEM),
SSLPri: tea.String(privkeyPEM),
}
setCdnDomainSSLCertificateResp, err := d.sdkClient.SetCdnDomainSSLCertificate(setCdnDomainSSLCertificateReq)
d.logger.Debug("sdk request 'cdn.SetCdnDomainSSLCertificate'", slog.Any("request", setCdnDomainSSLCertificateReq), slog.Any("response", setCdnDomainSSLCertificateResp))

View File

@ -5,13 +5,16 @@ import (
"errors"
"fmt"
"log/slog"
"strconv"
"strings"
"time"
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
alidcdn "github.com/alibabacloud-go/dcdn-20180115/v3/client"
"github.com/alibabacloud-go/tea/tea"
"github.com/samber/lo"
"github.com/certimate-go/certimate/pkg/core"
sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas"
)
type SSLDeployerProviderConfig struct {
@ -21,14 +24,17 @@ type SSLDeployerProviderConfig struct {
AccessKeySecret string `json:"accessKeySecret"`
// 阿里云资源组 ID。
ResourceGroupId string `json:"resourceGroupId,omitempty"`
// 阿里云地域。
Region string `json:"region"`
// 加速域名(支持泛域名)。
Domain string `json:"domain"`
}
type SSLDeployerProvider struct {
config *SSLDeployerProviderConfig
logger *slog.Logger
sdkClient *alidcdn.Client
config *SSLDeployerProviderConfig
logger *slog.Logger
sdkClient *alidcdn.Client
sslManager core.SSLManager
}
var _ core.SSLDeployer = (*SSLDeployerProvider)(nil)
@ -43,10 +49,23 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
sslmgr, err := sslmgrsp.NewSSLManagerProvider(&sslmgrsp.SSLManagerProviderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
ResourceGroupId: config.ResourceGroupId,
Region: lo.
If(config.Region == "" || strings.HasPrefix(config.Region, "cn-"), "cn-hangzhou").
Else("ap-southeast-1"),
})
if err != nil {
return nil, fmt.Errorf("could not create ssl manager: %w", err)
}
return &SSLDeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
config: config,
logger: slog.Default(),
sdkClient: client,
sslManager: sslmgr,
}, nil
}
@ -63,18 +82,28 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
return nil, errors.New("config `domain` is required")
}
// 上传证书
upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM)
if err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// "*.example.com" → ".example.com",适配阿里云 DCDN 要求的泛域名格式
domain := strings.TrimPrefix(d.config.Domain, "*")
// 配置域名证书
// REF: https://help.aliyun.com/zh/edge-security-acceleration/dcdn/developer-reference/api-dcdn-2018-01-15-setdcdndomainsslcertificate
certId, _ := strconv.ParseInt(upres.CertId, 10, 64)
setDcdnDomainSSLCertificateReq := &alidcdn.SetDcdnDomainSSLCertificateRequest{
DomainName: tea.String(domain),
CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
CertType: tea.String("upload"),
DomainName: tea.String(domain),
CertType: tea.String("cas"),
CertId: tea.Int64(int64(certId)),
CertRegion: lo.
If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")).
Else(tea.String("ap-southeast-1")),
SSLProtocol: tea.String("on"),
SSLPub: tea.String(certPEM),
SSLPri: tea.String(privkeyPEM),
}
setDcdnDomainSSLCertificateResp, err := d.sdkClient.SetDcdnDomainSSLCertificate(setDcdnDomainSSLCertificateReq)
d.logger.Debug("sdk request 'dcdn.SetDcdnDomainSSLCertificate'", slog.Any("request", setDcdnDomainSSLCertificateReq), slog.Any("response", setDcdnDomainSSLCertificateResp))

View File

@ -94,7 +94,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
// 为网站业务转发规则关联 SSL 证书
// REF: https://help.aliyun.com/zh/anti-ddos/anti-ddos-pro-and-premium/developer-reference/api-ddoscoo-2020-01-01-associatewebcert
certId, _ := strconv.Atoi(upres.CertId)
certId, _ := strconv.ParseInt(upres.CertId, 10, 32)
associateWebCertReq := &aliddos.AssociateWebCertRequest{
Domain: tea.String(d.config.Domain),
CertId: tea.Int32(int32(certId)),

View File

@ -5,12 +5,16 @@ import (
"errors"
"fmt"
"log/slog"
"time"
"strconv"
"strings"
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
"github.com/alibabacloud-go/tea/tea"
alivod "github.com/alibabacloud-go/vod-20170321/v4/client"
"github.com/samber/lo"
"github.com/certimate-go/certimate/pkg/core"
sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas"
)
type SSLDeployerProviderConfig struct {
@ -27,9 +31,10 @@ type SSLDeployerProviderConfig struct {
}
type SSLDeployerProvider struct {
config *SSLDeployerProviderConfig
logger *slog.Logger
sdkClient *alivod.Client
config *SSLDeployerProviderConfig
logger *slog.Logger
sdkClient *alivod.Client
sslManager core.SSLManager
}
var _ core.SSLDeployer = (*SSLDeployerProvider)(nil)
@ -44,10 +49,23 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
sslmgr, err := sslmgrsp.NewSSLManagerProvider(&sslmgrsp.SSLManagerProviderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
ResourceGroupId: config.ResourceGroupId,
Region: lo.
If(config.Region == "" || strings.HasPrefix(config.Region, "cn-"), "cn-hangzhou").
Else("ap-southeast-1"),
})
if err != nil {
return nil, fmt.Errorf("could not create ssl manager: %w", err)
}
return &SSLDeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
config: config,
logger: slog.Default(),
sdkClient: client,
sslManager: sslmgr,
}, nil
}
@ -64,15 +82,25 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
return nil, errors.New("config `domain` is required")
}
// 上传证书
upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM)
if err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// 设置域名证书
// REF: https://help.aliyun.com/zh/vod/developer-reference/api-vod-2017-03-21-setvoddomainsslcertificate
certId, _ := strconv.ParseInt(upres.CertId, 10, 64)
setVodDomainSSLCertificateReq := &alivod.SetVodDomainSSLCertificateRequest{
DomainName: tea.String(d.config.Domain),
CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
CertType: tea.String("upload"),
DomainName: tea.String(d.config.Domain),
CertType: tea.String("cas"),
CertId: tea.Int64(int64(certId)),
CertRegion: lo.
If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")).
Else(tea.String("ap-southeast-1")),
SSLProtocol: tea.String("on"),
SSLPub: tea.String(certPEM),
SSLPri: tea.String(privkeyPEM),
}
setVodDomainSSLCertificateResp, err := d.sdkClient.SetVodDomainSSLCertificate(setVodDomainSSLCertificateReq)
d.logger.Debug("sdk request 'live.SetVodDomainSSLCertificate'", slog.Any("request", setVodDomainSSLCertificateReq), slog.Any("response", setVodDomainSSLCertificateResp))

View File

@ -114,8 +114,8 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
modifySiteReq := &btwafsdk.ModifySiteRequest{
SiteId: lo.ToPtr(siteId),
Type: lo.ToPtr("openCert"),
Server: &btwafsdk.SiteServerInfo{
ListenSSLPorts: lo.ToPtr([]int32{d.config.SitePort}),
Server: &btwafsdk.SiteServerInfoMod{
ListenSSLPorts: lo.ToPtr([]string{fmt.Sprintf("%d", d.config.SitePort)}),
SSL: &btwafsdk.SiteServerSSLInfo{
IsSSL: lo.ToPtr(int32(1)),
FullChain: lo.ToPtr(certPEM),

View File

@ -30,6 +30,10 @@ type SSLDeployerProviderConfig struct {
SecretDataKeyForCrt string `json:"secretDataKeyForCrt,omitempty"`
// Kubernetes Secret 中用于存放私钥的 Key。
SecretDataKeyForKey string `json:"secretDataKeyForKey,omitempty"`
// Kubernetes Secret 注解。
SecretAnnotations map[string]string `json:"secretAnnotations,omitempty"`
// Kubernetes Secret 标签。
SecretLabels map[string]string `json:"secretLabels,omitempty"`
}
type SSLDeployerProvider struct {
@ -94,6 +98,17 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
"certimate/issuer-sn": certX509.Issuer.SerialNumber,
"certimate/issuer-org": strings.Join(certX509.Issuer.Organization, ","),
}
secretLabels := map[string]string{}
if d.config.SecretAnnotations != nil {
for k, v := range d.config.SecretAnnotations {
secretAnnotations[k] = v
}
}
if d.config.SecretLabels != nil {
for k, v := range d.config.SecretLabels {
secretLabels[k] = v
}
}
// 获取 Secret 实例,如果不存在则创建
secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Get(context.TODO(), d.config.SecretName, k8smeta.GetOptions{})
@ -106,6 +121,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
ObjectMeta: k8smeta.ObjectMeta{
Name: d.config.SecretName,
Annotations: secretAnnotations,
Labels: secretLabels,
},
Type: k8score.SecretType(d.config.SecretType),
}
@ -131,6 +147,13 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
secretPayload.ObjectMeta.Annotations[k] = v
}
}
if secretPayload.ObjectMeta.Labels == nil {
secretPayload.ObjectMeta.Labels = secretLabels
} else {
for k, v := range secretLabels {
secretPayload.ObjectMeta.Labels[k] = v
}
}
if secretPayload.Data == nil {
secretPayload.Data = make(map[string][]byte)
}

View File

@ -80,7 +80,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
// RCDN SSL 绑定域名
// REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-184214120
certId, _ := strconv.Atoi(upres.CertId)
certId, _ := strconv.ParseInt(upres.CertId, 10, 32)
rcdnInstanceSslBindReq := &rainyunsdk.RcdnInstanceSslBindRequest{
CertId: int32(certId),
Domains: []string{d.config.Domain},

View File

@ -28,6 +28,9 @@ type SSLDeployerProviderConfig struct {
Method string `json:"method,omitempty"`
// 请求标头。
Headers map[string]string `json:"headers,omitempty"`
// 请求超时(单位:秒)。
// 零值时默认值 30。
Timeout int32 `json:"timeout,omitempty"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
@ -49,6 +52,9 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv
SetTimeout(30 * time.Second).
SetRetryCount(3).
SetRetryWaitTime(5 * time.Second)
if config.Timeout > 0 {
client.SetTimeout(time.Duration(config.Timeout) * time.Second)
}
if config.AllowInsecureConnections {
client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
}

View File

@ -6,9 +6,9 @@ import (
)
type ModifySiteRequest struct {
SiteId *string `json:"site_id,omitempty"`
Type *string `json:"types,omitempty"`
Server *SiteServerInfo `json:"server,omitempty"`
SiteId *string `json:"site_id,omitempty"`
Type *string `json:"types,omitempty"`
Server *SiteServerInfoMod `json:"server,omitempty"`
}
type ModifySiteResponse struct {

View File

@ -32,6 +32,11 @@ type SiteServerInfo struct {
SSL *SiteServerSSLInfo `json:"ssl,omitempty"`
}
type SiteServerInfoMod struct {
ListenSSLPorts *[]string `json:"listen_ssl_port,omitempty"`
SSL *SiteServerSSLInfo `json:"ssl,omitempty"`
}
type SiteServerSSLInfo struct {
IsSSL *int32 `json:"is_ssl,omitempty"`
FullChain *string `json:"full_chain,omitempty"`

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -0,0 +1 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" viewBox="0 0 54 54" style="enable-background:new 0 0 54 54;" xml:space="preserve"><g><rect class="st0" width="54" height="54" fill="#102147"/></g><g><path class="st1" d="M20.6,9.8c-0.4-0.6-1-1-1.8-1H8.1C6.9,8.8,6,9.7,6,10.9l0,0c0,0.4,0.1,0.8,0.3,1.1l2.2,3.5l14.3-2.2L20.6,9.8z" fill="#007BFC"/><path class="st2" d="M22.8,13.3c-0.4-0.6-1.1-1-1.8-1H10.3c-1.2,0-2.1,0.9-2.1,2.1l0,0c0,0.4,0.1,0.8,0.3,1.1l3.1,4.9l14.3-2.2 L22.8,13.3z" fill="#51B9FF"/><path class="st3" d="M11.6,20.4c-0.2-0.3-0.3-0.7-0.3-1.1c0-1.2,0.9-2.1,2.1-2.1l0,0h10.8c0.7,0,1.4,0.4,1.8,1l9.6,15.3 c0.2,0.3,0.3,0.7,0.3,1.1c0,0.4-0.1,0.8-0.3,1.1l-5.4,8.5c-0.6,1-1.9,1.3-2.9,0.6c-0.3-0.2-0.5-0.4-0.6-0.6L11.6,20.4z" fill="#FFFFFF"/><path class="st3" d="M38.7,25c0.6,1,1.9,1.3,2.9,0.6c0.3-0.2,0.5-0.4,0.6-0.6l1.8-2.9l3.5-5.6c0.2-0.3,0.3-0.7,0.3-1.1 c0-0.4-0.1-0.8-0.3-1.1l-2.8-4.4c-0.4-0.6-1.1-1-1.8-1H32.3c-1.2,0-2.1,0.9-2.1,2.1l0,0c0,0.4,0.1,0.8,0.3,1.1L38.7,25z" fill="#FFFFFF"/></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -15,6 +15,7 @@ import AccessConfigFieldsProvider1Panel from "./forms/AccessConfigFieldsProvider
import AccessConfigFieldsProviderACMECA from "./forms/AccessConfigFieldsProviderACMECA";
import AccessConfigFieldsProviderACMEDNS from "./forms/AccessConfigFieldsProviderACMEDNS";
import AccessConfigFieldsProviderACMEHttpReq from "./forms/AccessConfigFieldsProviderACMEHttpReq";
import AccessConfigFieldsProviderActalisSSL from "./forms/AccessConfigFieldsProviderActalisSSL";
import AccessConfigFieldsProviderAliyun from "./forms/AccessConfigFieldsProviderAliyun";
import AccessConfigFieldsProviderAPISIX from "./forms/AccessConfigFieldsProviderAPISIX";
import AccessConfigFieldsProviderAWS from "./forms/AccessConfigFieldsProviderAWS";
@ -80,6 +81,7 @@ import AccessConfigFieldsProviderUniCloud from "./forms/AccessConfigFieldsProvid
import AccessConfigFieldsProviderUpyun from "./forms/AccessConfigFieldsProviderUpyun";
import AccessConfigFieldsProviderVercel from "./forms/AccessConfigFieldsProviderVercel";
import AccessConfigFieldsProviderVolcEngine from "./forms/AccessConfigFieldsProviderVolcEngine";
import AccessConfigFieldsProviderVultr from "./forms/AccessConfigFieldsProviderVultr";
import AccessConfigFieldsProviderWangsu from "./forms/AccessConfigFieldsProviderWangsu";
import AccessConfigFieldsProviderWebhook from "./forms/AccessConfigFieldsProviderWebhook";
import AccessConfigFieldsProviderWeComBot from "./forms/AccessConfigFieldsProviderWeComBot";
@ -140,6 +142,9 @@ const AccessForm = ({ className, style, disabled, initialValues, mode, usage, ..
case ACCESS_PROVIDERS.ACMEHTTPREQ: {
return <AccessConfigFieldsProviderACMEHttpReq />;
}
case ACCESS_PROVIDERS.ACTALISSSL: {
return <AccessConfigFieldsProviderActalisSSL />;
}
case ACCESS_PROVIDERS.ALIYUN: {
return <AccessConfigFieldsProviderAliyun />;
}
@ -335,6 +340,9 @@ const AccessForm = ({ className, style, disabled, initialValues, mode, usage, ..
case ACCESS_PROVIDERS.VOLCENGINE: {
return <AccessConfigFieldsProviderVolcEngine />;
}
case ACCESS_PROVIDERS.VULTR: {
return <AccessConfigFieldsProviderVultr />;
}
case ACCESS_PROVIDERS.WANGSU: {
return <AccessConfigFieldsProviderWangsu />;
}

View File

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

View File

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

View File

@ -509,7 +509,7 @@ const BizApplyNodeConfigForm = ({ node, ...props }: BizApplyNodeConfigFormProps)
>
<AutoComplete
allowClear
options={["classic", "tlsserver", "shortlived"].map((value) => ({ value }))}
options={["classic", "tlsserver", "shortlived"].map((s) => ({ value: s }))}
placeholder={t("workflow_node.apply.form.acme_profile.placeholder")}
filterOption={(inputValue, option) => option!.value.toLowerCase().includes(inputValue.toLowerCase())}
/>

View File

@ -1,5 +1,5 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { AutoComplete, Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
@ -19,6 +19,20 @@ const BizDeployNodeConfigFieldsProviderAliyunCDN = () => {
return (
<>
<Form.Item
name={[parentNamePath, "region"]}
initialValue={initialValues.region}
label={t("workflow_node.deploy.form.aliyun_cdn_region.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_cdn_region.tooltip") }}></span>}
>
<AutoComplete
allowClear
options={["cn-hangzhou", "ap-southeast-1"].map((s) => ({ value: s }))}
placeholder={t("workflow_node.deploy.form.aliyun_cdn_region.placeholder")}
/>
</Form.Item>
<Form.Item
name={[parentNamePath, "domain"]}
initialValue={initialValues.domain}
@ -34,6 +48,7 @@ const BizDeployNodeConfigFieldsProviderAliyunCDN = () => {
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
region: "",
domain: "",
};
};
@ -42,6 +57,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
const { t } = i18n;
return z.object({
region: z.string().nullish(),
domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")),
});
};

View File

@ -1,5 +1,5 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { AutoComplete, Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
@ -19,6 +19,20 @@ const BizDeployNodeConfigFieldsProviderAliyunDCDN = () => {
return (
<>
<Form.Item
name={[parentNamePath, "region"]}
initialValue={initialValues.region}
label={t("workflow_node.deploy.form.aliyun_dcdn_region.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_dcdn_region.tooltip") }}></span>}
>
<AutoComplete
allowClear
options={["cn-hangzhou", "ap-southeast-1"].map((s) => ({ value: s }))}
placeholder={t("workflow_node.deploy.form.aliyun_dcdn_region.placeholder")}
/>
</Form.Item>
<Form.Item
name={[parentNamePath, "domain"]}
initialValue={initialValues.domain}
@ -34,6 +48,7 @@ const BizDeployNodeConfigFieldsProviderAliyunDCDN = () => {
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
region: "",
domain: "",
};
};
@ -42,6 +57,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
const { t } = i18n;
return z.object({
region: z.string().nullish(),
domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")),
});
};

View File

@ -3,6 +3,8 @@ import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import CodeInput from "@/components/CodeInput";
import { useFormNestedFieldsContext } from "./_context";
const BizDeployNodeConfigFieldsProviderKubernetesSecret = () => {
@ -13,8 +15,23 @@ const BizDeployNodeConfigFieldsProviderKubernetesSecret = () => {
[parentNamePath]: getSchema({ i18n }),
});
const formRule = createSchemaFieldRule(formSchema);
const formInst = Form.useFormInstance();
const initialValues = getInitialValues();
const handleSecretAnnotationsBlur = () => {
let value = formInst.getFieldValue([parentNamePath, "secretAnnotations"]);
value = value.trim();
value = value.replace(/(?<!\r)\n/g, "\r\n");
formInst.setFieldValue([parentNamePath, "secretAnnotations"], value);
};
const handleSecretLabelsBlur = () => {
let value = formInst.getFieldValue([parentNamePath, "secretLabels"]);
value = value.trim();
value = value.replace(/(?<!\r)\n/g, "\r\n");
formInst.setFieldValue([parentNamePath, "secretLabels"], value);
};
return (
<>
<Form.Item
@ -66,6 +83,40 @@ const BizDeployNodeConfigFieldsProviderKubernetesSecret = () => {
>
<Input placeholder={t("workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "secretAnnotations"]}
initialValue={initialValues.secretAnnotations}
label={t("workflow_node.deploy.form.k8s_secret_annotations.label")}
extra={t("workflow_node.deploy.form.k8s_secret_annotations.help")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.k8s_secret_annotations.tooltip") }}></span>}
>
<CodeInput
height="auto"
minHeight="64px"
maxHeight="256px"
placeholder={t("workflow_node.deploy.form.k8s_secret_annotations.placeholder")}
onBlur={handleSecretAnnotationsBlur}
/>
</Form.Item>
<Form.Item
name={[parentNamePath, "secretLabels"]}
initialValue={initialValues.secretLabels}
label={t("workflow_node.deploy.form.k8s_secret_labels.label")}
extra={t("workflow_node.deploy.form.k8s_secret_labels.help")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.k8s_secret_labels.tooltip") }}></span>}
>
<CodeInput
height="auto"
minHeight="64px"
maxHeight="256px"
placeholder={t("workflow_node.deploy.form.k8s_secret_labels.placeholder")}
onBlur={handleSecretLabelsBlur}
/>
</Form.Item>
</>
);
};
@ -88,6 +139,34 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
secretType: z.string().nonempty(t("workflow_node.deploy.form.k8s_secret_type.placeholder")),
secretDataKeyForCrt: z.string().nonempty(t("workflow_node.deploy.form.k8s_secret_data_key_for_crt.placeholder")),
secretDataKeyForKey: z.string().nonempty(t("workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder")),
secretAnnotations: z
.string()
.nullish()
.refine((v) => {
if (!v) return true;
const lines = v.split(/\r?\n/);
for (const line of lines) {
if (line.split(":").length < 2) {
return false;
}
}
return true;
}, t("workflow_node.deploy.form.k8s_secret_annotations.errmsg.invalid")),
secretLabels: z
.string()
.nullish()
.refine((v) => {
if (!v) return true;
const lines = v.split(/\r?\n/);
for (const line of lines) {
if (line.split(":").length < 2) {
return false;
}
}
return true;
}, t("workflow_node.deploy.form.k8s_secret_labels.errmsg.invalid")),
});
};

View File

@ -1,5 +1,5 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form } from "antd";
import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
@ -51,6 +51,17 @@ const BizDeployNodeConfigFieldsProviderWebhook = () => {
<Form.Item>
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.webhook_data.guide") }}></span>} />
</Form.Item>
<Form.Item name={[parentNamePath, "timeout"]} label={t("workflow_node.deploy.form.webhook_timeout.label")} rules={[formRule]}>
<Input
type="number"
allowClear
min={0}
max={3600}
placeholder={t("workflow_node.deploy.form.webhook_timeout.placeholder")}
addonAfter={t("workflow_node.deploy.form.webhook_timeout.unit")}
/>
</Form.Item>
</>
);
};
@ -76,6 +87,10 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
return false;
}
}, t("workflow_node.deploy.form.webhook_data.errmsg.json_invalid")),
timeout: z.preprocess(
(v) => (v == null || v === "" ? void 0 : Number(v)),
z.number().int(t("workflow_node.deploy.form.webhook_timeout.placeholder")).gte(1, t("workflow_node.deploy.form.webhook_timeout.placeholder")).nullish()
),
});
};

View File

@ -1,5 +1,5 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form } from "antd";
import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
@ -51,6 +51,17 @@ const BizNotifyNodeConfigFieldsProviderWebhook = () => {
<Form.Item>
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.notify.form.webhook_data.guide") }}></span>} />
</Form.Item>
<Form.Item name={[parentNamePath, "timeout"]} label={t("workflow_node.notify.form.webhook_timeout.label")} rules={[formRule]}>
<Input
type="number"
allowClear
min={0}
max={3600}
placeholder={t("workflow_node.notify.form.webhook_timeout.placeholder")}
addonAfter={t("workflow_node.notify.form.webhook_timeout.unit")}
/>
</Form.Item>
</>
);
};
@ -76,6 +87,10 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
return false;
}
}, t("workflow_node.notify.form.webhook_data.errmsg.json_invalid")),
timeout: z.preprocess(
(v) => (v == null || v === "" ? void 0 : Number(v)),
z.number().int(t("workflow_node.notify.form.webhook_timeout.placeholder")).gte(1, t("workflow_node.notify.form.webhook_timeout.placeholder")).nullish()
),
});
};

View File

@ -19,6 +19,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
ACMECA: "acmeca",
ACMEDNS: "acmedns",
ACMEHTTPREQ: "acmehttpreq",
ACTALISSSL: "actalisssl",
ALIYUN: "aliyun",
APISIX: "apisix",
AWS: "aws",
@ -88,6 +89,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
UPYUN: "upyun",
VERCEL: "vercel",
VOLCENGINE: "volcengine",
VULTR: "vultr",
WANGSU: "wangsu",
WEBHOOK: "webhook",
WECOMBOT: "wecombot",
@ -178,6 +180,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.PORKBUN, "provider.porkbun", "/imgs/providers/porkbun.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.SPACESHIP, "provider.spaceship", "/imgs/providers/spaceship.png", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.VERCEL, "provider.vercel", "/imgs/providers/vercel.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.VULTR, "provider.vultr", "/imgs/providers/vultr.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.CMCCCLOUD, "provider.cmcccloud", "/imgs/providers/cmcccloud.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.WESTCN, "provider.westcn", "/imgs/providers/westcn.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.POWERDNS, "provider.powerdns", "/imgs/providers/powerdns.svg", [ACCESS_USAGES.DNS]],
@ -186,6 +189,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.LETSENCRYPT, "provider.letsencrypt", "/imgs/providers/letsencrypt.svg", [ACCESS_USAGES.CA], "builtin"],
[ACCESS_PROVIDERS.LETSENCRYPTSTAGING, "provider.letsencryptstaging", "/imgs/providers/letsencrypt.svg", [ACCESS_USAGES.CA], "builtin"],
[ACCESS_PROVIDERS.ACTALISSSL, "provider.actalisssl", "/imgs/providers/actalisssl.png", [ACCESS_USAGES.CA]],
[ACCESS_PROVIDERS.BUYPASS, "provider.buypass", "/imgs/providers/buypass.png", [ACCESS_USAGES.CA]],
[ACCESS_PROVIDERS.GOOGLETRUSTSERVICES, "provider.googletrustservices", "/imgs/providers/google.svg", [ACCESS_USAGES.CA]],
[ACCESS_PROVIDERS.SSLCOM, "provider.sslcom", "/imgs/providers/sslcom.svg", [ACCESS_USAGES.CA]],
@ -221,6 +225,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
*/
export const CA_PROVIDERS = Object.freeze({
ACMECA: `${ACCESS_PROVIDERS.ACMECA}`,
ACTALISSSL: `${ACCESS_PROVIDERS.ACTALISSSL}`,
BUYPASS: `${ACCESS_PROVIDERS.BUYPASS}`,
GOOGLETRUSTSERVICES: `${ACCESS_PROVIDERS.GOOGLETRUSTSERVICES}`,
LETSENCRYPT: `${ACCESS_PROVIDERS.LETSENCRYPT}`,
@ -242,6 +247,7 @@ export const caProvidersMap: Map<CAProvider["type"] | string, CAProvider> = new
[
[CA_PROVIDERS.LETSENCRYPT, "builtin"],
[CA_PROVIDERS.LETSENCRYPTSTAGING, "builtin"],
[CA_PROVIDERS.ACTALISSSL],
[CA_PROVIDERS.BUYPASS],
[CA_PROVIDERS.GOOGLETRUSTSERVICES],
[CA_PROVIDERS.SSLCOM],
@ -316,6 +322,7 @@ export const ACME_DNS01_PROVIDERS = Object.freeze({
VERCEL: `${ACCESS_PROVIDERS.VERCEL}`,
VOLCENGINE: `${ACCESS_PROVIDERS.VOLCENGINE}`, // 兼容旧值,等同于 `VOLCENGINE_DNS`
VOLCENGINE_DNS: `${ACCESS_PROVIDERS.VOLCENGINE}-dns`,
VULTR: `${ACCESS_PROVIDERS.VULTR}`,
WESTCN: `${ACCESS_PROVIDERS.WESTCN}`,
} as const);
@ -362,6 +369,7 @@ export const acmeDns01ProvidersMap: Map<ACMEDns01Provider["type"] | string, ACME
[ACME_DNS01_PROVIDERS.PORKBUN, "provider.porkbun"],
[ACME_DNS01_PROVIDERS.SPACESHIP, "provider.spaceship"],
[ACME_DNS01_PROVIDERS.VERCEL, "provider.vercel"],
[ACME_DNS01_PROVIDERS.VULTR, "provider.vultr"],
[ACME_DNS01_PROVIDERS.CMCCCLOUD_DNS, "provider.cmcccloud.dns"],
[ACME_DNS01_PROVIDERS.CTCCCLOUD_SMARTDNS, "provider.ctcccloud.smartdns"],
[ACME_DNS01_PROVIDERS.RAINYUN, "provider.rainyun"],

View File

@ -65,7 +65,7 @@
"access.form.acmedns_credentials.label": "ACME-DNS credentials",
"access.form.acmedns_credentials.placeholder": "Please enter ACME-DNS credentials",
"access.form.acmedns_credentials.tooltip": "For more information, see <a href=\"https://github.com/joohoi/acme-dns\" target=\"_blank\">https://github.com/joohoi/acme-dns</a>",
"access.form.acmedns_credentials.errmsg.json_invalid": "Please enter a valiod JSON string",
"access.form.acmedns_credentials.errmsg.json_invalid": "Please enter a valid JSON string",
"access.form.acmehttpreq_endpoint.label": "Endpoint",
"access.form.acmehttpreq_endpoint.placeholder": "Please enter endpoint",
"access.form.acmehttpreq_endpoint.tooltip": "For more information, see <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
@ -78,6 +78,12 @@
"access.form.acmehttpreq_password.label": "HTTP Basic Auth password (Optional)",
"access.form.acmehttpreq_password.placeholder": "Please enter HTTP Basic Auth password",
"access.form.acmehttpreq_password.tooltip": "For more information, see <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
"access.form.actalisssl_eab_kid.label": "ACME EAB KID",
"access.form.actalisssl_eab_kid.placeholder": "Please enter ACME EAB KID",
"access.form.actalisssl_eab_kid.tooltip": "For more information, see <a href=\"https://www.actalis.com/manage-with-acme\" target=\"_blank\">https://www.actalis.com/manage-with-acme</a>",
"access.form.actalisssl_eab_hmac_key.label": "ACME EAB HMAC key",
"access.form.actalisssl_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key",
"access.form.actalisssl_eab_hmac_key.tooltip": "For more information, see <a href=\"https://www.actalis.com/manage-with-acme\" target=\"_blank\">https://www.actalis.com/manage-with-acme</a>",
"access.form.aliyun_access_key_id.label": "Aliyun AccessKeyId",
"access.form.aliyun_access_key_id.placeholder": "Please enter Aliyun AccessKeyId",
"access.form.aliyun_access_key_id.tooltip": "For more information, see <a href=\"https://www.alibabacloud.com/help/en/acr/create-and-obtain-an-accesskey-pair\" target=\"_blank\">https://www.alibabacloud.com/help/en/acr/create-and-obtain-an-accesskey-pair</a>",
@ -492,6 +498,9 @@
"access.form.volcengine_secret_access_key.label": "VolcEngine SecretAccessKey",
"access.form.volcengine_secret_access_key.placeholder": "Please enter VolcEngine SecretAccessKey",
"access.form.volcengine_secret_access_key.tooltip": "For more information, see <a href=\"https://www.volcengine.com/docs/6291/216571\" target=\"_blank\">https://www.volcengine.com/docs/6291/216571</a>",
"access.form.vultr_api_key.label": "Vultr API key",
"access.form.vultr_api_key.placeholder": "Please enter Vultr API key",
"access.form.vultr_api_key.tooltip": "For more information, see <a href=\"https://docs.vultr.com/platform/other/users/manage-users/api-access/regenerate-user-api-key\" target=\"_blank\">https://docs.vultr.com/platform/other/users/manage-users/api-access/regenerate-user-api-key</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>",
@ -509,7 +518,7 @@
"access.form.webhook_headers.placeholder": "Please enter Webhook request headers",
"access.form.webhook_headers.errmsg.invalid": "Please enter a valid request headers",
"access.form.webhook_headers.tooltip": "Example: <br><i>Content-Type: application/json<br>User-Agent: certimate</i>",
"access.form.webhook_data.errmsg.json_invalid": "Please enter a valiod JSON string",
"access.form.webhook_data.errmsg.json_invalid": "Please enter a valid JSON string",
"access.form.webhook_data.label": "Webhook data (Optional)",
"access.form.webhook_data.placeholder": "Please enter the default Webhook data",
"access.form.webhook_data.help": "Notes: It can be overrided in the workflows.",

View File

@ -5,6 +5,7 @@
"provider.acmeca": "ACME Custom CA Endpoint",
"provider.acmedns": "ACME-DNS",
"provider.acmehttpreq": "ACME Custom HTTP Endpoint",
"provider.actalisssl": "Actalis SSL",
"provider.aliyun": "Alibaba Cloud",
"provider.aliyun.alb": "Alibaba Cloud - ALB (Application Load Balancer)",
"provider.aliyun.apigw": "Alibaba Cloud - API Gateway",
@ -171,6 +172,7 @@
"provider.volcengine.imagex": "Volcengine - ImageX",
"provider.volcengine.live": "Volcengine - Live",
"provider.volcengine.tos": "Volcengine - TOS (Tinder Object Storage)",
"provider.vultr": "Vultr",
"provider.wangsu": "Wangsu Cloud",
"provider.wangsu.cdn": "Wangsu Cloud - CDN (Content Delivery Network)",
"provider.wangsu.cdnpro": "Wangsu Cloud - CDN Pro (CDN 360)",

View File

@ -92,8 +92,8 @@
"workflow_node.apply.form.validity_lifetime.placeholder": "Please enter certificate's validity lifetime",
"workflow_node.apply.form.validity_lifetime.help": "Notes: Not all CAs support this feature.",
"workflow_node.apply.form.validity_lifetime.tooltip": "It determines the <em>NotAfter</em> field of the certificate in the ACME protocol. If you don't understand this option, just keep it by default.",
"workflow_node.apply.form.validity_lifetime.units.h": "Hour(s)",
"workflow_node.apply.form.validity_lifetime.units.d": "Day(s)",
"workflow_node.apply.form.validity_lifetime.units.h": "hours",
"workflow_node.apply.form.validity_lifetime.units.d": "days",
"workflow_node.apply.form.acme_profile.label": "Certificate ACME profile (Optional)",
"workflow_node.apply.form.acme_profile.placeholder": "Please enter certificate's ACME profile",
"workflow_node.apply.form.acme_profile.help": "Notes: Not all CAs support this feature.",
@ -256,9 +256,15 @@
"workflow_node.deploy.form.aliyun_clb_snidomain.placeholder": "Please enter Alibaba Cloud CLB SNI domain name",
"workflow_node.deploy.form.aliyun_clb_snidomain.help": "",
"workflow_node.deploy.form.aliyun_clb_snidomain.tooltip": "For more information, see <a href=\"https://slb.console.aliyun.com/clb\" target=\"_blank\">https://slb.console.aliyun.com/clb</a>",
"workflow_node.deploy.form.aliyun_cdn_region.label": "Alibaba Cloud CDN region",
"workflow_node.deploy.form.aliyun_cdn_region.placeholder": "Please enter Alibaba Cloud CDN region (e.g. cn-hangzhou)",
"workflow_node.deploy.form.aliyun_cdn_region.tooltip": "<ul style=\"list-style: disc;\"><li><strong>ap-southeast-1</strong> for Alibaba Cloud International</li><li><strong>cn-hangzhou</strong> for Alibaba Cloud in China</li></ul>",
"workflow_node.deploy.form.aliyun_cdn_domain.label": "Alibaba Cloud CDN domain",
"workflow_node.deploy.form.aliyun_cdn_domain.placeholder": "Please enter Alibaba Cloud CDN domain name",
"workflow_node.deploy.form.aliyun_cdn_domain.tooltip": "For more information, see <a href=\"https://cdn.console.aliyun.com\" target=\"_blank\">https://cdn.console.aliyun.com</a>",
"workflow_node.deploy.form.aliyun_dcdn_region.label": "Alibaba Cloud DCDN region",
"workflow_node.deploy.form.aliyun_dcdn_region.placeholder": "Please enter Alibaba Cloud DCDN region (e.g. cn-hangzhou)",
"workflow_node.deploy.form.aliyun_dcdn_region.tooltip": "<ul style=\"list-style: disc;\"><li><strong>ap-southeast-1</strong> for Alibaba Cloud International</li><li><strong>cn-hangzhou</strong> for Alibaba Cloud in China</li></ul>",
"workflow_node.deploy.form.aliyun_dcdn_domain.label": "Alibaba Cloud DCDN domain",
"workflow_node.deploy.form.aliyun_dcdn_domain.placeholder": "Please enter Alibaba Cloud DCDN domain name",
"workflow_node.deploy.form.aliyun_dcdn_domain.tooltip": "For more information, see <a href=\"https://dcdn.console.aliyun.com\" target=\"_blank\">https://dcdn.console.aliyun.com</a>",
@ -588,6 +594,16 @@
"workflow_node.deploy.form.k8s_secret_data_key_for_key.label": "Kubernetes Secret data key for private key",
"workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder": "Please enter Kubernetes Secret data key for private key",
"workflow_node.deploy.form.k8s_secret_data_key_for_key.tooltip": "For more information, see <a href=\"https://kubernetes.io/docs/concepts/configuration/secret/\" target=\"_blank\">https://kubernetes.io/docs/concepts/configuration/secret/</a>",
"workflow_node.deploy.form.k8s_secret_annotations.label": "Kubernetes Secret annotations (Optional)",
"workflow_node.deploy.form.k8s_secret_annotations.placeholder": "Please enter Kubernetes Secret annotations",
"workflow_node.deploy.form.k8s_secret_annotations.help": "Notes: One key value pair per line, separated by colon.",
"workflow_node.deploy.form.k8s_secret_annotations.errmsg.invalid": "Please enter a valid annotations",
"workflow_node.deploy.form.k8s_secret_annotations.tooltip": "Example: <br><i>environment: production<br>app: nginx</i>",
"workflow_node.deploy.form.k8s_secret_labels.label": "Kubernetes Secret labels (Optional)",
"workflow_node.deploy.form.k8s_secret_labels.placeholder": "Please enter Kubernetes Secret labels",
"workflow_node.deploy.form.k8s_secret_labels.help": "Notes: One key value pair per line, separated by colon.",
"workflow_node.deploy.form.k8s_secret_labels.errmsg.invalid": "Please enter a valid labels",
"workflow_node.deploy.form.k8s_secret_labels.tooltip": "Example: <br><i>environment: production<br>app: nginx</i>",
"workflow_node.deploy.form.kong_resource_type.label": "Resource type",
"workflow_node.deploy.form.kong_resource_type.placeholder": "Please select resource type",
"workflow_node.deploy.form.kong_resource_type.option.certificate.label": "SSL certificate",
@ -996,7 +1012,10 @@
"workflow_node.deploy.form.webhook_data.placeholder": "Please enter Webhook data",
"workflow_node.deploy.form.webhook_data.help": "Notes: Leave it blank to use the default Webhook data provided by the credential.",
"workflow_node.deploy.form.webhook_data.guide": "<details><summary>Supported variables: </summary><ol style=\"list-style: disc;\"><li><strong>${DOMAIN}</strong>: The primary domain of the certificate (<i>CommonName</i>).</li><li><strong>${DOMAINS}</strong>: The domain list of the certificate (<i>SubjectAltNames</i>).</li><li><strong>${CERTIFICATE}</strong>: The PEM format content of the certificate file.</li><li><strong>${SERVER_CERTIFICATE}</strong>: The PEM format content of the server certificate file.</li><li><strong>${INTERMEDIA_CERTIFICATE}</strong>: The PEM format content of the intermediate CA certificate file.</li><li><strong>${PRIVATE_KEY}</strong>: The PEM format content of the private key file.</li></ol></details><br>Please visit the credentials page for addtional notes.",
"workflow_node.deploy.form.webhook_data.errmsg.json_invalid": "Please enter a valiod JSON string",
"workflow_node.deploy.form.webhook_data.errmsg.json_invalid": "Please enter a valid JSON string",
"workflow_node.deploy.form.webhook_timeout.label": "Webhook timeout (Optional)",
"workflow_node.deploy.form.webhook_timeout.placeholder": "Please enter Webhook timeout",
"workflow_node.deploy.form.webhook_timeout.unit": "seconds",
"workflow_node.deploy.form.skip_on_last_succeeded.label": "Repeated deployment",
"workflow_node.deploy.form.skip_on_last_succeeded.prefix": "If the last deployment was successful, ",
"workflow_node.deploy.form.skip_on_last_succeeded.suffix": " to re-deploy.",
@ -1041,7 +1060,10 @@
"workflow_node.notify.form.webhook_data.placeholder": "Please enter Webhook data",
"workflow_node.notify.form.webhook_data.help": "Notes: Leave it blank to use the default Webhook data provided by the credential.",
"workflow_node.notify.form.webhook_data.guide": "<details><summary>Supported variables: </summary><ol style=\"list-style: disc;\"><li><strong>${SUBJECT}</strong>: The subject of notification.</li><li><strong>${MESSAGE}</strong>: The message of notification.</li></ol></details><br>Please visit the credentials page for addtional notes.",
"workflow_node.notify.form.webhook_data.errmsg.json_invalid": "Please enter a valiod JSON string",
"workflow_node.notify.form.webhook_data.errmsg.json_invalid": "Please enter a valid JSON string",
"workflow_node.notify.form.webhook_timeout.label": "Webhook timeout (Optional)",
"workflow_node.notify.form.webhook_timeout.placeholder": "Please enter Webhook timeout",
"workflow_node.notify.form.webhook_timeout.unit": "seconds",
"workflow_node.notify.form.skip_on_all_prev_skipped.label": "Silent behavior",
"workflow_node.notify.form.skip_on_all_prev_skipped.prefix": "If all the previous nodes were skipped, ",
"workflow_node.notify.form.skip_on_all_prev_skipped.suffix": " to notify.",

View File

@ -77,6 +77,12 @@
"access.form.acmehttpreq_password.label": "HTTP 基本认证密码(可选)",
"access.form.acmehttpreq_password.placeholder": "请输入 HTTP 基本认证密码",
"access.form.acmehttpreq_password.tooltip": "这是什么?请参阅 <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
"access.form.actalisssl_eab_kid.label": "ACME EAB KID",
"access.form.actalisssl_eab_kid.placeholder": "请输入 ACME EAB KID",
"access.form.actalisssl_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://www.actalis.com/manage-with-acme\" target=\"_blank\">https://www.actalis.com/manage-with-acme</a>",
"access.form.actalisssl_eab_hmac_key.label": "ACME EAB HMAC Key",
"access.form.actalisssl_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key",
"access.form.actalisssl_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://www.actalis.com/manage-with-acme\" target=\"_blank\">https://www.actalis.com/manage-with-acme</a>",
"access.form.aliyun_access_key_id.label": "阿里云 AccessKeyId",
"access.form.aliyun_access_key_id.placeholder": "请输入阿里云 AccessKeyId",
"access.form.aliyun_access_key_id.tooltip": "这是什么?请参阅 <a href=\"https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair\" target=\"_blank\">https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair</a>",
@ -491,6 +497,9 @@
"access.form.volcengine_secret_access_key.label": "火山引擎 SecretAccessKey",
"access.form.volcengine_secret_access_key.placeholder": "请输入火山引擎 SecretAccessKey",
"access.form.volcengine_secret_access_key.tooltip": "这是什么?请参阅 <a href=\"https://www.volcengine.com/docs/6291/216571\" target=\"_blank\">https://www.volcengine.com/docs/6291/216571</a>",
"access.form.vultr_api_key.label": "Vultr API Key",
"access.form.vultr_api_key.placeholder": "请输入 Vultr API Key",
"access.form.vultr_api_key.tooltip": "这是什么?请参阅 <a href=\"https://docs.vultr.com/platform/other/users/manage-users/api-access/regenerate-user-api-key\" target=\"_blank\">https://docs.vultr.com/platform/other/users/manage-users/api-access/regenerate-user-api-key</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>",

View File

@ -5,6 +5,7 @@
"provider.acmeca": "ACME 自定义 CA 端点",
"provider.acmedns": "ACME-DNS",
"provider.acmehttpreq": "ACME 自定义 HTTP 端点",
"provider.actalisssl": "Actalis SSL",
"provider.aliyun": "阿里云",
"provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB",
"provider.aliyun.apigw": "阿里云 - API 网关",
@ -171,6 +172,7 @@
"provider.volcengine.imagex": "火山引擎 - 图片服务 ImageX",
"provider.volcengine.live": "火山引擎 - 视频直播 Live",
"provider.volcengine.tos": "火山引擎 - 对象存储 TOS",
"provider.vultr": "Vultr",
"provider.wangsu": "网宿云",
"provider.wangsu.cdn": "网宿云 - 内容分发网络 CDN",
"provider.wangsu.cdnpro": "网宿云 - CDN Pro (CDN 360)",

View File

@ -255,9 +255,15 @@
"workflow_node.deploy.form.aliyun_clb_snidomain.placeholder": "请输入阿里云 CLB 扩展域名(支持泛域名)",
"workflow_node.deploy.form.aliyun_clb_snidomain.help": "提示:不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。",
"workflow_node.deploy.form.aliyun_clb_snidomain.tooltip": "这是什么?请参阅 <a href=\"https://slb.console.aliyun.com/clb\" target=\"_blank\">https://slb.console.aliyun.com/clb</a>",
"workflow_node.deploy.form.aliyun_cdn_region.label": "阿里云 CDN 服务地域",
"workflow_node.deploy.form.aliyun_cdn_region.placeholder": "请输入阿里云 CDN 服务地域例如cn-hangzhou",
"workflow_node.deploy.form.aliyun_cdn_region.tooltip": "中国站请填写 <strong>cn-hangzhou</strong><br>国际站请填写 <strong>ap-southeast-1</strong>。",
"workflow_node.deploy.form.aliyun_cdn_domain.label": "阿里云 CDN 加速域名",
"workflow_node.deploy.form.aliyun_cdn_domain.placeholder": "请输入阿里云 CDN 加速域名(支持泛域名)",
"workflow_node.deploy.form.aliyun_cdn_domain.tooltip": "这是什么?请参阅 <a href=\"https://cdn.console.aliyun.com\" target=\"_blank\">https://cdn.console.aliyun.com</a>",
"workflow_node.deploy.form.aliyun_dcdn_region.label": "阿里云 DCDN 服务地域",
"workflow_node.deploy.form.aliyun_dcdn_region.placeholder": "请输入阿里云 DCDN 服务地域例如cn-hangzhou",
"workflow_node.deploy.form.aliyun_dcdn_region.tooltip": "中国站请填写 <strong>cn-hangzhou</strong><br>国际站请填写 <strong>ap-southeast-1</strong>。",
"workflow_node.deploy.form.aliyun_dcdn_domain.label": "阿里云 DCDN 加速域名",
"workflow_node.deploy.form.aliyun_dcdn_domain.placeholder": "请输入阿里云 DCDN 加速域名(支持泛域名)",
"workflow_node.deploy.form.aliyun_dcdn_domain.tooltip": "这是什么?请参阅 <a href=\"https://dcdn.console.aliyun.com\" target=\"_blank\">https://dcdn.console.aliyun.com</a>",
@ -586,6 +592,16 @@
"workflow_node.deploy.form.k8s_secret_data_key_for_key.label": "Kubernetes Secret 数据键(用于存放私钥的字段)",
"workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder": "请输入 Kubernetes Secret 中用于存放私钥的数据键",
"workflow_node.deploy.form.k8s_secret_data_key_for_key.tooltip": "这是什么?请参阅 <a href=\"https://kubernetes.io/zh-cn/docs/concepts/configuration/secret/\" target=\"_blank\">https://kubernetes.io/zh-cn/docs/concepts/configuration/secret/</a>",
"workflow_node.deploy.form.k8s_secret_annotations.label": "Kubernetes Secret 注解(可选)",
"workflow_node.deploy.form.k8s_secret_annotations.placeholder": "请输入 Kubernetes Secret 注解",
"workflow_node.deploy.form.k8s_secret_annotations.help": "提示:每行一个键值对,以分号分隔。",
"workflow_node.deploy.form.k8s_secret_annotations.errmsg.invalid": "请输入有效的注解键值对",
"workflow_node.deploy.form.k8s_secret_annotations.tooltip": "示例:<br><i>environment: production<br>app: nginx</i>",
"workflow_node.deploy.form.k8s_secret_labels.label": "Kubernetes Secret 标签(可选)",
"workflow_node.deploy.form.k8s_secret_labels.placeholder": "请输入 Kubernetes Secret 标签",
"workflow_node.deploy.form.k8s_secret_labels.help": "提示:每行一个键值对,以分号分隔。",
"workflow_node.deploy.form.k8s_secret_labels.errmsg.invalid": "请输入有效的标签键值对",
"workflow_node.deploy.form.k8s_secret_labels.tooltip": "示例:<br><i>environment: production<br>app: nginx</i>",
"workflow_node.deploy.form.kong_resource_type.label": "证书部署方式",
"workflow_node.deploy.form.kong_resource_type.placeholder": "请选择证书部署方式",
"workflow_node.deploy.form.kong_resource_type.option.certificate.label": "替换指定证书",
@ -995,6 +1011,9 @@
"workflow_node.deploy.form.webhook_data.help": "提示:不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。",
"workflow_node.deploy.form.webhook_data.guide": "<details><summary>支持的变量:</summary><ol style=\"list-style: disc;\"><li><strong>${DOMAIN}</strong>:证书的主域名(即 <i>CommonName</i>)。</li><li><strong>${DOMAINS}</strong>:证书的多域名列表(即 <i>SubjectAltNames</i>)。</li><li><strong>${CERTIFICATE}</strong>:证书文件 PEM 格式内容。</li><li><strong>${SERVER_CERTIFICATE}</strong>证书文件仅含服务器证书PEM 格式内容。</li><li><strong>${INTERMEDIA_CERTIFICATE}</strong>证书文件仅含中间证书PEM 格式内容。</li><li><strong>${PRIVATE_KEY}</strong>:私钥文件 PEM 格式内容。</li></ol></details><br>其他注意事项请前往授权凭据页面查看。",
"workflow_node.deploy.form.webhook_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串",
"workflow_node.deploy.form.webhook_timeout.label": "Webhook 超时时间(可选)",
"workflow_node.deploy.form.webhook_timeout.placeholder": "请输入 Webhook 超时时间",
"workflow_node.deploy.form.webhook_timeout.unit": "秒",
"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.suffix": "此部署节点。",
@ -1040,6 +1059,9 @@
"workflow_node.notify.form.webhook_data.help": "提示:不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。",
"workflow_node.notify.form.webhook_data.guide": "<details><summary>支持的变量:</summary><ol style=\"list-style: disc;\"><li><strong>${SUBJECT}</strong>:通知主题。</li><li><strong>${MESSAGE}</strong>:通知内容。</ol></details><br>其他注意事项请前往授权凭据页面查看。",
"workflow_node.notify.form.webhook_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串",
"workflow_node.notify.form.webhook_timeout.label": "Webhook 超时时间(可选)",
"workflow_node.notify.form.webhook_timeout.placeholder": "请输入 Webhook 超时时间",
"workflow_node.notify.form.webhook_timeout.unit": "秒",
"workflow_node.notify.form.skip_on_all_prev_skipped.label": "静默行为",
"workflow_node.notify.form.skip_on_all_prev_skipped.prefix": "当前序申请、上传、部署等节点均已跳过执行时,",
"workflow_node.notify.form.skip_on_all_prev_skipped.suffix": "此通知节点。",

View File

@ -108,6 +108,76 @@ const SSLProviderEditFormLetsEncryptStagingConfig = () => {
);
};
const SSLProviderEditFormActalisSSLConfig = () => {
const { t } = useTranslation();
const { pending, settings, updateSettings } = useContext(SSLProviderContext);
const formSchema = z.object({
eabKid: z
.string(t("access.form.actalisssl_eab_kid.placeholder"))
.min(1, t("access.form.actalisssl_eab_kid.placeholder"))
.max(256, t("common.errmsg.string_max", { max: 256 })),
eabHmacKey: z
.string(t("access.form.actalisssl_eab_hmac_key.placeholder"))
.min(1, t("access.form.actalisssl_eab_hmac_key.placeholder"))
.max(256, t("common.errmsg.string_max", { max: 256 })),
});
const formRule = createSchemaFieldRule(formSchema);
const { form: formInst, formProps } = useAntdForm<z.infer<typeof formSchema>>({
initialValues: settings?.content?.config?.[CA_PROVIDERS.ACTALISSSL],
onSubmit: async (values) => {
const newSettings = produce(settings, (draft) => {
draft.content ??= {} as SSLProviderSettingsContent;
draft.content.provider = CA_PROVIDERS.ACTALISSSL;
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
draft.content.config[CA_PROVIDERS.ACTALISSSL] = values;
});
await updateSettings(newSettings);
setFormChanged(false);
},
});
const [formChanged, setFormChanged] = useState(false);
useEffect(() => {
setFormChanged(settings?.content?.provider !== CA_PROVIDERS.ACTALISSSL);
}, [settings?.content?.provider]);
const handleFormChange = () => {
setFormChanged(true);
};
return (
<Form {...formProps} form={formInst} disabled={pending} layout="vertical" onValuesChange={handleFormChange}>
<Form.Item
name="eabKid"
label={t("access.form.actalisssl_eab_kid.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.actalisssl_eab_kid.tooltip") }}></span>}
>
<Input autoComplete="new-password" placeholder={t("access.form.actalisssl_eab_kid.placeholder")} />
</Form.Item>
<Form.Item
name="eabHmacKey"
label={t("access.form.actalisssl_eab_hmac_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.actalisssl_eab_hmac_key.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.actalisssl_eab_hmac_key.placeholder")} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" disabled={!formChanged} loading={pending}>
{t("common.button.save")}
</Button>
</Form.Item>
</Form>
);
};
const SSLProviderEditFormBuypassConfig = () => {
const { t } = useTranslation();
@ -466,6 +536,7 @@ const SettingsSSLProvider = () => {
const providers = [
[CA_PROVIDERS.LETSENCRYPT, "provider.letsencrypt", "letsencrypt.org", "/imgs/providers/letsencrypt.svg"],
[CA_PROVIDERS.LETSENCRYPTSTAGING, "provider.letsencryptstaging", "letsencrypt.org", "/imgs/providers/letsencrypt.svg"],
[CA_PROVIDERS.ACTALISSSL, "provider.actalisssl", "actalis.com", "/imgs/providers/actalisssl.png"],
[CA_PROVIDERS.BUYPASS, "provider.buypass", "buypass.com", "/imgs/providers/buypass.png"],
[CA_PROVIDERS.GOOGLETRUSTSERVICES, "provider.googletrustservices", "pki.goog", "/imgs/providers/google.svg"],
[CA_PROVIDERS.SSLCOM, "provider.sslcom", "ssl.com", "/imgs/providers/sslcom.svg"],
@ -486,6 +557,8 @@ const SettingsSSLProvider = () => {
return <SSLProviderEditFormLetsEncryptConfig />;
case CA_PROVIDERS.LETSENCRYPTSTAGING:
return <SSLProviderEditFormLetsEncryptStagingConfig />;
case CA_PROVIDERS.ACTALISSSL:
return <SSLProviderEditFormActalisSSLConfig />;
case CA_PROVIDERS.BUYPASS:
return <SSLProviderEditFormBuypassConfig />;
case CA_PROVIDERS.GOOGLETRUSTSERVICES: