diff --git a/go.mod b/go.mod index 18d0a4ee..54651504 100644 --- a/go.mod +++ b/go.mod @@ -187,7 +187,7 @@ require ( github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nrdcg/namesilo v0.2.1 // indirect github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect - github.com/pkg/errors v0.9.1 + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/spf13/cast v1.7.1 // indirect github.com/spf13/cobra v1.9.1 // indirect diff --git a/internal/applicant/acme_ca.go b/internal/applicant/acme_ca.go index 3a066d08..67b7693e 100644 --- a/internal/applicant/acme_ca.go +++ b/internal/applicant/acme_ca.go @@ -3,12 +3,12 @@ package applicant import "github.com/usual2970/certimate/internal/domain" const ( - sslProviderLetsEncrypt = string(domain.ApplyCAProviderTypeLetsEncrypt) - sslProviderLetsEncryptStaging = string(domain.ApplyCAProviderTypeLetsEncryptStaging) - sslProviderBuypass = string(domain.ApplyCAProviderTypeBuypass) - sslProviderGoogleTrustServices = string(domain.ApplyCAProviderTypeGoogleTrustServices) - sslProviderSSLCom = string(domain.ApplyCAProviderTypeSSLCom) - sslProviderZeroSSL = string(domain.ApplyCAProviderTypeZeroSSL) + sslProviderLetsEncrypt = string(domain.CAProviderTypeLetsEncrypt) + sslProviderLetsEncryptStaging = string(domain.CAProviderTypeLetsEncryptStaging) + sslProviderBuypass = string(domain.CAProviderTypeBuypass) + sslProviderGoogleTrustServices = string(domain.CAProviderTypeGoogleTrustServices) + sslProviderSSLCom = string(domain.CAProviderTypeSSLCom) + sslProviderZeroSSL = string(domain.CAProviderTypeZeroSSL) sslProviderDefault = sslProviderLetsEncrypt ) @@ -25,6 +25,6 @@ var sslProviderUrls = map[string]string{ } type acmeSSLProviderConfig struct { - Config map[domain.ApplyCAProviderType]map[string]any `json:"config"` - Provider string `json:"provider"` + Config map[domain.CAProviderType]map[string]any `json:"config"` + Provider string `json:"provider"` } diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go index 7b8b94f9..ecb5218e 100644 --- a/internal/applicant/applicant.go +++ b/internal/applicant/applicant.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "os" "strconv" "strings" @@ -22,7 +23,7 @@ import ( "github.com/usual2970/certimate/internal/repository" ) -type ApplyCertResult struct { +type ApplyResult struct { CertificateFullChain string IssuerCertificate string PrivateKey string @@ -33,40 +34,30 @@ type ApplyCertResult struct { } type Applicant interface { - Apply() (*ApplyCertResult, error) + Apply(ctx context.Context) (*ApplyResult, error) } -type applicantOptions struct { - Domains []string - ContactEmail string - Provider domain.ApplyDNSProviderType - ProviderAccessConfig map[string]any - ProviderExtendedConfig map[string]any - CAProvider domain.ApplyCAProviderType - CAProviderAccessConfig map[string]any - CAProviderExtendedConfig map[string]any - KeyAlgorithm string - Nameservers []string - DnsPropagationTimeout int32 - DnsTTL int32 - DisableFollowCNAME bool - ReplacedARIAcct string - ReplacedARICert string +type ApplicantWithWorkflowNodeConfig struct { + Node *domain.WorkflowNode + Logger *slog.Logger } -func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) { - if node.Type != domain.WorkflowNodeTypeApply { +func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, error) { + if config.Node == nil { + return nil, fmt.Errorf("node is nil") + } + if config.Node.Type != domain.WorkflowNodeTypeApply { return nil, fmt.Errorf("node type is not '%s'", string(domain.WorkflowNodeTypeApply)) } - nodeConfig := node.GetConfigForApply() - options := &applicantOptions{ + nodeConfig := config.Node.GetConfigForApply() + options := &applicantProviderOptions{ Domains: sliceutil.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }), ContactEmail: nodeConfig.ContactEmail, - Provider: domain.ApplyDNSProviderType(nodeConfig.Provider), + Provider: domain.ACMEDns01ProviderType(nodeConfig.Provider), ProviderAccessConfig: make(map[string]any), ProviderExtendedConfig: nodeConfig.ProviderConfig, - CAProvider: domain.ApplyCAProviderType(nodeConfig.CAProvider), + CAProvider: domain.CAProviderType(nodeConfig.CAProvider), CAProviderAccessConfig: make(map[string]any), CAProviderExtendedConfig: nodeConfig.CAProviderConfig, KeyAlgorithm: nodeConfig.KeyAlgorithm, @@ -97,7 +88,7 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) { settings, _ := settingsRepo.GetByName(context.Background(), "sslProvider") sslProviderConfig := &acmeSSLProviderConfig{ - Config: make(map[domain.ApplyCAProviderType]map[string]any), + Config: make(map[domain.CAProviderType]map[string]any), Provider: sslProviderDefault, } if settings != nil { @@ -108,12 +99,12 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) { } } - options.CAProvider = domain.ApplyCAProviderType(sslProviderConfig.Provider) + options.CAProvider = domain.CAProviderType(sslProviderConfig.Provider) options.CAProviderAccessConfig = sslProviderConfig.Config[options.CAProvider] } certRepo := repository.NewCertificateRepository() - lastCertificate, _ := certRepo.GetByWorkflowNodeId(context.Background(), node.Id) + lastCertificate, _ := certRepo.GetByWorkflowNodeId(context.Background(), config.Node.Id) if lastCertificate != nil { newCertSan := slices.Clone(options.Domains) oldCertSan := strings.Split(lastCertificate.SubjectAltNames, ";") @@ -130,18 +121,46 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) { } } - applicant, err := createApplicant(options) + applicant, err := createApplicantProvider(options) if err != nil { return nil, err } - return &proxyApplicant{ + return &applicantImpl{ applicant: applicant, options: options, }, nil } -func apply(challengeProvider challenge.Provider, options *applicantOptions) (*ApplyCertResult, error) { +type applicantImpl struct { + applicant challenge.Provider + options *applicantProviderOptions +} + +var _ Applicant = (*applicantImpl)(nil) + +func (d *applicantImpl) Apply(ctx context.Context) (*ApplyResult, error) { + limiter := getLimiter(fmt.Sprintf("apply_%s", d.options.ContactEmail)) + if err := limiter.Wait(ctx); err != nil { + return nil, err + } + + return applyUseLego(d.applicant, d.options) +} + +const ( + limitBurst = 300 + limitRate float64 = float64(1) / float64(36) +) + +var limiters sync.Map + +func getLimiter(key string) *rate.Limiter { + limiter, _ := limiters.LoadOrStore(key, rate.NewLimiter(rate.Limit(limitRate), 300)) + return limiter.(*rate.Limiter) +} + +func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOptions) (*ApplyResult, error) { user, err := newAcmeUser(string(options.CAProvider), options.ContactEmail) if err != nil { return nil, err @@ -153,7 +172,7 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap // Create an ACME client config config := lego.NewConfig(user) - config.Certificate.KeyType = parseKeyAlgorithm(domain.CertificateKeyAlgorithmType(options.KeyAlgorithm)) + config.Certificate.KeyType = parseLegoKeyAlgorithm(domain.CertificateKeyAlgorithmType(options.KeyAlgorithm)) config.CADirURL = sslProviderUrls[user.CA] if user.CA == sslProviderSSLCom { if strings.HasPrefix(options.KeyAlgorithm, "RSA") { @@ -175,7 +194,7 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap challengeOptions = append(challengeOptions, dns01.AddRecursiveNameservers(dns01.ParseNameservers(options.Nameservers))) challengeOptions = append(challengeOptions, dns01.DisableAuthoritativeNssPropagationRequirement()) } - client.Challenge.SetDNS01Provider(challengeProvider, challengeOptions...) + client.Challenge.SetDNS01Provider(legoProvider, challengeOptions...) // New users need to register first if !user.hasRegistration() { @@ -199,7 +218,7 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap return nil, err } - return &ApplyCertResult{ + return &ApplyResult{ CertificateFullChain: strings.TrimSpace(string(certResource.Certificate)), IssuerCertificate: strings.TrimSpace(string(certResource.IssuerCertificate)), PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)), @@ -210,47 +229,20 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap }, nil } -func parseKeyAlgorithm(algo domain.CertificateKeyAlgorithmType) certcrypto.KeyType { - switch algo { - case domain.CertificateKeyAlgorithmTypeRSA2048: - return certcrypto.RSA2048 - case domain.CertificateKeyAlgorithmTypeRSA3072: - return certcrypto.RSA3072 - case domain.CertificateKeyAlgorithmTypeRSA4096: - return certcrypto.RSA4096 - case domain.CertificateKeyAlgorithmTypeRSA8192: - return certcrypto.RSA8192 - case domain.CertificateKeyAlgorithmTypeEC256: - return certcrypto.EC256 - case domain.CertificateKeyAlgorithmTypeEC384: - return certcrypto.EC384 - case domain.CertificateKeyAlgorithmTypeEC512: - return certcrypto.KeyType("P512") +func parseLegoKeyAlgorithm(algo domain.CertificateKeyAlgorithmType) certcrypto.KeyType { + alogMap := map[domain.CertificateKeyAlgorithmType]certcrypto.KeyType{ + domain.CertificateKeyAlgorithmTypeRSA2048: certcrypto.RSA2048, + domain.CertificateKeyAlgorithmTypeRSA3072: certcrypto.RSA3072, + domain.CertificateKeyAlgorithmTypeRSA4096: certcrypto.RSA4096, + domain.CertificateKeyAlgorithmTypeRSA8192: certcrypto.RSA8192, + domain.CertificateKeyAlgorithmTypeEC256: certcrypto.EC256, + domain.CertificateKeyAlgorithmTypeEC384: certcrypto.EC384, + domain.CertificateKeyAlgorithmTypeEC512: certcrypto.KeyType("P512"), + } + + if keyType, ok := alogMap[algo]; ok { + return keyType } return certcrypto.RSA2048 } - -// TODO: 暂时使用代理模式以兼容之前版本代码,后续重新实现此处逻辑 -type proxyApplicant struct { - applicant challenge.Provider - options *applicantOptions -} - -var limiters sync.Map - -const ( - limitBurst = 300 - limitRate float64 = float64(1) / float64(36) -) - -func getLimiter(key string) *rate.Limiter { - limiter, _ := limiters.LoadOrStore(key, rate.NewLimiter(rate.Limit(limitRate), 300)) - return limiter.(*rate.Limiter) -} - -func (d *proxyApplicant) Apply() (*ApplyCertResult, error) { - limiter := getLimiter(fmt.Sprintf("apply_%s", d.options.ContactEmail)) - limiter.Wait(context.Background()) - return apply(d.applicant, d.options) -} diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index d4ad473f..c54d1fe2 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -38,13 +38,31 @@ import ( maputil "github.com/usual2970/certimate/internal/pkg/utils/map" ) -func createApplicant(options *applicantOptions) (challenge.Provider, error) { +type applicantProviderOptions struct { + Domains []string + ContactEmail string + Provider domain.ACMEDns01ProviderType + ProviderAccessConfig map[string]any + ProviderExtendedConfig map[string]any + CAProvider domain.CAProviderType + CAProviderAccessConfig map[string]any + CAProviderExtendedConfig map[string]any + KeyAlgorithm string + Nameservers []string + DnsPropagationTimeout int32 + DnsTTL int32 + DisableFollowCNAME bool + ReplacedARIAcct string + ReplacedARICert string +} + +func createApplicantProvider(options *applicantProviderOptions) (challenge.Provider, error) { /* 注意:如果追加新的常量值,请保持以 ASCII 排序。 NOTICE: If you add new constant, please keep ASCII order. */ switch options.Provider { - case domain.ApplyDNSProviderTypeACMEHttpReq: + case domain.ACMEDns01ProviderTypeACMEHttpReq: { access := domain.AccessConfigForACMEHttpReq{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -61,7 +79,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeAliyun, domain.ApplyDNSProviderTypeAliyunDNS: + case domain.ACMEDns01ProviderTypeAliyun, domain.ACMEDns01ProviderTypeAliyunDNS: { access := domain.AccessConfigForAliyun{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -77,7 +95,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeAWS, domain.ApplyDNSProviderTypeAWSRoute53: + case domain.ACMEDns01ProviderTypeAWS, domain.ACMEDns01ProviderTypeAWSRoute53: { access := domain.AccessConfigForAWS{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -95,7 +113,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeAzure, domain.ApplyDNSProviderTypeAzureDNS: + case domain.ACMEDns01ProviderTypeAzure, domain.ACMEDns01ProviderTypeAzureDNS: { access := domain.AccessConfigForAzure{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -113,7 +131,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeBaiduCloud, domain.ApplyDNSProviderTypeBaiduCloudDNS: + case domain.ACMEDns01ProviderTypeBaiduCloud, domain.ACMEDns01ProviderTypeBaiduCloudDNS: { access := domain.AccessConfigForBaiduCloud{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -129,7 +147,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeBunny: + case domain.ACMEDns01ProviderTypeBunny: { access := domain.AccessConfigForBunny{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -144,7 +162,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeCloudflare: + case domain.ACMEDns01ProviderTypeCloudflare: { access := domain.AccessConfigForCloudflare{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -160,7 +178,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeClouDNS: + case domain.ACMEDns01ProviderTypeClouDNS: { access := domain.AccessConfigForClouDNS{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -176,7 +194,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeCMCCCloud: + case domain.ACMEDns01ProviderTypeCMCCCloud: { access := domain.AccessConfigForCMCCCloud{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -192,7 +210,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeDeSEC: + case domain.ACMEDns01ProviderTypeDeSEC: { access := domain.AccessConfigForDeSEC{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -207,7 +225,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeDNSLA: + case domain.ACMEDns01ProviderTypeDNSLA: { access := domain.AccessConfigForDNSLA{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -223,7 +241,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeDynv6: + case domain.ACMEDns01ProviderTypeDynv6: { access := domain.AccessConfigForDynv6{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -238,7 +256,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeGcore: + case domain.ACMEDns01ProviderTypeGcore: { access := domain.AccessConfigForGcore{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -253,7 +271,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeGname: + case domain.ACMEDns01ProviderTypeGname: { access := domain.AccessConfigForGname{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -269,7 +287,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeGoDaddy: + case domain.ACMEDns01ProviderTypeGoDaddy: { access := domain.AccessConfigForGoDaddy{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -285,7 +303,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeHuaweiCloud, domain.ApplyDNSProviderTypeHuaweiCloudDNS: + case domain.ACMEDns01ProviderTypeHuaweiCloud, domain.ACMEDns01ProviderTypeHuaweiCloudDNS: { access := domain.AccessConfigForHuaweiCloud{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -302,7 +320,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeJDCloud, domain.ApplyDNSProviderTypeJDCloudDNS: + case domain.ACMEDns01ProviderTypeJDCloud, domain.ACMEDns01ProviderTypeJDCloudDNS: { access := domain.AccessConfigForJDCloud{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -319,7 +337,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeNamecheap: + case domain.ACMEDns01ProviderTypeNamecheap: { access := domain.AccessConfigForNamecheap{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -335,7 +353,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeNameDotCom: + case domain.ACMEDns01ProviderTypeNameDotCom: { access := domain.AccessConfigForNameDotCom{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -351,7 +369,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeNameSilo: + case domain.ACMEDns01ProviderTypeNameSilo: { access := domain.AccessConfigForNameSilo{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -366,7 +384,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeNS1: + case domain.ACMEDns01ProviderTypeNS1: { access := domain.AccessConfigForNS1{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -381,7 +399,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypePorkbun: + case domain.ACMEDns01ProviderTypePorkbun: { access := domain.AccessConfigForPorkbun{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -397,7 +415,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypePowerDNS: + case domain.ACMEDns01ProviderTypePowerDNS: { access := domain.AccessConfigForPowerDNS{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -413,7 +431,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeRainYun: + case domain.ACMEDns01ProviderTypeRainYun: { access := domain.AccessConfigForRainYun{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -428,7 +446,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeTencentCloud, domain.ApplyDNSProviderTypeTencentCloudDNS, domain.ApplyDNSProviderTypeTencentCloudEO: + case domain.ACMEDns01ProviderTypeTencentCloud, domain.ACMEDns01ProviderTypeTencentCloudDNS, domain.ACMEDns01ProviderTypeTencentCloudEO: { access := domain.AccessConfigForTencentCloud{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -436,7 +454,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { } switch options.Provider { - case domain.ApplyDNSProviderTypeTencentCloud, domain.ApplyDNSProviderTypeTencentCloudDNS: + case domain.ACMEDns01ProviderTypeTencentCloud, domain.ACMEDns01ProviderTypeTencentCloudDNS: applicant, err := pTencentCloud.NewChallengeProvider(&pTencentCloud.ChallengeProviderConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, @@ -445,7 +463,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { }) return applicant, err - case domain.ApplyDNSProviderTypeTencentCloudEO: + case domain.ACMEDns01ProviderTypeTencentCloudEO: applicant, err := pTencentCloudEO.NewChallengeProvider(&pTencentCloudEO.ChallengeProviderConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, @@ -460,7 +478,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { } } - case domain.ApplyDNSProviderTypeVercel: + case domain.ACMEDns01ProviderTypeVercel: { access := domain.AccessConfigForVercel{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -476,7 +494,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeVolcEngine, domain.ApplyDNSProviderTypeVolcEngineDNS: + case domain.ACMEDns01ProviderTypeVolcEngine, domain.ACMEDns01ProviderTypeVolcEngineDNS: { access := domain.AccessConfigForVolcEngine{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -492,7 +510,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) { return applicant, err } - case domain.ApplyDNSProviderTypeWestcn: + case domain.ACMEDns01ProviderTypeWestcn: { access := domain.AccessConfigForWestcn{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index 972e7aa3..bdacf08e 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -11,31 +11,29 @@ import ( ) type Deployer interface { - SetLogger(*slog.Logger) - Deploy(ctx context.Context) error } -type deployerOptions struct { - Provider domain.DeployProviderType - ProviderAccessConfig map[string]any - ProviderDeployConfig map[string]any +type DeployerWithWorkflowNodeConfig struct { + Node *domain.WorkflowNode + Logger *slog.Logger + CertificatePEM string + PrivateKeyPEM string } -func NewWithDeployNode(node *domain.WorkflowNode, certdata struct { - Certificate string - PrivateKey string -}, -) (Deployer, error) { - if node.Type != domain.WorkflowNodeTypeDeploy { +func NewWithWorkflowNode(config DeployerWithWorkflowNodeConfig) (Deployer, error) { + if config.Node == nil { + return nil, fmt.Errorf("node is nil") + } + if config.Node.Type != domain.WorkflowNodeTypeDeploy { return nil, fmt.Errorf("node type is not '%s'", string(domain.WorkflowNodeTypeDeploy)) } - nodeConfig := node.GetConfigForDeploy() - options := &deployerOptions{ - Provider: domain.DeployProviderType(nodeConfig.Provider), - ProviderAccessConfig: make(map[string]any), - ProviderDeployConfig: nodeConfig.ProviderConfig, + nodeConfig := config.Node.GetConfigForDeploy() + options := &deployerProviderOptions{ + Provider: domain.DeploymentProviderType(nodeConfig.Provider), + ProviderAccessConfig: make(map[string]any), + ProviderExtendedConfig: nodeConfig.ProviderConfig, } accessRepo := repository.NewAccessRepository() @@ -48,34 +46,27 @@ func NewWithDeployNode(node *domain.WorkflowNode, certdata struct { } } - deployer, err := createDeployer(options) + deployerProvider, err := createDeployerProvider(options) if err != nil { return nil, err } - return &proxyDeployer{ - deployer: deployer, - deployCertificate: certdata.Certificate, - deployPrivateKey: certdata.PrivateKey, + return &deployerImpl{ + provider: deployerProvider.WithLogger(config.Logger), + certPEM: config.CertificatePEM, + privkeyPEM: config.PrivateKeyPEM, }, nil } -// TODO: 暂时使用代理模式以兼容之前版本代码,后续重新实现此处逻辑 -type proxyDeployer struct { - deployer deployer.Deployer - deployCertificate string - deployPrivateKey string +type deployerImpl struct { + provider deployer.Deployer + certPEM string + privkeyPEM string } -func (d *proxyDeployer) SetLogger(logger *slog.Logger) { - if logger == nil { - panic("logger is nil") - } +var _ Deployer = (*deployerImpl)(nil) - d.deployer.WithLogger(logger) -} - -func (d *proxyDeployer) Deploy(ctx context.Context) error { - _, err := d.deployer.Deploy(ctx, d.deployCertificate, d.deployPrivateKey) +func (d *deployerImpl) Deploy(ctx context.Context) error { + _, err := d.provider.Deploy(ctx, d.certPEM, d.privkeyPEM) return err } diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 6b6daea1..2e7bcbcf 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -2,6 +2,7 @@ package deployer import ( "fmt" + "net/http" "strings" "github.com/usual2970/certimate/internal/domain" @@ -78,17 +79,24 @@ import ( 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" + httputil "github.com/usual2970/certimate/internal/pkg/utils/http" maputil "github.com/usual2970/certimate/internal/pkg/utils/map" sliceutil "github.com/usual2970/certimate/internal/pkg/utils/slice" ) -func createDeployer(options *deployerOptions) (deployer.Deployer, error) { +type deployerProviderOptions struct { + Provider domain.DeploymentProviderType + ProviderAccessConfig map[string]any + ProviderExtendedConfig map[string]any +} + +func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer, error) { /* 注意:如果追加新的常量值,请保持以 ASCII 排序。 NOTICE: If you add new constant, please keep ASCII order. */ switch options.Provider { - case domain.DeployProviderType1PanelConsole, domain.DeployProviderType1PanelSite: + case domain.DeploymentProviderType1PanelConsole, domain.DeploymentProviderType1PanelSite: { access := domain.AccessConfigFor1Panel{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -96,23 +104,23 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderType1PanelConsole: + case domain.DeploymentProviderType1PanelConsole: deployer, err := p1PanelConsole.NewDeployer(&p1PanelConsole.DeployerConfig{ ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - AutoRestart: maputil.GetBool(options.ProviderDeployConfig, "autoRestart"), + AutoRestart: maputil.GetBool(options.ProviderExtendedConfig, "autoRestart"), }) return deployer, err - case domain.DeployProviderType1PanelSite: + case domain.DeploymentProviderType1PanelSite: deployer, err := p1PanelSite.NewDeployer(&p1PanelSite.DeployerConfig{ ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - ResourceType: p1PanelSite.ResourceType(maputil.GetOrDefaultString(options.ProviderDeployConfig, "resourceType", string(p1PanelSite.RESOURCE_TYPE_WEBSITE))), - WebsiteId: maputil.GetInt64(options.ProviderDeployConfig, "websiteId"), - CertificateId: maputil.GetInt64(options.ProviderDeployConfig, "certificateId"), + ResourceType: p1PanelSite.ResourceType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "resourceType", string(p1PanelSite.RESOURCE_TYPE_WEBSITE))), + WebsiteId: maputil.GetInt64(options.ProviderExtendedConfig, "websiteId"), + CertificateId: maputil.GetInt64(options.ProviderExtendedConfig, "certificateId"), }) return deployer, err @@ -121,7 +129,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunAPIGW, domain.DeployProviderTypeAliyunCAS, domain.DeployProviderTypeAliyunCASDeploy, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunESA, domain.DeployProviderTypeAliyunFC, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunVOD, domain.DeployProviderTypeAliyunWAF: + case domain.DeploymentProviderTypeAliyunALB, domain.DeploymentProviderTypeAliyunAPIGW, domain.DeploymentProviderTypeAliyunCAS, domain.DeploymentProviderTypeAliyunCASDeploy, domain.DeploymentProviderTypeAliyunCDN, domain.DeploymentProviderTypeAliyunCLB, domain.DeploymentProviderTypeAliyunDCDN, domain.DeploymentProviderTypeAliyunESA, domain.DeploymentProviderTypeAliyunFC, domain.DeploymentProviderTypeAliyunLive, domain.DeploymentProviderTypeAliyunNLB, domain.DeploymentProviderTypeAliyunOSS, domain.DeploymentProviderTypeAliyunVOD, domain.DeploymentProviderTypeAliyunWAF: { access := domain.AccessConfigForAliyun{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -129,142 +137,142 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeAliyunALB: + case domain.DeploymentProviderTypeAliyunALB: deployer, err := pAliyunALB.NewDeployer(&pAliyunALB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pAliyunALB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderDeployConfig, "listenerId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pAliyunALB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeAliyunAPIGW: + case domain.DeploymentProviderTypeAliyunAPIGW: deployer, err := pAliyunAPIGW.NewDeployer(&pAliyunAPIGW.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ServiceType: pAliyunAPIGW.ServiceType(maputil.GetString(options.ProviderDeployConfig, "serviceType")), - GatewayId: maputil.GetString(options.ProviderDeployConfig, "gatewayId"), - GroupId: maputil.GetString(options.ProviderDeployConfig, "groupId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ServiceType: pAliyunAPIGW.ServiceType(maputil.GetString(options.ProviderExtendedConfig, "serviceType")), + GatewayId: maputil.GetString(options.ProviderExtendedConfig, "gatewayId"), + GroupId: maputil.GetString(options.ProviderExtendedConfig, "groupId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeAliyunCAS: + case domain.DeploymentProviderTypeAliyunCAS: deployer, err := pAliyunCAS.NewDeployer(&pAliyunCAS.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), }) return deployer, err - case domain.DeployProviderTypeAliyunCASDeploy: + case domain.DeploymentProviderTypeAliyunCASDeploy: deployer, err := pAliyunCASDeploy.NewDeployer(&pAliyunCASDeploy.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderDeployConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), - ContactIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderDeployConfig, "contactIds"), ";"), func(s string) bool { return s != "" }), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), + ContactIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "contactIds"), ";"), func(s string) bool { return s != "" }), }) return deployer, err - case domain.DeployProviderTypeAliyunCDN: + case domain.DeploymentProviderTypeAliyunCDN: deployer, err := pAliyunCDN.NewDeployer(&pAliyunCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeAliyunCLB: + case domain.DeploymentProviderTypeAliyunCLB: deployer, err := pAliyunCLB.NewDeployer(&pAliyunCLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pAliyunCLB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerPort: maputil.GetOrDefaultInt32(options.ProviderDeployConfig, "listenerPort", 443), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pAliyunCLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerPort: maputil.GetOrDefaultInt32(options.ProviderExtendedConfig, "listenerPort", 443), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeAliyunDCDN: + case domain.DeploymentProviderTypeAliyunDCDN: deployer, err := pAliyunDCDN.NewDeployer(&pAliyunDCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeAliyunESA: + case domain.DeploymentProviderTypeAliyunESA: deployer, err := pAliyunESA.NewDeployer(&pAliyunESA.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - SiteId: maputil.GetInt64(options.ProviderDeployConfig, "siteId"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + SiteId: maputil.GetInt64(options.ProviderExtendedConfig, "siteId"), }) return deployer, err - case domain.DeployProviderTypeAliyunFC: + case domain.DeploymentProviderTypeAliyunFC: deployer, err := pAliyunFC.NewDeployer(&pAliyunFC.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ServiceVersion: maputil.GetOrDefaultString(options.ProviderDeployConfig, "serviceVersion", "3.0"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ServiceVersion: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "serviceVersion", "3.0"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeAliyunLive: + case domain.DeploymentProviderTypeAliyunLive: deployer, err := pAliyunLive.NewDeployer(&pAliyunLive.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeAliyunNLB: + case domain.DeploymentProviderTypeAliyunNLB: deployer, err := pAliyunNLB.NewDeployer(&pAliyunNLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pAliyunNLB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderDeployConfig, "listenerId"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pAliyunNLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), }) return deployer, err - case domain.DeployProviderTypeAliyunOSS: + case domain.DeploymentProviderTypeAliyunOSS: deployer, err := pAliyunOSS.NewDeployer(&pAliyunOSS.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Bucket: maputil.GetString(options.ProviderDeployConfig, "bucket"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeAliyunVOD: + case domain.DeploymentProviderTypeAliyunVOD: deployer, err := pAliyunVOD.NewDeployer(&pAliyunVOD.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeAliyunWAF: + case domain.DeploymentProviderTypeAliyunWAF: deployer, err := pAliyunWAF.NewDeployer(&pAliyunWAF.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ServiceVersion: maputil.GetOrDefaultString(options.ProviderDeployConfig, "serviceVersion", "3.0"), - InstanceId: maputil.GetString(options.ProviderDeployConfig, "instanceId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ServiceVersion: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "serviceVersion", "3.0"), + InstanceId: maputil.GetString(options.ProviderExtendedConfig, "instanceId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -273,7 +281,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeAWSACM, domain.DeployProviderTypeAWSCloudFront: + case domain.DeploymentProviderTypeAWSACM, domain.DeploymentProviderTypeAWSCloudFront: { access := domain.AccessConfigForAWS{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -281,20 +289,20 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeAWSACM: + case domain.DeploymentProviderTypeAWSACM: deployer, err := pAWSACM.NewDeployer(&pAWSACM.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), }) return deployer, err - case domain.DeployProviderTypeAWSCloudFront: + case domain.DeploymentProviderTypeAWSCloudFront: deployer, err := pAWSCloudFront.NewDeployer(&pAWSCloudFront.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - DistributionId: maputil.GetString(options.ProviderDeployConfig, "distributionId"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + DistributionId: maputil.GetString(options.ProviderExtendedConfig, "distributionId"), }) return deployer, err @@ -303,7 +311,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeAzureKeyVault: + case domain.DeploymentProviderTypeAzureKeyVault: { access := domain.AccessConfigForAzure{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -311,14 +319,14 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeAzureKeyVault: + case domain.DeploymentProviderTypeAzureKeyVault: deployer, err := pAzureKeyVault.NewDeployer(&pAzureKeyVault.DeployerConfig{ TenantId: access.TenantId, ClientId: access.ClientId, ClientSecret: access.ClientSecret, CloudName: access.CloudName, - KeyVaultName: maputil.GetString(options.ProviderDeployConfig, "keyvaultName"), - CertificateName: maputil.GetString(options.ProviderDeployConfig, "certificateName"), + KeyVaultName: maputil.GetString(options.ProviderExtendedConfig, "keyvaultName"), + CertificateName: maputil.GetString(options.ProviderExtendedConfig, "certificateName"), }) return deployer, err @@ -327,7 +335,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeBaiduCloudAppBLB, domain.DeployProviderTypeBaiduCloudBLB, domain.DeployProviderTypeBaiduCloudCDN, domain.DeployProviderTypeBaiduCloudCert: + case domain.DeploymentProviderTypeBaiduCloudAppBLB, domain.DeploymentProviderTypeBaiduCloudBLB, domain.DeploymentProviderTypeBaiduCloudCDN, domain.DeploymentProviderTypeBaiduCloudCert: { access := domain.AccessConfigForBaiduCloud{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -335,39 +343,39 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeBaiduCloudAppBLB: + case domain.DeploymentProviderTypeBaiduCloudAppBLB: deployer, err := pBaiduCloudAppBLB.NewDeployer(&pBaiduCloudAppBLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pBaiduCloudAppBLB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerPort: maputil.GetInt32(options.ProviderDeployConfig, "listenerPort"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pBaiduCloudAppBLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerPort: maputil.GetInt32(options.ProviderExtendedConfig, "listenerPort"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeBaiduCloudBLB: + case domain.DeploymentProviderTypeBaiduCloudBLB: deployer, err := pBaiduCloudBLB.NewDeployer(&pBaiduCloudBLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pBaiduCloudBLB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerPort: maputil.GetInt32(options.ProviderDeployConfig, "listenerPort"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pBaiduCloudBLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerPort: maputil.GetInt32(options.ProviderExtendedConfig, "listenerPort"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeBaiduCloudCDN: + case domain.DeploymentProviderTypeBaiduCloudCDN: deployer, err := pBaiduCloudCDN.NewDeployer(&pBaiduCloudCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeBaiduCloudCert: + case domain.DeploymentProviderTypeBaiduCloudCert: deployer, err := pBaiduCloudCert.NewDeployer(&pBaiduCloudCert.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, @@ -379,7 +387,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeBaishanCDN: + case domain.DeploymentProviderTypeBaishanCDN: { access := domain.AccessConfigForBaishan{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -387,11 +395,11 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeBaishanCDN: + case domain.DeploymentProviderTypeBaishanCDN: deployer, err := pBaishanCDN.NewDeployer(&pBaishanCDN.DeployerConfig{ ApiToken: access.ApiToken, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), - CertificateId: maputil.GetString(options.ProviderDeployConfig, "certificateId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), }) return deployer, err @@ -400,7 +408,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeBaotaPanelConsole, domain.DeployProviderTypeBaotaPanelSite: + case domain.DeploymentProviderTypeBaotaPanelConsole, domain.DeploymentProviderTypeBaotaPanelSite: { access := domain.AccessConfigForBaotaPanel{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -408,23 +416,23 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeBaotaPanelConsole: + case domain.DeploymentProviderTypeBaotaPanelConsole: deployer, err := pBaotaPanelConsole.NewDeployer(&pBaotaPanelConsole.DeployerConfig{ ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - AutoRestart: maputil.GetBool(options.ProviderDeployConfig, "autoRestart"), + AutoRestart: maputil.GetBool(options.ProviderExtendedConfig, "autoRestart"), }) return deployer, err - case domain.DeployProviderTypeBaotaPanelSite: + case domain.DeploymentProviderTypeBaotaPanelSite: deployer, err := pBaotaPanelSite.NewDeployer(&pBaotaPanelSite.DeployerConfig{ ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, - SiteType: maputil.GetOrDefaultString(options.ProviderDeployConfig, "siteType", "other"), - SiteName: maputil.GetString(options.ProviderDeployConfig, "siteName"), - SiteNames: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderDeployConfig, "siteNames"), ";"), func(s string) bool { return s != "" }), + SiteType: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "siteType", "other"), + SiteName: maputil.GetString(options.ProviderExtendedConfig, "siteName"), + SiteNames: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "siteNames"), ";"), func(s string) bool { return s != "" }), }) return deployer, err @@ -433,7 +441,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeBunnyCDN: + case domain.DeploymentProviderTypeBunnyCDN: { access := domain.AccessConfigForBunny{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -442,13 +450,13 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { deployer, err := pBunnyCDN.NewDeployer(&pBunnyCDN.DeployerConfig{ ApiKey: access.ApiKey, - PullZoneId: maputil.GetString(options.ProviderDeployConfig, "pullZoneId"), - Hostname: maputil.GetString(options.ProviderDeployConfig, "hostname"), + PullZoneId: maputil.GetString(options.ProviderExtendedConfig, "pullZoneId"), + Hostname: maputil.GetString(options.ProviderExtendedConfig, "hostname"), }) return deployer, err } - case domain.DeployProviderTypeBytePlusCDN: + case domain.DeploymentProviderTypeBytePlusCDN: { access := domain.AccessConfigForBytePlus{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -456,11 +464,11 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeBytePlusCDN: + case domain.DeploymentProviderTypeBytePlusCDN: deployer, err := pBytePlusCDN.NewDeployer(&pBytePlusCDN.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -469,7 +477,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeCacheFly: + case domain.DeploymentProviderTypeCacheFly: { access := domain.AccessConfigForCacheFly{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -482,7 +490,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { return deployer, err } - case domain.DeployProviderTypeCdnfly: + case domain.DeploymentProviderTypeCdnfly: { access := domain.AccessConfigForCdnfly{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -493,14 +501,14 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { ApiUrl: access.ApiUrl, ApiKey: access.ApiKey, ApiSecret: access.ApiSecret, - ResourceType: pCdnfly.ResourceType(maputil.GetOrDefaultString(options.ProviderDeployConfig, "resourceType", string(pCdnfly.RESOURCE_TYPE_SITE))), - SiteId: maputil.GetString(options.ProviderDeployConfig, "siteId"), - CertificateId: maputil.GetString(options.ProviderDeployConfig, "certificateId"), + ResourceType: pCdnfly.ResourceType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "resourceType", string(pCdnfly.RESOURCE_TYPE_SITE))), + SiteId: maputil.GetString(options.ProviderExtendedConfig, "siteId"), + CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), }) return deployer, err } - case domain.DeployProviderTypeDogeCloudCDN: + case domain.DeploymentProviderTypeDogeCloudCDN: { access := domain.AccessConfigForDogeCloud{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -510,12 +518,12 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { deployer, err := pDogeCDN.NewDeployer(&pDogeCDN.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err } - case domain.DeployProviderTypeEdgioApplications: + case domain.DeploymentProviderTypeEdgioApplications: { access := domain.AccessConfigForEdgio{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -525,12 +533,12 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { deployer, err := pEdgioApplications.NewDeployer(&pEdgioApplications.DeployerConfig{ ClientId: access.ClientId, ClientSecret: access.ClientSecret, - EnvironmentId: maputil.GetString(options.ProviderDeployConfig, "environmentId"), + EnvironmentId: maputil.GetString(options.ProviderExtendedConfig, "environmentId"), }) return deployer, err } - case domain.DeployProviderTypeGcoreCDN: + case domain.DeploymentProviderTypeGcoreCDN: { access := domain.AccessConfigForGcore{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -538,10 +546,10 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeGcoreCDN: + case domain.DeploymentProviderTypeGcoreCDN: deployer, err := pGcoreCDN.NewDeployer(&pGcoreCDN.DeployerConfig{ ApiToken: access.ApiToken, - ResourceId: maputil.GetInt64(options.ProviderDeployConfig, "resourceId"), + ResourceId: maputil.GetInt64(options.ProviderExtendedConfig, "resourceId"), }) return deployer, err @@ -550,7 +558,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeHuaweiCloudCDN, domain.DeployProviderTypeHuaweiCloudELB, domain.DeployProviderTypeHuaweiCloudSCM, domain.DeployProviderTypeHuaweiCloudWAF: + case domain.DeploymentProviderTypeHuaweiCloudCDN, domain.DeploymentProviderTypeHuaweiCloudELB, domain.DeploymentProviderTypeHuaweiCloudSCM, domain.DeploymentProviderTypeHuaweiCloudWAF: { access := domain.AccessConfigForHuaweiCloud{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -558,42 +566,42 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeHuaweiCloudCDN: + case domain.DeploymentProviderTypeHuaweiCloudCDN: deployer, err := pHuaweiCloudCDN.NewDeployer(&pHuaweiCloudCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeHuaweiCloudELB: + case domain.DeploymentProviderTypeHuaweiCloudELB: deployer, err := pHuaweiCloudELB.NewDeployer(&pHuaweiCloudELB.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pHuaweiCloudELB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - CertificateId: maputil.GetString(options.ProviderDeployConfig, "certificateId"), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderDeployConfig, "listenerId"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pHuaweiCloudELB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), }) return deployer, err - case domain.DeployProviderTypeHuaweiCloudSCM: + case domain.DeploymentProviderTypeHuaweiCloudSCM: deployer, err := pHuaweiCloudSCM.NewDeployer(&pHuaweiCloudSCM.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, }) return deployer, err - case domain.DeployProviderTypeHuaweiCloudWAF: + case domain.DeploymentProviderTypeHuaweiCloudWAF: deployer, err := pHuaweiCloudWAF.NewDeployer(&pHuaweiCloudWAF.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pHuaweiCloudWAF.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - CertificateId: maputil.GetString(options.ProviderDeployConfig, "certificateId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pHuaweiCloudWAF.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -602,7 +610,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeJDCloudALB, domain.DeployProviderTypeJDCloudCDN, domain.DeployProviderTypeJDCloudLive, domain.DeployProviderTypeJDCloudVOD: + case domain.DeploymentProviderTypeJDCloudALB, domain.DeploymentProviderTypeJDCloudCDN, domain.DeploymentProviderTypeJDCloudLive, domain.DeploymentProviderTypeJDCloudVOD: { access := domain.AccessConfigForJDCloud{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -610,38 +618,38 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeJDCloudALB: + case domain.DeploymentProviderTypeJDCloudALB: deployer, err := pJDCloudALB.NewDeployer(&pJDCloudALB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - RegionId: maputil.GetString(options.ProviderDeployConfig, "regionId"), - ResourceType: pJDCloudALB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderDeployConfig, "listenerId"), + RegionId: maputil.GetString(options.ProviderExtendedConfig, "regionId"), + ResourceType: pJDCloudALB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), }) return deployer, err - case domain.DeployProviderTypeJDCloudCDN: + case domain.DeploymentProviderTypeJDCloudCDN: deployer, err := pJDCloudCDN.NewDeployer(&pJDCloudCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeJDCloudLive: + case domain.DeploymentProviderTypeJDCloudLive: deployer, err := pJDCloudLive.NewDeployer(&pJDCloudLive.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeJDCloudVOD: + case domain.DeploymentProviderTypeJDCloudVOD: deployer, err := pJDCloudVOD.NewDeployer(&pJDCloudVOD.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -650,24 +658,24 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeLocal: + case domain.DeploymentProviderTypeLocal: { deployer, err := pLocal.NewDeployer(&pLocal.DeployerConfig{ - ShellEnv: pLocal.ShellEnvType(maputil.GetString(options.ProviderDeployConfig, "shellEnv")), - PreCommand: maputil.GetString(options.ProviderDeployConfig, "preCommand"), - PostCommand: maputil.GetString(options.ProviderDeployConfig, "postCommand"), - OutputFormat: pLocal.OutputFormatType(maputil.GetOrDefaultString(options.ProviderDeployConfig, "format", string(pLocal.OUTPUT_FORMAT_PEM))), - OutputCertPath: maputil.GetString(options.ProviderDeployConfig, "certPath"), - OutputKeyPath: maputil.GetString(options.ProviderDeployConfig, "keyPath"), - PfxPassword: maputil.GetString(options.ProviderDeployConfig, "pfxPassword"), - JksAlias: maputil.GetString(options.ProviderDeployConfig, "jksAlias"), - JksKeypass: maputil.GetString(options.ProviderDeployConfig, "jksKeypass"), - JksStorepass: maputil.GetString(options.ProviderDeployConfig, "jksStorepass"), + ShellEnv: pLocal.ShellEnvType(maputil.GetString(options.ProviderExtendedConfig, "shellEnv")), + PreCommand: maputil.GetString(options.ProviderExtendedConfig, "preCommand"), + PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"), + OutputFormat: pLocal.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pLocal.OUTPUT_FORMAT_PEM))), + OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"), + OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"), + PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"), + JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"), + JksKeypass: maputil.GetString(options.ProviderExtendedConfig, "jksKeypass"), + JksStorepass: maputil.GetString(options.ProviderExtendedConfig, "jksStorepass"), }) return deployer, err } - case domain.DeployProviderTypeKubernetesSecret: + case domain.DeploymentProviderTypeKubernetesSecret: { access := domain.AccessConfigForKubernetes{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -676,16 +684,16 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { deployer, err := pK8sSecret.NewDeployer(&pK8sSecret.DeployerConfig{ KubeConfig: access.KubeConfig, - Namespace: maputil.GetOrDefaultString(options.ProviderDeployConfig, "namespace", "default"), - SecretName: maputil.GetString(options.ProviderDeployConfig, "secretName"), - SecretType: maputil.GetOrDefaultString(options.ProviderDeployConfig, "secretType", "kubernetes.io/tls"), - SecretDataKeyForCrt: maputil.GetOrDefaultString(options.ProviderDeployConfig, "secretDataKeyForCrt", "tls.crt"), - SecretDataKeyForKey: maputil.GetOrDefaultString(options.ProviderDeployConfig, "secretDataKeyForKey", "tls.key"), + Namespace: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "namespace", "default"), + SecretName: maputil.GetString(options.ProviderExtendedConfig, "secretName"), + SecretType: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "secretType", "kubernetes.io/tls"), + SecretDataKeyForCrt: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForCrt", "tls.crt"), + SecretDataKeyForKey: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForKey", "tls.key"), }) return deployer, err } - case domain.DeployProviderTypeQiniuCDN, domain.DeployProviderTypeQiniuKodo, domain.DeployProviderTypeQiniuPili: + case domain.DeploymentProviderTypeQiniuCDN, domain.DeploymentProviderTypeQiniuKodo, domain.DeploymentProviderTypeQiniuPili: { access := domain.AccessConfigForQiniu{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -693,20 +701,20 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeQiniuCDN, domain.DeployProviderTypeQiniuKodo: + case domain.DeploymentProviderTypeQiniuCDN, domain.DeploymentProviderTypeQiniuKodo: deployer, err := pQiniuCDN.NewDeployer(&pQiniuCDN.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeQiniuPili: + case domain.DeploymentProviderTypeQiniuPili: deployer, err := pQiniuPili.NewDeployer(&pQiniuPili.DeployerConfig{ AccessKey: access.AccessKey, SecretKey: access.SecretKey, - Hub: maputil.GetString(options.ProviderDeployConfig, "hub"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Hub: maputil.GetString(options.ProviderExtendedConfig, "hub"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -715,7 +723,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeRainYunRCDN: + case domain.DeploymentProviderTypeRainYunRCDN: { access := domain.AccessConfigForRainYun{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -723,11 +731,11 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeTencentCloudCDN: + case domain.DeploymentProviderTypeTencentCloudCDN: deployer, err := pRainYunRCDN.NewDeployer(&pRainYunRCDN.DeployerConfig{ ApiKey: access.ApiKey, - InstanceId: maputil.GetInt32(options.ProviderDeployConfig, "instanceId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + InstanceId: maputil.GetInt32(options.ProviderExtendedConfig, "instanceId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -736,7 +744,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeSafeLine: + case domain.DeploymentProviderTypeSafeLine: { access := domain.AccessConfigForSafeLine{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -747,13 +755,13 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { ApiUrl: access.ApiUrl, ApiToken: access.ApiToken, AllowInsecureConnections: access.AllowInsecureConnections, - ResourceType: pSafeLine.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - CertificateId: maputil.GetInt32(options.ProviderDeployConfig, "certificateId"), + ResourceType: pSafeLine.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + CertificateId: maputil.GetInt32(options.ProviderExtendedConfig, "certificateId"), }) return deployer, err } - case domain.DeployProviderTypeSSH: + case domain.DeploymentProviderTypeSSH: { access := domain.AccessConfigForSSH{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -767,21 +775,21 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { SshPassword: access.Password, SshKey: access.Key, SshKeyPassphrase: access.KeyPassphrase, - UseSCP: maputil.GetBool(options.ProviderDeployConfig, "useSCP"), - PreCommand: maputil.GetString(options.ProviderDeployConfig, "preCommand"), - PostCommand: maputil.GetString(options.ProviderDeployConfig, "postCommand"), - OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderDeployConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))), - OutputCertPath: maputil.GetString(options.ProviderDeployConfig, "certPath"), - OutputKeyPath: maputil.GetString(options.ProviderDeployConfig, "keyPath"), - PfxPassword: maputil.GetString(options.ProviderDeployConfig, "pfxPassword"), - JksAlias: maputil.GetString(options.ProviderDeployConfig, "jksAlias"), - JksKeypass: maputil.GetString(options.ProviderDeployConfig, "jksKeypass"), - JksStorepass: maputil.GetString(options.ProviderDeployConfig, "jksStorepass"), + UseSCP: maputil.GetBool(options.ProviderExtendedConfig, "useSCP"), + PreCommand: maputil.GetString(options.ProviderExtendedConfig, "preCommand"), + PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"), + OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))), + OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"), + OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"), + PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"), + JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"), + JksKeypass: maputil.GetString(options.ProviderExtendedConfig, "jksKeypass"), + JksStorepass: maputil.GetString(options.ProviderExtendedConfig, "jksStorepass"), }) return deployer, err } - case domain.DeployProviderTypeTencentCloudCDN, domain.DeployProviderTypeTencentCloudCLB, domain.DeployProviderTypeTencentCloudCOS, domain.DeployProviderTypeTencentCloudCSS, domain.DeployProviderTypeTencentCloudECDN, domain.DeployProviderTypeTencentCloudEO, domain.DeployProviderTypeTencentCloudSCF, domain.DeployProviderTypeTencentCloudSSL, domain.DeployProviderTypeTencentCloudSSLDeploy, domain.DeployProviderTypeTencentCloudVOD, domain.DeployProviderTypeTencentCloudWAF: + case domain.DeploymentProviderTypeTencentCloudCDN, domain.DeploymentProviderTypeTencentCloudCLB, domain.DeploymentProviderTypeTencentCloudCOS, domain.DeploymentProviderTypeTencentCloudCSS, domain.DeploymentProviderTypeTencentCloudECDN, domain.DeploymentProviderTypeTencentCloudEO, domain.DeploymentProviderTypeTencentCloudSCF, domain.DeploymentProviderTypeTencentCloudSSL, domain.DeploymentProviderTypeTencentCloudSSLDeploy, domain.DeploymentProviderTypeTencentCloudVOD, domain.DeploymentProviderTypeTencentCloudWAF: { access := domain.AccessConfigForTencentCloud{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -789,103 +797,103 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeTencentCloudCDN: + case domain.DeploymentProviderTypeTencentCloudCDN: deployer, err := pTencentCloudCDN.NewDeployer(&pTencentCloudCDN.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeTencentCloudCLB: + case domain.DeploymentProviderTypeTencentCloudCLB: deployer, err := pTencentCloudCLB.NewDeployer(&pTencentCloudCLB.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pTencentCloudCLB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderDeployConfig, "listenerId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pTencentCloudCLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeTencentCloudCOS: + case domain.DeploymentProviderTypeTencentCloudCOS: deployer, err := pTencentCloudCOS.NewDeployer(&pTencentCloudCOS.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Bucket: maputil.GetString(options.ProviderDeployConfig, "bucket"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeTencentCloudCSS: + case domain.DeploymentProviderTypeTencentCloudCSS: deployer, err := pTencentCloudCSS.NewDeployer(&pTencentCloudCSS.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeTencentCloudECDN: + case domain.DeploymentProviderTypeTencentCloudECDN: deployer, err := pTencentCloudECDN.NewDeployer(&pTencentCloudECDN.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeTencentCloudEO: + case domain.DeploymentProviderTypeTencentCloudEO: deployer, err := pTencentCloudEO.NewDeployer(&pTencentCloudEO.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - ZoneId: maputil.GetString(options.ProviderDeployConfig, "zoneId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + ZoneId: maputil.GetString(options.ProviderExtendedConfig, "zoneId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeTencentCloudSCF: + case domain.DeploymentProviderTypeTencentCloudSCF: deployer, err := pTencentCloudSCF.NewDeployer(&pTencentCloudSCF.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeTencentCloudSSL: + case domain.DeploymentProviderTypeTencentCloudSSL: deployer, err := pTencentCloudSSL.NewDeployer(&pTencentCloudSSL.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, }) return deployer, err - case domain.DeployProviderTypeTencentCloudSSLDeploy: + case domain.DeploymentProviderTypeTencentCloudSSLDeploy: deployer, err := pTencentCloudSSLDeploy.NewDeployer(&pTencentCloudSSLDeploy.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: maputil.GetString(options.ProviderDeployConfig, "resourceType"), - ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderDeployConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: maputil.GetString(options.ProviderExtendedConfig, "resourceType"), + ResourceIds: sliceutil.Filter(strings.Split(maputil.GetString(options.ProviderExtendedConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), }) return deployer, err - case domain.DeployProviderTypeTencentCloudVOD: + case domain.DeploymentProviderTypeTencentCloudVOD: deployer, err := pTencentCloudVOD.NewDeployer(&pTencentCloudVOD.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - SubAppId: maputil.GetInt64(options.ProviderDeployConfig, "subAppId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + SubAppId: maputil.GetInt64(options.ProviderExtendedConfig, "subAppId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeTencentCloudWAF: + case domain.DeploymentProviderTypeTencentCloudWAF: deployer, err := pTencentCloudWAF.NewDeployer(&pTencentCloudWAF.DeployerConfig{ SecretId: access.SecretId, SecretKey: access.SecretKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), - DomainId: maputil.GetString(options.ProviderDeployConfig, "domainId"), - InstanceId: maputil.GetString(options.ProviderDeployConfig, "instanceId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + DomainId: maputil.GetString(options.ProviderExtendedConfig, "domainId"), + InstanceId: maputil.GetString(options.ProviderExtendedConfig, "instanceId"), }) return deployer, err @@ -894,7 +902,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeUCloudUCDN, domain.DeployProviderTypeUCloudUS3: + case domain.DeploymentProviderTypeUCloudUCDN, domain.DeploymentProviderTypeUCloudUS3: { access := domain.AccessConfigForUCloud{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -902,23 +910,23 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeUCloudUCDN: + case domain.DeploymentProviderTypeUCloudUCDN: deployer, err := pUCloudUCDN.NewDeployer(&pUCloudUCDN.DeployerConfig{ PrivateKey: access.PrivateKey, PublicKey: access.PublicKey, ProjectId: access.ProjectId, - DomainId: maputil.GetString(options.ProviderDeployConfig, "domainId"), + DomainId: maputil.GetString(options.ProviderExtendedConfig, "domainId"), }) return deployer, err - case domain.DeployProviderTypeUCloudUS3: + case domain.DeploymentProviderTypeUCloudUS3: deployer, err := pUCloudUS3.NewDeployer(&pUCloudUS3.DeployerConfig{ PrivateKey: access.PrivateKey, PublicKey: access.PublicKey, ProjectId: access.ProjectId, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Bucket: maputil.GetString(options.ProviderDeployConfig, "bucket"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -927,7 +935,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeUpyunCDN, domain.DeployProviderTypeUpyunFile: + case domain.DeploymentProviderTypeUpyunCDN, domain.DeploymentProviderTypeUpyunFile: { access := domain.AccessConfigForUpyun{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -935,11 +943,11 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeUpyunCDN, domain.DeployProviderTypeUpyunFile: + case domain.DeploymentProviderTypeUpyunCDN, domain.DeploymentProviderTypeUpyunFile: deployer, err := pUpyunCDN.NewDeployer(&pUpyunCDN.DeployerConfig{ Username: access.Username, Password: access.Password, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -948,7 +956,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeVolcEngineALB, domain.DeployProviderTypeVolcEngineCDN, domain.DeployProviderTypeVolcEngineCertCenter, domain.DeployProviderTypeVolcEngineCLB, domain.DeployProviderTypeVolcEngineDCDN, domain.DeployProviderTypeVolcEngineImageX, domain.DeployProviderTypeVolcEngineLive, domain.DeployProviderTypeVolcEngineTOS: + case domain.DeploymentProviderTypeVolcEngineALB, domain.DeploymentProviderTypeVolcEngineCDN, domain.DeploymentProviderTypeVolcEngineCertCenter, domain.DeploymentProviderTypeVolcEngineCLB, domain.DeploymentProviderTypeVolcEngineDCDN, domain.DeploymentProviderTypeVolcEngineImageX, domain.DeploymentProviderTypeVolcEngineLive, domain.DeploymentProviderTypeVolcEngineTOS: { access := domain.AccessConfigForVolcEngine{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -956,78 +964,78 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeVolcEngineALB: + case domain.DeploymentProviderTypeVolcEngineALB: deployer, err := pVolcEngineALB.NewDeployer(&pVolcEngineALB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pVolcEngineALB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderDeployConfig, "listenerId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pVolcEngineALB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeVolcEngineCDN: + case domain.DeploymentProviderTypeVolcEngineCDN: deployer, err := pVolcEngineCDN.NewDeployer(&pVolcEngineCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeVolcEngineCertCenter: + case domain.DeploymentProviderTypeVolcEngineCertCenter: deployer, err := pVolcEngineCertCenter.NewDeployer(&pVolcEngineCertCenter.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), }) return deployer, err - case domain.DeployProviderTypeVolcEngineCLB: + case domain.DeploymentProviderTypeVolcEngineCLB: deployer, err := pVolcEngineCLB.NewDeployer(&pVolcEngineCLB.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ResourceType: pVolcEngineCLB.ResourceType(maputil.GetString(options.ProviderDeployConfig, "resourceType")), - LoadbalancerId: maputil.GetString(options.ProviderDeployConfig, "loadbalancerId"), - ListenerId: maputil.GetString(options.ProviderDeployConfig, "listenerId"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ResourceType: pVolcEngineCLB.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + LoadbalancerId: maputil.GetString(options.ProviderExtendedConfig, "loadbalancerId"), + ListenerId: maputil.GetString(options.ProviderExtendedConfig, "listenerId"), }) return deployer, err - case domain.DeployProviderTypeVolcEngineDCDN: + case domain.DeploymentProviderTypeVolcEngineDCDN: deployer, err := pVolcEngineDCDN.NewDeployer(&pVolcEngineDCDN.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeVolcEngineImageX: + case domain.DeploymentProviderTypeVolcEngineImageX: deployer, err := pVolcEngineImageX.NewDeployer(&pVolcEngineImageX.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - ServiceId: maputil.GetString(options.ProviderDeployConfig, "serviceId"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + ServiceId: maputil.GetString(options.ProviderExtendedConfig, "serviceId"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeVolcEngineLive: + case domain.DeploymentProviderTypeVolcEngineLive: deployer, err := pVolcEngineLive.NewDeployer(&pVolcEngineLive.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err - case domain.DeployProviderTypeVolcEngineTOS: + case domain.DeploymentProviderTypeVolcEngineTOS: deployer, err := pVolcEngineTOS.NewDeployer(&pVolcEngineTOS.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.SecretAccessKey, - Region: maputil.GetString(options.ProviderDeployConfig, "region"), - Bucket: maputil.GetString(options.ProviderDeployConfig, "bucket"), - Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + Region: maputil.GetString(options.ProviderExtendedConfig, "region"), + Bucket: maputil.GetString(options.ProviderExtendedConfig, "bucket"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), }) return deployer, err @@ -1036,7 +1044,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeWangsuCDNPro: + case domain.DeploymentProviderTypeWangsuCDNPro: { access := domain.AccessConfigForWangsu{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -1044,15 +1052,15 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } switch options.Provider { - case domain.DeployProviderTypeWangsuCDNPro: + case domain.DeploymentProviderTypeWangsuCDNPro: deployer, err := pWangsuCDNPro.NewDeployer(&pWangsuCDNPro.DeployerConfig{ AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, ApiKey: access.ApiKey, - 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"), + Environment: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "environment", "production"), + Domain: maputil.GetString(options.ProviderExtendedConfig, "domain"), + CertificateId: maputil.GetString(options.ProviderExtendedConfig, "certificateId"), + WebhookId: maputil.GetString(options.ProviderExtendedConfig, "webhookId"), }) return deployer, err @@ -1061,16 +1069,38 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } - case domain.DeployProviderTypeWebhook: + case domain.DeploymentProviderTypeWebhook: { access := domain.AccessConfigForWebhook{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { return nil, fmt.Errorf("failed to populate provider access config: %w", err) } + mergedHeaders := make(map[string]string) + if defaultHeadersString := access.HeadersString; defaultHeadersString != "" { + h, err := httputil.ParseHeaders(defaultHeadersString) + if err != nil { + return nil, fmt.Errorf("failed to parse webhook headers: %w", err) + } + for key := range h { + mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key) + } + } + if extendedHeadersString := maputil.GetString(options.ProviderExtendedConfig, "headers"); extendedHeadersString != "" { + h, err := httputil.ParseHeaders(extendedHeadersString) + if err != nil { + return nil, fmt.Errorf("failed to parse webhook headers: %w", err) + } + for key := range h { + mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key) + } + } + deployer, err := pWebhook.NewDeployer(&pWebhook.DeployerConfig{ WebhookUrl: access.Url, - WebhookData: maputil.GetString(options.ProviderDeployConfig, "webhookData"), + WebhookData: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", access.DefaultDataForDeployment), + Method: access.Method, + Headers: mergedHeaders, AllowInsecureConnections: access.AllowInsecureConnections, }) return deployer, err diff --git a/internal/domain/access.go b/internal/domain/access.go index fdceae48..fd36417c 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -11,6 +11,7 @@ type Access struct { Name string `json:"name" db:"name"` Provider string `json:"provider" db:"provider"` Config map[string]any `json:"config" db:"config"` + Reserve string `json:"reserve,omitempty" db:"reserve"` DeletedAt *time.Time `json:"deleted" db:"deleted"` } @@ -97,6 +98,11 @@ type AccessConfigForDeSEC struct { Token string `json:"token"` } +type AccessConfigForDingTalkBot struct { + WebhookUrl string `json:"webhookUrl"` + Secret string `json:"secret"` +} + type AccessConfigForDNSLA struct { ApiId string `json:"apiId"` ApiSecret string `json:"apiSecret"` @@ -116,6 +122,16 @@ type AccessConfigForEdgio struct { ClientSecret string `json:"clientSecret"` } +type AccessConfigForEmail struct { + SmtpHost string `json:"smtpHost"` + SmtpPort int32 `json:"smtpPort"` + SmtpTls bool `json:"smtpTls"` + Username string `json:"username"` + Password string `json:"password"` + DefaultSenderAddress string `json:"defaultSenderAddress,omitempty"` + DefaultReceiverAddress string `json:"defaultReceiverAddress,omitempty"` +} + type AccessConfigForGcore struct { ApiToken string `json:"apiToken"` } @@ -149,6 +165,17 @@ type AccessConfigForKubernetes struct { KubeConfig string `json:"kubeConfig,omitempty"` } +type AccessConfigForLarkBot struct { + WebhookUrl string `json:"webhookUrl"` +} + +type AccessConfigForMattermost struct { + ServerUrl string `json:"serverUrl"` + Username string `json:"username"` + Password string `json:"password"` + DefaultChannelId string `json:"defaultChannelId,omitempty"` +} + type AccessConfigForNamecheap struct { Username string `json:"username"` ApiKey string `json:"apiKey"` @@ -206,6 +233,11 @@ type AccessConfigForSSLCom struct { EabHmacKey string `json:"eabHmacKey"` } +type AccessConfigForTelegram struct { + BotToken string `json:"botToken"` + DefaultChatId int64 `json:"defaultChatId,omitempty"` +} + type AccessConfigForTencentCloud struct { SecretId string `json:"secretId"` SecretKey string `json:"secretKey"` @@ -239,8 +271,16 @@ type AccessConfigForWangsu struct { } type AccessConfigForWebhook struct { - Url string `json:"url"` - AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + Url string `json:"url"` + Method string `json:"method,omitempty"` + HeadersString string `json:"headers,omitempty"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + DefaultDataForDeployment string `json:"defaultDataForDeployment,omitempty"` + DefaultDataForNotification string `json:"defaultDataForNotification,omitempty"` +} + +type AccessConfigForWeComBot struct { + WebhookUrl string `json:"webhookUrl"` } type AccessConfigForWestcn struct { diff --git a/internal/domain/notify.go b/internal/domain/notify.go index 6142dae5..1a244e9b 100644 --- a/internal/domain/notify.go +++ b/internal/domain/notify.go @@ -8,6 +8,7 @@ type NotifyChannelType string 注意:如果追加新的常量值,请保持以 ASCII 排序。 NOTICE: If you add new constant, please keep ASCII order. */ +// Deprecated: v0.4.x 将废弃 const ( NotifyChannelTypeBark = NotifyChannelType("bark") NotifyChannelTypeDingTalk = NotifyChannelType("dingtalk") diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 267adc12..cb2ae3c9 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -19,6 +19,7 @@ const ( AccessProviderTypeBaishan = AccessProviderType("baishan") AccessProviderTypeBaotaPanel = AccessProviderType("baotapanel") AccessProviderTypeBytePlus = AccessProviderType("byteplus") + AccessProviderTypeBunny = AccessProviderType("bunny") AccessProviderTypeBuypass = AccessProviderType("buypass") AccessProviderTypeCacheFly = AccessProviderType("cachefly") AccessProviderTypeCdnfly = AccessProviderType("cdnfly") @@ -28,10 +29,12 @@ const ( AccessProviderTypeCTCCCloud = AccessProviderType("ctcccloud") // 联通云(预留) AccessProviderTypeCUCCCloud = AccessProviderType("cucccloud") // 天翼云(预留) AccessProviderTypeDeSEC = AccessProviderType("desec") + AccessProviderTypeDingTalkBot = AccessProviderType("dingtalkbot") AccessProviderTypeDNSLA = AccessProviderType("dnsla") AccessProviderTypeDogeCloud = AccessProviderType("dogecloud") AccessProviderTypeDynv6 = AccessProviderType("dynv6") AccessProviderTypeEdgio = AccessProviderType("edgio") + AccessProviderTypeEmail = AccessProviderType("email") AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留) AccessProviderTypeGname = AccessProviderType("gname") AccessProviderTypeGcore = AccessProviderType("gcore") @@ -41,9 +44,11 @@ const ( AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud") AccessProviderTypeJDCloud = AccessProviderType("jdcloud") AccessProviderTypeKubernetes = AccessProviderType("k8s") + AccessProviderTypeLarkBot = AccessProviderType("larkbot") AccessProviderTypeLetsEncrypt = AccessProviderType("letsencrypt") AccessProviderTypeLetsEncryptStaging = AccessProviderType("letsencryptstaging") AccessProviderTypeLocal = AccessProviderType("local") + AccessProviderTypeMattermost = AccessProviderType("mattermost") AccessProviderTypeNamecheap = AccessProviderType("namecheap") AccessProviderTypeNameDotCom = AccessProviderType("namedotcom") AccessProviderTypeNameSilo = AccessProviderType("namesilo") @@ -56,6 +61,7 @@ const ( AccessProviderTypeSafeLine = AccessProviderType("safeline") AccessProviderTypeSSH = AccessProviderType("ssh") AccessProviderTypeSSLCOM = AccessProviderType("sslcom") + AccessProviderTypeTelegram = AccessProviderType("telegram") AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud") AccessProviderTypeUCloud = AccessProviderType("ucloud") AccessProviderTypeUpyun = AccessProviderType("upyun") @@ -63,78 +69,79 @@ const ( AccessProviderTypeVolcEngine = AccessProviderType("volcengine") AccessProviderTypeWangsu = AccessProviderType("wangsu") AccessProviderTypeWebhook = AccessProviderType("webhook") + AccessProviderTypeWeComBot = AccessProviderType("wecombot") AccessProviderTypeWestcn = AccessProviderType("westcn") AccessProviderTypeZeroSSL = AccessProviderType("zerossl") ) -type ApplyCAProviderType string +type CAProviderType string /* -申请证书 CA 提供商常量值。 -始终等于授权提供商类型。 - - 注意:如果追加新的常量值,请保持以 ASCII 排序。 - NOTICE: If you add new constant, please keep ASCII order. -*/ -const ( - ApplyCAProviderTypeBuypass = ApplyCAProviderType(string(AccessProviderTypeBuypass)) - ApplyCAProviderTypeGoogleTrustServices = ApplyCAProviderType(string(AccessProviderTypeGoogleTrustServices)) - ApplyCAProviderTypeLetsEncrypt = ApplyCAProviderType(string(AccessProviderTypeLetsEncrypt)) - ApplyCAProviderTypeLetsEncryptStaging = ApplyCAProviderType(string(AccessProviderTypeLetsEncryptStaging)) - ApplyCAProviderTypeSSLCom = ApplyCAProviderType(string(AccessProviderTypeSSLCOM)) - ApplyCAProviderTypeZeroSSL = ApplyCAProviderType(string(AccessProviderTypeZeroSSL)) -) - -type ApplyDNSProviderType string - -/* -申请证书 DNS 提供商常量值。 +证书颁发机构提供商常量值。 短横线前的部分始终等于授权提供商类型。 注意:如果追加新的常量值,请保持以 ASCII 排序。 NOTICE: If you add new constant, please keep ASCII order. */ const ( - ApplyDNSProviderTypeACMEHttpReq = ApplyDNSProviderType("acmehttpreq") - ApplyDNSProviderTypeAliyun = ApplyDNSProviderType("aliyun") // 兼容旧值,等同于 [ApplyDNSProviderTypeAliyunDNS] - ApplyDNSProviderTypeAliyunDNS = ApplyDNSProviderType("aliyun-dns") - ApplyDNSProviderTypeAWS = ApplyDNSProviderType("aws") // 兼容旧值,等同于 [ApplyDNSProviderTypeAWSRoute53] - ApplyDNSProviderTypeAWSRoute53 = ApplyDNSProviderType("aws-route53") - ApplyDNSProviderTypeAzure = ApplyDNSProviderType("azure") // 兼容旧值,等同于 [ApplyDNSProviderTypeAzure] - ApplyDNSProviderTypeAzureDNS = ApplyDNSProviderType("azure-dns") - ApplyDNSProviderTypeBaiduCloud = ApplyDNSProviderType("baiducloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeBaiduCloudDNS] - ApplyDNSProviderTypeBaiduCloudDNS = ApplyDNSProviderType("baiducloud-dns") - ApplyDNSProviderTypeBunny = ApplyDNSProviderType("bunny") - ApplyDNSProviderTypeCloudflare = ApplyDNSProviderType("cloudflare") - ApplyDNSProviderTypeClouDNS = ApplyDNSProviderType("cloudns") - ApplyDNSProviderTypeCMCCCloud = ApplyDNSProviderType("cmcccloud") - ApplyDNSProviderTypeDeSEC = ApplyDNSProviderType("desec") - ApplyDNSProviderTypeDNSLA = ApplyDNSProviderType("dnsla") - ApplyDNSProviderTypeDynv6 = ApplyDNSProviderType("dynv6") - ApplyDNSProviderTypeGcore = ApplyDNSProviderType("gcore") - ApplyDNSProviderTypeGname = ApplyDNSProviderType("gname") - ApplyDNSProviderTypeGoDaddy = ApplyDNSProviderType("godaddy") - ApplyDNSProviderTypeHuaweiCloud = ApplyDNSProviderType("huaweicloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeHuaweiCloudDNS] - ApplyDNSProviderTypeHuaweiCloudDNS = ApplyDNSProviderType("huaweicloud-dns") - ApplyDNSProviderTypeJDCloud = ApplyDNSProviderType("jdcloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeJDCloudDNS] - ApplyDNSProviderTypeJDCloudDNS = ApplyDNSProviderType("jdcloud-dns") - ApplyDNSProviderTypeNamecheap = ApplyDNSProviderType("namecheap") - ApplyDNSProviderTypeNameDotCom = ApplyDNSProviderType("namedotcom") - ApplyDNSProviderTypeNameSilo = ApplyDNSProviderType("namesilo") - ApplyDNSProviderTypeNS1 = ApplyDNSProviderType("ns1") - ApplyDNSProviderTypePorkbun = ApplyDNSProviderType("porkbun") - ApplyDNSProviderTypePowerDNS = ApplyDNSProviderType("powerdns") - ApplyDNSProviderTypeRainYun = ApplyDNSProviderType("rainyun") - ApplyDNSProviderTypeTencentCloud = ApplyDNSProviderType("tencentcloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeTencentCloudDNS] - ApplyDNSProviderTypeTencentCloudDNS = ApplyDNSProviderType("tencentcloud-dns") - ApplyDNSProviderTypeTencentCloudEO = ApplyDNSProviderType("tencentcloud-eo") - ApplyDNSProviderTypeVercel = ApplyDNSProviderType("vercel") - ApplyDNSProviderTypeVolcEngine = ApplyDNSProviderType("volcengine") // 兼容旧值,等同于 [ApplyDNSProviderTypeVolcEngineDNS] - ApplyDNSProviderTypeVolcEngineDNS = ApplyDNSProviderType("volcengine-dns") - ApplyDNSProviderTypeWestcn = ApplyDNSProviderType("westcn") + CAProviderTypeBuypass = CAProviderType(AccessProviderTypeBuypass) + CAProviderTypeGoogleTrustServices = CAProviderType(AccessProviderTypeGoogleTrustServices) + CAProviderTypeLetsEncrypt = CAProviderType(AccessProviderTypeLetsEncrypt) + CAProviderTypeLetsEncryptStaging = CAProviderType(AccessProviderTypeLetsEncryptStaging) + CAProviderTypeSSLCom = CAProviderType(AccessProviderTypeSSLCOM) + CAProviderTypeZeroSSL = CAProviderType(AccessProviderTypeZeroSSL) ) -type DeployProviderType string +type ACMEDns01ProviderType string + +/* +ACME DNS-01 提供商常量值。 +短横线前的部分始终等于授权提供商类型。 + + 注意:如果追加新的常量值,请保持以 ASCII 排序。 + NOTICE: If you add new constant, please keep ASCII order. +*/ +const ( + ACMEDns01ProviderTypeACMEHttpReq = ACMEDns01ProviderType(AccessProviderTypeACMEHttpReq) + ACMEDns01ProviderTypeAliyun = ACMEDns01ProviderType(AccessProviderTypeAliyun) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAliyunDNS] + ACMEDns01ProviderTypeAliyunDNS = ACMEDns01ProviderType(AccessProviderTypeAliyun + "-dns") + ACMEDns01ProviderTypeAWS = ACMEDns01ProviderType(AccessProviderTypeAWS) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAWSRoute53] + ACMEDns01ProviderTypeAWSRoute53 = ACMEDns01ProviderType(AccessProviderTypeAWS + "-route53") + ACMEDns01ProviderTypeAzure = ACMEDns01ProviderType(AccessProviderTypeAzure) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAzure] + ACMEDns01ProviderTypeAzureDNS = ACMEDns01ProviderType(AccessProviderTypeAzure + "-dns") + ACMEDns01ProviderTypeBaiduCloud = ACMEDns01ProviderType(AccessProviderTypeBaiduCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeBaiduCloudDNS] + ACMEDns01ProviderTypeBaiduCloudDNS = ACMEDns01ProviderType(AccessProviderTypeBaiduCloud + "-dns") + ACMEDns01ProviderTypeBunny = ACMEDns01ProviderType(AccessProviderTypeBunny) + ACMEDns01ProviderTypeCloudflare = ACMEDns01ProviderType(AccessProviderTypeCloudflare) + ACMEDns01ProviderTypeClouDNS = ACMEDns01ProviderType(AccessProviderTypeClouDNS) + ACMEDns01ProviderTypeCMCCCloud = ACMEDns01ProviderType(AccessProviderTypeCMCCCloud) + ACMEDns01ProviderTypeDeSEC = ACMEDns01ProviderType(AccessProviderTypeDeSEC) + ACMEDns01ProviderTypeDNSLA = ACMEDns01ProviderType(AccessProviderTypeDNSLA) + ACMEDns01ProviderTypeDynv6 = ACMEDns01ProviderType(AccessProviderTypeDynv6) + ACMEDns01ProviderTypeGcore = ACMEDns01ProviderType(AccessProviderTypeGcore) + ACMEDns01ProviderTypeGname = ACMEDns01ProviderType(AccessProviderTypeGname) + ACMEDns01ProviderTypeGoDaddy = ACMEDns01ProviderType(AccessProviderTypeGoDaddy) + ACMEDns01ProviderTypeHuaweiCloud = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeHuaweiCloudDNS] + ACMEDns01ProviderTypeHuaweiCloudDNS = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud + "-dns") + ACMEDns01ProviderTypeJDCloud = ACMEDns01ProviderType(AccessProviderTypeJDCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeJDCloudDNS] + ACMEDns01ProviderTypeJDCloudDNS = ACMEDns01ProviderType(AccessProviderTypeJDCloud + "-dns") + ACMEDns01ProviderTypeNamecheap = ACMEDns01ProviderType(AccessProviderTypeNamecheap) + ACMEDns01ProviderTypeNameDotCom = ACMEDns01ProviderType(AccessProviderTypeNameDotCom) + ACMEDns01ProviderTypeNameSilo = ACMEDns01ProviderType(AccessProviderTypeNameSilo) + ACMEDns01ProviderTypeNS1 = ACMEDns01ProviderType(AccessProviderTypeNS1) + ACMEDns01ProviderTypePorkbun = ACMEDns01ProviderType(AccessProviderTypePorkbun) + ACMEDns01ProviderTypePowerDNS = ACMEDns01ProviderType(AccessProviderTypePowerDNS) + ACMEDns01ProviderTypeRainYun = ACMEDns01ProviderType(AccessProviderTypeRainYun) + ACMEDns01ProviderTypeTencentCloud = ACMEDns01ProviderType(AccessProviderTypeTencentCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeTencentCloudDNS] + ACMEDns01ProviderTypeTencentCloudDNS = ACMEDns01ProviderType(AccessProviderTypeTencentCloud + "-dns") + ACMEDns01ProviderTypeTencentCloudEO = ACMEDns01ProviderType(AccessProviderTypeTencentCloud + "-eo") + ACMEDns01ProviderTypeVercel = ACMEDns01ProviderType(AccessProviderTypeVercel) + ACMEDns01ProviderTypeVolcEngine = ACMEDns01ProviderType(AccessProviderTypeVolcEngine) // 兼容旧值,等同于 [ACMEDns01ProviderTypeVolcEngineDNS] + ACMEDns01ProviderTypeVolcEngineDNS = ACMEDns01ProviderType(AccessProviderTypeVolcEngine + "-dns") + ACMEDns01ProviderTypeWestcn = ACMEDns01ProviderType(AccessProviderTypeWestcn) +) + +type DeploymentProviderType string /* 部署证书主机提供商常量值。 @@ -144,78 +151,97 @@ type DeployProviderType string NOTICE: If you add new constant, please keep ASCII order. */ const ( - DeployProviderType1PanelConsole = DeployProviderType("1panel-console") - DeployProviderType1PanelSite = DeployProviderType("1panel-site") - DeployProviderTypeAliyunALB = DeployProviderType("aliyun-alb") - DeployProviderTypeAliyunAPIGW = DeployProviderType("aliyun-apigw") - DeployProviderTypeAliyunCAS = DeployProviderType("aliyun-cas") - DeployProviderTypeAliyunCASDeploy = DeployProviderType("aliyun-casdeploy") - DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn") - DeployProviderTypeAliyunCLB = DeployProviderType("aliyun-clb") - DeployProviderTypeAliyunDCDN = DeployProviderType("aliyun-dcdn") - DeployProviderTypeAliyunESA = DeployProviderType("aliyun-esa") - DeployProviderTypeAliyunFC = DeployProviderType("aliyun-fc") - DeployProviderTypeAliyunLive = DeployProviderType("aliyun-live") - DeployProviderTypeAliyunNLB = DeployProviderType("aliyun-nlb") - DeployProviderTypeAliyunOSS = DeployProviderType("aliyun-oss") - DeployProviderTypeAliyunVOD = DeployProviderType("aliyun-vod") - DeployProviderTypeAliyunWAF = DeployProviderType("aliyun-waf") - DeployProviderTypeAWSACM = DeployProviderType("aws-acm") - DeployProviderTypeAWSCloudFront = DeployProviderType("aws-cloudfront") - DeployProviderTypeAzureKeyVault = DeployProviderType("azure-keyvault") - DeployProviderTypeBaiduCloudAppBLB = DeployProviderType("baiducloud-appblb") - DeployProviderTypeBaiduCloudBLB = DeployProviderType("baiducloud-blb") - DeployProviderTypeBaiduCloudCDN = DeployProviderType("baiducloud-cdn") - DeployProviderTypeBaiduCloudCert = DeployProviderType("baiducloud-cert") - DeployProviderTypeBaishanCDN = DeployProviderType("baishan-cdn") - DeployProviderTypeBaotaPanelConsole = DeployProviderType("baotapanel-console") - DeployProviderTypeBaotaPanelSite = DeployProviderType("baotapanel-site") - DeployProviderTypeBunnyCDN = DeployProviderType("bunny-cdn") - DeployProviderTypeBytePlusCDN = DeployProviderType("byteplus-cdn") - DeployProviderTypeCacheFly = DeployProviderType("cachefly") - DeployProviderTypeCdnfly = DeployProviderType("cdnfly") - DeployProviderTypeDogeCloudCDN = DeployProviderType("dogecloud-cdn") - DeployProviderTypeEdgioApplications = DeployProviderType("edgio-applications") - DeployProviderTypeGcoreCDN = DeployProviderType("gcore-cdn") - DeployProviderTypeHuaweiCloudCDN = DeployProviderType("huaweicloud-cdn") - DeployProviderTypeHuaweiCloudELB = DeployProviderType("huaweicloud-elb") - DeployProviderTypeHuaweiCloudSCM = DeployProviderType("huaweicloud-scm") - DeployProviderTypeHuaweiCloudWAF = DeployProviderType("huaweicloud-waf") - DeployProviderTypeJDCloudALB = DeployProviderType("jdcloud-alb") - DeployProviderTypeJDCloudCDN = DeployProviderType("jdcloud-cdn") - DeployProviderTypeJDCloudLive = DeployProviderType("jdcloud-live") - DeployProviderTypeJDCloudVOD = DeployProviderType("jdcloud-vod") - DeployProviderTypeKubernetesSecret = DeployProviderType("k8s-secret") - DeployProviderTypeLocal = DeployProviderType("local") - DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn") - DeployProviderTypeQiniuKodo = DeployProviderType("qiniu-kodo") - DeployProviderTypeQiniuPili = DeployProviderType("qiniu-pili") - DeployProviderTypeRainYunRCDN = DeployProviderType("rainyun-rcdn") - DeployProviderTypeSafeLine = DeployProviderType("safeline") - DeployProviderTypeSSH = DeployProviderType("ssh") - DeployProviderTypeTencentCloudCDN = DeployProviderType("tencentcloud-cdn") - DeployProviderTypeTencentCloudCLB = DeployProviderType("tencentcloud-clb") - DeployProviderTypeTencentCloudCOS = DeployProviderType("tencentcloud-cos") - DeployProviderTypeTencentCloudCSS = DeployProviderType("tencentcloud-css") - DeployProviderTypeTencentCloudECDN = DeployProviderType("tencentcloud-ecdn") - DeployProviderTypeTencentCloudEO = DeployProviderType("tencentcloud-eo") - DeployProviderTypeTencentCloudSCF = DeployProviderType("tencentcloud-scf") - DeployProviderTypeTencentCloudSSL = DeployProviderType("tencentcloud-ssl") - DeployProviderTypeTencentCloudSSLDeploy = DeployProviderType("tencentcloud-ssldeploy") - DeployProviderTypeTencentCloudVOD = DeployProviderType("tencentcloud-vod") - DeployProviderTypeTencentCloudWAF = DeployProviderType("tencentcloud-waf") - DeployProviderTypeUCloudUCDN = DeployProviderType("ucloud-ucdn") - DeployProviderTypeUCloudUS3 = DeployProviderType("ucloud-us3") - DeployProviderTypeUpyunCDN = DeployProviderType("upyun-cdn") - DeployProviderTypeUpyunFile = DeployProviderType("upyun-file") - DeployProviderTypeVolcEngineALB = DeployProviderType("volcengine-alb") - DeployProviderTypeVolcEngineCDN = DeployProviderType("volcengine-cdn") - DeployProviderTypeVolcEngineCertCenter = DeployProviderType("volcengine-certcenter") - DeployProviderTypeVolcEngineCLB = DeployProviderType("volcengine-clb") - DeployProviderTypeVolcEngineDCDN = DeployProviderType("volcengine-dcdn") - DeployProviderTypeVolcEngineImageX = DeployProviderType("volcengine-imagex") - DeployProviderTypeVolcEngineLive = DeployProviderType("volcengine-live") - DeployProviderTypeVolcEngineTOS = DeployProviderType("volcengine-tos") - DeployProviderTypeWangsuCDNPro = DeployProviderType("wangsu-cdnpro") - DeployProviderTypeWebhook = DeployProviderType("webhook") + DeploymentProviderType1PanelConsole = DeploymentProviderType(AccessProviderType1Panel + "-console") + DeploymentProviderType1PanelSite = DeploymentProviderType(AccessProviderType1Panel + "-site") + DeploymentProviderTypeAliyunALB = DeploymentProviderType(AccessProviderTypeAliyun + "-alb") + DeploymentProviderTypeAliyunAPIGW = DeploymentProviderType(AccessProviderTypeAliyun + "-apigw") + DeploymentProviderTypeAliyunCAS = DeploymentProviderType(AccessProviderTypeAliyun + "-cas") + DeploymentProviderTypeAliyunCASDeploy = DeploymentProviderType(AccessProviderTypeAliyun + "-casdeploy") + DeploymentProviderTypeAliyunCDN = DeploymentProviderType(AccessProviderTypeAliyun + "-cdn") + DeploymentProviderTypeAliyunCLB = DeploymentProviderType(AccessProviderTypeAliyun + "-clb") + DeploymentProviderTypeAliyunDCDN = DeploymentProviderType(AccessProviderTypeAliyun + "-dcdn") + DeploymentProviderTypeAliyunESA = DeploymentProviderType(AccessProviderTypeAliyun + "-esa") + DeploymentProviderTypeAliyunFC = DeploymentProviderType(AccessProviderTypeAliyun + "-fc") + DeploymentProviderTypeAliyunLive = DeploymentProviderType(AccessProviderTypeAliyun + "-live") + DeploymentProviderTypeAliyunNLB = DeploymentProviderType(AccessProviderTypeAliyun + "-nlb") + DeploymentProviderTypeAliyunOSS = DeploymentProviderType(AccessProviderTypeAliyun + "-oss") + DeploymentProviderTypeAliyunVOD = DeploymentProviderType(AccessProviderTypeAliyun + "-vod") + DeploymentProviderTypeAliyunWAF = DeploymentProviderType(AccessProviderTypeAliyun + "-waf") + DeploymentProviderTypeAWSACM = DeploymentProviderType(AccessProviderTypeAWS + "-acm") + DeploymentProviderTypeAWSCloudFront = DeploymentProviderType(AccessProviderTypeAWS + "-cloudfront") + DeploymentProviderTypeAzureKeyVault = DeploymentProviderType(AccessProviderTypeAzure + "-keyvault") + DeploymentProviderTypeBaiduCloudAppBLB = DeploymentProviderType(AccessProviderTypeBaiduCloud + "-appblb") + DeploymentProviderTypeBaiduCloudBLB = DeploymentProviderType(AccessProviderTypeBaiduCloud + "-blb") + DeploymentProviderTypeBaiduCloudCDN = DeploymentProviderType(AccessProviderTypeBaiduCloud + "-cdn") + DeploymentProviderTypeBaiduCloudCert = DeploymentProviderType(AccessProviderTypeBaiduCloud + "-cert") + DeploymentProviderTypeBaishanCDN = DeploymentProviderType(AccessProviderTypeBaishan + "-cdn") + DeploymentProviderTypeBaotaPanelConsole = DeploymentProviderType(AccessProviderTypeBaotaPanel + "-console") + DeploymentProviderTypeBaotaPanelSite = DeploymentProviderType(AccessProviderTypeBaotaPanel + "-site") + DeploymentProviderTypeBunnyCDN = DeploymentProviderType(AccessProviderTypeBunny + "-cdn") + DeploymentProviderTypeBytePlusCDN = DeploymentProviderType(AccessProviderTypeBytePlus + "-cdn") + DeploymentProviderTypeCacheFly = DeploymentProviderType(AccessProviderTypeCacheFly) + DeploymentProviderTypeCdnfly = DeploymentProviderType(AccessProviderTypeCdnfly) + DeploymentProviderTypeDogeCloudCDN = DeploymentProviderType(AccessProviderTypeDogeCloud + "-cdn") + DeploymentProviderTypeEdgioApplications = DeploymentProviderType(AccessProviderTypeEdgio + "-applications") + DeploymentProviderTypeGcoreCDN = DeploymentProviderType(AccessProviderTypeGcore + "-cdn") + DeploymentProviderTypeHuaweiCloudCDN = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-cdn") + DeploymentProviderTypeHuaweiCloudELB = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-elb") + DeploymentProviderTypeHuaweiCloudSCM = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-scm") + DeploymentProviderTypeHuaweiCloudWAF = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-waf") + DeploymentProviderTypeJDCloudALB = DeploymentProviderType(AccessProviderTypeJDCloud + "-alb") + DeploymentProviderTypeJDCloudCDN = DeploymentProviderType(AccessProviderTypeJDCloud + "-cdn") + DeploymentProviderTypeJDCloudLive = DeploymentProviderType(AccessProviderTypeJDCloud + "-live") + DeploymentProviderTypeJDCloudVOD = DeploymentProviderType(AccessProviderTypeJDCloud + "-vod") + DeploymentProviderTypeKubernetesSecret = DeploymentProviderType(AccessProviderTypeKubernetes + "-secret") + DeploymentProviderTypeLocal = DeploymentProviderType(AccessProviderTypeLocal) + DeploymentProviderTypeQiniuCDN = DeploymentProviderType(AccessProviderTypeQiniu + "-cdn") + DeploymentProviderTypeQiniuKodo = DeploymentProviderType(AccessProviderTypeQiniu + "-kodo") + DeploymentProviderTypeQiniuPili = DeploymentProviderType(AccessProviderTypeQiniu + "-pili") + DeploymentProviderTypeRainYunRCDN = DeploymentProviderType(AccessProviderTypeRainYun + "-rcdn") + DeploymentProviderTypeSafeLine = DeploymentProviderType(AccessProviderTypeSafeLine) + DeploymentProviderTypeSSH = DeploymentProviderType(AccessProviderTypeSSH) + DeploymentProviderTypeTencentCloudCDN = DeploymentProviderType(AccessProviderTypeTencentCloud + "-cdn") + DeploymentProviderTypeTencentCloudCLB = DeploymentProviderType(AccessProviderTypeTencentCloud + "-clb") + DeploymentProviderTypeTencentCloudCOS = DeploymentProviderType(AccessProviderTypeTencentCloud + "-cos") + DeploymentProviderTypeTencentCloudCSS = DeploymentProviderType(AccessProviderTypeTencentCloud + "-css") + DeploymentProviderTypeTencentCloudECDN = DeploymentProviderType(AccessProviderTypeTencentCloud + "-ecdn") + DeploymentProviderTypeTencentCloudEO = DeploymentProviderType(AccessProviderTypeTencentCloud + "-eo") + DeploymentProviderTypeTencentCloudSCF = DeploymentProviderType(AccessProviderTypeTencentCloud + "-scf") + DeploymentProviderTypeTencentCloudSSL = DeploymentProviderType(AccessProviderTypeTencentCloud + "-ssl") + DeploymentProviderTypeTencentCloudSSLDeploy = DeploymentProviderType(AccessProviderTypeTencentCloud + "-ssldeploy") + DeploymentProviderTypeTencentCloudVOD = DeploymentProviderType(AccessProviderTypeTencentCloud + "-vod") + DeploymentProviderTypeTencentCloudWAF = DeploymentProviderType(AccessProviderTypeTencentCloud + "-waf") + DeploymentProviderTypeUCloudUCDN = DeploymentProviderType(AccessProviderTypeUCloud + "-ucdn") + DeploymentProviderTypeUCloudUS3 = DeploymentProviderType(AccessProviderTypeUCloud + "-us3") + DeploymentProviderTypeUpyunCDN = DeploymentProviderType(AccessProviderTypeUpyun + "-cdn") + DeploymentProviderTypeUpyunFile = DeploymentProviderType(AccessProviderTypeUpyun + "-file") + DeploymentProviderTypeVolcEngineALB = DeploymentProviderType(AccessProviderTypeVolcEngine + "-alb") + DeploymentProviderTypeVolcEngineCDN = DeploymentProviderType(AccessProviderTypeVolcEngine + "-cdn") + DeploymentProviderTypeVolcEngineCertCenter = DeploymentProviderType(AccessProviderTypeVolcEngine + "-certcenter") + DeploymentProviderTypeVolcEngineCLB = DeploymentProviderType(AccessProviderTypeVolcEngine + "-clb") + DeploymentProviderTypeVolcEngineDCDN = DeploymentProviderType(AccessProviderTypeVolcEngine + "-dcdn") + DeploymentProviderTypeVolcEngineImageX = DeploymentProviderType(AccessProviderTypeVolcEngine + "-imagex") + DeploymentProviderTypeVolcEngineLive = DeploymentProviderType(AccessProviderTypeVolcEngine + "-live") + DeploymentProviderTypeVolcEngineTOS = DeploymentProviderType(AccessProviderTypeVolcEngine + "-tos") + DeploymentProviderTypeWangsuCDNPro = DeploymentProviderType(AccessProviderTypeWangsu + "-cdnpro") + DeploymentProviderTypeWebhook = DeploymentProviderType(AccessProviderTypeWebhook) +) + +type NotificationProviderType string + +/* +消息通知提供商常量值。 +短横线前的部分始终等于授权提供商类型。 + + 注意:如果追加新的常量值,请保持以 ASCII 排序。 + NOTICE: If you add new constant, please keep ASCII order. +*/ +const ( + NotificationProviderTypeDingTalkBot = NotificationProviderType(AccessProviderTypeDingTalkBot) + NotificationProviderTypeEmail = NotificationProviderType(AccessProviderTypeEmail) + NotificationProviderTypeLarkBot = NotificationProviderType(AccessProviderTypeLarkBot) + NotificationProviderTypeMattermost = NotificationProviderType(AccessProviderTypeMattermost) + NotificationProviderTypeTelegram = NotificationProviderType(AccessProviderTypeTelegram) + NotificationProviderTypeWebhook = NotificationProviderType(AccessProviderTypeWebhook) + NotificationProviderTypeWeComBot = NotificationProviderType(AccessProviderTypeWeComBot) ) diff --git a/internal/domain/settings.go b/internal/domain/settings.go index ebe6b9d7..7063ed83 100644 --- a/internal/domain/settings.go +++ b/internal/domain/settings.go @@ -13,6 +13,7 @@ type Settings struct { Content string `json:"content" db:"content"` } +// Deprecated: v0.4.x 将废弃 type NotifyTemplatesSettingsContent struct { NotifyTemplates []struct { Subject string `json:"subject"` @@ -20,8 +21,10 @@ type NotifyTemplatesSettingsContent struct { } `json:"notifyTemplates"` } +// Deprecated: v0.4.x 将废弃 type NotifyChannelsSettingsContent map[string]map[string]any +// Deprecated: v0.4.x 将废弃 func (s *Settings) GetNotifyChannelConfig(channel string) (map[string]any, error) { conf := &NotifyChannelsSettingsContent{} if err := json.Unmarshal([]byte(s.Content), conf); err != nil { diff --git a/internal/domain/workflow.go b/internal/domain/workflow.go index cfa1c8cc..65bc35d0 100644 --- a/internal/domain/workflow.go +++ b/internal/domain/workflow.go @@ -71,7 +71,7 @@ type WorkflowNodeConfigForApply struct { CAProvider string `json:"caProvider,omitempty"` // CA 提供商(零值将使用全局配置) CAProviderAccessId string `json:"caProviderAccessId,omitempty"` // CA 提供商授权记录 ID CAProviderConfig map[string]any `json:"caProviderConfig,omitempty"` // CA 提供商额外配置 - KeyAlgorithm string `json:"keyAlgorithm"` // 密钥算法 + KeyAlgorithm string `json:"keyAlgorithm"` // 证书算法 Nameservers string `json:"nameservers,omitempty"` // DNS 服务器列表,以半角分号分隔 DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` // DNS 传播超时时间(零值取决于提供商的默认值) DnsTTL int32 `json:"dnsTTL,omitempty"` // DNS TTL(零值取决于提供商的默认值) @@ -95,9 +95,12 @@ type WorkflowNodeConfigForDeploy struct { } type WorkflowNodeConfigForNotify struct { - Channel string `json:"channel"` // 通知渠道 - Subject string `json:"subject"` // 通知主题 - Message string `json:"message"` // 通知内容 + Channel string `json:"channel,omitempty"` // Deprecated: v0.4.x 将废弃 + Provider string `json:"provider"` // 通知提供商 + ProviderAccessId string `json:"providerAccessId"` // 通知提供商授权记录 ID + ProviderConfig map[string]any `json:"providerConfig,omitempty"` // 通知提供商额外配置 + Subject string `json:"subject"` // 通知主题 + Message string `json:"message"` // 通知内容 } func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply { @@ -111,10 +114,10 @@ func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply { ContactEmail: maputil.GetString(n.Config, "contactEmail"), Provider: maputil.GetString(n.Config, "provider"), ProviderAccessId: maputil.GetString(n.Config, "providerAccessId"), - ProviderConfig: maputil.GetAnyMap(n.Config, "providerConfig"), + ProviderConfig: maputil.GetKVMapAny(n.Config, "providerConfig"), CAProvider: maputil.GetString(n.Config, "caProvider"), CAProviderAccessId: maputil.GetString(n.Config, "caProviderAccessId"), - CAProviderConfig: maputil.GetAnyMap(n.Config, "caProviderConfig"), + CAProviderConfig: maputil.GetKVMapAny(n.Config, "caProviderConfig"), KeyAlgorithm: maputil.GetString(n.Config, "keyAlgorithm"), Nameservers: maputil.GetString(n.Config, "nameservers"), DnsPropagationTimeout: maputil.GetInt32(n.Config, "dnsPropagationTimeout"), @@ -138,16 +141,19 @@ func (n *WorkflowNode) GetConfigForDeploy() WorkflowNodeConfigForDeploy { Certificate: maputil.GetString(n.Config, "certificate"), Provider: maputil.GetString(n.Config, "provider"), ProviderAccessId: maputil.GetString(n.Config, "providerAccessId"), - ProviderConfig: maputil.GetAnyMap(n.Config, "providerConfig"), + ProviderConfig: maputil.GetKVMapAny(n.Config, "providerConfig"), SkipOnLastSucceeded: maputil.GetBool(n.Config, "skipOnLastSucceeded"), } } func (n *WorkflowNode) GetConfigForNotify() WorkflowNodeConfigForNotify { return WorkflowNodeConfigForNotify{ - Channel: maputil.GetString(n.Config, "channel"), - Subject: maputil.GetString(n.Config, "subject"), - Message: maputil.GetString(n.Config, "message"), + Channel: maputil.GetString(n.Config, "channel"), + Provider: maputil.GetString(n.Config, "provider"), + ProviderAccessId: maputil.GetString(n.Config, "providerAccessId"), + ProviderConfig: maputil.GetKVMapAny(n.Config, "providerConfig"), + Subject: maputil.GetString(n.Config, "subject"), + Message: maputil.GetString(n.Config, "message"), } } diff --git a/internal/notify/notifier.go b/internal/notify/notifier.go new file mode 100644 index 00000000..955e88c3 --- /dev/null +++ b/internal/notify/notifier.go @@ -0,0 +1,72 @@ +package notify + +import ( + "context" + "fmt" + "log/slog" + + "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/pkg/core/notifier" + "github.com/usual2970/certimate/internal/repository" +) + +type Notifier interface { + Notify(ctx context.Context) error +} + +type NotifierWithWorkflowNodeConfig struct { + Node *domain.WorkflowNode + Logger *slog.Logger + Subject string + Message string +} + +func NewWithWorkflowNode(config NotifierWithWorkflowNodeConfig) (Notifier, error) { + if config.Node == nil { + return nil, fmt.Errorf("node is nil") + } + if config.Node.Type != domain.WorkflowNodeTypeNotify { + return nil, fmt.Errorf("node type is not '%s'", string(domain.WorkflowNodeTypeNotify)) + } + + nodeConfig := config.Node.GetConfigForNotify() + options := ¬ifierProviderOptions{ + Provider: domain.NotificationProviderType(nodeConfig.Provider), + ProviderAccessConfig: make(map[string]any), + ProviderExtendedConfig: nodeConfig.ProviderConfig, + } + + accessRepo := repository.NewAccessRepository() + if nodeConfig.ProviderAccessId != "" { + access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId) + if err != nil { + return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err) + } else { + options.ProviderAccessConfig = access.Config + } + } + + notifierProvider, err := createNotifierProvider(options) + if err != nil { + return nil, err + } + + return ¬ifierImpl{ + provider: notifierProvider.WithLogger(config.Logger), + subject: config.Subject, + message: config.Message, + }, nil +} + +type notifierImpl struct { + provider notifier.Notifier + subject string + message string +} + +var _ Notifier = (*notifierImpl)(nil) + +func (n *notifierImpl) Notify(ctx context.Context) error { + _, err := n.provider.Notify(ctx, n.subject, n.message) + return err +} diff --git a/internal/notify/notify.go b/internal/notify/notify.go index 3d447c05..92970341 100644 --- a/internal/notify/notify.go +++ b/internal/notify/notify.go @@ -13,6 +13,7 @@ import ( "github.com/usual2970/certimate/internal/repository" ) +// Deprecated: v0.4.x 将废弃 func SendToAllChannels(subject, message string) error { notifiers, err := getEnabledNotifiers() if err != nil { @@ -38,8 +39,9 @@ func SendToAllChannels(subject, message string) error { return err } +// Deprecated: v0.4.x 将废弃 func SendToChannel(subject, message string, channel string, channelConfig map[string]any) error { - notifier, err := createNotifier(domain.NotifyChannelType(channel), channelConfig) + notifier, err := createNotifierProviderUseGlobalSettings(domain.NotifyChannelType(channel), channelConfig) if err != nil { return err } @@ -48,6 +50,7 @@ func SendToChannel(subject, message string, channel string, channelConfig map[st return err } +// Deprecated: v0.4.x 将废弃 func getEnabledNotifiers() ([]notifier.Notifier, error) { settingsRepo := repository.NewSettingsRepository() settings, err := settingsRepo.GetByName(context.Background(), "notifyChannels") @@ -66,7 +69,7 @@ func getEnabledNotifiers() ([]notifier.Notifier, error) { continue } - notifier, err := createNotifier(domain.NotifyChannelType(k), v) + notifier, err := createNotifierProviderUseGlobalSettings(domain.NotifyChannelType(k), v) if err != nil { continue } diff --git a/internal/notify/providers.go b/internal/notify/providers.go index bae9ba72..c57b9c82 100644 --- a/internal/notify/providers.go +++ b/internal/notify/providers.go @@ -2,105 +2,152 @@ package notify import ( "fmt" + "net/http" "github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/pkg/core/notifier" - pBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark" pDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk" pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" - pGotify "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/gotify" pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark" pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost" - pPushover "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushover" - pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus" - pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan" pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" pWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" pWeCom "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecom" + httputil "github.com/usual2970/certimate/internal/pkg/utils/http" maputil "github.com/usual2970/certimate/internal/pkg/utils/map" ) -func createNotifier(channel domain.NotifyChannelType, channelConfig map[string]any) (notifier.Notifier, error) { +type notifierProviderOptions struct { + Provider domain.NotificationProviderType + ProviderAccessConfig map[string]any + ProviderExtendedConfig map[string]any +} + +func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier, error) { /* 注意:如果追加新的常量值,请保持以 ASCII 排序。 NOTICE: If you add new constant, please keep ASCII order. */ - switch channel { - case domain.NotifyChannelTypeBark: - return pBark.NewNotifier(&pBark.NotifierConfig{ - DeviceKey: maputil.GetString(channelConfig, "deviceKey"), - ServerUrl: maputil.GetString(channelConfig, "serverUrl"), - }) + switch options.Provider { + case domain.NotificationProviderTypeDingTalkBot: + { + access := domain.AccessConfigForDingTalkBot{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } - case domain.NotifyChannelTypeDingTalk: - return pDingTalk.NewNotifier(&pDingTalk.NotifierConfig{ - AccessToken: maputil.GetString(channelConfig, "accessToken"), - Secret: maputil.GetString(channelConfig, "secret"), - }) + return pDingTalk.NewNotifier(&pDingTalk.NotifierConfig{ + WebhookUrl: access.WebhookUrl, + Secret: access.Secret, + }) + } - case domain.NotifyChannelTypeEmail: - return pEmail.NewNotifier(&pEmail.NotifierConfig{ - SmtpHost: maputil.GetString(channelConfig, "smtpHost"), - SmtpPort: maputil.GetInt32(channelConfig, "smtpPort"), - SmtpTLS: maputil.GetOrDefaultBool(channelConfig, "smtpTLS", true), - Username: maputil.GetOrDefaultString(channelConfig, "username", maputil.GetString(channelConfig, "senderAddress")), - Password: maputil.GetString(channelConfig, "password"), - SenderAddress: maputil.GetString(channelConfig, "senderAddress"), - ReceiverAddress: maputil.GetString(channelConfig, "receiverAddress"), - }) + case domain.NotificationProviderTypeEmail: + { + access := domain.AccessConfigForEmail{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } - case domain.NotifyChannelTypeGotify: - return pGotify.NewNotifier(&pGotify.NotifierConfig{ - Url: maputil.GetString(channelConfig, "url"), - Token: maputil.GetString(channelConfig, "token"), - Priority: maputil.GetOrDefaultInt64(channelConfig, "priority", 1), - }) + return pEmail.NewNotifier(&pEmail.NotifierConfig{ + SmtpHost: access.SmtpHost, + SmtpPort: access.SmtpPort, + SmtpTls: access.SmtpTls, + Username: access.Username, + Password: access.Password, + SenderAddress: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "senderAddress", access.DefaultSenderAddress), + ReceiverAddress: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "receiverAddress", access.DefaultReceiverAddress), + }) + } - case domain.NotifyChannelTypeLark: - return pLark.NewNotifier(&pLark.NotifierConfig{ - WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"), - }) + case domain.NotificationProviderTypeLarkBot: + { + access := domain.AccessConfigForLarkBot{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } - case domain.NotifyChannelTypeMattermost: - return pMattermost.NewNotifier(&pMattermost.NotifierConfig{ - ServerUrl: maputil.GetString(channelConfig, "serverUrl"), - ChannelId: maputil.GetString(channelConfig, "channelId"), - Username: maputil.GetString(channelConfig, "username"), - Password: maputil.GetString(channelConfig, "password"), - }) - case domain.NotifyChannelTypePushover: - return pPushover.NewNotifier(&pPushover.NotifierConfig{ - Token: maputil.GetString(channelConfig, "token"), - User: maputil.GetString(channelConfig, "user"), - }) + return pLark.NewNotifier(&pLark.NotifierConfig{ + WebhookUrl: access.WebhookUrl, + }) + } - case domain.NotifyChannelTypePushPlus: - return pPushPlus.NewNotifier(&pPushPlus.NotifierConfig{ - Token: maputil.GetString(channelConfig, "token"), - }) + case domain.NotificationProviderTypeMattermost: + { + access := domain.AccessConfigForMattermost{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } - case domain.NotifyChannelTypeServerChan: - return pServerChan.NewNotifier(&pServerChan.NotifierConfig{ - Url: maputil.GetString(channelConfig, "url"), - }) + return pMattermost.NewNotifier(&pMattermost.NotifierConfig{ + ServerUrl: access.ServerUrl, + Username: access.Username, + Password: access.Password, + ChannelId: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "channelId", access.DefaultChannelId), + }) + } - case domain.NotifyChannelTypeTelegram: - return pTelegram.NewNotifier(&pTelegram.NotifierConfig{ - ApiToken: maputil.GetString(channelConfig, "apiToken"), - ChatId: maputil.GetInt64(channelConfig, "chatId"), - }) + case domain.NotificationProviderTypeTelegram: + { + access := domain.AccessConfigForTelegram{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } - case domain.NotifyChannelTypeWebhook: - return pWebhook.NewNotifier(&pWebhook.NotifierConfig{ - Url: maputil.GetString(channelConfig, "url"), - AllowInsecureConnections: maputil.GetBool(channelConfig, "allowInsecureConnections"), - }) + return pTelegram.NewNotifier(&pTelegram.NotifierConfig{ + BotToken: access.BotToken, + ChatId: maputil.GetOrDefaultInt64(options.ProviderExtendedConfig, "chatId", access.DefaultChatId), + }) + } - case domain.NotifyChannelTypeWeCom: - return pWeCom.NewNotifier(&pWeCom.NotifierConfig{ - WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"), - }) + case domain.NotificationProviderTypeWebhook: + { + access := domain.AccessConfigForWebhook{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + mergedHeaders := make(map[string]string) + if defaultHeadersString := access.HeadersString; defaultHeadersString != "" { + h, err := httputil.ParseHeaders(defaultHeadersString) + if err != nil { + return nil, fmt.Errorf("failed to parse webhook headers: %w", err) + } + for key := range h { + mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key) + } + } + if extendedHeadersString := maputil.GetString(options.ProviderExtendedConfig, "headers"); extendedHeadersString != "" { + h, err := httputil.ParseHeaders(extendedHeadersString) + if err != nil { + return nil, fmt.Errorf("failed to parse webhook headers: %w", err) + } + for key := range h { + mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key) + } + } + + return pWebhook.NewNotifier(&pWebhook.NotifierConfig{ + WebhookUrl: access.Url, + WebhookData: maputil.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", access.DefaultDataForNotification), + Method: access.Method, + Headers: mergedHeaders, + AllowInsecureConnections: access.AllowInsecureConnections, + }) + } + + case domain.NotificationProviderTypeWeComBot: + { + access := domain.AccessConfigForWeComBot{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + return pWeCom.NewNotifier(&pWeCom.NotifierConfig{ + WebhookUrl: access.WebhookUrl, + }) + } } - return nil, fmt.Errorf("unsupported notifier channel '%s'", channelConfig) + return nil, fmt.Errorf("unsupported notifier provider '%s'", options.Provider) } diff --git a/internal/notify/providers_deprecated.go b/internal/notify/providers_deprecated.go new file mode 100644 index 00000000..1e862866 --- /dev/null +++ b/internal/notify/providers_deprecated.go @@ -0,0 +1,108 @@ +package notify + +import ( + "fmt" + + "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/pkg/core/notifier" + pBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark" + pDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk" + pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" + pGotify "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/gotify" + pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark" + pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost" + pPushover "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushover" + pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus" + pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan" + pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" + pWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" + pWeCom "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecom" + maputil "github.com/usual2970/certimate/internal/pkg/utils/map" +) + +// Deprecated: v0.4.x 将废弃 +func createNotifierProviderUseGlobalSettings(channel domain.NotifyChannelType, channelConfig map[string]any) (notifier.Notifier, error) { + /* + 注意:如果追加新的常量值,请保持以 ASCII 排序。 + NOTICE: If you add new constant, please keep ASCII order. + */ + switch channel { + case domain.NotifyChannelTypeBark: + return pBark.NewNotifier(&pBark.NotifierConfig{ + DeviceKey: maputil.GetString(channelConfig, "deviceKey"), + ServerUrl: maputil.GetString(channelConfig, "serverUrl"), + }) + + case domain.NotifyChannelTypeDingTalk: + return pDingTalk.NewNotifier(&pDingTalk.NotifierConfig{ + WebhookUrl: "https://oapi.dingtalk.com/robot/send?access_token=" + maputil.GetString(channelConfig, "accessToken"), + Secret: maputil.GetString(channelConfig, "secret"), + }) + + case domain.NotifyChannelTypeEmail: + return pEmail.NewNotifier(&pEmail.NotifierConfig{ + SmtpHost: maputil.GetString(channelConfig, "smtpHost"), + SmtpPort: maputil.GetInt32(channelConfig, "smtpPort"), + SmtpTls: maputil.GetOrDefaultBool(channelConfig, "smtpTLS", true), + Username: maputil.GetOrDefaultString(channelConfig, "username", maputil.GetString(channelConfig, "senderAddress")), + Password: maputil.GetString(channelConfig, "password"), + SenderAddress: maputil.GetString(channelConfig, "senderAddress"), + ReceiverAddress: maputil.GetString(channelConfig, "receiverAddress"), + }) + + case domain.NotifyChannelTypeGotify: + return pGotify.NewNotifier(&pGotify.NotifierConfig{ + Url: maputil.GetString(channelConfig, "url"), + Token: maputil.GetString(channelConfig, "token"), + Priority: maputil.GetOrDefaultInt64(channelConfig, "priority", 1), + }) + + case domain.NotifyChannelTypeLark: + return pLark.NewNotifier(&pLark.NotifierConfig{ + WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"), + }) + + case domain.NotifyChannelTypeMattermost: + return pMattermost.NewNotifier(&pMattermost.NotifierConfig{ + ServerUrl: maputil.GetString(channelConfig, "serverUrl"), + ChannelId: maputil.GetString(channelConfig, "channelId"), + Username: maputil.GetString(channelConfig, "username"), + Password: maputil.GetString(channelConfig, "password"), + }) + + case domain.NotifyChannelTypePushover: + return pPushover.NewNotifier(&pPushover.NotifierConfig{ + Token: maputil.GetString(channelConfig, "token"), + User: maputil.GetString(channelConfig, "user"), + }) + + case domain.NotifyChannelTypePushPlus: + return pPushPlus.NewNotifier(&pPushPlus.NotifierConfig{ + Token: maputil.GetString(channelConfig, "token"), + }) + + case domain.NotifyChannelTypeServerChan: + return pServerChan.NewNotifier(&pServerChan.NotifierConfig{ + Url: maputil.GetString(channelConfig, "url"), + }) + + case domain.NotifyChannelTypeTelegram: + return pTelegram.NewNotifier(&pTelegram.NotifierConfig{ + BotToken: maputil.GetString(channelConfig, "apiToken"), + ChatId: maputil.GetInt64(channelConfig, "chatId"), + }) + + case domain.NotifyChannelTypeWebhook: + return pWebhook.NewNotifier(&pWebhook.NotifierConfig{ + WebhookUrl: maputil.GetString(channelConfig, "url"), + AllowInsecureConnections: maputil.GetBool(channelConfig, "allowInsecureConnections"), + }) + + case domain.NotifyChannelTypeWeCom: + return pWeCom.NewNotifier(&pWeCom.NotifierConfig{ + WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"), + }) + } + + return nil, fmt.Errorf("unsupported notifier channel '%s'", channelConfig) +} diff --git a/internal/notify/service.go b/internal/notify/service.go index 9b7cc416..1d1f6c25 100644 --- a/internal/notify/service.go +++ b/internal/notify/service.go @@ -8,25 +8,30 @@ import ( "github.com/usual2970/certimate/internal/domain/dtos" ) +// Deprecated: v0.4.x 将废弃 const ( notifyTestTitle = "测试通知" notifyTestBody = "欢迎使用 Certimate ,这是一条测试通知。" ) +// Deprecated: v0.4.x 将废弃 type settingsRepository interface { GetByName(ctx context.Context, name string) (*domain.Settings, error) } +// Deprecated: v0.4.x 将废弃 type NotifyService struct { settingsRepo settingsRepository } +// Deprecated: v0.4.x 将废弃 func NewNotifyService(settingsRepo settingsRepository) *NotifyService { return &NotifyService{ settingsRepo: settingsRepo, } } +// Deprecated: v0.4.x 将废弃 func (n *NotifyService) Test(ctx context.Context, req *dtos.NotifyTestPushReq) error { settings, err := n.settingsRepo.GetByName(ctx, "notifyChannels") if err != nil { diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go index ddf49218..1d60d315 100644 --- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go @@ -137,6 +137,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId listListenersLimit := int32(100) var listListenersToken *string = nil for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listListenersReq := &alialb.ListListenersRequest{ MaxResults: tea.Int32(listListenersLimit), NextToken: listListenersToken, @@ -166,6 +172,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners listListenersToken = nil for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listListenersReq := &alialb.ListListenersRequest{ MaxResults: tea.Int32(listListenersLimit), NextToken: listListenersToken, @@ -262,6 +274,12 @@ func (d *DeployerProvider) updateListenerCertificate(ctx context.Context, cloudL listListenerCertificatesLimit := int32(100) var listListenerCertificatesToken *string = nil for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listListenerCertificatesReq := &alialb.ListListenerCertificatesRequest{ NextToken: listListenerCertificatesToken, MaxResults: tea.Int32(listListenerCertificatesLimit), diff --git a/internal/pkg/core/deployer/providers/aliyun-apigw/aliyun_apigw.go b/internal/pkg/core/deployer/providers/aliyun-apigw/aliyun_apigw.go index 2d85be7a..82a05c33 100644 --- a/internal/pkg/core/deployer/providers/aliyun-apigw/aliyun_apigw.go +++ b/internal/pkg/core/deployer/providers/aliyun-apigw/aliyun_apigw.go @@ -142,6 +142,12 @@ func (d *DeployerProvider) deployToCloudNative(ctx context.Context, certPEM stri listDomainsPageNumber := int32(1) listDomainsPageSize := int32(10) for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listDomainsReq := &aliapig.ListDomainsRequest{ GatewayId: tea.String(d.config.GatewayId), NameLike: tea.String(d.config.Domain), diff --git a/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go b/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go index a819de13..077dea5c 100644 --- a/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go +++ b/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go @@ -126,8 +126,10 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE // 循环获取部署任务详情,等待任务状态变更 // REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-describedeploymentjob for { - if ctx.Err() != nil { + select { + case <-ctx.Done(): return nil, ctx.Err() + default: } describeDeploymentJobReq := &alicas.DescribeDeploymentJobRequest{ diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go index 583eeabd..6ff33049 100644 --- a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go @@ -132,6 +132,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId describeLoadBalancerListenersLimit := int32(100) var describeLoadBalancerListenersToken *string = nil for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + describeLoadBalancerListenersReq := &alislb.DescribeLoadBalancerListenersRequest{ RegionId: tea.String(d.config.Region), MaxResults: tea.Int32(describeLoadBalancerListenersLimit), @@ -166,8 +172,14 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listenerPort := range listenerPorts { - if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listenerPort, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listenerPort, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go index 326273f8..d6879f61 100644 --- a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -125,6 +125,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId listListenersLimit := int32(100) var listListenersToken *string = nil for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listListenersReq := &alinlb.ListListenersRequest{ MaxResults: tea.Int32(listListenersLimit), NextToken: listListenersToken, @@ -158,8 +164,14 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listenerId := range listenerIds { - if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/baiducloud-appblb/baiducloud_appblb.go b/internal/pkg/core/deployer/providers/baiducloud-appblb/baiducloud_appblb.go index 90084f6b..aabce14c 100644 --- a/internal/pkg/core/deployer/providers/baiducloud-appblb/baiducloud_appblb.go +++ b/internal/pkg/core/deployer/providers/baiducloud-appblb/baiducloud_appblb.go @@ -152,8 +152,14 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listener := range listeners { - if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { + errs = append(errs, err) + } } } @@ -209,8 +215,14 @@ func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId str var errs []error for _, listener := range listeners { - if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/baiducloud-blb/baiducloud_blb.go b/internal/pkg/core/deployer/providers/baiducloud-blb/baiducloud_blb.go index 8c350492..a16ea102 100644 --- a/internal/pkg/core/deployer/providers/baiducloud-blb/baiducloud_blb.go +++ b/internal/pkg/core/deployer/providers/baiducloud-blb/baiducloud_blb.go @@ -152,8 +152,14 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listener := range listeners { - if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { + errs = append(errs, err) + } } } @@ -209,8 +215,14 @@ func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId str var errs []error for _, listener := range listeners { - if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listener.Type, listener.Port, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go index d74f6c60..e659c9a1 100644 --- a/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go +++ b/internal/pkg/core/deployer/providers/byteplus-cdn/byteplus_cdn.go @@ -117,16 +117,22 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE var errs []error for _, domain := range domains { - // 关联证书与加速域名 - // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-batchdeploycert - batchDeployCertReq := &bpcdn.BatchDeployCertRequest{ - CertId: upres.CertId, - Domain: domain, - } - batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq) - d.logger.Debug("sdk request 'cdn.BatchDeployCert'", slog.Any("request", batchDeployCertReq), slog.Any("response", batchDeployCertResp)) - if err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return nil, ctx.Err() + + default: + // 关联证书与加速域名 + // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-batchdeploycert + batchDeployCertReq := &bpcdn.BatchDeployCertRequest{ + CertId: upres.CertId, + Domain: domain, + } + batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq) + d.logger.Debug("sdk request 'cdn.BatchDeployCert'", slog.Any("request", batchDeployCertReq), slog.Any("response", batchDeployCertResp)) + if err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go index f0c8175e..748111dd 100644 --- a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go @@ -160,6 +160,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, certPEM str listListenersLimit := int32(2000) var listListenersMarker *string = nil for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listListenersReq := &hcelbmodel.ListListenersRequest{ Limit: typeutil.ToPtr(listListenersLimit), Marker: listListenersMarker, @@ -201,8 +207,14 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, certPEM str var errs []error for _, listenerId := range listenerIds { - if err := d.modifyListenerCertificate(ctx, listenerId, upres.CertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := d.modifyListenerCertificate(ctx, listenerId, upres.CertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/huaweicloud-waf/huaweicloud_waf.go b/internal/pkg/core/deployer/providers/huaweicloud-waf/huaweicloud_waf.go index ff208fe0..8fe96ee0 100644 --- a/internal/pkg/core/deployer/providers/huaweicloud-waf/huaweicloud_waf.go +++ b/internal/pkg/core/deployer/providers/huaweicloud-waf/huaweicloud_waf.go @@ -172,6 +172,12 @@ func (d *DeployerProvider) deployToCloudServer(ctx context.Context, certPEM stri listHostPage := int32(1) listHostPageSize := int32(100) for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listHostReq := &hcwafmodel.ListHostRequest{ Hostname: typeutil.ToPtr(strings.TrimPrefix(d.config.Domain, "*")), Page: typeutil.ToPtr(listHostPage), @@ -239,6 +245,12 @@ func (d *DeployerProvider) deployToPremiumHost(ctx context.Context, certPEM stri listPremiumHostPage := int32(1) listPremiumHostPageSize := int32(100) for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + listPremiumHostReq := &hcwafmodel.ListPremiumHostRequest{ Hostname: typeutil.ToPtr(strings.TrimPrefix(d.config.Domain, "*")), Page: typeutil.ToPtr(fmt.Sprintf("%d", listPremiumHostPage)), diff --git a/internal/pkg/core/deployer/providers/jdcloud-alb/jdcloud_alb.go b/internal/pkg/core/deployer/providers/jdcloud-alb/jdcloud_alb.go index 339bacc8..ca42126e 100644 --- a/internal/pkg/core/deployer/providers/jdcloud-alb/jdcloud_alb.go +++ b/internal/pkg/core/deployer/providers/jdcloud-alb/jdcloud_alb.go @@ -132,6 +132,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId describeListenersPageNumber := 1 describeListenersPageSize := 100 for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + describeListenersReq := jdlbapi.NewDescribeListenersRequest(d.config.RegionId) describeListenersReq.SetFilters([]jdcommon.Filter{{Name: "loadBalancerId", Values: []string{d.config.LoadbalancerId}}}) describeListenersReq.SetPageSize(describeListenersPageNumber) @@ -164,8 +170,13 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listenerId := range listenerIds { - if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/jdcloud-vod/jdcloud_vod.go b/internal/pkg/core/deployer/providers/jdcloud-vod/jdcloud_vod.go index 61c5a6f6..6f61625d 100644 --- a/internal/pkg/core/deployer/providers/jdcloud-vod/jdcloud_vod.go +++ b/internal/pkg/core/deployer/providers/jdcloud-vod/jdcloud_vod.go @@ -65,6 +65,12 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE listDomainsPageNumber := 1 listDomainsPageSize := 100 for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listDomainsReq := jdvodapi.NewListDomainsRequest() listDomainsReq.SetPageNumber(1) listDomainsReq.SetPageSize(100) diff --git a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go index 7625d6ae..0c2f8902 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go @@ -188,8 +188,13 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listenerId := range listenerIds { - if err := d.modifyListenerCertificate(ctx, d.config.LoadbalancerId, listenerId, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.modifyListenerCertificate(ctx, d.config.LoadbalancerId, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go b/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go index f6090190..5f13660d 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go @@ -108,8 +108,10 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE // 循环获取部署任务详情,等待任务状态变更 // REF: https://cloud.tencent.com.cn/document/api/400/91658 for { - if ctx.Err() != nil { + select { + case <-ctx.Done(): return nil, ctx.Err() + default: } describeHostDeployRecordDetailReq := tcssl.NewDescribeHostDeployRecordDetailRequest() diff --git a/internal/pkg/core/deployer/providers/volcengine-alb/volcengine_alb.go b/internal/pkg/core/deployer/providers/volcengine-alb/volcengine_alb.go index c3d0a2d6..b17ae729 100644 --- a/internal/pkg/core/deployer/providers/volcengine-alb/volcengine_alb.go +++ b/internal/pkg/core/deployer/providers/volcengine-alb/volcengine_alb.go @@ -132,6 +132,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId describeListenersPageSize := int64(100) describeListenersPageNumber := int64(1) for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + describeListenersReq := &vealb.DescribeListenersInput{ LoadBalancerId: ve.String(d.config.LoadbalancerId), Protocol: ve.String("HTTPS"), @@ -163,8 +169,13 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listenerId := range listenerIds { - if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go index f8642b7f..e9b2c325 100644 --- a/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go +++ b/internal/pkg/core/deployer/providers/volcengine-cdn/volcengine_cdn.go @@ -117,16 +117,21 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE var errs []error for _, domain := range domains { - // 关联证书与加速域名 - // REF: https://www.volcengine.com/docs/6454/125712 - batchDeployCertReq := &vecdn.BatchDeployCertRequest{ - CertId: upres.CertId, - Domain: domain, - } - batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq) - d.logger.Debug("sdk request 'cdn.BatchDeployCert'", slog.Any("request", batchDeployCertReq), slog.Any("response", batchDeployCertResp)) - if err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + // 关联证书与加速域名 + // REF: https://www.volcengine.com/docs/6454/125712 + batchDeployCertReq := &vecdn.BatchDeployCertRequest{ + CertId: upres.CertId, + Domain: domain, + } + batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq) + d.logger.Debug("sdk request 'cdn.BatchDeployCert'", slog.Any("request", batchDeployCertReq), slog.Any("response", batchDeployCertResp)) + if err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go b/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go index e95aac0e..3b6a37bf 100644 --- a/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go +++ b/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go @@ -128,6 +128,12 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId describeListenersPageSize := int64(100) describeListenersPageNumber := int64(1) for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + describeListenersReq := &veclb.DescribeListenersInput{ LoadBalancerId: ve.String(d.config.LoadbalancerId), Protocol: ve.String("HTTPS"), @@ -159,8 +165,13 @@ func (d *DeployerProvider) deployToLoadbalancer(ctx context.Context, cloudCertId var errs []error for _, listenerId := range listenerIds { - if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go index 1b1336b6..46c0b9dc 100644 --- a/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go +++ b/internal/pkg/core/deployer/providers/volcengine-live/volcengine_live.go @@ -125,17 +125,22 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE var errs []error for _, domain := range domains { - // 绑定证书 - // REF: https://www.volcengine.com/docs/6469/1186278#%E7%BB%91%E5%AE%9A%E8%AF%81%E4%B9%A6 - bindCertReq := &velive.BindCertBody{ - ChainID: upres.CertId, - Domain: domain, - HTTPS: ve.Bool(true), - } - bindCertResp, err := d.sdkClient.BindCert(ctx, bindCertReq) - d.logger.Debug("sdk request 'live.BindCert'", slog.Any("request", bindCertReq), slog.Any("response", bindCertResp)) - if err != nil { - errs = append(errs, err) + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + // 绑定证书 + // REF: https://www.volcengine.com/docs/6469/1186278#%E7%BB%91%E5%AE%9A%E8%AF%81%E4%B9%A6 + bindCertReq := &velive.BindCertBody{ + ChainID: upres.CertId, + Domain: domain, + HTTPS: ve.Bool(true), + } + bindCertResp, err := d.sdkClient.BindCert(ctx, bindCertReq) + d.logger.Debug("sdk request 'live.BindCert'", slog.Any("request", bindCertReq), slog.Any("response", bindCertResp)) + if err != nil { + errs = append(errs, err) + } } } diff --git a/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go b/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go index f09cfb92..2faf1b03 100644 --- a/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go +++ b/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go @@ -199,8 +199,10 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE wangsuTaskId = wangsuTaskMatches[1] } for { - if ctx.Err() != nil { + select { + case <-ctx.Done(): return nil, ctx.Err() + default: } getDeploymentTaskDetailResp, err := d.sdkClient.GetDeploymentTaskDetail(wangsuTaskId) diff --git a/internal/pkg/core/deployer/providers/webhook/webhook.go b/internal/pkg/core/deployer/providers/webhook/webhook.go index 28e0bd7d..07b2eaaa 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook.go @@ -6,6 +6,8 @@ import ( "encoding/json" "fmt" "log/slog" + "net/http" + "net/url" "strings" "time" @@ -18,8 +20,13 @@ import ( type DeployerConfig struct { // Webhook URL。 WebhookUrl string `json:"webhookUrl"` - // Webhook 回调数据(JSON 格式)。 + // Webhook 回调数据(application/json 或 application/x-www-form-urlencoded 格式)。 WebhookData string `json:"webhookData,omitempty"` + // 请求谓词。 + // 零值时默认为 "POST"。 + Method string `json:"method,omitempty"` + // 请求标头。 + Headers map[string]string `json:"headers,omitempty"` // 是否允许不安全的连接。 AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } @@ -62,31 +69,111 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { } func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 解析证书内容 certX509, err := certutil.ParseCertificateFromPEM(certPEM) if err != nil { return nil, fmt.Errorf("failed to parse x509: %w", err) } - var webhookData interface{} - err = json.Unmarshal([]byte(d.config.WebhookData), &webhookData) + // 处理 Webhook URL + webhookUrl, err := url.Parse(d.config.WebhookUrl) if err != nil { - return nil, fmt.Errorf("failed to unmarshall webhook data: %w", err) + return nil, fmt.Errorf("failed to parse webhook url: %w", err) + } else if webhookUrl.Scheme != "http" && webhookUrl.Scheme != "https" { + return nil, fmt.Errorf("unsupported webhook url scheme '%s'", webhookUrl.Scheme) + } else { + webhookUrl.Path = strings.ReplaceAll(webhookUrl.Path, "${DOMAIN}", url.PathEscape(certX509.Subject.CommonName)) } - replaceJsonValueRecursively(webhookData, "${DOMAIN}", certX509.Subject.CommonName) - replaceJsonValueRecursively(webhookData, "${DOMAINS}", strings.Join(certX509.DNSNames, ";")) - replaceJsonValueRecursively(webhookData, "${SUBJECT_ALT_NAMES}", strings.Join(certX509.DNSNames, ";")) - replaceJsonValueRecursively(webhookData, "${CERTIFICATE}", certPEM) - replaceJsonValueRecursively(webhookData, "${PRIVATE_KEY}", privkeyPEM) + // 处理 Webhook 请求谓词 + webhookMethod := strings.ToUpper(d.config.Method) + if webhookMethod == "" { + webhookMethod = http.MethodPost + } else if webhookMethod != http.MethodGet && + webhookMethod != http.MethodPost && + webhookMethod != http.MethodPut && + webhookMethod != http.MethodPatch && + webhookMethod != http.MethodDelete { + return nil, fmt.Errorf("unsupported webhook request method '%s'", webhookMethod) + } - resp, err := d.httpClient.R(). + // 处理 Webhook 请求标头 + webhookHeaders := make(http.Header) + for k, v := range d.config.Headers { + webhookHeaders.Set(k, v) + } + + // 处理 Webhook 请求内容类型 + const CONTENT_TYPE_JSON = "application/json" + const CONTENT_TYPE_FORM = "application/x-www-form-urlencoded" + const CONTENT_TYPE_MULTIPART = "multipart/form-data" + webhookContentType := webhookHeaders.Get("Content-Type") + if webhookContentType == "" { + webhookContentType = CONTENT_TYPE_JSON + webhookHeaders.Set("Content-Type", CONTENT_TYPE_JSON) + } else if strings.HasPrefix(webhookContentType, CONTENT_TYPE_JSON) && + strings.HasPrefix(webhookContentType, CONTENT_TYPE_FORM) && + strings.HasPrefix(webhookContentType, CONTENT_TYPE_MULTIPART) { + return nil, fmt.Errorf("unsupported webhook content type '%s'", webhookContentType) + } + + // 处理 Webhook 请求数据 + var webhookData interface{} + if d.config.WebhookData == "" { + webhookData = map[string]string{ + "name": strings.Join(certX509.DNSNames, ";"), + "cert": certPEM, + "privkey": privkeyPEM, + } + } else { + err = json.Unmarshal([]byte(d.config.WebhookData), &webhookData) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err) + } + + replaceJsonValueRecursively(webhookData, "${DOMAIN}", certX509.Subject.CommonName) + replaceJsonValueRecursively(webhookData, "${DOMAINS}", strings.Join(certX509.DNSNames, ";")) + replaceJsonValueRecursively(webhookData, "${CERTIFICATE}", certPEM) + replaceJsonValueRecursively(webhookData, "${PRIVATE_KEY}", privkeyPEM) + + if webhookMethod == http.MethodGet || webhookContentType == CONTENT_TYPE_FORM || webhookContentType == CONTENT_TYPE_MULTIPART { + temp := make(map[string]string) + jsonb, err := json.Marshal(webhookData) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err) + } else if err := json.Unmarshal(jsonb, &temp); err != nil { + return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err) + } else { + webhookData = temp + } + } + } + + // 生成请求 + // 其中 GET 请求需转换为查询参数 + req := d.httpClient.R(). SetContext(ctx). - SetHeader("Content-Type", "application/json"). - SetBody(webhookData). - Post(d.config.WebhookUrl) + SetHeaderMultiValues(webhookHeaders) + req.URL = webhookUrl.String() + req.Method = webhookMethod + if webhookMethod == http.MethodGet { + req.SetQueryParams(webhookData.(map[string]string)) + } else { + switch webhookContentType { + case CONTENT_TYPE_JSON: + req.SetBody(webhookData) + case CONTENT_TYPE_FORM: + req.SetFormData(webhookData.(map[string]string)) + case CONTENT_TYPE_MULTIPART: + req.SetMultipartFormData(webhookData.(map[string]string)) + } + } + + // 发送请求 + resp, err := req.SetDebug(true).Send() if err != nil { return nil, fmt.Errorf("failed to send webhook request: %w", err) - } else if resp.StatusCode() != 200 { + } else if resp.IsError() { return nil, fmt.Errorf("unexpected webhook response status code: %d", resp.StatusCode()) } diff --git a/internal/pkg/core/deployer/providers/webhook/webhook_test.go b/internal/pkg/core/deployer/providers/webhook/webhook_test.go index 0bd670b3..8642ef14 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook_test.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook_test.go @@ -12,10 +12,11 @@ import ( ) var ( - fInputCertPath string - fInputKeyPath string - fWebhookUrl string - fWebhookData string + fInputCertPath string + fInputKeyPath string + fWebhookUrl string + fWebhookContentType string + fWebhookData string ) func init() { @@ -24,6 +25,7 @@ func init() { flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fWebhookUrl, argsPrefix+"URL", "", "") + flag.StringVar(&fWebhookContentType, argsPrefix+"CONTENTTYPE", "application/json", "") flag.StringVar(&fWebhookData, argsPrefix+"DATA", "", "") } @@ -34,7 +36,8 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_WEBHOOK_INPUTCERTPATH="/path/to/your-input-cert.pem" \ --CERTIMATE_DEPLOYER_WEBHOOK_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_DEPLOYER_WEBHOOK_URL="https://example.com/your-webhook-url" \ - --CERTIMATE_DEPLOYER_WEBHOOK_DATA="{\"certificate\":\"${Certificate}\",\"privateKey\":\"${PrivateKey}\"}" + --CERTIMATE_DEPLOYER_WEBHOOK_CONTENTTYPE="application/json" \ + --CERTIMATE_DEPLOYER_WEBHOOK_DATA="{\"certificate\":\"${CERTIFICATE}\",\"privateKey\":\"${PRIVATE_KEY}\"}" */ func TestDeploy(t *testing.T) { flag.Parse() @@ -45,12 +48,17 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), fmt.Sprintf("WEBHOOKURL: %v", fWebhookUrl), + fmt.Sprintf("WEBHOOKCONTENTTYPE: %v", fWebhookContentType), fmt.Sprintf("WEBHOOKDATA: %v", fWebhookData), }, "\n")) deployer, err := provider.NewDeployer(&provider.DeployerConfig{ - WebhookUrl: fWebhookUrl, - WebhookData: fWebhookData, + WebhookUrl: fWebhookUrl, + WebhookData: fWebhookData, + Method: "POST", + Headers: map[string]string{ + "Content-Type": fWebhookContentType, + }, AllowInsecureConnections: true, }) if err != nil { diff --git a/internal/pkg/core/notifier/providers/bark/bark.go b/internal/pkg/core/notifier/providers/bark/bark.go index c25ae3f9..ccdd5736 100644 --- a/internal/pkg/core/notifier/providers/bark/bark.go +++ b/internal/pkg/core/notifier/providers/bark/bark.go @@ -32,6 +32,7 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { return &NotifierProvider{ config: config, + logger: slog.Default(), }, nil } diff --git a/internal/pkg/core/notifier/providers/dingtalk/dingtalk.go b/internal/pkg/core/notifier/providers/dingtalk/dingtalk.go index 4f9964e9..9eb94dcf 100644 --- a/internal/pkg/core/notifier/providers/dingtalk/dingtalk.go +++ b/internal/pkg/core/notifier/providers/dingtalk/dingtalk.go @@ -2,7 +2,9 @@ package dingtalk import ( "context" + "fmt" "log/slog" + "net/url" "github.com/nikoksr/notify/service/dingding" @@ -10,8 +12,8 @@ import ( ) type NotifierConfig struct { - // 钉钉机器人的 Token。 - AccessToken string `json:"accessToken"` + // 钉钉机器人的 Webhook 地址。 + WebhookUrl string `json:"webhookUrl"` // 钉钉机器人的 Secret。 Secret string `json:"secret"` } @@ -30,6 +32,7 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { return &NotifierProvider{ config: config, + logger: slog.Default(), }, nil } @@ -43,8 +46,13 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { } func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { + webhookUrl, err := url.Parse(n.config.WebhookUrl) + if err != nil { + return nil, fmt.Errorf("invalid webhook url: %w", err) + } + srv := dingding.New(&dingding.Config{ - Token: n.config.AccessToken, + Token: webhookUrl.Query().Get("access_token"), Secret: n.config.Secret, }) diff --git a/internal/pkg/core/notifier/providers/email/email.go b/internal/pkg/core/notifier/providers/email/email.go index d16603fa..69d39012 100644 --- a/internal/pkg/core/notifier/providers/email/email.go +++ b/internal/pkg/core/notifier/providers/email/email.go @@ -19,7 +19,7 @@ type NotifierConfig struct { // 零值时根据是否启用 TLS 决定。 SmtpPort int32 `json:"smtpPort"` // 是否启用 TLS。 - SmtpTLS bool `json:"smtpTLS"` + SmtpTls bool `json:"smtpTls"` // 用户名。 Username string `json:"username"` // 密码。 @@ -44,6 +44,7 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { return &NotifierProvider{ config: config, + logger: slog.Default(), }, nil } @@ -64,7 +65,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s var smtpAddr string if n.config.SmtpPort == 0 { - if n.config.SmtpTLS { + if n.config.SmtpTls { smtpAddr = fmt.Sprintf("%s:465", n.config.SmtpHost) } else { smtpAddr = fmt.Sprintf("%s:25", n.config.SmtpHost) @@ -74,7 +75,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s } var yak *mailyak.MailYak - if n.config.SmtpTLS { + if n.config.SmtpTls { yak, err = mailyak.NewWithTLS(smtpAddr, smtpAuth, newTlsConfig()) if err != nil { return nil, err diff --git a/internal/pkg/core/notifier/providers/email/email_test.go b/internal/pkg/core/notifier/providers/email/email_test.go index 1828a674..30bfba07 100644 --- a/internal/pkg/core/notifier/providers/email/email_test.go +++ b/internal/pkg/core/notifier/providers/email/email_test.go @@ -67,7 +67,7 @@ func TestNotify(t *testing.T) { notifier, err := provider.NewNotifier(&provider.NotifierConfig{ SmtpHost: fSmtpHost, SmtpPort: int32(fSmtpPort), - SmtpTLS: fSmtpTLS, + SmtpTls: fSmtpTLS, Username: fUsername, Password: fPassword, SenderAddress: fSenderAddress, diff --git a/internal/pkg/core/notifier/providers/gotify/gotify.go b/internal/pkg/core/notifier/providers/gotify/gotify.go index ad0c515e..aed6e7c8 100644 --- a/internal/pkg/core/notifier/providers/gotify/gotify.go +++ b/internal/pkg/core/notifier/providers/gotify/gotify.go @@ -9,25 +9,21 @@ import ( "log/slog" "net/http" - "github.com/pkg/errors" - "github.com/usual2970/certimate/internal/pkg/core/notifier" ) type NotifierConfig struct { - // Gotify 服务地址 - // 示例:https://gotify.example.com + // Gotify 服务地址。 Url string `json:"url"` - // Gotify Token + // Gotify Token。 Token string `json:"token"` - // Gotify 消息优先级 - Priority int64 `json:"priority"` + // Gotify 消息优先级。 + Priority int64 `json:"priority,omitempty"` } type NotifierProvider struct { - config *NotifierConfig - logger *slog.Logger - // 未来将移除 + config *NotifierConfig + logger *slog.Logger httpClient *http.Client } @@ -40,6 +36,7 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { return &NotifierProvider{ config: config, + logger: slog.Default(), httpClient: http.DefaultClient, }, nil } @@ -54,7 +51,6 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { } func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - // Gotify 原生实现, notify 库没有实现, 等待合并 reqBody := &struct { Title string `json:"title"` Message string `json:"message"` @@ -65,10 +61,9 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s Priority: n.config.Priority, } - // Make request body, err := json.Marshal(reqBody) if err != nil { - return nil, errors.Wrap(err, "encode message body") + return nil, fmt.Errorf("gotify api error: failed to encode message body: %w", err) } req, err := http.NewRequestWithContext( @@ -78,27 +73,24 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s bytes.NewReader(body), ) if err != nil { - return nil, errors.Wrap(err, "create new request") + return nil, fmt.Errorf("gotify api error: failed to create new request: %w", err) } req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", n.config.Token)) req.Header.Set("Content-Type", "application/json; charset=utf-8") - // Send request to gotify service resp, err := n.httpClient.Do(req) if err != nil { - return nil, errors.Wrapf(err, "send request to gotify server") + return nil, fmt.Errorf("gotify api error: failed to send request: %w", err) } defer resp.Body.Close() - // Read response and verify success result, err := io.ReadAll(resp.Body) if err != nil { - return nil, errors.Wrap(err, "read response") + return nil, fmt.Errorf("gotify api error: failed to read response: %w", err) + } else if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("gotify api error: unexpected status code: %d, resp: %s", resp.StatusCode, string(result)) } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("gotify returned status code %d: %s", resp.StatusCode, string(result)) - } return ¬ifier.NotifyResult{}, nil } diff --git a/internal/pkg/core/notifier/providers/lark/lark.go b/internal/pkg/core/notifier/providers/lark/lark.go index 09cc6444..e8ad7816 100644 --- a/internal/pkg/core/notifier/providers/lark/lark.go +++ b/internal/pkg/core/notifier/providers/lark/lark.go @@ -28,6 +28,7 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { return &NotifierProvider{ config: config, + logger: slog.Default(), }, nil } diff --git a/internal/pkg/core/notifier/providers/mattermost/mattermost.go b/internal/pkg/core/notifier/providers/mattermost/mattermost.go index c6ed748d..ed3a507a 100644 --- a/internal/pkg/core/notifier/providers/mattermost/mattermost.go +++ b/internal/pkg/core/notifier/providers/mattermost/mattermost.go @@ -7,20 +7,21 @@ import ( "io" "log/slog" "net/http" + "strings" "github.com/nikoksr/notify/service/mattermost" "github.com/usual2970/certimate/internal/pkg/core/notifier" ) type NotifierConfig struct { - // Mattermost 服务地址。 + // 服务地址。 ServerUrl string `json:"serverUrl"` - // 频道ID - ChannelId string `json:"channelId"` - // 用户名 + // 用户名。 Username string `json:"username"` - // 密码 + // 密码。 Password string `json:"password"` + // 频道 ID。 + ChannelId string `json:"channelId"` } type NotifierProvider struct { @@ -37,6 +38,7 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { return &NotifierProvider{ config: config, + logger: slog.Default(), }, nil } @@ -50,7 +52,7 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { } func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - srv := mattermost.New(n.config.ServerUrl) + srv := mattermost.New(strings.TrimRight(n.config.ServerUrl, "/")) if err := srv.LoginWithCredentials(ctx, n.config.Username, n.config.Password); err != nil { return nil, err diff --git a/internal/pkg/core/notifier/providers/pushover/pushover.go b/internal/pkg/core/notifier/providers/pushover/pushover.go index 8f84dfd2..f306df1f 100644 --- a/internal/pkg/core/notifier/providers/pushover/pushover.go +++ b/internal/pkg/core/notifier/providers/pushover/pushover.go @@ -9,20 +9,19 @@ import ( "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 + // Pushover API Token。 + Token string `json:"token"` + // 用户或分组标识。 + User string `json:"user"` } type NotifierProvider struct { - config *NotifierConfig - logger *slog.Logger - // 未来将移除 + config *NotifierConfig + logger *slog.Logger httpClient *http.Client } @@ -35,6 +34,7 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { return &NotifierProvider{ config: config, + logger: slog.Default(), httpClient: http.DefaultClient, }, nil } @@ -48,10 +48,8 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { return n } -// Notify 发送通知 -// 参考文档:https://pushover.net/api func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - // 请求体 + // REF: https://pushover.net/api reqBody := &struct { Token string `json:"token"` User string `json:"user"` @@ -64,10 +62,9 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s Message: message, } - // Make request body, err := json.Marshal(reqBody) if err != nil { - return nil, errors.Wrap(err, "encode message body") + return nil, fmt.Errorf("pushover api error: failed to encode message body: %w", err) } req, err := http.NewRequestWithContext( @@ -77,25 +74,22 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s bytes.NewReader(body), ) if err != nil { - return nil, errors.Wrap(err, "create new request") + return nil, fmt.Errorf("pushover api error: failed to create new request: %w", err) } 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") + return nil, fmt.Errorf("pushover api error: failed to send request: %w", err) } 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 nil, fmt.Errorf("pushover api error: failed to read response: %w", err) + } else if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("pushover api error: unexpected status code: %d, resp: %s", resp.StatusCode, string(result)) } return ¬ifier.NotifyResult{}, nil diff --git a/internal/pkg/core/notifier/providers/pushplus/pushplus.go b/internal/pkg/core/notifier/providers/pushplus/pushplus.go index 4edac14e..a0ef4c7f 100644 --- a/internal/pkg/core/notifier/providers/pushplus/pushplus.go +++ b/internal/pkg/core/notifier/providers/pushplus/pushplus.go @@ -9,20 +9,17 @@ import ( "log/slog" "net/http" - "github.com/pkg/errors" - "github.com/usual2970/certimate/internal/pkg/core/notifier" ) type NotifierConfig struct { - // PushPlus Token + // PushPlus Token。 Token string `json:"token"` } type NotifierProvider struct { - config *NotifierConfig - logger *slog.Logger - // 未来将移除 + config *NotifierConfig + logger *slog.Logger httpClient *http.Client } @@ -35,6 +32,7 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { return &NotifierProvider{ config: config, + logger: slog.Default(), httpClient: http.DefaultClient, }, nil } @@ -48,10 +46,8 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { return n } -// Notify 发送通知 -// 参考文档:https://pushplus.plus/doc/guide/api.html func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - // 请求体 + // REF: https://pushplus.plus/doc/guide/api.html reqBody := &struct { Token string `json:"token"` Title string `json:"title"` @@ -62,10 +58,9 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s Content: message, } - // Make request body, err := json.Marshal(reqBody) if err != nil { - return nil, errors.Wrap(err, "encode message body") + return nil, fmt.Errorf("pushplus api error: failed to encode message body: %w", err) } req, err := http.NewRequestWithContext( @@ -75,38 +70,32 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s bytes.NewReader(body), ) if err != nil { - return nil, errors.Wrap(err, "create new request") + return nil, fmt.Errorf("pushplus api error: failed to create new request: %w", err) } req.Header.Set("Content-Type", "application/json; charset=utf-8") - // Send request to pushplus service resp, err := n.httpClient.Do(req) if err != nil { - return nil, errors.Wrapf(err, "send request to pushplus server") + return nil, fmt.Errorf("pushplus api error: failed to send request: %w", err) } defer resp.Body.Close() result, err := io.ReadAll(resp.Body) if err != nil { - return nil, errors.Wrap(err, "read response") + return nil, fmt.Errorf("pushplus api error: failed to read response: %w", err) + } else if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("pushplus api error: unexpected status code: %d, resp: %s", resp.StatusCode, string(result)) } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("pushplus returned status code %d: %s", resp.StatusCode, string(result)) - } - - // 解析响应 var errorResponse struct { Code int `json:"code"` Msg string `json:"msg"` } if err := json.Unmarshal(result, &errorResponse); err != nil { - return nil, errors.Wrap(err, "decode response") - } - - if errorResponse.Code != 200 { - return nil, fmt.Errorf("pushplus returned error: %s", errorResponse.Msg) + return nil, fmt.Errorf("pushplus api error: failed to decode response: %w", err) + } else if errorResponse.Code != 200 { + return nil, fmt.Errorf("pushplus api error: unexpected response code: %d, msg: %s", errorResponse.Code, errorResponse.Msg) } return ¬ifier.NotifyResult{}, nil diff --git a/internal/pkg/core/notifier/providers/serverchan/serverchan.go b/internal/pkg/core/notifier/providers/serverchan/serverchan.go index 9cf45431..89724b08 100644 --- a/internal/pkg/core/notifier/providers/serverchan/serverchan.go +++ b/internal/pkg/core/notifier/providers/serverchan/serverchan.go @@ -29,6 +29,7 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { return &NotifierProvider{ config: config, + logger: slog.Default(), }, nil } diff --git a/internal/pkg/core/notifier/providers/telegram/telegram.go b/internal/pkg/core/notifier/providers/telegram/telegram.go index 631369e6..218f7ee3 100644 --- a/internal/pkg/core/notifier/providers/telegram/telegram.go +++ b/internal/pkg/core/notifier/providers/telegram/telegram.go @@ -10,8 +10,8 @@ import ( ) type NotifierConfig struct { - // Telegram API Token。 - ApiToken string `json:"apiToken"` + // Telegram Bot API Token。 + BotToken string `json:"botToken"` // Telegram Chat ID。 ChatId int64 `json:"chatId"` } @@ -30,6 +30,7 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { return &NotifierProvider{ config: config, + logger: slog.Default(), }, nil } @@ -43,7 +44,7 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { } func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - srv, err := telegram.New(n.config.ApiToken) + srv, err := telegram.New(n.config.BotToken) if err != nil { return nil, err } diff --git a/internal/pkg/core/notifier/providers/telegram/telegram_test.go b/internal/pkg/core/notifier/providers/telegram/telegram_test.go index 8a83fa4b..e9a7d10b 100644 --- a/internal/pkg/core/notifier/providers/telegram/telegram_test.go +++ b/internal/pkg/core/notifier/providers/telegram/telegram_test.go @@ -45,7 +45,7 @@ func TestNotify(t *testing.T) { }, "\n")) notifier, err := provider.NewNotifier(&provider.NotifierConfig{ - ApiToken: fApiToken, + BotToken: fApiToken, ChatId: fChartId, }) if err != nil { diff --git a/internal/pkg/core/notifier/providers/webhook/webhook.go b/internal/pkg/core/notifier/providers/webhook/webhook.go index f2dfb4b6..0e7caaa5 100644 --- a/internal/pkg/core/notifier/providers/webhook/webhook.go +++ b/internal/pkg/core/notifier/providers/webhook/webhook.go @@ -3,24 +3,37 @@ package webhook import ( "context" "crypto/tls" + "encoding/json" + "fmt" "log/slog" "net/http" + "net/url" + "strings" + "time" - webhook "github.com/nikoksr/notify/service/http" + "github.com/go-resty/resty/v2" "github.com/usual2970/certimate/internal/pkg/core/notifier" ) type NotifierConfig struct { // Webhook URL。 - Url string `json:"url"` + WebhookUrl string `json:"webhookUrl"` + // Webhook 回调数据(application/json 或 application/x-www-form-urlencoded 格式)。 + WebhookData string `json:"webhookData,omitempty"` + // 请求谓词。 + // 零值时默认为 "POST"。 + Method string `json:"method,omitempty"` + // 请求标头。 + Headers map[string]string `json:"headers,omitempty"` // 是否允许不安全的连接。 AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } type NotifierProvider struct { - config *NotifierConfig - logger *slog.Logger + config *NotifierConfig + logger *slog.Logger + httpClient *resty.Client } var _ notifier.Notifier = (*NotifierProvider)(nil) @@ -30,8 +43,18 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { panic("config is nil") } + client := resty.New(). + SetTimeout(30 * time.Second). + SetRetryCount(3). + SetRetryWaitTime(5 * time.Second) + if config.AllowInsecureConnections { + client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) + } + return &NotifierProvider{ - config: config, + config: config, + logger: slog.Default(), + httpClient: client, }, nil } @@ -45,20 +68,120 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { } func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - srv := webhook.New() - srv.AddReceiversURLs(n.config.Url) - - if n.config.AllowInsecureConnections { - tlsConfig := &tls.Config{InsecureSkipVerify: true} - transport := &http.Transport{TLSClientConfig: tlsConfig} - client := &http.Client{Transport: transport} - srv.WithClient(client) - } - - err = srv.Send(ctx, subject, message) + // 处理 Webhook URL + webhookUrl, err := url.Parse(n.config.WebhookUrl) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to parse webhook url: %w", err) + } else if webhookUrl.Scheme != "http" && webhookUrl.Scheme != "https" { + return nil, fmt.Errorf("unsupported webhook url scheme '%s'", webhookUrl.Scheme) } + // 处理 Webhook 请求谓词 + webhookMethod := strings.ToUpper(n.config.Method) + if webhookMethod == "" { + webhookMethod = http.MethodPost + } else if webhookMethod != http.MethodGet && + webhookMethod != http.MethodPost && + webhookMethod != http.MethodPut && + webhookMethod != http.MethodPatch && + webhookMethod != http.MethodDelete { + return nil, fmt.Errorf("unsupported webhook request method '%s'", webhookMethod) + } + + // 处理 Webhook 请求标头 + webhookHeaders := make(http.Header) + for k, v := range n.config.Headers { + webhookHeaders.Set(k, v) + } + + // 处理 Webhook 请求内容类型 + const CONTENT_TYPE_JSON = "application/json" + const CONTENT_TYPE_FORM = "application/x-www-form-urlencoded" + const CONTENT_TYPE_MULTIPART = "multipart/form-data" + webhookContentType := webhookHeaders.Get("Content-Type") + if webhookContentType == "" { + webhookContentType = CONTENT_TYPE_JSON + webhookHeaders.Set("Content-Type", CONTENT_TYPE_JSON) + } else if strings.HasPrefix(webhookContentType, CONTENT_TYPE_JSON) && + strings.HasPrefix(webhookContentType, CONTENT_TYPE_FORM) && + strings.HasPrefix(webhookContentType, CONTENT_TYPE_MULTIPART) { + return nil, fmt.Errorf("unsupported webhook content type '%s'", webhookContentType) + } + + // 处理 Webhook 请求数据 + var webhookData interface{} + if n.config.WebhookData == "" { + webhookData = map[string]string{ + "subject": subject, + "message": message, + } + } else { + err = json.Unmarshal([]byte(n.config.WebhookData), &webhookData) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err) + } + + replaceJsonValueRecursively(webhookData, "${SUBJECT}", subject) + replaceJsonValueRecursively(webhookData, "${MESSAGE}", message) + + if webhookMethod == http.MethodGet || webhookContentType == CONTENT_TYPE_FORM || webhookContentType == CONTENT_TYPE_MULTIPART { + temp := make(map[string]string) + jsonb, err := json.Marshal(webhookData) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err) + } else if err := json.Unmarshal(jsonb, &temp); err != nil { + return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err) + } else { + webhookData = temp + } + } + } + + // 生成请求 + // 其中 GET 请求需转换为查询参数 + req := n.httpClient.R(). + SetContext(ctx). + SetHeaderMultiValues(webhookHeaders) + req.URL = webhookUrl.String() + req.Method = webhookMethod + if webhookMethod == http.MethodGet { + req.SetQueryParams(webhookData.(map[string]string)) + } else { + switch webhookContentType { + case CONTENT_TYPE_JSON: + req.SetBody(webhookData) + case CONTENT_TYPE_FORM: + req.SetFormData(webhookData.(map[string]string)) + case CONTENT_TYPE_MULTIPART: + req.SetMultipartFormData(webhookData.(map[string]string)) + } + } + + // 发送请求 + resp, err := req.Send() + if err != nil { + return nil, fmt.Errorf("failed to send webhook request: %w", err) + } else if resp.IsError() { + return nil, fmt.Errorf("unexpected webhook response status code: %d", resp.StatusCode()) + } + + n.logger.Debug("webhook responded", slog.Any("response", resp.String())) + return ¬ifier.NotifyResult{}, nil } + +func replaceJsonValueRecursively(data interface{}, oldStr, newStr string) interface{} { + switch v := data.(type) { + case map[string]any: + for k, val := range v { + v[k] = replaceJsonValueRecursively(val, oldStr, newStr) + } + case []any: + for i, val := range v { + v[i] = replaceJsonValueRecursively(val, oldStr, newStr) + } + case string: + return strings.ReplaceAll(v, oldStr, newStr) + } + return data +} diff --git a/internal/pkg/core/notifier/providers/webhook/webhook_test.go b/internal/pkg/core/notifier/providers/webhook/webhook_test.go index ffe25593..c416b3c9 100644 --- a/internal/pkg/core/notifier/providers/webhook/webhook_test.go +++ b/internal/pkg/core/notifier/providers/webhook/webhook_test.go @@ -15,19 +15,24 @@ const ( mockMessage = "test_message" ) -var fUrl string +var ( + fWebhookUrl string + fWebhookContentType string +) func init() { argsPrefix := "CERTIMATE_NOTIFIER_WEBHOOK_" - flag.StringVar(&fUrl, argsPrefix+"URL", "", "") + flag.StringVar(&fWebhookUrl, argsPrefix+"URL", "", "") + flag.StringVar(&fWebhookContentType, argsPrefix+"CONTENTTYPE", "application/json", "") } /* Shell command to run this test: go test -v ./webhook_test.go -args \ - --CERTIMATE_NOTIFIER_WEBHOOK_URL="https://example.com/your-webhook-url" + --CERTIMATE_NOTIFIER_WEBHOOK_URL="https://example.com/your-webhook-url" \ + --CERTIMATE_NOTIFIER_WEBHOOK_CONTENTTYPE="application/json" */ func TestNotify(t *testing.T) { flag.Parse() @@ -35,11 +40,15 @@ func TestNotify(t *testing.T) { t.Run("Notify", func(t *testing.T) { t.Log(strings.Join([]string{ "args:", - fmt.Sprintf("URL: %v", fUrl), + fmt.Sprintf("URL: %v", fWebhookUrl), }, "\n")) notifier, err := provider.NewNotifier(&provider.NotifierConfig{ - Url: fUrl, + WebhookUrl: fWebhookUrl, + Method: "POST", + Headers: map[string]string{ + "Content-Type": fWebhookContentType, + }, AllowInsecureConnections: true, }) if err != nil { diff --git a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go index e4817ff8..e5a0b0ba 100644 --- a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go +++ b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go @@ -93,6 +93,12 @@ func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPEM string, searchWebsiteSSLPageNumber := int32(1) searchWebsiteSSLPageSize := int32(100) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + searchWebsiteSSLReq := &opsdk.SearchWebsiteSSLRequest{ Page: searchWebsiteSSLPageNumber, PageSize: searchWebsiteSSLPageSize, diff --git a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go index 487ee8b5..9d7be223 100644 --- a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go +++ b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go @@ -71,6 +71,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE listUserCertificateOrderPage := int64(1) listUserCertificateOrderLimit := int64(50) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listUserCertificateOrderReq := &alicas.ListUserCertificateOrderRequest{ CurrentPage: tea.Int64(listUserCertificateOrderPage), ShowSize: tea.Int64(listUserCertificateOrderLimit), diff --git a/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go b/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go index 8babb671..f808083c 100644 --- a/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go +++ b/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go @@ -74,6 +74,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE var listCertificatesNextToken *string = nil listCertificatesMaxItems := int32(1000) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listCertificatesReq := &awsacm.ListCertificatesInput{ NextToken: listCertificatesNextToken, MaxItems: aws.Int32(listCertificatesMaxItems), diff --git a/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go b/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go index e94655df..1235893c 100644 --- a/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go +++ b/internal/pkg/core/uploader/providers/byteplus-cdn/byteplus_cdn.go @@ -74,6 +74,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE Source: bytepluscdn.GetStrPtr("cert_center"), } for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listCertInfoResp, err := u.sdkClient.ListCertInfo(listCertInfoReq) u.logger.Debug("sdk request 'cdn.ListCertInfo'", slog.Any("request", listCertInfoReq), slog.Any("response", listCertInfoResp)) if err != nil { diff --git a/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go index 9a7d74b1..9369144e 100644 --- a/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go @@ -76,6 +76,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE listCertificatesLimit := int32(2000) var listCertificatesMarker *string = nil for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listCertificatesReq := &hcelbmodel.ListCertificatesRequest{ Limit: typeutil.ToPtr(listCertificatesLimit), Marker: listCertificatesMarker, diff --git a/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go b/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go index a26b471a..f8435733 100644 --- a/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go +++ b/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go @@ -72,6 +72,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE listCertificatesLimit := int32(50) listCertificatesOffset := int32(0) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listCertificatesReq := &hcscmmodel.ListCertificatesRequest{ Limit: typeutil.ToPtr(listCertificatesLimit), Offset: typeutil.ToPtr(listCertificatesOffset), diff --git a/internal/pkg/core/uploader/providers/huaweicloud-waf/huaweicloud_waf.go b/internal/pkg/core/uploader/providers/huaweicloud-waf/huaweicloud_waf.go index 9e3bbd59..d0c61775 100644 --- a/internal/pkg/core/uploader/providers/huaweicloud-waf/huaweicloud_waf.go +++ b/internal/pkg/core/uploader/providers/huaweicloud-waf/huaweicloud_waf.go @@ -77,6 +77,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE listCertificatesPage := int32(1) listCertificatesPageSize := int32(100) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listCertificatesReq := &hcwafmodel.ListCertificatesRequest{ Page: typeutil.ToPtr(listCertificatesPage), Pagesize: typeutil.ToPtr(listCertificatesPageSize), diff --git a/internal/pkg/core/uploader/providers/jdcloud-ssl/jdcloud_ssl.go b/internal/pkg/core/uploader/providers/jdcloud-ssl/jdcloud_ssl.go index a0cbb1d9..b26755a6 100644 --- a/internal/pkg/core/uploader/providers/jdcloud-ssl/jdcloud_ssl.go +++ b/internal/pkg/core/uploader/providers/jdcloud-ssl/jdcloud_ssl.go @@ -77,6 +77,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE describeCertsPageNumber := 1 describeCertsPageSize := 10 for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + describeCertsReq := jdsslapi.NewDescribeCertsRequest() describeCertsReq.SetDomainName(certX509.Subject.CommonName) describeCertsReq.SetPageNumber(describeCertsPageNumber) diff --git a/internal/pkg/core/uploader/providers/rainyun-sslcenter/rainyun_sslcenter.go b/internal/pkg/core/uploader/providers/rainyun-sslcenter/rainyun_sslcenter.go index 1cfdecc7..cb493110 100644 --- a/internal/pkg/core/uploader/providers/rainyun-sslcenter/rainyun_sslcenter.go +++ b/internal/pkg/core/uploader/providers/rainyun-sslcenter/rainyun_sslcenter.go @@ -93,6 +93,12 @@ func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPEM string) sslCenterListPage := int32(1) sslCenterListPerPage := int32(100) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + sslCenterListReq := &rainyunsdk.SslCenterListRequest{ Filters: &rainyunsdk.SslCenterListFilters{ Domain: &certX509.Subject.CommonName, diff --git a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go index eb4ce1bf..90eb1683 100644 --- a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go +++ b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go @@ -124,6 +124,12 @@ func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPEM string) getCertificateListPage := int(1) getCertificateListLimit := int(1000) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + getCertificateListReq := u.sdkClient.NewGetCertificateListRequest() getCertificateListReq.Mode = ucloud.String("trust") getCertificateListReq.Domain = ucloud.String(certX509.Subject.CommonName) diff --git a/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go b/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go index 48e03722..b529e84a 100644 --- a/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go +++ b/internal/pkg/core/uploader/providers/volcengine-cdn/volcengine_cdn.go @@ -75,6 +75,12 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE Source: "volc_cert_center", } for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + listCertInfoResp, err := u.sdkClient.ListCertInfo(listCertInfoReq) u.logger.Debug("sdk request 'cdn.ListCertInfo'", slog.Any("request", listCertInfoReq), slog.Any("response", listCertInfoResp)) if err != nil { diff --git a/internal/pkg/utils/http/parser.go b/internal/pkg/utils/http/parser.go new file mode 100644 index 00000000..872fb6b5 --- /dev/null +++ b/internal/pkg/utils/http/parser.go @@ -0,0 +1,33 @@ +package httputil + +import ( + "bufio" + "net/http" + "net/textproto" + "strings" +) + +// 从表示 HTTP 标头的字符串解析并返回一个 http.Header 对象。 +// +// 入参: +// - headers: 表示 HTTP 标头的字符串。 +// +// 出参: +// - header: http.Header 对象。 +// - err: 错误。 +func ParseHeaders(headers string) (http.Header, error) { + str := strings.TrimSpace(headers) + "\r\n\r\n" + if len(str) == 4 { + return make(http.Header), nil + } + + br := bufio.NewReader(strings.NewReader(str)) + tp := textproto.NewReader(br) + + mimeHeader, err := tp.ReadMIMEHeader() + if err != nil { + return nil, err + } + + return http.Header(mimeHeader), err +} diff --git a/internal/pkg/utils/map/getter.go b/internal/pkg/utils/map/getter.go index c4e6fbe1..f30f6d33 100644 --- a/internal/pkg/utils/map/getter.go +++ b/internal/pkg/utils/map/getter.go @@ -199,6 +199,28 @@ func GetOrDefaultBool(dict map[string]any, key string, defaultValue bool) bool { return defaultValue } +// 以 `map[string]V` 形式从字典中获取指定键的值。 +// +// 入参: +// - dict: 字典。 +// - key: 键。 +// +// 出参: +// - 字典中键对应的 `map[string]V` 对象。 +func GetKVMap[V any](dict map[string]any, key string) map[string]V { + if dict == nil { + return make(map[string]V) + } + + if val, ok := dict[key]; ok { + if result, ok := val.(map[string]V); ok { + return result + } + } + + return make(map[string]V) +} + // 以 `map[string]any` 形式从字典中获取指定键的值。 // // 入参: @@ -207,16 +229,6 @@ func GetOrDefaultBool(dict map[string]any, key string, defaultValue bool) bool { // // 出参: // - 字典中键对应的 `map[string]any` 对象。 -func GetAnyMap(dict map[string]any, key string) map[string]any { - if dict == nil { - return make(map[string]any) - } - - if val, ok := dict[key]; ok { - if result, ok := val.(map[string]any); ok { - return result - } - } - - return make(map[string]any) +func GetKVMapAny(dict map[string]any, key string) map[string]any { + return GetKVMap[any](dict, key) } diff --git a/internal/repository/access.go b/internal/repository/access.go index d25a6366..16cc7378 100644 --- a/internal/repository/access.go +++ b/internal/repository/access.go @@ -53,6 +53,7 @@ func (r *AccessRepository) castRecordToModel(record *core.Record) (*domain.Acces Name: record.GetString("name"), Provider: record.GetString("provider"), Config: config, + Reserve: record.GetString("reserve"), } return access, nil } diff --git a/internal/workflow/dispatcher/invoker.go b/internal/workflow/dispatcher/invoker.go index 5f344458..c644b26b 100644 --- a/internal/workflow/dispatcher/invoker.go +++ b/internal/workflow/dispatcher/invoker.go @@ -47,8 +47,10 @@ func (w *workflowInvoker) GetLogs() domain.WorkflowLogs { func (w *workflowInvoker) processNode(ctx context.Context, node *domain.WorkflowNode) error { current := node for current != nil { - if ctx.Err() != nil { + select { + case <-ctx.Done(): return ctx.Err() + default: } if current.Type == domain.WorkflowNodeTypeBranch || current.Type == domain.WorkflowNodeTypeExecuteResultBranch { diff --git a/internal/workflow/node-processor/apply_node.go b/internal/workflow/node-processor/apply_node.go index d9415cb9..97b7575d 100644 --- a/internal/workflow/node-processor/apply_node.go +++ b/internal/workflow/node-processor/apply_node.go @@ -41,22 +41,25 @@ func (n *applyNode) Process(ctx context.Context) error { } // 检测是否可以跳过本次执行 - if skippable, skipReason := n.checkCanSkip(ctx, lastOutput); skippable { - n.logger.Info(fmt.Sprintf("skip this application, because %s", skipReason)) + if skippable, reason := n.checkCanSkip(ctx, lastOutput); skippable { + n.logger.Info(fmt.Sprintf("skip this application, because %s", reason)) return nil - } else if skipReason != "" { - n.logger.Info(fmt.Sprintf("re-apply, because %s", skipReason)) + } else if reason != "" { + n.logger.Info(fmt.Sprintf("re-apply, because %s", reason)) } // 初始化申请器 - applicant, err := applicant.NewWithApplyNode(n.node) + applicant, err := applicant.NewWithWorkflowNode(applicant.ApplicantWithWorkflowNodeConfig{ + Node: n.node, + Logger: n.logger, + }) if err != nil { n.logger.Warn("failed to create applicant provider") return err } // 申请证书 - applyResult, err := applicant.Apply() + applyResult, err := applicant.Apply(ctx) if err != nil { n.logger.Warn("failed to apply") return err diff --git a/internal/workflow/node-processor/deploy_node.go b/internal/workflow/node-processor/deploy_node.go index edb3c53d..d60a5a7a 100644 --- a/internal/workflow/node-processor/deploy_node.go +++ b/internal/workflow/node-processor/deploy_node.go @@ -54,26 +54,27 @@ func (n *deployNode) Process(ctx context.Context) error { // 检测是否可以跳过本次执行 if lastOutput != nil && certificate.CreatedAt.Before(lastOutput.UpdatedAt) { - if skippable, skipReason := n.checkCanSkip(ctx, lastOutput); skippable { - n.logger.Info(fmt.Sprintf("skip this deployment, because %s", skipReason)) + if skippable, reason := n.checkCanSkip(ctx, lastOutput); skippable { + n.logger.Info(fmt.Sprintf("skip this deployment, because %s", reason)) return nil - } else if skipReason != "" { - n.logger.Info(fmt.Sprintf("re-deploy, because %s", skipReason)) + } else if reason != "" { + n.logger.Info(fmt.Sprintf("re-deploy, because %s", reason)) } } // 初始化部署器 - deployer, err := deployer.NewWithDeployNode(n.node, struct { - Certificate string - PrivateKey string - }{Certificate: certificate.Certificate, PrivateKey: certificate.PrivateKey}) + deployer, err := deployer.NewWithWorkflowNode(deployer.DeployerWithWorkflowNodeConfig{ + Node: n.node, + Logger: n.logger, + CertificatePEM: certificate.Certificate, + PrivateKeyPEM: certificate.PrivateKey, + }) if err != nil { n.logger.Warn("failed to create deployer provider") return err } // 部署证书 - deployer.SetLogger(n.logger) if err := deployer.Deploy(ctx); err != nil { n.logger.Warn("failed to deploy") return err diff --git a/internal/workflow/node-processor/notify_node.go b/internal/workflow/node-processor/notify_node.go index 1c2b49d8..1840938b 100644 --- a/internal/workflow/node-processor/notify_node.go +++ b/internal/workflow/node-processor/notify_node.go @@ -30,25 +30,50 @@ func (n *notifyNode) Process(ctx context.Context) error { nodeConfig := n.node.GetConfigForNotify() - // 获取通知配置 - settings, err := n.settingsRepo.GetByName(ctx, "notifyChannels") + if nodeConfig.Provider == "" { + // Deprecated: v0.4.x 将废弃 + // 兼容旧版本的通知渠道 + n.logger.Warn("WARNING! you are using the notification channel from global settings, which will be deprecated in the future") + + // 获取通知配置 + settings, err := n.settingsRepo.GetByName(ctx, "notifyChannels") + if err != nil { + return err + } + + // 获取通知渠道 + channelConfig, err := settings.GetNotifyChannelConfig(nodeConfig.Channel) + if err != nil { + return err + } + + // 发送通知 + if err := notify.SendToChannel(nodeConfig.Subject, nodeConfig.Message, nodeConfig.Channel, channelConfig); err != nil { + n.logger.Warn("failed to notify", slog.String("channel", nodeConfig.Channel)) + return err + } + + n.logger.Info("notify completed") + return nil + } + + // 初始化通知器 + deployer, err := notify.NewWithWorkflowNode(notify.NotifierWithWorkflowNodeConfig{ + Node: n.node, + Logger: n.logger, + Subject: nodeConfig.Subject, + Message: nodeConfig.Message, + }) if err != nil { + n.logger.Warn("failed to create notifier provider") return err } - // 获取通知渠道 - channelConfig, err := settings.GetNotifyChannelConfig(nodeConfig.Channel) - if err != nil { + // 推送通知 + if err := deployer.Notify(ctx); err != nil { + n.logger.Warn("failed to notify") return err } - // 发送通知 - if err := notify.SendToChannel(nodeConfig.Subject, nodeConfig.Message, nodeConfig.Channel, channelConfig); err != nil { - n.logger.Warn("failed to notify", slog.String("channel", nodeConfig.Channel)) - return err - } - - n.logger.Info("notify completed") - return nil } diff --git a/internal/workflow/node-processor/upload_node.go b/internal/workflow/node-processor/upload_node.go index 891f2978..2da19eed 100644 --- a/internal/workflow/node-processor/upload_node.go +++ b/internal/workflow/node-processor/upload_node.go @@ -39,11 +39,11 @@ func (n *uploadNode) Process(ctx context.Context) error { } // 检测是否可以跳过本次执行 - if skippable, skipReason := n.checkCanSkip(ctx, lastOutput); skippable { - n.logger.Info(fmt.Sprintf("skip this upload, because %s", skipReason)) + if skippable, reason := n.checkCanSkip(ctx, lastOutput); skippable { + n.logger.Info(fmt.Sprintf("skip this upload, because %s", reason)) return nil - } else if skipReason != "" { - n.logger.Info(fmt.Sprintf("re-upload, because %s", skipReason)) + } else if reason != "" { + n.logger.Info(fmt.Sprintf("re-upload, because %s", reason)) } // 生成证书实体 diff --git a/migrations/1745726400_upgrade.go b/migrations/1745726400_upgrade.go new file mode 100644 index 00000000..e4449f36 --- /dev/null +++ b/migrations/1745726400_upgrade.go @@ -0,0 +1,88 @@ +package migrations + +import ( + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + // update collection `access` + { + collection, err := app.FindCollectionByNameOrId("4yzbv8urny5ja1e") + if err != nil { + return err + } + + if err := collection.Fields.AddMarshaledJSONAt(4, []byte(`{ + "autogeneratePattern": "", + "hidden": false, + "id": "text2859962647", + "max": 0, + "min": 0, + "name": "reserve", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }`)); err != nil { + return err + } + + if err := app.Save(collection); err != nil { + return err + } + } + + // migrate data + { + accesses, err := app.FindAllRecords("access") + if err != nil { + return err + } + + for _, access := range accesses { + changed := false + + if access.GetString("provider") == "buypass" { + access.Set("reserve", "ca") + changed = true + } else if access.GetString("provider") == "googletrustservices" { + access.Set("reserve", "ca") + changed = true + } else if access.GetString("provider") == "sslcom" { + access.Set("reserve", "ca") + changed = true + } else if access.GetString("provider") == "zerossl" { + access.Set("reserve", "ca") + changed = true + } + + if access.GetString("provider") == "webhook" { + config := make(map[string]any) + if err := access.UnmarshalJSONField("config", &config); err != nil { + return err + } + + config["method"] = "POST" + config["headers"] = "Content-Type: application/json" + access.Set("config", config) + changed = true + } + + if changed { + err = app.Save(access) + if err != nil { + return err + } + } + } + } + + return nil + }, func(app core.App) error { + return nil + }) +} diff --git a/ui/public/imgs/providers/dingtalk.svg b/ui/public/imgs/providers/dingtalk.svg new file mode 100644 index 00000000..24d3881a --- /dev/null +++ b/ui/public/imgs/providers/dingtalk.svg @@ -0,0 +1 @@ + diff --git a/ui/public/imgs/providers/email.svg b/ui/public/imgs/providers/email.svg new file mode 100644 index 00000000..a74f0c41 --- /dev/null +++ b/ui/public/imgs/providers/email.svg @@ -0,0 +1 @@ + diff --git a/ui/public/imgs/providers/lark.svg b/ui/public/imgs/providers/lark.svg new file mode 100644 index 00000000..ab32e57c --- /dev/null +++ b/ui/public/imgs/providers/lark.svg @@ -0,0 +1 @@ + diff --git a/ui/public/imgs/providers/mattermost.svg b/ui/public/imgs/providers/mattermost.svg new file mode 100644 index 00000000..18cc2819 --- /dev/null +++ b/ui/public/imgs/providers/mattermost.svg @@ -0,0 +1 @@ + diff --git a/ui/public/imgs/providers/telegram.svg b/ui/public/imgs/providers/telegram.svg new file mode 100644 index 00000000..f4cd38c1 --- /dev/null +++ b/ui/public/imgs/providers/telegram.svg @@ -0,0 +1 @@ + diff --git a/ui/public/imgs/providers/wecom.svg b/ui/public/imgs/providers/wecom.svg new file mode 100644 index 00000000..e99e59a7 --- /dev/null +++ b/ui/public/imgs/providers/wecom.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/access/AccessEditDrawer.tsx b/ui/src/components/access/AccessEditDrawer.tsx index 48002af7..7fa2f5d8 100644 --- a/ui/src/components/access/AccessEditDrawer.tsx +++ b/ui/src/components/access/AccessEditDrawer.tsx @@ -14,14 +14,14 @@ export type AccessEditDrawerProps = { data?: AccessFormProps["initialValues"]; loading?: boolean; open?: boolean; - range?: AccessFormProps["range"]; scene: AccessFormProps["scene"]; trigger?: React.ReactNode; + usage?: AccessFormProps["usage"]; onOpenChange?: (open: boolean) => void; afterSubmit?: (record: AccessModel) => void; }; -const AccessEditDrawer = ({ data, loading, trigger, scene, range, afterSubmit, ...props }: AccessEditDrawerProps) => { +const AccessEditDrawer = ({ data, loading, trigger, scene, usage, afterSubmit, ...props }: AccessEditDrawerProps) => { const { t } = useTranslation(); const [notificationApi, NotificationContextHolder] = notification.useNotification(); @@ -109,7 +109,7 @@ const AccessEditDrawer = ({ data, loading, trigger, scene, range, afterSubmit, . width={720} onClose={() => setOpen(false)} > - + ); diff --git a/ui/src/components/access/AccessEditModal.tsx b/ui/src/components/access/AccessEditModal.tsx index 0d181877..9cfb5b0f 100644 --- a/ui/src/components/access/AccessEditModal.tsx +++ b/ui/src/components/access/AccessEditModal.tsx @@ -14,14 +14,14 @@ export type AccessEditModalProps = { data?: AccessFormProps["initialValues"]; loading?: boolean; open?: boolean; - range?: AccessFormProps["range"]; + usage?: AccessFormProps["usage"]; scene: AccessFormProps["scene"]; trigger?: React.ReactNode; onOpenChange?: (open: boolean) => void; afterSubmit?: (record: AccessModel) => void; }; -const AccessEditModal = ({ data, loading, trigger, scene, range, afterSubmit, ...props }: AccessEditModalProps) => { +const AccessEditModal = ({ data, loading, trigger, scene, usage, afterSubmit, ...props }: AccessEditModalProps) => { const { t } = useTranslation(); const [notificationApi, NotificationContextHolder] = notification.useNotification(); @@ -105,7 +105,7 @@ const AccessEditModal = ({ data, loading, trigger, scene, range, afterSubmit, .. onCancel={handleCancelClick} >
- +
diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index a00b0f2e..85ad8a78 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -25,10 +25,12 @@ import AccessFormCloudflareConfig from "./AccessFormCloudflareConfig"; import AccessFormClouDNSConfig from "./AccessFormClouDNSConfig"; import AccessFormCMCCCloudConfig from "./AccessFormCMCCCloudConfig"; import AccessFormDeSECConfig from "./AccessFormDeSECConfig"; +import AccessFormDingTalkBotConfig from "./AccessFormDingTalkBotConfig"; import AccessFormDNSLAConfig from "./AccessFormDNSLAConfig"; import AccessFormDogeCloudConfig from "./AccessFormDogeCloudConfig"; import AccessFormDynv6Config from "./AccessFormDynv6Config"; import AccessFormEdgioConfig from "./AccessFormEdgioConfig"; +import AccessFormEmailConfig from "./AccessFormEmailConfig"; import AccessFormGcoreConfig from "./AccessFormGcoreConfig"; import AccessFormGnameConfig from "./AccessFormGnameConfig"; import AccessFormGoDaddyConfig from "./AccessFormGoDaddyConfig"; @@ -36,6 +38,8 @@ import AccessFormGoogleTrustServicesConfig from "./AccessFormGoogleTrustServices import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig"; import AccessFormJDCloudConfig from "./AccessFormJDCloudConfig"; import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig"; +import AccessFormLarkBotConfig from "./AccessFormLarkBotConfig"; +import AccessFormMattermostConfig from "./AccessFormMattermostConfig"; import AccessFormNamecheapConfig from "./AccessFormNamecheapConfig"; import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig"; import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig"; @@ -47,6 +51,7 @@ import AccessFormRainYunConfig from "./AccessFormRainYunConfig"; import AccessFormSafeLineConfig from "./AccessFormSafeLineConfig"; import AccessFormSSHConfig from "./AccessFormSSHConfig"; import AccessFormSSLComConfig from "./AccessFormSSLComConfig"; +import AccessFormTelegramConfig from "./AccessFormTelegramConfig"; import AccessFormTencentCloudConfig from "./AccessFormTencentCloudConfig"; import AccessFormUCloudConfig from "./AccessFormUCloudConfig"; import AccessFormUpyunConfig from "./AccessFormUpyunConfig"; @@ -54,20 +59,21 @@ import AccessFormVercelConfig from "./AccessFormVercelConfig"; import AccessFormVolcEngineConfig from "./AccessFormVolcEngineConfig"; import AccessFormWangsuConfig from "./AccessFormWangsuConfig"; import AccessFormWebhookConfig from "./AccessFormWebhookConfig"; +import AccessFormWeComBotConfig from "./AccessFormWeComBotConfig"; import AccessFormWestcnConfig from "./AccessFormWestcnConfig"; import AccessFormZeroSSLConfig from "./AccessFormZeroSSLConfig"; type AccessFormFieldValues = Partial>; -type AccessFormRanges = "both-dns-hosting" | "ca-only" | "notify-only"; type AccessFormScenes = "add" | "edit"; +type AccessFormUsages = "both-dns-hosting" | "ca-only" | "notification-only"; export type AccessFormProps = { className?: string; style?: React.CSSProperties; disabled?: boolean; initialValues?: AccessFormFieldValues; - range?: AccessFormRanges; scene: AccessFormScenes; + usage?: AccessFormUsages; onValuesChange?: (values: AccessFormFieldValues) => void; }; @@ -77,7 +83,7 @@ export type AccessFormInstance = { validateFields: FormInstance["validateFields"]; }; -const AccessForm = forwardRef(({ className, style, disabled, initialValues, range, scene, onValuesChange }, ref) => { +const AccessForm = forwardRef(({ className, style, disabled, initialValues, usage, scene, onValuesChange }, ref) => { const { t } = useTranslation(); const formSchema = z.object({ @@ -88,13 +94,14 @@ const AccessForm = forwardRef(({ className, .trim(), provider: z.nativeEnum(ACCESS_PROVIDERS, { message: - range === "ca-only" + usage === "ca-only" ? t("access.form.certificate_authority.placeholder") - : range === "notify-only" + : usage === "notification-only" ? t("access.form.notification_channel.placeholder") : t("access.form.provider.placeholder"), }), config: z.any(), + reserve: z.string().nullish(), }); const formRule = createSchemaFieldRule(formSchema); const { form: formInst, formProps } = useAntdForm({ @@ -102,33 +109,33 @@ const AccessForm = forwardRef(({ className, }); const providerLabel = useMemo(() => { - switch (range) { + switch (usage) { case "ca-only": return t("access.form.certificate_authority.label"); - case "notify-only": + case "notification-only": return t("access.form.notification_channel.label"); } return t("access.form.provider.label"); - }, [range]); + }, [usage]); const providerPlaceholder = useMemo(() => { - switch (range) { + switch (usage) { case "ca-only": return t("access.form.certificate_authority.placeholder"); - case "notify-only": + case "notification-only": return t("access.form.notification_channel.placeholder"); } return t("access.form.provider.placeholder"); - }, [range]); + }, [usage]); const providerTooltip = useMemo(() => { - switch (range) { + switch (usage) { case "both-dns-hosting": return ; } return undefined; - }, [range]); + }, [usage]); const fieldProvider = Form.useWatch("provider", formInst); @@ -179,6 +186,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.DESEC: return ; + case ACCESS_PROVIDERS.DINGTALKBOT: + return ; case ACCESS_PROVIDERS.DNSLA: return ; case ACCESS_PROVIDERS.DOGECLOUD: @@ -195,12 +204,18 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.EDGIO: return ; + case ACCESS_PROVIDERS.EMAIL: + return ; case ACCESS_PROVIDERS.HUAWEICLOUD: return ; case ACCESS_PROVIDERS.JDCLOUD: return ; case ACCESS_PROVIDERS.KUBERNETES: return ; + case ACCESS_PROVIDERS.LARKBOT: + return ; + case ACCESS_PROVIDERS.MATTERMOST: + return ; case ACCESS_PROVIDERS.NAMECHEAP: return ; case ACCESS_PROVIDERS.NAMEDOTCOM: @@ -221,6 +236,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.SSH: return ; + case ACCESS_PROVIDERS.TELEGRAM: + return ; case ACCESS_PROVIDERS.SSLCOM: return ; case ACCESS_PROVIDERS.TENCENTCLOUD: @@ -236,7 +253,14 @@ const AccessForm = forwardRef(({ className, case ACCESS_PROVIDERS.WANGSU: return ; case ACCESS_PROVIDERS.WEBHOOK: - return ; + return ( + + ); + case ACCESS_PROVIDERS.WECOMBOT: + return ; case ACCESS_PROVIDERS.WESTCN: return ; case ACCESS_PROVIDERS.ZEROSSL: @@ -260,6 +284,7 @@ const AccessForm = forwardRef(({ className, getFieldsValue: () => { const values = formInst.getFieldsValue(true); values.config = nestedFormInst.getFieldsValue(); + values.reserve = usage === "ca-only" ? "ca" : usage === "notification-only" ? "notification" : undefined; return values; }, resetFields: (fields) => { @@ -288,20 +313,20 @@ const AccessForm = forwardRef(({ className, { - if (range == null) return true; + if (usage == null) return true; - switch (range) { + switch (usage) { case "both-dns-hosting": return record.usages.includes(ACCESS_USAGES.DNS) || record.usages.includes(ACCESS_USAGES.HOSTING); case "ca-only": return record.usages.includes(ACCESS_USAGES.CA); - case "notify-only": + case "notification-only": return record.usages.includes(ACCESS_USAGES.NOTIFICATION); } }} disabled={scene !== "add"} placeholder={providerPlaceholder} - showOptionTags={range == null || (range === "both-dns-hosting" ? { [ACCESS_USAGES.DNS]: true, [ACCESS_USAGES.HOSTING]: true } : false)} + showOptionTags={usage == null || (usage === "both-dns-hosting" ? { [ACCESS_USAGES.DNS]: true, [ACCESS_USAGES.HOSTING]: true } : false)} showSearch={!disabled} /> diff --git a/ui/src/components/access/AccessFormDingTalkBotConfig.tsx b/ui/src/components/access/AccessFormDingTalkBotConfig.tsx new file mode 100644 index 00000000..35aebf6e --- /dev/null +++ b/ui/src/components/access/AccessFormDingTalkBotConfig.tsx @@ -0,0 +1,68 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForDingTalkBot } from "@/domain/access"; + +type AccessFormDingTalkBotConfigFieldValues = Nullish; + +export type AccessFormDingTalkBotConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormDingTalkBotConfigFieldValues; + onValuesChange?: (values: AccessFormDingTalkBotConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormDingTalkBotConfigFieldValues => { + return { + webhookUrl: "", + secret: "", + }; +}; + +const AccessFormDingTalkBotConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormDingTalkBotConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + webhookUrl: z.string().url(t("common.errmsg.url_invalid")), + secret: z.string().nonempty(t("access.form.dingtalkbot_secret.placeholder")).trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormDingTalkBotConfig; diff --git a/ui/src/components/access/AccessFormEmailConfig.tsx b/ui/src/components/access/AccessFormEmailConfig.tsx new file mode 100644 index 00000000..ae79794a --- /dev/null +++ b/ui/src/components/access/AccessFormEmailConfig.tsx @@ -0,0 +1,125 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, InputNumber, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForEmail } from "@/domain/access"; +import { validEmailAddress, validPortNumber } from "@/utils/validators"; + +type AccessFormEmailConfigFieldValues = Nullish; + +export type AccessFormEmailConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormEmailConfigFieldValues; + onValuesChange?: (values: AccessFormEmailConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormEmailConfigFieldValues => { + return { + smtpHost: "", + smtpPort: 465, + smtpTls: true, + username: "", + password: "", + }; +}; + +const AccessFormEmailConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormEmailConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + smtpHost: z + .string() + .min(1, t("access.form.email_smtp_host.placeholder")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + smtpPort: z.preprocess( + (v) => Number(v), + z.number().refine((v) => validPortNumber(v), t("common.errmsg.port_invalid")) + ), + smtpTls: z.boolean().nullish(), + username: z + .string() + .min(1, t("access.form.email_username.placeholder")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + password: z + .string() + .min(1, t("access.form.email_password.placeholder")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + defaultSenderAddress: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + return validEmailAddress(v); + }, t("common.errmsg.email_invalid")), + defaultReceiverAddress: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + return validEmailAddress(v); + }, t("common.errmsg.email_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleTlsSwitchChange = (checked: boolean) => { + const oldPort = formInst.getFieldValue("smtpPort"); + const newPort = checked && (oldPort == null || oldPort === 25) ? 465 : !checked && (oldPort == null || oldPort === 465) ? 25 : oldPort; + if (newPort !== oldPort) { + formInst.setFieldValue("smtpPort", newPort); + } + }; + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+
+
+ + + +
+ +
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +
+ ); +}; + +export default AccessFormEmailConfig; diff --git a/ui/src/components/access/AccessFormLarkBotConfig.tsx b/ui/src/components/access/AccessFormLarkBotConfig.tsx new file mode 100644 index 00000000..2a07505e --- /dev/null +++ b/ui/src/components/access/AccessFormLarkBotConfig.tsx @@ -0,0 +1,57 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForLarkBot } from "@/domain/access"; + +type AccessFormLarkBotConfigFieldValues = Nullish; + +export type AccessFormLarkBotConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormLarkBotConfigFieldValues; + onValuesChange?: (values: AccessFormLarkBotConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormLarkBotConfigFieldValues => { + return { + webhookUrl: "", + }; +}; + +const AccessFormLarkBotConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormLarkBotConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + webhookUrl: z.string().url(t("common.errmsg.url_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default AccessFormLarkBotConfig; diff --git a/ui/src/components/access/AccessFormMattermostConfig.tsx b/ui/src/components/access/AccessFormMattermostConfig.tsx new file mode 100644 index 00000000..a583cc19 --- /dev/null +++ b/ui/src/components/access/AccessFormMattermostConfig.tsx @@ -0,0 +1,79 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForMattermost } from "@/domain/access"; + +type AccessFormMattermostConfigFieldValues = Nullish; + +export type AccessFormMattermostConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormMattermostConfigFieldValues; + onValuesChange?: (values: AccessFormMattermostConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormMattermostConfigFieldValues => { + return { + serverUrl: "http://:8065/", + username: "", + password: "", + }; +}; + +const AccessFormMattermostConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormMattermostConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + serverUrl: z.string().url(t("common.errmsg.url_invalid")), + username: z.string().nonempty(t("access.form.mattermost_username.placeholder")), + password: z.string().nonempty(t("access.form.mattermost_password.placeholder")), + defaultChannelId: z.string().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + + + + + + + + + } + > + + +
+ ); +}; + +export default AccessFormMattermostConfig; diff --git a/ui/src/components/access/AccessFormSSHConfig.tsx b/ui/src/components/access/AccessFormSSHConfig.tsx index 6d3615cf..ebaeba90 100644 --- a/ui/src/components/access/AccessFormSSHConfig.tsx +++ b/ui/src/components/access/AccessFormSSHConfig.tsx @@ -31,13 +31,11 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues const { t } = useTranslation(); const formSchema = z.object({ - host: z - .string({ message: t("access.form.ssh_host.placeholder") }) - .refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")), + host: z.string().refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")), port: z.preprocess( (v) => Number(v), z - .number({ message: t("access.form.ssh_port.placeholder") }) + .number() .int(t("access.form.ssh_port.placeholder")) .refine((v) => validPortNumber(v), t("common.errmsg.port_invalid")) ), diff --git a/ui/src/components/access/AccessFormTelegramConfig.tsx b/ui/src/components/access/AccessFormTelegramConfig.tsx new file mode 100644 index 00000000..a4eccafb --- /dev/null +++ b/ui/src/components/access/AccessFormTelegramConfig.tsx @@ -0,0 +1,81 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForTelegram } from "@/domain/access"; + +type AccessFormTelegramConfigFieldValues = Nullish; + +export type AccessFormTelegramConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormTelegramConfigFieldValues; + onValuesChange?: (values: AccessFormTelegramConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormTelegramConfigFieldValues => { + return { + botToken: "", + }; +}; + +const AccessFormTelegramConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormTelegramConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + botToken: z + .string({ message: t("access.form.telegram_bot_token.placeholder") }) + .min(1, t("access.form.telegram_bot_token.placeholder")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + defaultChatId: z + .preprocess( + (v) => (v == null || v === "" ? undefined : Number(v)), + z + .number() + .nullish() + .refine((v) => { + if (v == null || v + "" === "") return true; + return /^\d+$/.test(v + "") && +v! > 0; + }, t("access.form.telegram_default_chat_id.placeholder")) + ) + .nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormTelegramConfig; diff --git a/ui/src/components/access/AccessFormWeComBotConfig.tsx b/ui/src/components/access/AccessFormWeComBotConfig.tsx new file mode 100644 index 00000000..16a205f5 --- /dev/null +++ b/ui/src/components/access/AccessFormWeComBotConfig.tsx @@ -0,0 +1,57 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForWeComBot } from "@/domain/access"; + +type AccessFormWeComBotConfigFieldValues = Nullish; + +export type AccessFormWeComBotConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormWeComBotConfigFieldValues; + onValuesChange?: (values: AccessFormWeComBotConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormWeComBotConfigFieldValues => { + return { + webhookUrl: "", + }; +}; + +const AccessFormWeComBotConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormWeComBotConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + webhookUrl: z.string().url(t("common.errmsg.url_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default AccessFormWeComBotConfig; diff --git a/ui/src/components/access/AccessFormWebhookConfig.tsx b/ui/src/components/access/AccessFormWebhookConfig.tsx index 89280d79..6a07553a 100644 --- a/ui/src/components/access/AccessFormWebhookConfig.tsx +++ b/ui/src/components/access/AccessFormWebhookConfig.tsx @@ -1,8 +1,10 @@ import { useTranslation } from "react-i18next"; -import { Form, type FormInstance, Input, Switch } from "antd"; +import { DownOutlined as DownOutlinedIcon } from "@ant-design/icons"; +import { Alert, Button, Dropdown, Form, type FormInstance, Input, Select, Switch } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; +import Show from "@/components/Show"; import { type AccessConfigForWebhook } from "@/domain/access"; type AccessFormWebhookConfigFieldValues = Nullish; @@ -12,24 +14,241 @@ export type AccessFormWebhookConfigProps = { formName: string; disabled?: boolean; initialValues?: AccessFormWebhookConfigFieldValues; + usage?: "deployment" | "notification" | "none"; onValuesChange?: (values: AccessFormWebhookConfigFieldValues) => void; }; const initFormModel = (): AccessFormWebhookConfigFieldValues => { return { url: "", + method: "POST", + headers: "Content-Type: application/json", + allowInsecureConnections: false, + defaultDataForDeployment: JSON.stringify( + { + name: "${DOMAINS}", + cert: "${CERTIFICATE}", + privkey: "${PRIVATE_KEY}", + }, + null, + 2 + ), + defaultDataForNotification: JSON.stringify( + { + subject: "${SUBJECT}", + message: "${MESSAGE}", + }, + null, + 2 + ), }; }; -const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormWebhookConfigProps) => { +const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialValues, usage, onValuesChange }: AccessFormWebhookConfigProps) => { const { t } = useTranslation(); const formSchema = z.object({ - url: z.string({ message: t("access.form.webhook_url.placeholder") }).url(t("common.errmsg.url_invalid")), + url: z.string().url(t("common.errmsg.url_invalid")), + method: z.union([z.literal("GET"), z.literal("POST"), z.literal("PUT"), z.literal("PATCH"), z.literal("DELETE")], { + message: t("access.form.webhook_method.placeholder"), + }), + headers: 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("access.form.webhook_headers.errmsg.invalid")), allowInsecureConnections: z.boolean().nullish(), + defaultDataForDeployment: z + .string() + .nullish() + .refine((v) => { + if (usage && usage !== "deployment") return true; + if (!v) return true; + + try { + const obj = JSON.parse(v); + return typeof obj === "object" && !Array.isArray(obj); + } catch { + return false; + } + }, t("access.form.webhook_default_data.errmsg.json_invalid")), + defaultDataForNotification: z + .string() + .nullish() + .refine((v) => { + if (usage && usage !== "notification") return true; + if (!v) return true; + + try { + const obj = JSON.parse(v); + return typeof obj === "object" && !Array.isArray(obj); + } catch { + return false; + } + }, t("access.form.webhook_default_data.errmsg.json_invalid")), }); const formRule = createSchemaFieldRule(formSchema); + const handleWebhookHeadersBlur = (e: React.FocusEvent) => { + let value = e.target.value; + value = value.trim(); + value = value.replace(/(?) => { + const value = e.target.value; + try { + const json = JSON.stringify(JSON.parse(value), null, 2); + formInst.setFieldValue("defaultDataForDeployment", json); + } catch { + return; + } + }; + + const handleWebhookDataForNotificationBlur = (e: React.FocusEvent) => { + const value = e.target.value; + try { + const json = JSON.stringify(JSON.parse(value), null, 2); + formInst.setFieldValue("defaultDataForNotification", json); + } catch { + return; + } + }; + + const handlePresetDataForDeploymentClick = () => { + formInst.setFieldValue("defaultDataForDeployment", initFormModel().defaultDataForDeployment); + }; + + const handlePresetDataForNotificationClick = (key: string) => { + switch (key) { + case "bark": + formInst.setFieldValue("url", "https://api.day.app/push"); + formInst.setFieldValue("method", "POST"); + formInst.setFieldValue("headers", "Content-Type: application/json\r\nAuthorization: Bearer "); + formInst.setFieldValue( + "defaultDataForNotification", + JSON.stringify( + { + title: "${SUBJECT}", + body: "${MESSAGE}", + group: "", + device_keys: "", + }, + null, + 2 + ) + ); + break; + + case "gotify": + formInst.setFieldValue("url", "https:///"); + formInst.setFieldValue("method", "POST"); + formInst.setFieldValue("headers", "Content-Type: application/json\r\nAuthorization: Bearer "); + formInst.setFieldValue( + "defaultDataForNotification", + JSON.stringify( + { + title: "${SUBJECT}", + message: "${MESSAGE}", + priority: 1, + }, + null, + 2 + ) + ); + break; + + case "ntfy": + formInst.setFieldValue("url", "https:///"); + formInst.setFieldValue("method", "POST"); + formInst.setFieldValue("headers", "Content-Type: application/json"); + formInst.setFieldValue( + "defaultDataForNotification", + JSON.stringify( + { + topic: "", + title: "${SUBJECT}", + message: "${MESSAGE}", + priority: 1, + }, + null, + 2 + ) + ); + break; + + case "pushover": + formInst.setFieldValue("url", "https://api.pushover.net/1/messages.json"); + formInst.setFieldValue("method", "POST"); + formInst.setFieldValue("headers", "Content-Type: application/json"); + formInst.setFieldValue( + "defaultDataForNotification", + JSON.stringify( + { + token: "", + user: "", + title: "${SUBJECT}", + message: "${MESSAGE}", + }, + null, + 2 + ) + ); + break; + + case "pushplus": + formInst.setFieldValue("url", "https://www.pushplus.plus/send"); + formInst.setFieldValue("method", "POST"); + formInst.setFieldValue("headers", "Content-Type: application/json"); + formInst.setFieldValue( + "defaultDataForNotification", + JSON.stringify( + { + token: "", + title: "${SUBJECT}", + content: "${MESSAGE}", + }, + null, + 2 + ) + ); + break; + + case "serverchan": + formInst.setFieldValue("url", "https://sctapi.ftqq.com/.send"); + formInst.setFieldValue("method", "POST"); + formInst.setFieldValue("headers", "Content-Type: application/json"); + formInst.setFieldValue( + "defaultDataForNotification", + JSON.stringify( + { + text: "${SUBJECT}", + desp: "${MESSAGE}", + }, + null, + 2 + ) + ); + break; + + default: + formInst.setFieldValue("method", "POST"); + formInst.setFieldValue("headers", "Content-Type: application/json"); + formInst.setFieldValue("defaultDataForNotification", initFormModel().defaultDataForNotification); + break; + } + }; + const handleFormChange = (_: unknown, values: z.infer) => { onValuesChange?.(values); }; @@ -47,6 +266,92 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa + + - + - + diff --git a/ui/src/components/provider/ApplyDNSProviderPicker.tsx b/ui/src/components/provider/ACMEDns01ProviderPicker.tsx similarity index 86% rename from ui/src/components/provider/ApplyDNSProviderPicker.tsx rename to ui/src/components/provider/ACMEDns01ProviderPicker.tsx index bcf65965..7f99cf6a 100644 --- a/ui/src/components/provider/ApplyDNSProviderPicker.tsx +++ b/ui/src/components/provider/ACMEDns01ProviderPicker.tsx @@ -3,9 +3,9 @@ import { useTranslation } from "react-i18next"; import { Avatar, Card, Col, Empty, Flex, Input, type InputRef, Row, Typography } from "antd"; import Show from "@/components/Show"; -import { applyDNSProvidersMap } from "@/domain/provider"; +import { acmeDns01ProvidersMap } from "@/domain/provider"; -export type ApplyDNSProviderPickerProps = { +export type ACMEDns01ProviderPickerProps = { className?: string; style?: React.CSSProperties; autoFocus?: boolean; @@ -13,7 +13,7 @@ export type ApplyDNSProviderPickerProps = { onSelect?: (value: string) => void; }; -const ApplyDNSProviderPicker = ({ className, style, autoFocus, placeholder, onSelect }: ApplyDNSProviderPickerProps) => { +const ACMEDns01ProviderPicker = ({ className, style, autoFocus, placeholder, onSelect }: ACMEDns01ProviderPickerProps) => { const { t } = useTranslation(); const [keyword, setKeyword] = useState(); @@ -25,7 +25,7 @@ const ApplyDNSProviderPicker = ({ className, style, autoFocus, placeholder, onSe }, []); const providers = useMemo(() => { - return Array.from(applyDNSProvidersMap.values()).filter((provider) => { + return Array.from(acmeDns01ProvidersMap.values()).filter((provider) => { if (keyword) { const value = keyword.toLowerCase(); return provider.type.toLowerCase().includes(value) || t(provider.name).toLowerCase().includes(value); @@ -72,4 +72,4 @@ const ApplyDNSProviderPicker = ({ className, style, autoFocus, placeholder, onSe ); }; -export default memo(ApplyDNSProviderPicker); +export default memo(ACMEDns01ProviderPicker); diff --git a/ui/src/components/provider/ApplyDNSProviderSelect.tsx b/ui/src/components/provider/ACMEDns01ProviderSelect.tsx similarity index 77% rename from ui/src/components/provider/ApplyDNSProviderSelect.tsx rename to ui/src/components/provider/ACMEDns01ProviderSelect.tsx index f7e3c259..b03adf7b 100644 --- a/ui/src/components/provider/ApplyDNSProviderSelect.tsx +++ b/ui/src/components/provider/ACMEDns01ProviderSelect.tsx @@ -2,21 +2,21 @@ import { memo, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Avatar, Select, type SelectProps, Space, Typography } from "antd"; -import { type ApplyDNSProvider, applyDNSProvidersMap } from "@/domain/provider"; +import { type ACMEDns01Provider, acmeDns01ProvidersMap } from "@/domain/provider"; -export type ApplyDNSProviderSelectProps = Omit< +export type ACMEDns01ProviderSelectProps = Omit< SelectProps, "filterOption" | "filterSort" | "labelRender" | "options" | "optionFilterProp" | "optionLabelProp" | "optionRender" > & { - filter?: (record: ApplyDNSProvider) => boolean; + filter?: (record: ACMEDns01Provider) => boolean; }; -const ApplyDNSProviderSelect = ({ filter, ...props }: ApplyDNSProviderSelectProps) => { +const ACMEDns01ProviderSelect = ({ filter, ...props }: ACMEDns01ProviderSelectProps) => { const { t } = useTranslation(); - const [options, setOptions] = useState>([]); + const [options, setOptions] = useState>([]); useEffect(() => { - const allItems = Array.from(applyDNSProvidersMap.values()); + const allItems = Array.from(acmeDns01ProvidersMap.values()); const filteredItems = filter != null ? allItems.filter(filter) : allItems; setOptions( filteredItems.map((item) => ({ @@ -29,7 +29,7 @@ const ApplyDNSProviderSelect = ({ filter, ...props }: ApplyDNSProviderSelectProp }, [filter]); const renderOption = (key: string) => { - const provider = applyDNSProvidersMap.get(key); + const provider = acmeDns01ProvidersMap.get(key); return ( @@ -64,4 +64,4 @@ const ApplyDNSProviderSelect = ({ filter, ...props }: ApplyDNSProviderSelectProp ); }; -export default memo(ApplyDNSProviderSelect); +export default memo(ACMEDns01ProviderSelect); diff --git a/ui/src/components/provider/ApplyCAProviderSelect.tsx b/ui/src/components/provider/CAProviderSelect.tsx similarity index 79% rename from ui/src/components/provider/ApplyCAProviderSelect.tsx rename to ui/src/components/provider/CAProviderSelect.tsx index fdedac9b..15d31230 100644 --- a/ui/src/components/provider/ApplyCAProviderSelect.tsx +++ b/ui/src/components/provider/CAProviderSelect.tsx @@ -2,28 +2,28 @@ import { memo, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Avatar, Select, type SelectProps, Space, Typography } from "antd"; -import { type ApplyCAProvider, applyCAProvidersMap } from "@/domain/provider"; +import { type CAProvider, caProvidersMap } from "@/domain/provider"; -export type ApplyCAProviderSelectProps = Omit< +export type CAProviderSelectProps = Omit< SelectProps, "filterOption" | "filterSort" | "labelRender" | "options" | "optionFilterProp" | "optionLabelProp" | "optionRender" > & { - filter?: (record: ApplyCAProvider) => boolean; + filter?: (record: CAProvider) => boolean; }; -const ApplyCAProviderSelect = ({ filter, ...props }: ApplyCAProviderSelectProps) => { +const CAProviderSelect = ({ filter, ...props }: CAProviderSelectProps) => { const { t } = useTranslation(); - const [options, setOptions] = useState>([]); + const [options, setOptions] = useState>([]); useEffect(() => { - const allItems = Array.from(applyCAProvidersMap.values()); + const allItems = Array.from(caProvidersMap.values()); const filteredItems = filter != null ? allItems.filter(filter) : allItems; setOptions([ { key: "", value: "", - label: "provider.default_ca_provider.label", - data: {} as ApplyCAProvider, + label: t("provider.default_ca_provider.label"), + data: {} as CAProvider, }, ...filteredItems.map((item) => ({ key: item.type, @@ -45,7 +45,7 @@ const ApplyCAProviderSelect = ({ filter, ...props }: ApplyCAProviderSelectProps) ); } - const provider = applyCAProvidersMap.get(key); + const provider = caProvidersMap.get(key); return ( @@ -80,4 +80,4 @@ const ApplyCAProviderSelect = ({ filter, ...props }: ApplyCAProviderSelectProps) ); }; -export default memo(ApplyCAProviderSelect); +export default memo(CAProviderSelect); diff --git a/ui/src/components/provider/DeployProviderPicker.tsx b/ui/src/components/provider/DeploymentProviderPicker.tsx similarity index 76% rename from ui/src/components/provider/DeployProviderPicker.tsx rename to ui/src/components/provider/DeploymentProviderPicker.tsx index 10aa3e09..8ba09b92 100644 --- a/ui/src/components/provider/DeployProviderPicker.tsx +++ b/ui/src/components/provider/DeploymentProviderPicker.tsx @@ -3,9 +3,9 @@ import { useTranslation } from "react-i18next"; import { Avatar, Card, Col, Empty, Flex, Input, type InputRef, Row, Tabs, Tooltip, Typography } from "antd"; import Show from "@/components/Show"; -import { DEPLOY_CATEGORIES, deployProvidersMap } from "@/domain/provider"; +import { DEPLOYMENT_CATEGORIES, deploymentProvidersMap } from "@/domain/provider"; -export type DeployProviderPickerProps = { +export type DeploymentProviderPickerProps = { className?: string; style?: React.CSSProperties; autoFocus?: boolean; @@ -13,10 +13,10 @@ export type DeployProviderPickerProps = { onSelect?: (value: string) => void; }; -const DeployProviderPicker = ({ className, style, autoFocus, placeholder, onSelect }: DeployProviderPickerProps) => { +const DeploymentProviderPicker = ({ className, style, autoFocus, placeholder, onSelect }: DeploymentProviderPickerProps) => { const { t } = useTranslation(); - const [category, setCategory] = useState(DEPLOY_CATEGORIES.ALL); + const [category, setCategory] = useState(DEPLOYMENT_CATEGORIES.ALL); const [keyword, setKeyword] = useState(); const keywordInputRef = useRef(null); @@ -27,9 +27,9 @@ const DeployProviderPicker = ({ className, style, autoFocus, placeholder, onSele }, []); const providers = useMemo(() => { - return Array.from(deployProvidersMap.values()) + return Array.from(deploymentProvidersMap.values()) .filter((provider) => { - if (category && category !== DEPLOY_CATEGORIES.ALL) { + if (category && category !== DEPLOYMENT_CATEGORIES.ALL) { return provider.category === category; } @@ -56,17 +56,17 @@ const DeployProviderPicker = ({ className, style, autoFocus, placeholder, onSele
({ key: key, label: t(`provider.category.${key}`), @@ -110,4 +110,4 @@ const DeployProviderPicker = ({ className, style, autoFocus, placeholder, onSele ); }; -export default memo(DeployProviderPicker); +export default memo(DeploymentProviderPicker); diff --git a/ui/src/components/provider/DeployProviderSelect.tsx b/ui/src/components/provider/DeploymentProviderSelect.tsx similarity index 77% rename from ui/src/components/provider/DeployProviderSelect.tsx rename to ui/src/components/provider/DeploymentProviderSelect.tsx index 5be10cb7..0b38cedf 100644 --- a/ui/src/components/provider/DeployProviderSelect.tsx +++ b/ui/src/components/provider/DeploymentProviderSelect.tsx @@ -2,21 +2,21 @@ import { memo, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Avatar, Select, type SelectProps, Space, Typography } from "antd"; -import { type DeployProvider, deployProvidersMap } from "@/domain/provider"; +import { type DeploymentProvider, deploymentProvidersMap } from "@/domain/provider"; -export type DeployProviderSelectProps = Omit< +export type DeploymentProviderSelectProps = Omit< SelectProps, "filterOption" | "filterSort" | "labelRender" | "options" | "optionFilterProp" | "optionLabelProp" | "optionRender" > & { - filter?: (record: DeployProvider) => boolean; + filter?: (record: DeploymentProvider) => boolean; }; -const DeployProviderSelect = ({ filter, ...props }: DeployProviderSelectProps) => { +const DeploymentProviderSelect = ({ filter, ...props }: DeploymentProviderSelectProps) => { const { t } = useTranslation(); - const [options, setOptions] = useState>([]); + const [options, setOptions] = useState>([]); useEffect(() => { - const allItems = Array.from(deployProvidersMap.values()); + const allItems = Array.from(deploymentProvidersMap.values()); const filteredItems = filter != null ? allItems.filter(filter) : allItems; setOptions( filteredItems.map((item) => ({ @@ -29,7 +29,7 @@ const DeployProviderSelect = ({ filter, ...props }: DeployProviderSelectProps) = }, [filter]); const renderOption = (key: string) => { - const provider = deployProvidersMap.get(key); + const provider = deploymentProvidersMap.get(key); return ( @@ -64,4 +64,4 @@ const DeployProviderSelect = ({ filter, ...props }: DeployProviderSelectProps) = ); }; -export default memo(DeployProviderSelect); +export default memo(DeploymentProviderSelect); diff --git a/ui/src/components/provider/NotificationProviderSelect.tsx b/ui/src/components/provider/NotificationProviderSelect.tsx new file mode 100644 index 00000000..98a1005c --- /dev/null +++ b/ui/src/components/provider/NotificationProviderSelect.tsx @@ -0,0 +1,67 @@ +import { memo, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Avatar, Select, type SelectProps, Space, Typography } from "antd"; + +import { type NotificationProvider, notificationProvidersMap } from "@/domain/provider"; + +export type NotificationProviderSelectProps = Omit< + SelectProps, + "filterOption" | "filterSort" | "labelRender" | "options" | "optionFilterProp" | "optionLabelProp" | "optionRender" +> & { + filter?: (record: NotificationProvider) => boolean; +}; + +const NotificationProviderSelect = ({ filter, ...props }: NotificationProviderSelectProps) => { + const { t } = useTranslation(); + + const [options, setOptions] = useState>([]); + useEffect(() => { + const allItems = Array.from(notificationProvidersMap.values()); + const filteredItems = filter != null ? allItems.filter(filter) : allItems; + setOptions( + filteredItems.map((item) => ({ + key: item.type, + value: item.type, + label: t(item.name), + data: item, + })) + ); + }, [filter]); + + const renderOption = (key: string) => { + const provider = notificationProvidersMap.get(key); + return ( + + + + {t(provider?.name ?? "")} + + + ); + }; + + return ( + + + + } + > + + + + ); +}; + +export default NotifyNodeConfigFormEmailConfig; diff --git a/ui/src/components/workflow/node/NotifyNodeConfigFormMattermostConfig.tsx b/ui/src/components/workflow/node/NotifyNodeConfigFormMattermostConfig.tsx new file mode 100644 index 00000000..c091298b --- /dev/null +++ b/ui/src/components/workflow/node/NotifyNodeConfigFormMattermostConfig.tsx @@ -0,0 +1,61 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type NotifyNodeConfigFormMattermostConfigFieldValues = Nullish<{ + channelId?: string; +}>; + +export type NotifyNodeConfigFormMattermostConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: NotifyNodeConfigFormMattermostConfigFieldValues; + onValuesChange?: (values: NotifyNodeConfigFormMattermostConfigFieldValues) => void; +}; + +const initFormModel = (): NotifyNodeConfigFormMattermostConfigFieldValues => { + return {}; +}; + +const NotifyNodeConfigFormMattermostConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: NotifyNodeConfigFormMattermostConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + channelId: z.string().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default NotifyNodeConfigFormMattermostConfig; diff --git a/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramConfig.tsx b/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramConfig.tsx new file mode 100644 index 00000000..07774413 --- /dev/null +++ b/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramConfig.tsx @@ -0,0 +1,66 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type NotifyNodeConfigFormTelegramConfigFieldValues = Nullish<{ + chatId?: string | number; +}>; + +export type NotifyNodeConfigFormTelegramConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: NotifyNodeConfigFormTelegramConfigFieldValues; + onValuesChange?: (values: NotifyNodeConfigFormTelegramConfigFieldValues) => void; +}; + +const initFormModel = (): NotifyNodeConfigFormTelegramConfigFieldValues => { + return {}; +}; + +const NotifyNodeConfigFormTelegramConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: NotifyNodeConfigFormTelegramConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + chatId: z + .preprocess( + (v) => (v == null || v === "" ? undefined : Number(v)), + z + .number() + .nullish() + .refine((v) => { + if (v == null || v + "" === "") return true; + return /^\d+$/.test(v + "") && +v! > 0; + }, t("workflow_node.notify.form.telegram_chat_id.placeholder")) + ) + .nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default NotifyNodeConfigFormTelegramConfig; diff --git a/ui/src/components/workflow/node/NotifyNodeConfigFormWebhookConfig.tsx b/ui/src/components/workflow/node/NotifyNodeConfigFormWebhookConfig.tsx new file mode 100644 index 00000000..acaf00c2 --- /dev/null +++ b/ui/src/components/workflow/node/NotifyNodeConfigFormWebhookConfig.tsx @@ -0,0 +1,86 @@ +import { useTranslation } from "react-i18next"; +import { Alert, Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type NotifyNodeConfigFormWebhookConfigFieldValues = Nullish<{ + webhookData: string; +}>; + +export type NotifyNodeConfigFormWebhookConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: NotifyNodeConfigFormWebhookConfigFieldValues; + onValuesChange?: (values: NotifyNodeConfigFormWebhookConfigFieldValues) => void; +}; + +const initFormModel = (): NotifyNodeConfigFormWebhookConfigFieldValues => { + return {}; +}; + +const NotifyNodeConfigFormWebhookConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: NotifyNodeConfigFormWebhookConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + webhookData: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + + try { + const obj = JSON.parse(v); + return typeof obj === "object" && !Array.isArray(obj); + } catch { + return false; + } + }, t("workflow_node.notify.form.webhook_data.errmsg.json_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleWebhookDataBlur = (e: React.FocusEvent) => { + const value = e.target.value; + try { + const json = JSON.stringify(JSON.parse(value), null, 2); + formInst.setFieldValue("webhookData", json); + } catch { + return; + } + }; + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + + } /> + +
+ ); +}; + +export default NotifyNodeConfigFormWebhookConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 2b217e11..b7c54306 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -22,10 +22,12 @@ export interface AccessModel extends BaseModel { | AccessConfigForClouDNS | AccessConfigForCMCCCloud | AccessConfigForDeSEC + | AccessConfigForDingTalkBot | AccessConfigForDNSLA | AccessConfigForDogeCloud | AccessConfigForDynv6 | AccessConfigForEdgio + | AccessConfigForEmail | AccessConfigForGcore | AccessConfigForGname | AccessConfigForGoDaddy @@ -33,6 +35,8 @@ export interface AccessModel extends BaseModel { | AccessConfigForHuaweiCloud | AccessConfigForJDCloud | AccessConfigForKubernetes + | AccessConfigForLarkBot + | AccessConfigForMattermost | AccessConfigForNamecheap | AccessConfigForNameDotCom | AccessConfigForNameSilo @@ -43,6 +47,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForSafeLine | AccessConfigForSSH | AccessConfigForSSLCom + | AccessConfigForTelegram | AccessConfigForTencentCloud | AccessConfigForUCloud | AccessConfigForUpyun @@ -50,9 +55,11 @@ export interface AccessModel extends BaseModel { | AccessConfigForVolcEngine | AccessConfigForWangsu | AccessConfigForWebhook + | AccessConfigForWeComBot | AccessConfigForWestcn | AccessConfigForZeroSSL ); + reserve?: "ca" | "notification"; } // #region AccessConfig @@ -139,6 +146,11 @@ export type AccessConfigForDeSEC = { token: string; }; +export type AccessConfigForDingTalkBot = { + webhookUrl: string; + secret?: string; +}; + export type AccessConfigForDNSLA = { apiId: string; apiSecret: string; @@ -158,6 +170,16 @@ export type AccessConfigForEdgio = { clientSecret: string; }; +export type AccessConfigForEmail = { + smtpHost: string; + smtpPort: number; + smtpTls: boolean; + username: string; + password: string; + defaultSenderAddress?: string; + defaultReceiverAddress?: string; +}; + export type AccessConfigForGcore = { apiToken: string; }; @@ -191,6 +213,17 @@ export type AccessConfigForKubernetes = { kubeConfig?: string; }; +export type AccessConfigForLarkBot = { + webhookUrl: string; +}; + +export type AccessConfigForMattermost = { + serverUrl: string; + username: string; + password: string; + defaultChannelId?: string; +}; + export type AccessConfigForNamecheap = { username: string; apiKey: string; @@ -248,6 +281,11 @@ export type AccessConfigForSSLCom = { eabHmacKey: string; }; +export type AccessConfigForTelegram = { + botToken: string; + defaultChatId?: number; +}; + export type AccessConfigForTencentCloud = { secretId: string; secretKey: string; @@ -282,7 +320,15 @@ export type AccessConfigForWangsu = { export type AccessConfigForWebhook = { url: string; + method: string; + headers?: string; allowInsecureConnections?: boolean; + defaultDataForDeployment?: string; + defaultDataForNotification?: string; +}; + +export type AccessConfigForWeComBot = { + webhookUrl: string; }; export type AccessConfigForWestcn = { diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index a91e34cf..73cc7e7d 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -21,10 +21,12 @@ export const ACCESS_PROVIDERS = Object.freeze({ CLOUDNS: "cloudns", CMCCCLOUD: "cmcccloud", DESEC: "desec", + DINGTALKBOT: "dingtalkbot", DNSLA: "dnsla", DOGECLOUD: "dogecloud", DYNV6: "dynv6", EDGIO: "edgio", + EMAIL: "email", GCORE: "gcore", GNAME: "gname", GODADDY: "godaddy", @@ -32,9 +34,11 @@ export const ACCESS_PROVIDERS = Object.freeze({ HUAWEICLOUD: "huaweicloud", JDCLOUD: "jdcloud", KUBERNETES: "k8s", + LARKBOT: "larkbot", LETSENCRYPT: "letsencrypt", LETSENCRYPTSTAGING: "letsencryptstaging", LOCAL: "local", + MATTERMOST: "mattermost", NAMECHEAP: "namecheap", NAMEDOTCOM: "namedotcom", NAMESILO: "namesilo", @@ -46,6 +50,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ SAFELINE: "safeline", SSH: "ssh", SSLCOM: "sslcom", + TELEGRAM: "telegram", TENCENTCLOUD: "tencentcloud", UCLOUD: "ucloud", UPYUN: "upyun", @@ -53,6 +58,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ VOLCENGINE: "volcengine", WANGSU: "wangsu", WEBHOOK: "webhook", + WECOMBOT: "wecombot", WESTCN: "westcn", ZEROSSL: "zerossl", } as const); @@ -137,6 +143,13 @@ export const accessProvidersMap: Map [ e[0] as string, { @@ -150,12 +163,12 @@ export const accessProvidersMap: Map = new Map( +export const caProvidersMap: Map = new Map( /* 注意:此处的顺序决定显示在前端的顺序。 NOTICE: The following order determines the order displayed at the frontend. */ [ - [APPLY_CA_PROVIDERS.LETSENCRYPT, "builtin"], - [APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING, "builtin"], - [APPLY_CA_PROVIDERS.BUYPASS], - [APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES], - [APPLY_CA_PROVIDERS.SSLCOM], - [APPLY_CA_PROVIDERS.ZEROSSL], + [CA_PROVIDERS.LETSENCRYPT, "builtin"], + [CA_PROVIDERS.LETSENCRYPTSTAGING, "builtin"], + [CA_PROVIDERS.BUYPASS], + [CA_PROVIDERS.GOOGLETRUSTSERVICES], + [CA_PROVIDERS.SSLCOM], + [CA_PROVIDERS.ZEROSSL], ].map(([type, builtin]) => [ type, { - type: type as ApplyCAProviderType, + type: type as CAProviderType, name: accessProvidersMap.get(type.split("-")[0])!.name, icon: accessProvidersMap.get(type.split("-")[0])!.icon, provider: type.split("-")[0] as AccessProviderType, @@ -199,12 +212,12 @@ export const applyCAProvidersMap: Map = new Map( +export const acmeDns01ProvidersMap: Map = new Map( /* - 注意:此处的顺序决定显示在前端的顺序。 - NOTICE: The following order determines the order displayed at the frontend. - */ + 注意:此处的顺序决定显示在前端的顺序。 + NOTICE: The following order determines the order displayed at the frontend. + */ [ - [APPLY_DNS_PROVIDERS.ALIYUN_DNS, "provider.aliyun.dns"], - [APPLY_DNS_PROVIDERS.TENCENTCLOUD_DNS, "provider.tencentcloud.dns"], - [APPLY_DNS_PROVIDERS.TENCENTCLOUD_EO, "provider.tencentcloud.eo"], - [APPLY_DNS_PROVIDERS.BAIDUCLOUD_DNS, "provider.baiducloud.dns"], - [APPLY_DNS_PROVIDERS.HUAWEICLOUD_DNS, "provider.huaweicloud.dns"], - [APPLY_DNS_PROVIDERS.VOLCENGINE_DNS, "provider.volcengine.dns"], - [APPLY_DNS_PROVIDERS.JDCLOUD_DNS, "provider.jdcloud.dns"], - [APPLY_DNS_PROVIDERS.AWS_ROUTE53, "provider.aws.route53"], - [APPLY_DNS_PROVIDERS.AZURE_DNS, "provider.azure.dns"], - [APPLY_DNS_PROVIDERS.BUNNY, "provider.bunny"], - [APPLY_DNS_PROVIDERS.CLOUDFLARE, "provider.cloudflare"], - [APPLY_DNS_PROVIDERS.CLOUDNS, "provider.cloudns"], - [APPLY_DNS_PROVIDERS.DESEC, "provider.desec"], - [APPLY_DNS_PROVIDERS.DNSLA, "provider.dnsla"], - [APPLY_DNS_PROVIDERS.DYNV6, "provider.dynv6"], - [APPLY_DNS_PROVIDERS.GCORE, "provider.gcore"], - [APPLY_DNS_PROVIDERS.GNAME, "provider.gname"], - [APPLY_DNS_PROVIDERS.GODADDY, "provider.godaddy"], - [APPLY_DNS_PROVIDERS.NAMECHEAP, "provider.namecheap"], - [APPLY_DNS_PROVIDERS.NAMEDOTCOM, "provider.namedotcom"], - [APPLY_DNS_PROVIDERS.NAMESILO, "provider.namesilo"], - [APPLY_DNS_PROVIDERS.NS1, "provider.ns1"], - [APPLY_DNS_PROVIDERS.PORKBUN, "provider.porkbun"], - [APPLY_DNS_PROVIDERS.VERCEL, "provider.vercel"], - [APPLY_DNS_PROVIDERS.CMCCCLOUD, "provider.cmcccloud"], - [APPLY_DNS_PROVIDERS.RAINYUN, "provider.rainyun"], - [APPLY_DNS_PROVIDERS.WESTCN, "provider.westcn"], - [APPLY_DNS_PROVIDERS.POWERDNS, "provider.powerdns"], - [APPLY_DNS_PROVIDERS.ACMEHTTPREQ, "provider.acmehttpreq"], + [ACME_DNS01_PROVIDERS.ALIYUN_DNS, "provider.aliyun.dns"], + [ACME_DNS01_PROVIDERS.TENCENTCLOUD_DNS, "provider.tencentcloud.dns"], + [ACME_DNS01_PROVIDERS.TENCENTCLOUD_EO, "provider.tencentcloud.eo"], + [ACME_DNS01_PROVIDERS.BAIDUCLOUD_DNS, "provider.baiducloud.dns"], + [ACME_DNS01_PROVIDERS.HUAWEICLOUD_DNS, "provider.huaweicloud.dns"], + [ACME_DNS01_PROVIDERS.VOLCENGINE_DNS, "provider.volcengine.dns"], + [ACME_DNS01_PROVIDERS.JDCLOUD_DNS, "provider.jdcloud.dns"], + [ACME_DNS01_PROVIDERS.AWS_ROUTE53, "provider.aws.route53"], + [ACME_DNS01_PROVIDERS.AZURE_DNS, "provider.azure.dns"], + [ACME_DNS01_PROVIDERS.BUNNY, "provider.bunny"], + [ACME_DNS01_PROVIDERS.CLOUDFLARE, "provider.cloudflare"], + [ACME_DNS01_PROVIDERS.CLOUDNS, "provider.cloudns"], + [ACME_DNS01_PROVIDERS.DESEC, "provider.desec"], + [ACME_DNS01_PROVIDERS.DNSLA, "provider.dnsla"], + [ACME_DNS01_PROVIDERS.DYNV6, "provider.dynv6"], + [ACME_DNS01_PROVIDERS.GCORE, "provider.gcore"], + [ACME_DNS01_PROVIDERS.GNAME, "provider.gname"], + [ACME_DNS01_PROVIDERS.GODADDY, "provider.godaddy"], + [ACME_DNS01_PROVIDERS.NAMECHEAP, "provider.namecheap"], + [ACME_DNS01_PROVIDERS.NAMEDOTCOM, "provider.namedotcom"], + [ACME_DNS01_PROVIDERS.NAMESILO, "provider.namesilo"], + [ACME_DNS01_PROVIDERS.NS1, "provider.ns1"], + [ACME_DNS01_PROVIDERS.PORKBUN, "provider.porkbun"], + [ACME_DNS01_PROVIDERS.VERCEL, "provider.vercel"], + [ACME_DNS01_PROVIDERS.CMCCCLOUD, "provider.cmcccloud"], + [ACME_DNS01_PROVIDERS.RAINYUN, "provider.rainyun"], + [ACME_DNS01_PROVIDERS.WESTCN, "provider.westcn"], + [ACME_DNS01_PROVIDERS.POWERDNS, "provider.powerdns"], + [ACME_DNS01_PROVIDERS.ACMEHTTPREQ, "provider.acmehttpreq"], ].map(([type, name]) => [ type, { - type: type as ApplyDNSProviderType, + type: type as ACMEDns01ProviderType, name: name, icon: accessProvidersMap.get(type.split("-")[0])!.icon, provider: type.split("-")[0] as AccessProviderType, @@ -300,12 +313,12 @@ export const applyDNSProvidersMap: Map = new Map( +export const deploymentProvidersMap: Map = new Map( /* 注意:此处的顺序决定显示在前端的顺序。 NOTICE: The following order determines the order displayed at the frontend. */ [ - [DEPLOY_PROVIDERS.LOCAL, "provider.local", DEPLOY_CATEGORIES.OTHER, "builtin"], - [DEPLOY_PROVIDERS.SSH, "provider.ssh", DEPLOY_CATEGORIES.OTHER], - [DEPLOY_PROVIDERS.WEBHOOK, "provider.webhook", DEPLOY_CATEGORIES.OTHER], - [DEPLOY_PROVIDERS.KUBERNETES_SECRET, "provider.kubernetes.secret", DEPLOY_CATEGORIES.OTHER], - [DEPLOY_PROVIDERS.ALIYUN_OSS, "provider.aliyun.oss", DEPLOY_CATEGORIES.STORAGE], - [DEPLOY_PROVIDERS.ALIYUN_CDN, "provider.aliyun.cdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.ALIYUN_DCDN, "provider.aliyun.dcdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.ALIYUN_ESA, "provider.aliyun.esa", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.ALIYUN_CLB, "provider.aliyun.clb", DEPLOY_CATEGORIES.LOADBALANCE], - [DEPLOY_PROVIDERS.ALIYUN_ALB, "provider.aliyun.alb", DEPLOY_CATEGORIES.LOADBALANCE], - [DEPLOY_PROVIDERS.ALIYUN_NLB, "provider.aliyun.nlb", DEPLOY_CATEGORIES.LOADBALANCE], - [DEPLOY_PROVIDERS.ALIYUN_WAF, "provider.aliyun.waf", DEPLOY_CATEGORIES.FIREWALL], - [DEPLOY_PROVIDERS.ALIYUN_LIVE, "provider.aliyun.live", DEPLOY_CATEGORIES.AV], - [DEPLOY_PROVIDERS.ALIYUN_VOD, "provider.aliyun.vod", DEPLOY_CATEGORIES.AV], - [DEPLOY_PROVIDERS.ALIYUN_FC, "provider.aliyun.fc", DEPLOY_CATEGORIES.SERVERLESS], - [DEPLOY_PROVIDERS.ALIYUN_APIGW, "provider.aliyun.apigw", DEPLOY_CATEGORIES.OTHER], - [DEPLOY_PROVIDERS.ALIYUN_CAS, "provider.aliyun.cas_upload", DEPLOY_CATEGORIES.OTHER], - [DEPLOY_PROVIDERS.ALIYUN_CAS_DEPLOY, "provider.aliyun.cas_deploy", DEPLOY_CATEGORIES.OTHER], - [DEPLOY_PROVIDERS.TENCENTCLOUD_COS, "provider.tencentcloud.cos", DEPLOY_CATEGORIES.STORAGE], - [DEPLOY_PROVIDERS.TENCENTCLOUD_CDN, "provider.tencentcloud.cdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.TENCENTCLOUD_ECDN, "provider.tencentcloud.ecdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.TENCENTCLOUD_EO, "provider.tencentcloud.eo", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.TENCENTCLOUD_CLB, "provider.tencentcloud.clb", DEPLOY_CATEGORIES.LOADBALANCE], - [DEPLOY_PROVIDERS.TENCENTCLOUD_WAF, "provider.tencentcloud.waf", DEPLOY_CATEGORIES.FIREWALL], - [DEPLOY_PROVIDERS.TENCENTCLOUD_CSS, "provider.tencentcloud.css", DEPLOY_CATEGORIES.AV], - [DEPLOY_PROVIDERS.TENCENTCLOUD_VOD, "provider.tencentcloud.vod", DEPLOY_CATEGORIES.AV], - [DEPLOY_PROVIDERS.TENCENTCLOUD_SCF, "provider.tencentcloud.scf", DEPLOY_CATEGORIES.SERVERLESS], - [DEPLOY_PROVIDERS.TENCENTCLOUD_SSL, "provider.tencentcloud.ssl_upload", DEPLOY_CATEGORIES.OTHER], - [DEPLOY_PROVIDERS.TENCENTCLOUD_SSL_DEPLOY, "provider.tencentcloud.ssl_deploy", DEPLOY_CATEGORIES.OTHER], - [DEPLOY_PROVIDERS.BAIDUCLOUD_CDN, "provider.baiducloud.cdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.BAIDUCLOUD_BLB, "provider.baiducloud.blb", DEPLOY_CATEGORIES.LOADBALANCE], - [DEPLOY_PROVIDERS.BAIDUCLOUD_APPBLB, "provider.baiducloud.appblb", DEPLOY_CATEGORIES.LOADBALANCE], - [DEPLOY_PROVIDERS.BAIDUCLOUD_CERT, "provider.baiducloud.cert_upload", DEPLOY_CATEGORIES.OTHER], - [DEPLOY_PROVIDERS.HUAWEICLOUD_CDN, "provider.huaweicloud.cdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.HUAWEICLOUD_ELB, "provider.huaweicloud.elb", DEPLOY_CATEGORIES.LOADBALANCE], - [DEPLOY_PROVIDERS.HUAWEICLOUD_WAF, "provider.huaweicloud.waf", DEPLOY_CATEGORIES.FIREWALL], - [DEPLOY_PROVIDERS.HUAWEICLOUD_SCM, "provider.huaweicloud.scm_upload", DEPLOY_CATEGORIES.OTHER], - [DEPLOY_PROVIDERS.VOLCENGINE_TOS, "provider.volcengine.tos", DEPLOY_CATEGORIES.STORAGE], - [DEPLOY_PROVIDERS.VOLCENGINE_CDN, "provider.volcengine.cdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.VOLCENGINE_DCDN, "provider.volcengine.dcdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.VOLCENGINE_CLB, "provider.volcengine.clb", 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_LIVE, "provider.volcengine.live", DEPLOY_CATEGORIES.AV], - [DEPLOY_PROVIDERS.VOLCENGINE_CERTCENTER, "provider.volcengine.certcenter_upload", DEPLOY_CATEGORIES.OTHER], - [DEPLOY_PROVIDERS.JDCLOUD_ALB, "provider.jdcloud.alb", DEPLOY_CATEGORIES.LOADBALANCE], - [DEPLOY_PROVIDERS.JDCLOUD_CDN, "provider.jdcloud.cdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.JDCLOUD_LIVE, "provider.jdcloud.live", DEPLOY_CATEGORIES.AV], - [DEPLOY_PROVIDERS.JDCLOUD_VOD, "provider.jdcloud.vod", DEPLOY_CATEGORIES.AV], - [DEPLOY_PROVIDERS.QINIU_KODO, "provider.qiniu.kodo", DEPLOY_CATEGORIES.STORAGE], - [DEPLOY_PROVIDERS.QINIU_CDN, "provider.qiniu.cdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.QINIU_PILI, "provider.qiniu.pili", DEPLOY_CATEGORIES.AV], - [DEPLOY_PROVIDERS.UPYUN_FILE, "provider.upyun.file", DEPLOY_CATEGORIES.STORAGE], - [DEPLOY_PROVIDERS.UPYUN_CDN, "provider.upyun.cdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.BAISHAN_CDN, "provider.baishan.cdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.WANGSU_CDNPRO, "provider.wangsu.cdnpro", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.DOGECLOUD_CDN, "provider.dogecloud.cdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.BYTEPLUS_CDN, "provider.byteplus.cdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.UCLOUD_US3, "provider.ucloud.us3", DEPLOY_CATEGORIES.STORAGE], - [DEPLOY_PROVIDERS.UCLOUD_UCDN, "provider.ucloud.ucdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.RAINYUN_RCDN, "provider.rainyun.rcdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.AWS_CLOUDFRONT, "provider.aws.cloudfront", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.AWS_ACM, "provider.aws.acm", DEPLOY_CATEGORIES.OTHER], - [DEPLOY_PROVIDERS.AZURE_KEYVAULT, "provider.azure.keyvault", DEPLOY_CATEGORIES.OTHER], - [DEPLOY_PROVIDERS.BUNNY_CDN, "provider.bunny.cdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.CACHEFLY, "provider.cachefly", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.CDNFLY, "provider.cdnfly", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS.EDGIO_APPLICATIONS, "provider.edgio.applications", DEPLOY_CATEGORIES.WEBSITE], - [DEPLOY_PROVIDERS.GCORE_CDN, "provider.gcore.cdn", DEPLOY_CATEGORIES.CDN], - [DEPLOY_PROVIDERS["1PANEL_SITE"], "provider.1panel.site", DEPLOY_CATEGORIES.WEBSITE], - [DEPLOY_PROVIDERS["1PANEL_CONSOLE"], "provider.1panel.console", DEPLOY_CATEGORIES.OTHER], - [DEPLOY_PROVIDERS.BAOTAPANEL_SITE, "provider.baotapanel.site", DEPLOY_CATEGORIES.WEBSITE], - [DEPLOY_PROVIDERS.BAOTAPANEL_CONSOLE, "provider.baotapanel.console", DEPLOY_CATEGORIES.OTHER], - [DEPLOY_PROVIDERS.SAFELINE, "provider.safeline", DEPLOY_CATEGORIES.FIREWALL], + [DEPLOYMENT_PROVIDERS.LOCAL, "provider.local", DEPLOYMENT_CATEGORIES.OTHER, "builtin"], + [DEPLOYMENT_PROVIDERS.SSH, "provider.ssh", DEPLOYMENT_CATEGORIES.OTHER], + [DEPLOYMENT_PROVIDERS.WEBHOOK, "provider.webhook", DEPLOYMENT_CATEGORIES.OTHER], + [DEPLOYMENT_PROVIDERS.KUBERNETES_SECRET, "provider.kubernetes.secret", DEPLOYMENT_CATEGORIES.OTHER], + [DEPLOYMENT_PROVIDERS.ALIYUN_OSS, "provider.aliyun.oss", DEPLOYMENT_CATEGORIES.STORAGE], + [DEPLOYMENT_PROVIDERS.ALIYUN_CDN, "provider.aliyun.cdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.ALIYUN_DCDN, "provider.aliyun.dcdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.ALIYUN_ESA, "provider.aliyun.esa", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.ALIYUN_CLB, "provider.aliyun.clb", DEPLOYMENT_CATEGORIES.LOADBALANCE], + [DEPLOYMENT_PROVIDERS.ALIYUN_ALB, "provider.aliyun.alb", DEPLOYMENT_CATEGORIES.LOADBALANCE], + [DEPLOYMENT_PROVIDERS.ALIYUN_NLB, "provider.aliyun.nlb", DEPLOYMENT_CATEGORIES.LOADBALANCE], + [DEPLOYMENT_PROVIDERS.ALIYUN_WAF, "provider.aliyun.waf", DEPLOYMENT_CATEGORIES.FIREWALL], + [DEPLOYMENT_PROVIDERS.ALIYUN_LIVE, "provider.aliyun.live", DEPLOYMENT_CATEGORIES.AV], + [DEPLOYMENT_PROVIDERS.ALIYUN_VOD, "provider.aliyun.vod", DEPLOYMENT_CATEGORIES.AV], + [DEPLOYMENT_PROVIDERS.ALIYUN_FC, "provider.aliyun.fc", DEPLOYMENT_CATEGORIES.SERVERLESS], + [DEPLOYMENT_PROVIDERS.ALIYUN_APIGW, "provider.aliyun.apigw", DEPLOYMENT_CATEGORIES.OTHER], + [DEPLOYMENT_PROVIDERS.ALIYUN_CAS, "provider.aliyun.cas_upload", DEPLOYMENT_CATEGORIES.OTHER], + [DEPLOYMENT_PROVIDERS.ALIYUN_CAS_DEPLOY, "provider.aliyun.cas_deploy", DEPLOYMENT_CATEGORIES.OTHER], + [DEPLOYMENT_PROVIDERS.TENCENTCLOUD_COS, "provider.tencentcloud.cos", DEPLOYMENT_CATEGORIES.STORAGE], + [DEPLOYMENT_PROVIDERS.TENCENTCLOUD_CDN, "provider.tencentcloud.cdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.TENCENTCLOUD_ECDN, "provider.tencentcloud.ecdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.TENCENTCLOUD_EO, "provider.tencentcloud.eo", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.TENCENTCLOUD_CLB, "provider.tencentcloud.clb", DEPLOYMENT_CATEGORIES.LOADBALANCE], + [DEPLOYMENT_PROVIDERS.TENCENTCLOUD_WAF, "provider.tencentcloud.waf", DEPLOYMENT_CATEGORIES.FIREWALL], + [DEPLOYMENT_PROVIDERS.TENCENTCLOUD_CSS, "provider.tencentcloud.css", DEPLOYMENT_CATEGORIES.AV], + [DEPLOYMENT_PROVIDERS.TENCENTCLOUD_VOD, "provider.tencentcloud.vod", DEPLOYMENT_CATEGORIES.AV], + [DEPLOYMENT_PROVIDERS.TENCENTCLOUD_SCF, "provider.tencentcloud.scf", DEPLOYMENT_CATEGORIES.SERVERLESS], + [DEPLOYMENT_PROVIDERS.TENCENTCLOUD_SSL, "provider.tencentcloud.ssl_upload", DEPLOYMENT_CATEGORIES.OTHER], + [DEPLOYMENT_PROVIDERS.TENCENTCLOUD_SSL_DEPLOY, "provider.tencentcloud.ssl_deploy", DEPLOYMENT_CATEGORIES.OTHER], + [DEPLOYMENT_PROVIDERS.BAIDUCLOUD_CDN, "provider.baiducloud.cdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.BAIDUCLOUD_BLB, "provider.baiducloud.blb", DEPLOYMENT_CATEGORIES.LOADBALANCE], + [DEPLOYMENT_PROVIDERS.BAIDUCLOUD_APPBLB, "provider.baiducloud.appblb", DEPLOYMENT_CATEGORIES.LOADBALANCE], + [DEPLOYMENT_PROVIDERS.BAIDUCLOUD_CERT, "provider.baiducloud.cert_upload", DEPLOYMENT_CATEGORIES.OTHER], + [DEPLOYMENT_PROVIDERS.HUAWEICLOUD_CDN, "provider.huaweicloud.cdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.HUAWEICLOUD_ELB, "provider.huaweicloud.elb", DEPLOYMENT_CATEGORIES.LOADBALANCE], + [DEPLOYMENT_PROVIDERS.HUAWEICLOUD_WAF, "provider.huaweicloud.waf", DEPLOYMENT_CATEGORIES.FIREWALL], + [DEPLOYMENT_PROVIDERS.HUAWEICLOUD_SCM, "provider.huaweicloud.scm_upload", DEPLOYMENT_CATEGORIES.OTHER], + [DEPLOYMENT_PROVIDERS.VOLCENGINE_TOS, "provider.volcengine.tos", DEPLOYMENT_CATEGORIES.STORAGE], + [DEPLOYMENT_PROVIDERS.VOLCENGINE_CDN, "provider.volcengine.cdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.VOLCENGINE_DCDN, "provider.volcengine.dcdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.VOLCENGINE_CLB, "provider.volcengine.clb", DEPLOYMENT_CATEGORIES.LOADBALANCE], + [DEPLOYMENT_PROVIDERS.VOLCENGINE_ALB, "provider.volcengine.alb", DEPLOYMENT_CATEGORIES.LOADBALANCE], + [DEPLOYMENT_PROVIDERS.VOLCENGINE_IMAGEX, "provider.volcengine.imagex", DEPLOYMENT_CATEGORIES.STORAGE], + [DEPLOYMENT_PROVIDERS.VOLCENGINE_LIVE, "provider.volcengine.live", DEPLOYMENT_CATEGORIES.AV], + [DEPLOYMENT_PROVIDERS.VOLCENGINE_CERTCENTER, "provider.volcengine.certcenter_upload", DEPLOYMENT_CATEGORIES.OTHER], + [DEPLOYMENT_PROVIDERS.JDCLOUD_ALB, "provider.jdcloud.alb", DEPLOYMENT_CATEGORIES.LOADBALANCE], + [DEPLOYMENT_PROVIDERS.JDCLOUD_CDN, "provider.jdcloud.cdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.JDCLOUD_LIVE, "provider.jdcloud.live", DEPLOYMENT_CATEGORIES.AV], + [DEPLOYMENT_PROVIDERS.JDCLOUD_VOD, "provider.jdcloud.vod", DEPLOYMENT_CATEGORIES.AV], + [DEPLOYMENT_PROVIDERS.QINIU_KODO, "provider.qiniu.kodo", DEPLOYMENT_CATEGORIES.STORAGE], + [DEPLOYMENT_PROVIDERS.QINIU_CDN, "provider.qiniu.cdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.QINIU_PILI, "provider.qiniu.pili", DEPLOYMENT_CATEGORIES.AV], + [DEPLOYMENT_PROVIDERS.UPYUN_FILE, "provider.upyun.file", DEPLOYMENT_CATEGORIES.STORAGE], + [DEPLOYMENT_PROVIDERS.UPYUN_CDN, "provider.upyun.cdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.BAISHAN_CDN, "provider.baishan.cdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.WANGSU_CDNPRO, "provider.wangsu.cdnpro", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.DOGECLOUD_CDN, "provider.dogecloud.cdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.BYTEPLUS_CDN, "provider.byteplus.cdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.UCLOUD_US3, "provider.ucloud.us3", DEPLOYMENT_CATEGORIES.STORAGE], + [DEPLOYMENT_PROVIDERS.UCLOUD_UCDN, "provider.ucloud.ucdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.RAINYUN_RCDN, "provider.rainyun.rcdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.AWS_CLOUDFRONT, "provider.aws.cloudfront", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.AWS_ACM, "provider.aws.acm", DEPLOYMENT_CATEGORIES.OTHER], + [DEPLOYMENT_PROVIDERS.AZURE_KEYVAULT, "provider.azure.keyvault", DEPLOYMENT_CATEGORIES.OTHER], + [DEPLOYMENT_PROVIDERS.BUNNY_CDN, "provider.bunny.cdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.CACHEFLY, "provider.cachefly", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.CDNFLY, "provider.cdnfly", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS.EDGIO_APPLICATIONS, "provider.edgio.applications", DEPLOYMENT_CATEGORIES.WEBSITE], + [DEPLOYMENT_PROVIDERS.GCORE_CDN, "provider.gcore.cdn", DEPLOYMENT_CATEGORIES.CDN], + [DEPLOYMENT_PROVIDERS["1PANEL_SITE"], "provider.1panel.site", DEPLOYMENT_CATEGORIES.WEBSITE], + [DEPLOYMENT_PROVIDERS["1PANEL_CONSOLE"], "provider.1panel.console", DEPLOYMENT_CATEGORIES.OTHER], + [DEPLOYMENT_PROVIDERS.BAOTAPANEL_SITE, "provider.baotapanel.site", DEPLOYMENT_CATEGORIES.WEBSITE], + [DEPLOYMENT_PROVIDERS.BAOTAPANEL_CONSOLE, "provider.baotapanel.console", DEPLOYMENT_CATEGORIES.OTHER], + [DEPLOYMENT_PROVIDERS.SAFELINE, "provider.safeline", DEPLOYMENT_CATEGORIES.FIREWALL], ].map(([type, name, category, builtin]) => [ type, { - type: type as DeployProviderType, + type: type as DeploymentProviderType, name: name, icon: accessProvidersMap.get(type.split("-")[0])!.icon, provider: type.split("-")[0] as AccessProviderType, - category: category as DeployCategoryType, + category: category as DeploymentCategoryType, builtin: builtin === "builtin", }, ]) ); // #endregion + +// #region NotificationProvider +/* + 注意:如果追加新的常量值,请保持以 ASCII 排序。 + NOTICE: If you add new constant, please keep ASCII order. + */ +export const NOTIFICATION_PROVIDERS = Object.freeze({ + DINGTALKBOT: `${ACCESS_PROVIDERS.DINGTALKBOT}`, + EMAIL: `${ACCESS_PROVIDERS.EMAIL}`, + LARKBOT: `${ACCESS_PROVIDERS.LARKBOT}`, + MATTERMOST: `${ACCESS_PROVIDERS.MATTERMOST}`, + TELEGRAM: `${ACCESS_PROVIDERS.TELEGRAM}`, + WEBHOOK: `${ACCESS_PROVIDERS.WEBHOOK}`, + WECOMBOT: `${ACCESS_PROVIDERS.WECOMBOT}`, +} as const); + +export type NotificationProviderType = (typeof CA_PROVIDERS)[keyof typeof CA_PROVIDERS]; + +export type NotificationProvider = { + type: NotificationProviderType; + name: string; + icon: string; + provider: AccessProviderType; +}; + +export const notificationProvidersMap: Map = new Map( + /* + 注意:此处的顺序决定显示在前端的顺序。 + NOTICE: The following order determines the order displayed at the frontend. + */ + [ + [NOTIFICATION_PROVIDERS.EMAIL], + [NOTIFICATION_PROVIDERS.WEBHOOK], + [NOTIFICATION_PROVIDERS.DINGTALKBOT], + [NOTIFICATION_PROVIDERS.LARKBOT], + [NOTIFICATION_PROVIDERS.WECOMBOT], + [NOTIFICATION_PROVIDERS.MATTERMOST], + [NOTIFICATION_PROVIDERS.TELEGRAM], + ].map(([type]) => [ + type, + { + type: type as CAProviderType, + name: accessProvidersMap.get(type.split("-")[0])!.name, + icon: accessProvidersMap.get(type.split("-")[0])!.icon, + provider: type.split("-")[0] as AccessProviderType, + }, + ]) +); +// #endregion diff --git a/ui/src/domain/settings.ts b/ui/src/domain/settings.ts index e789f7e3..a711f543 100644 --- a/ui/src/domain/settings.ts +++ b/ui/src/domain/settings.ts @@ -1,8 +1,11 @@ -import { type ApplyCAProviderType } from "./provider"; +import { type CAProviderType } from "./provider"; export const SETTINGS_NAMES = Object.freeze({ EMAILS: "emails", NOTIFY_TEMPLATES: "notifyTemplates", + /** + * @deprecated + */ NOTIFY_CHANNELS: "notifyChannels", SSL_PROVIDER: "sslProvider", PERSISTENCE: "persistence", @@ -38,6 +41,9 @@ export const defaultNotifyTemplate: NotifyTemplate = { // #endregion // #region Settings: NotifyChannels +/** + * @deprecated + */ export const NOTIFY_CHANNELS = Object.freeze({ BARK: "bark", DINGTALK: "dingtalk", @@ -53,8 +59,14 @@ export const NOTIFY_CHANNELS = Object.freeze({ WECOM: "wecom", } as const); +/** + * @deprecated + */ export type NotifyChannels = (typeof NOTIFY_CHANNELS)[keyof typeof NOTIFY_CHANNELS]; +/** + * @deprecated + */ export type NotifyChannelsSettingsContent = { /* 注意:如果追加新的类型,请保持以 ASCII 排序。 @@ -75,12 +87,18 @@ export type NotifyChannelsSettingsContent = { [NOTIFY_CHANNELS.WECOM]?: WeComNotifyChannelConfig; }; +/** + * @deprecated + */ export type BarkNotifyChannelConfig = { deviceKey: string; serverUrl: string; enabled?: boolean; }; +/** + * @deprecated + */ export type EmailNotifyChannelConfig = { smtpHost: string; smtpPort: number; @@ -92,12 +110,18 @@ export type EmailNotifyChannelConfig = { enabled?: boolean; }; +/** + * @deprecated + */ export type DingTalkNotifyChannelConfig = { accessToken: string; secret: string; enabled?: boolean; }; +/** + * @deprecated + */ export type GotifyNotifyChannelConfig = { url: string; token: string; @@ -105,56 +129,86 @@ export type GotifyNotifyChannelConfig = { enabled?: boolean; }; +/** + * @deprecated + */ export type LarkNotifyChannelConfig = { webhookUrl: string; enabled?: boolean; }; +/** + * @deprecated + */ export type MattermostNotifyChannelConfig = { serverUrl: string; channel: string; username: string; password: string; enabled?: boolean; -} +}; +/** + * @deprecated + */ export type PushoverNotifyChannelConfig = { token: string; user: string; enabled?: boolean; }; +/** + * @deprecated + */ export type PushPlusNotifyChannelConfig = { token: string; enabled?: boolean; }; +/** + * @deprecated + */ export type ServerChanNotifyChannelConfig = { url: string; enabled?: boolean; }; +/** + * @deprecated + */ export type TelegramNotifyChannelConfig = { apiToken: string; chatId: string; enabled?: boolean; }; +/** + * @deprecated + */ export type WebhookNotifyChannelConfig = { url: string; enabled?: boolean; }; +/** + * @deprecated + */ export type WeComNotifyChannelConfig = { webhookUrl: string; enabled?: boolean; }; +/** + * @deprecated + */ export type NotifyChannel = { type: string; name: string; }; +/** + * @deprecated + */ export const notifyChannelsMap: Map = new Map( [ [NOTIFY_CHANNELS.EMAIL, "common.notifier.email"], @@ -175,7 +229,7 @@ export const notifyChannelsMap: Map = new // #region Settings: SSLProvider export type SSLProviderSettingsContent = { - provider: ApplyCAProviderType; + provider: CAProviderType; config: { [key: string]: Record | undefined; }; diff --git a/ui/src/domain/workflow.ts b/ui/src/domain/workflow.ts index 11ea13da..06226425 100644 --- a/ui/src/domain/workflow.ts +++ b/ui/src/domain/workflow.ts @@ -154,9 +154,15 @@ export type WorkflowNodeConfigForDeploy = { }; export type WorkflowNodeConfigForNotify = { - channel: string; subject: string; message: string; + /** + * @deprecated + */ + channel?: string; + provider: string; + providerAccessId: string; + providerConfig?: Record; }; export type WorkflowNodeConfigForBranch = never; diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 2959a9b6..592d6e64 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -18,9 +18,9 @@ "access.props.provider.usage.ca": "Certificate authority", "access.props.provider.usage.notification": "Notification channel", "access.props.provider.builtin": "Built-in", - "access.props.range.both_dns_hosting": "Provider", - "access.props.range.ca_only": "Certificate authority", - "access.props.range.notify_only": "Notification channel", + "access.props.usage.both_dns_hosting": "Provider", + "access.props.usage.ca_only": "Certificate authority", + "access.props.usage.notification_only": "Notification channel", "access.props.created_at": "Created at", "access.props.updated_at": "Updated at", @@ -28,7 +28,7 @@ "access.form.name.placeholder": "Please enter authorization name", "access.form.provider.label": "Provider", "access.form.provider.placeholder": "Please select a provider", - "access.form.provider.tooltip": "DNS provider: The provider that hosts your domain names and manages your DNS records.
Host provider: The provider that hosts your servers or cloud services for deploying certificates.

Cannot be edited after saving.", + "access.form.provider.tooltip": "DNS provider: The provider that hosts your domain names and manages your DNS records.
Hosting provider: The provider that hosts your servers or cloud services for deploying certificates.

Cannot be edited after saving.", "access.form.certificate_authority.label": "Certificate authority", "access.form.certificate_authority.placeholder": "Please select a certificate authority", "access.form.notification_channel.label": "Notification channel", @@ -145,6 +145,12 @@ "access.form.desec_token.label": "deSEC token", "access.form.desec_token.placeholder": "Please enter deSEC token", "access.form.desec_token.tooltip": "For more information, see https://desec.readthedocs.io/en/latest/auth/tokens.html", + "access.form.dingtalkbot_webhook_url.label": "DingTalk bot Webhook URL", + "access.form.dingtalkbot_webhook_url.placeholder": "Please enter DingTalk bot Webhook URL", + "access.form.dingtalkbot_webhook_url.tooltip": "For more information, see https://open.dingtalk.com/document/orgapp/obtain-the-webhook-address-of-a-custom-robot", + "access.form.dingtalkbot_secret.label": "DingTalk bot secret", + "access.form.dingtalkbot_secret.placeholder": "Please enter DingTalk bot secret", + "access.form.dingtalkbot_secret.tooltip": "For more information, see https://open.dingtalk.com/document/orgapp/customize-robot-security-settings", "access.form.dnsla_api_id.label": "DNS.LA API ID", "access.form.dnsla_api_id.placeholder": "Please enter DNS.LA API ID", "access.form.dnsla_api_id.tooltip": "For more information, see https://www.dns.la/docs/ApiDoc", @@ -166,6 +172,19 @@ "access.form.edgio_client_secret.label": "Edgio ClientSecret", "access.form.edgio_client_secret.placeholder": "Please enter Edgio ClientSecret", "access.form.edgio_client_secret.tooltip": "For more information, see https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients", + "access.form.email_smtp_host.label": "SMTP host", + "access.form.email_smtp_host.placeholder": "Please enter SMTP host", + "access.form.email_smtp_port.label": "SMTP port", + "access.form.email_smtp_port.placeholder": "Please enter SMTP port", + "access.form.email_smtp_tls.label": "Use SSL/TLS", + "access.form.email_username.label": "Username", + "access.form.email_username.placeholder": "please enter username", + "access.form.email_password.label": "Password", + "access.form.email_password.placeholder": "please enter password", + "access.form.email_default_sender_address.label": "Default sender email address (Optional)", + "access.form.email_default_sender_address.placeholder": "Please enter default sender email address", + "access.form.email_default_receiver_address.label": "Default receiver email address (Optional)", + "access.form.email_default_receiver_address.placeholder": "Please enter default receiver email address", "access.form.gcore_api_token.label": "Gcore API token", "access.form.gcore_api_token.placeholder": "Please enter Gcore API token", "access.form.gcore_api_token.tooltip": "For more information, see https://api.gcore.com/docs/iam#section/Authentication", @@ -203,6 +222,18 @@ "access.form.k8s_kubeconfig.placeholder": "Please enter KubeConfig file", "access.form.k8s_kubeconfig.upload": "Choose File ...", "access.form.k8s_kubeconfig.tooltip": "For more information, see https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/

Leave it blank to use the Pod's ServiceAccount.", + "access.form.larkbot_webhook_url.label": "Lark bot Webhook URL", + "access.form.larkbot_webhook_url.placeholder": "Please enter Lark bot Webhook URL", + "access.form.larkbot_webhook_url.tooltip": "For more information, see https://www.feishu.cn/hc/en-US/articles/807992406756", + "access.form.mattermost_server_url.label": "Mattermost server URL", + "access.form.mattermost_server_url.placeholder": "Please enter Mattermost server URL", + "access.form.mattermost_username.label": "Mattermost username", + "access.form.mattermost_username.placeholder": "Please enter Mattermost username", + "access.form.mattermost_password.label": "Mattermost password", + "access.form.mattermost_password.placeholder": "Please enter Mattermost password", + "access.form.mattermost_default_channel_id.label": "Default Mattermost channel ID (Optional)", + "access.form.mattermost_default_channel_id.placeholder": "Please enter default Mattermost channel ID", + "access.form.mattermost_default_channel_id.tooltip": "How to get the channel ID? Select the target channel from the left sidebar, click on the channel name at the top, and choose ”Channel Details.” You can directly see the channel ID on the pop-up page.", "access.form.namecheap_username.label": "Namecheap username", "access.form.namecheap_username.placeholder": "Please enter Namecheap username", "access.form.namecheap_username.tooltip": "For more information, see https://www.namecheap.com/support/api/intro/", @@ -274,6 +305,12 @@ "access.form.sslcom_eab_hmac_key.label": "ACME EAB HMAC key", "access.form.sslcom_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key", "access.form.sslcom_eab_hmac_key.tooltip": "For more information, see https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/", + "access.form.telegram_bot_token.label": "Telegram bot token", + "access.form.telegram_bot_token.placeholder": "Please enter Telegram bot token", + "access.form.telegram_bot_token.tooltip": "How to get the bot token? Please refer to https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", + "access.form.telegram_default_chat_id.label": "Default Telegram chat ID (Optional)", + "access.form.telegram_default_chat_id.placeholder": "Please enter default Telegram chat ID", + "access.form.telegram_default_chat_id.tooltip": "How to get the chat ID? Please refer to https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", "access.form.tencentcloud_secret_id.label": "Tencent Cloud SecretId", "access.form.tencentcloud_secret_id.placeholder": "Please enter Tencent Cloud SecretId", "access.form.tencentcloud_secret_id.tooltip": "For more information, see https://cloud.tencent.com/document/product/598/40488?lang=en", @@ -312,10 +349,34 @@ "access.form.wangsu_api_key.tooltip": "For more information, see https://en.wangsu.com/document/account-manage/15776", "access.form.webhook_url.label": "Webhook URL", "access.form.webhook_url.placeholder": "Please enter Webhook URL", + "access.form.webhook_method.label": "Webhook request method", + "access.form.webhook_method.placeholder": "Please select Webhook request method", + "access.form.webhook_headers.label": "Webhook request headers (Optional)", + "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": "Format:
key1: val2
key2: val2


Example:
Content-Type: application/json
User-Agent: certimate
", + "access.form.webhook_default_data.errmsg.json_invalid": "Please enter a valiod JSON string", + "access.form.webhook_default_data_for_deployment.label": "Webhook data for deployment (Optional)", + "access.form.webhook_default_data_for_deployment.placeholder": "Please enter Webhook data", + "access.form.webhook_default_data_for_deployment.guide": "Tips: The Webhook data should be in JSON format.

The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL. Supported variables:
  1. ${DOMAIN}: The primary domain of the certificate (CommonName).
  2. ${DOMAINS}: The domain list of the certificate (SubjectAltNames).
  3. ${CERTIFICATE}: The PEM format content of the certificate file.
  4. ${PRIVATE_KEY}: The PEM format content of the private key file.

When the request method is GET, the data will be passed as query string. Otherwise, the data will be encoded in the format indicated by the Content-Type in the request headers. Supported formats:
  1. application/json (default).
  2. application/x-www-form-urlencoded: Nested data is not supported.
  3. multipart/form-data: Nested data is not supported.
  4. ", + "access.form.webhook_default_data_for_notification.label": "Webhook data for notification (Optional)", + "access.form.webhook_default_data_for_notification.placeholder": "Please enter Webhook data", + "access.form.webhook_default_data_for_notification.guide": "Tips: The Webhook data should be in JSON format.

    The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL. Supported variables:
    1. ${SUBJECT}: The subject of notification.
    2. ${MESSAGE}: The message of notification.

    When the request method is GET, the data will be passed as query string. Otherwise, the data will be encoded in the format indicated by the Content-Type in the request headers. Supported formats:
    1. application/json (default).
    2. application/x-www-form-urlencoded: Nested data is not supported.
    3. multipart/form-data: Nested data is not supported.
    4. ", + "access.form.webhook_preset_data.button": "Use preset template", + "access.form.webhook_preset_data.option.bark.label": "Bark", + "access.form.webhook_preset_data.option.gotify.label": "Gotify", + "access.form.webhook_preset_data.option.ntfy.label": "ntfy", + "access.form.webhook_preset_data.option.pushover.label": "Pushover", + "access.form.webhook_preset_data.option.pushplus.label": "PushPlus", + "access.form.webhook_preset_data.option.serverchan.label": "ServerChan", + "access.form.webhook_preset_data.option.common.label": "General template", "access.form.webhook_allow_insecure_conns.label": "Insecure SSL/TLS connections", "access.form.webhook_allow_insecure_conns.tooltip": "Allowing insecure connections may lead to data leak or tampering. Use this option only when under trusted networks.", "access.form.webhook_allow_insecure_conns.switch.on": "Allow", "access.form.webhook_allow_insecure_conns.switch.off": "Disallow", + "access.form.wecombot_webhook_url.label": "WeCom bot Webhook URL", + "access.form.wecombot_webhook_url.placeholder": "Please enter WeCom bot Webhook URL", + "access.form.wecombot_webhook_url.tooltip": "For more information, see https://open.work.weixin.qq.com/help2/pc/18401", "access.form.westcn_username.label": "West.cn username", "access.form.westcn_username.placeholder": "Please enter West.cn username", "access.form.westcn_username.tooltip": "For more information, see https://www.west.cn/CustomerCenter/doc/apiv2.html", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 1d4b9c2b..9829faab 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -52,12 +52,14 @@ "provider.ctcccloud": "China Telecom Cloud (State Cloud)", "provider.cucccloud": "China Unicom Cloud", "provider.desec": "deSEC", + "provider.dingtalkbot": "DingTalk Bot", "provider.dnsla": "DNS.LA", "provider.dogecloud": "Doge Cloud", "provider.dogecloud.cdn": "Doge Cloud - CDN (Content Delivery Network)", "provider.dynv6": "dynv6", "provider.edgio": "Edgio", "provider.edgio.applications": "Edgio - Applications", + "provider.email": "Email", "provider.fastly": "Fastly", "provider.gcore": "Gcore", "provider.gcore.cdn": "Gcore - CDN (Content Delivery Network)", @@ -80,9 +82,11 @@ "provider.jdcloud.vod": "JD Cloud - VOD (Video on Demand)", "provider.kubernetes": "Kubernetes", "provider.kubernetes.secret": "Kubernetes - Secret", + "provider.larkbot": "Lark Bot", "provider.letsencrypt": "Let's Encrypt", "provider.letsencryptstaging": "Let's Encrypt Staging Environment", "provider.local": "Local deployment", + "provider.mattermost": "Mattermost", "provider.namecheap": "Namecheap", "provider.namedotcom": "Name.com", "provider.namesilo": "NameSilo", @@ -98,6 +102,7 @@ "provider.safeline": "SafeLine", "provider.ssh": "SSH deployment", "provider.sslcom": "SSL.com", + "provider.telegram": "Telegram", "provider.tencentcloud": "Tencent Cloud", "provider.tencentcloud.cdn": "Tencent Cloud - CDN (Content Delivery Network)", "provider.tencentcloud.clb": "Tencent Cloud - CLB (Cloud Load Balancer)", @@ -131,6 +136,7 @@ "provider.wangsu": "Wangsu Cloud", "provider.wangsu.cdnpro": "Wangsu Cloud - CDN Pro", "provider.webhook": "Webhook", + "provider.wecombot": "WeCom Bot", "provider.westcn": "West.cn", "provider.zerossl": "ZeroSSL", diff --git a/ui/src/i18n/locales/en/nls.settings.json b/ui/src/i18n/locales/en/nls.settings.json index b56113c4..ee97efd7 100644 --- a/ui/src/i18n/locales/en/nls.settings.json +++ b/ui/src/i18n/locales/en/nls.settings.json @@ -82,7 +82,6 @@ "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 https://pushover.net/api#identifiers", - "settings.notification.channel.form.pushplus_token.placeholder": "Please enter Token", "settings.notification.channel.form.pushplus_token.label": "Token", "settings.notification.channel.form.pushplus_token.placeholder": "Please enter token", "settings.notification.channel.form.pushplus_token.tooltip": "For more information, see https://www.pushplus.plus/push1.html", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index b04c7f04..8a353e51 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -93,8 +93,8 @@ "workflow_node.deploy.search.provider.placeholder": "Search deploy target ...", "workflow_node.deploy.form.provider.label": "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.placeholder": "Please select an authorization of host provider", + "workflow_node.deploy.form.provider_access.label": "Hosting provider authorization", + "workflow_node.deploy.form.provider_access.placeholder": "Please select an authorization of Hosting provider", "workflow_node.deploy.form.provider_access.tooltip": "Used to invoke API during deployment.", "workflow_node.deploy.form.provider_access.button": "Create", "workflow_node.deploy.form.provider_access.guide_for_local": "Tips: If you are running Certimate in Docker, the \"Local\" refers to the container rather than the host.", @@ -685,11 +685,11 @@ "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 https://cdnpro.console.wangsu.com/v2/index/#/certificate", - "workflow_node.deploy.form.webhook_data.label": "Webhook data (JSON format)", - "workflow_node.deploy.form.webhook_data.placeholder": "Please enter Webhook data", - "workflow_node.deploy.form.webhook_data.guide": "Tips: The Webhook data should be a key-value pair in JSON format. The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL.

      Supported variables:
      ${DOMAIN}: The primary domain of the certificate (CommonName).
      ${DOMAINS}: The domain list of the certificate (SubjectAltNames).
      ${CERTIFICATE}: The PEM format content of the certificate file.
      ${PRIVATE_KEY}: The PEM format content of the private key file.", + "workflow_node.deploy.form.webhook_data.label": "Webhook data (Optional)", + "workflow_node.deploy.form.webhook_data.placeholder": "Please enter Webhook data to override the default value", + "workflow_node.deploy.form.webhook_data.tooltip": "Leave it blank to use the default Webhook data provided by the authorization.", + "workflow_node.deploy.form.webhook_data.guide": "Supported variables:
      1. ${DOMAIN}: The primary domain of the certificate (CommonName).
      2. ${DOMAINS}: The domain list of the certificate (SubjectAltNames).
      3. ${CERTIFICATE}: The PEM format content of the certificate file.
      4. ${PRIVATE_KEY}: The PEM format content of the private key file.

      Please visit the authorization management 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_preset.button": "Use preset template", "workflow_node.deploy.form.strategy_config.label": "Strategy settings", "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, ", @@ -712,9 +712,32 @@ "workflow_node.notify.form.subject.placeholder": "Please enter subject", "workflow_node.notify.form.message.label": "Message", "workflow_node.notify.form.message.placeholder": "Please enter message", - "workflow_node.notify.form.channel.label": "Channel", + "workflow_node.notify.form.channel.label": "Channel (Deprecated)", "workflow_node.notify.form.channel.placeholder": "Please select channel", "workflow_node.notify.form.channel.button": "Configure", + "workflow_node.notify.form.provider.label": "Notification channel", + "workflow_node.notify.form.provider.placeholder": "Please select notification channel", + "workflow_node.notify.form.provider_access.label": "Notification provider authorization", + "workflow_node.notify.form.provider_access.placeholder": "Please select an authorization of notification provider", + "workflow_node.notify.form.provider_access.button": "Create", + "workflow_node.notify.form.params_config.label": "Parameter settings", + "workflow_node.notify.form.email_sender_address.label": "Sender email address (Optional)", + "workflow_node.notify.form.email_sender_address.placeholder": "Please enter sender email address to override the default value", + "workflow_node.notify.form.email_sender_address.tooltip": "Leave it blank to use the default sender email address provided by the authorization.", + "workflow_node.notify.form.email_receiver_address.label": "Receiver email address (Optional)", + "workflow_node.notify.form.email_receiver_address.placeholder": "Please enter receiver email address to override the default value", + "workflow_node.notify.form.email_receiver_address.tooltip": "Leave it blank to use the default receiver email address provided by the selected authorization.", + "workflow_node.notify.form.mattermost_channel_id.label": "Mattermost channel ID (Optional)", + "workflow_node.notify.form.mattermost_channel_id.placeholder": "Please enter Mattermost channel ID to override the default value", + "workflow_node.notify.form.mattermost_channel_id.tooltip": "Leave it blank to use the default channel ID provided by the authorization.", + "workflow_node.notify.form.telegram_chat_id.label": "Telegram chat ID (Optional)", + "workflow_node.notify.form.telegram_chat_id.placeholder": "Please enter Telegram chat ID to override the default value", + "workflow_node.notify.form.telegram_chat_id.tooltip": "Leave it blank to use the default chat ID provided by the selected authorization.", + "workflow_node.notify.form.webhook_data.label": "Webhook data (Optional)", + "workflow_node.notify.form.webhook_data.placeholder": "Please enter Webhook data to override the default value", + "workflow_node.notify.form.webhook_data.tooltip": "Leave it blank to use the default Webhook data provided by the authorization.", + "workflow_node.notify.form.webhook_data.guide": "Supported variables:
      1. ${SUBJECT}: The subject of notification.
      2. ${MESSAGE}: The message of notification.

      Please visit the authorization management page for addtional notes.", + "workflow_node.notify.form.webhook_data.errmsg.json_invalid": "Please enter a valiod JSON string", "workflow_node.end.label": "End", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index ce6ad588..04f74b58 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -18,9 +18,9 @@ "access.props.provider.usage.ca": "证书颁发机构", "access.props.provider.usage.notification": "通知渠道", "access.props.provider.builtin": "内置", - "access.props.range.both_dns_hosting": "提供商", - "access.props.range.ca_only": "证书颁发机构", - "access.props.range.notify_only": "通知渠道", + "access.props.usage.both_dns_hosting": "提供商", + "access.props.usage.ca_only": "证书颁发机构", + "access.props.usage.notification_only": "通知渠道", "access.props.created_at": "创建时间", "access.props.updated_at": "更新时间", @@ -139,6 +139,12 @@ "access.form.desec_token.label": "deSEC Token", "access.form.desec_token.placeholder": "请输入 deSEC Token", "access.form.desec_token.tooltip": "这是什么?请参阅 https://desec.readthedocs.io/en/latest/auth/tokens.html", + "access.form.dingtalkbot_webhook_url.label": "钉钉群机器人 Webhook 地址", + "access.form.dingtalkbot_webhook_url.placeholder": "请输入钉钉群机器人 Webhook 地址", + "access.form.dingtalkbot_webhook_url.tooltip": "这是什么?请参阅 https://open.dingtalk.com/document/orgapp/obtain-the-webhook-address-of-a-custom-robot", + "access.form.dingtalkbot_secret.label": "钉钉群机器人加签密钥", + "access.form.dingtalkbot_secret.placeholder": "请输入钉钉群机器人加签密钥", + "access.form.dingtalkbot_secret.tooltip": "这是什么?请参阅 https://open.dingtalk.com/document/orgapp/customize-robot-security-settings", "access.form.dnsla_api_id.label": "DNS.LA API ID", "access.form.dnsla_api_id.placeholder": "请输入 DNS.LA API ID", "access.form.dnsla_api_id.tooltip": "这是什么?请参阅 https://www.dns.la/docs/ApiDoc", @@ -160,6 +166,19 @@ "access.form.edgio_client_secret.label": "Edgio 客户端密码", "access.form.edgio_client_secret.placeholder": "请输入 Edgio 客户端密码", "access.form.edgio_client_secret.tooltip": "这是什么?请参阅 https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients", + "access.form.email_smtp_host.label": "SMTP 服务器地址", + "access.form.email_smtp_host.placeholder": "请输入 SMTP 服务器地址", + "access.form.email_smtp_port.label": "SMTP 服务器端口", + "access.form.email_smtp_port.placeholder": "请输入 SMTP 服务器端口", + "access.form.email_smtp_tls.label": "SSL/TLS 连接", + "access.form.email_username.label": "用户名", + "access.form.email_username.placeholder": "请输入用户名", + "access.form.email_password.label": "密码", + "access.form.email_password.placeholder": "请输入密码", + "access.form.email_default_sender_address.label": "默认的发送邮箱地址(可选)", + "access.form.email_default_sender_address.placeholder": "请输入默认的发送邮箱地址", + "access.form.email_default_receiver_address.label": "默认的接收邮箱地址(可选)", + "access.form.email_default_receiver_address.placeholder": "请输入默认的接收邮箱地址", "access.form.gcore_api_token.label": "Gcore API Token", "access.form.gcore_api_token.placeholder": "请输入 Gcore API Token", "access.form.gcore_api_token.tooltip": "这是什么?请参阅 https://api.gcore.com/docs/iam#section/Authentication", @@ -197,6 +216,18 @@ "access.form.k8s_kubeconfig.placeholder": "请选择 KubeConfig 文件", "access.form.k8s_kubeconfig.upload": "选择文件", "access.form.k8s_kubeconfig.tooltip": "这是什么?请参阅 https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/

      为空时,将使用 Pod 的 ServiceAccount 作为凭证。", + "access.form.larkbot_webhook_url.label": "飞书群机器人 Webhook 地址", + "access.form.larkbot_webhook_url.placeholder": "请输入飞书群机器人 Webhook 地址", + "access.form.larkbot_webhook_url.tooltip": "这是什么?请参阅 https://www.feishu.cn/hc/zh-CN/articles/807992406756", + "access.form.mattermost_server_url.label": "Mattermost 服务地址", + "access.form.mattermost_server_url.placeholder": "请输入 Mattermost 服务地址", + "access.form.mattermost_username.label": "Mattermost 用户名", + "access.form.mattermost_username.placeholder": "请输入 Mattermost 用户名", + "access.form.mattermost_password.label": "Mattermost 密码", + "access.form.mattermost_password.placeholder": "请输入 Mattermost 密码", + "access.form.mattermost_default_channel_id.label": "默认的 Mattermost 频道 ID(可选)", + "access.form.mattermost_default_channel_id.placeholder": "请输入默认的 Mattermost 频道 ID", + "access.form.mattermost_default_channel_id.tooltip": "如何获取频道 ID?从左侧边栏中选择目标频道,点击顶部的频道名称,选择“频道详情”,即可在弹出页面中直接看到频道 ID。", "access.form.namecheap_username.label": "Namecheap 用户名", "access.form.namecheap_username.placeholder": "请输入 Namecheap 用户名", "access.form.namecheap_username.tooltip": "这是什么?请参阅 https://www.namecheap.com/support/api/intro/", @@ -268,6 +299,12 @@ "access.form.sslcom_eab_hmac_key.label": "ACME EAB HMAC key", "access.form.sslcom_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC key", "access.form.sslcom_eab_hmac_key.tooltip": "这是什么?请参阅 https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/", + "access.form.telegram_bot_token.label": "Telegram 机器人 API Token", + "access.form.telegram_bot_token.placeholder": "请输入 Telegram 机器人 API Token", + "access.form.telegram_bot_token.tooltip": "如何获取机器人 API Token?请参阅 https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", + "access.form.telegram_default_chat_id.label": "默认的 Telegram 会话 ID(可选)", + "access.form.telegram_default_chat_id.placeholder": "请输入默认的 Telegram 会话 ID", + "access.form.telegram_default_chat_id.tooltip": "如何获取会话 ID?请参阅 https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", "access.form.tencentcloud_secret_id.label": "腾讯云 SecretId", "access.form.tencentcloud_secret_id.placeholder": "请输入腾讯云 SecretId", "access.form.tencentcloud_secret_id.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/598/40488", @@ -312,10 +349,34 @@ "access.form.wangsu_api_key.tooltip": "这是什么?请参阅 https://www.wangsu.com/document/account-manage/15776", "access.form.webhook_url.label": "Webhook 回调地址", "access.form.webhook_url.placeholder": "请输入 Webhook 回调地址", + "access.form.webhook_method.label": "Webhook 请求谓词", + "access.form.webhook_method.placeholder": "请选择 Webhook 请求谓词", + "access.form.webhook_headers.label": "Webhook 请求标头(可选)", + "access.form.webhook_headers.placeholder": "请输入 Webhook 请求标头", + "access.form.webhook_headers.errmsg.invalid": "请输入有效的请求标头", + "access.form.webhook_headers.tooltip": "格式:
      key1: val2
      key2: val2


      示例:
      Content-Type: application/json
      User-Agent: certimate
      ", + "access.form.webhook_default_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串", + "access.form.webhook_default_data_for_deployment.label": "默认的 Webhook 部署证书回调数据(可选)", + "access.form.webhook_default_data_for_deployment.placeholder": "请输入默认的 Webhook 回调数据", + "access.form.webhook_default_data_for_deployment.guide": "小贴士:回调数据是一个 JSON 格式的数据。

      其中值支持模板变量,将在被发送到指定的 Webhook URL 时被替换为实际值;其他内容将保持原样。支持的变量:
      1. ${DOMAIN}:证书的主域名(即 CommonName)。
      2. ${DOMAINS}:证书的多域名列表(即 SubjectAltNames)。
      3. ${CERTIFICATE}:证书文件 PEM 格式内容。
      4. ${PRIVATE_KEY}:私钥文件 PEM 格式内容。

      当请求谓词为 GET 时,回调数据将作为查询参数;否则,回调数据将按照请求标头中 Content-Type 所指示的格式进行编码。支持的格式:
      1. application/json(默认)。
      2. application/x-www-form-urlencoded:不支持嵌套数据。
      3. multipart/form-data:不支持嵌套数据。
      4. ", + "access.form.webhook_default_data_for_notification.label": "默认的 Webhook 推送通知回调数据(可选)", + "access.form.webhook_default_data_for_notification.placeholder": "请输入默认的 Webhook 回调数据", + "access.form.webhook_default_data_for_notification.guide": "小贴士:回调数据是一个 JSON 格式的数据。

        其中值支持模板变量,将在被发送到指定的 Webhook URL 时被替换为实际值;其他内容将保持原样。支持的变量:
        1. ${DOMAIN}:证书的主域名(即 CommonName)。
        2. ${SUBJECT}:通知主题。
        3. ${MESSAGE}:通知内容。

        当请求谓词为 GET 时,回调数据将作为查询参数;否则,回调数据将按照请求标头中 Content-Type 所指示的格式进行编码。支持的格式:
        1. application/json(默认)。
        2. application/x-www-form-urlencoded:不支持嵌套数据。
        3. multipart/form-data:不支持嵌套数据。
        4. ", + "access.form.webhook_preset_data.button": "使用预设模板", + "access.form.webhook_preset_data.option.bark.label": "Bark", + "access.form.webhook_preset_data.option.gotify.label": "Gotify", + "access.form.webhook_preset_data.option.ntfy.label": "ntfy", + "access.form.webhook_preset_data.option.pushover.label": "Pushover", + "access.form.webhook_preset_data.option.pushplus.label": "PushPlus 推送加", + "access.form.webhook_preset_data.option.serverchan.label": "Server 酱", + "access.form.webhook_preset_data.option.common.label": "通用模板", "access.form.webhook_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", "access.form.webhook_allow_insecure_conns.tooltip": "忽略 SSL/TLS 证书错误可能导致数据泄露或被篡改。建议仅在可信网络下启用。", "access.form.webhook_allow_insecure_conns.switch.on": "允许", "access.form.webhook_allow_insecure_conns.switch.off": "不允许", + "access.form.wecombot_webhook_url.label": "企业微信群机器人 Webhook 地址", + "access.form.wecombot_webhook_url.placeholder": "请输入企业微信群机器人 Webhook 地址", + "access.form.wecombot_webhook_url.tooltip": "这是什么?请参阅 https://open.work.weixin.qq.com/help2/pc/18401", "access.form.westcn_username.label": "西部数码用户名", "access.form.westcn_username.placeholder": "请输入西部数码用户名", "access.form.westcn_username.tooltip": "这是什么?请参阅 https://www.west.cn/CustomerCenter/doc/apiv2.html", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 86fd3e5b..28c3ad82 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -52,12 +52,14 @@ "provider.ctcccloud": "联通云", "provider.cucccloud": "天翼云", "provider.desec": "deSEC", + "provider.dingtalkbot": "钉钉群机器人", "provider.dnsla": "DNS.LA", "provider.dogecloud": "多吉云", "provider.dogecloud.cdn": "多吉云 - 内容分发网络 CDN", "provider.dynv6": "dynv6", "provider.edgio": "Edgio", "provider.edgio.applications": "Edgio - Applications", + "provider.email": "邮件", "provider.fastly": "Fastly", "provider.gcore": "Gcore", "provider.gcore.cdn": "Gcore - 内容分发网络 CDN", @@ -80,9 +82,11 @@ "provider.jdcloud.vod": "京东云 - 视频点播", "provider.kubernetes": "Kubernetes", "provider.kubernetes.secret": "Kubernetes - Secret", + "provider.larkbot": "飞书群机器人", "provider.letsencrypt": "Let's Encrypt", "provider.letsencryptstaging": "Let's Encrypt 测试环境", "provider.local": "本地部署", + "provider.mattermost": "Mattermost", "provider.namecheap": "Namecheap", "provider.namedotcom": "Name.com", "provider.namesilo": "NameSilo", @@ -98,6 +102,7 @@ "provider.safeline": "雷池", "provider.ssh": "SSH 部署", "provider.sslcom": "SSL.com", + "provider.telegram": "Telegram", "provider.tencentcloud": "腾讯云", "provider.tencentcloud.cdn": "腾讯云 - 内容分发网络 CDN", "provider.tencentcloud.clb": "腾讯云 - 负载均衡 CLB", @@ -131,6 +136,7 @@ "provider.wangsu": "网宿云", "provider.wangsu.cdnpro": "网宿云 - CDN Pro", "provider.webhook": "Webhook", + "provider.wecombot": "企业微信群机器人", "provider.westcn": "西部数码", "provider.zerossl": "ZeroSSL", diff --git a/ui/src/i18n/locales/zh/nls.settings.json b/ui/src/i18n/locales/zh/nls.settings.json index 24342452..cde8ec0f 100644 --- a/ui/src/i18n/locales/zh/nls.settings.json +++ b/ui/src/i18n/locales/zh/nls.settings.json @@ -82,7 +82,6 @@ "settings.notification.channel.form.pushover_user.placeholder": "请输入用户/分组 Key", "settings.notification.channel.form.pushover_user.label": "用户/分组 Key", "settings.notification.channel.form.pushover_user.tooltip": "这是什么?请参阅 https://pushover.net/api#identifiers", - "settings.notification.channel.form.pushplus_token.placeholder": "请输入Token", "settings.notification.channel.form.pushplus_token.label": "Token", "settings.notification.channel.form.pushplus_token.placeholder": "请输入 Token", "settings.notification.channel.form.pushplus_token.tooltip": "这是什么?请参阅 https://www.pushplus.plus/push1.html", @@ -90,7 +89,7 @@ "settings.notification.channel.form.serverchan_url.placeholder": "请输入服务器地址(形如: https://sctapi.ftqq.com/*****.send)", "settings.notification.channel.form.serverchan_url.tooltip": "这是什么?请参阅 https://sct.ftqq.com/forward", "settings.notification.channel.form.telegram_api_token.label": "机器人 API Token", - "settings.notification.channel.form.telegram_api_token.placeholder": "请输入机器人 API token", + "settings.notification.channel.form.telegram_api_token.placeholder": "请输入机器人 API Token", "settings.notification.channel.form.telegram_api_token.tooltip": "这是什么?请参阅 https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", "settings.notification.channel.form.telegram_chat_id.label": "会话 ID", "settings.notification.channel.form.telegram_chat_id.placeholder": "请输入会话 ID", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 1e1387f8..d58ebe90 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -57,12 +57,12 @@ "workflow_node.apply.form.advanced_config.label": "高级设置", "workflow_node.apply.form.ca_provider.label": "证书颁发机构(可选)", "workflow_node.apply.form.ca_provider.placeholder": "请选择证书颁发机构", - "workflow_node.apply.form.ca_provider.button": "去配置", + "workflow_node.apply.form.ca_provider.button": "设置", "workflow_node.apply.form.ca_provider_access.label": "证书颁发机构授权", "workflow_node.apply.form.ca_provider_access.placeholder": "请选择证书颁发机构授权", "workflow_node.apply.form.ca_provider_access.button": "新建", - "workflow_node.apply.form.key_algorithm.label": "数字证书算法", - "workflow_node.apply.form.key_algorithm.placeholder": "请选择数字证书算法", + "workflow_node.apply.form.key_algorithm.label": "证书算法", + "workflow_node.apply.form.key_algorithm.placeholder": "请选择证书算法", "workflow_node.apply.form.nameservers.label": "DNS 递归服务器(可选)", "workflow_node.apply.form.nameservers.placeholder": "请输入 DNS 递归服务器(多个值请用半角分号隔开)", "workflow_node.apply.form.nameservers.tooltip": "在 ACME DNS-01 质询时使用自定义的 DNS 递归服务器。如果你不了解该选项的用途,保持默认即可。点此了解更多。", @@ -684,11 +684,11 @@ "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": "这是什么?请参阅 https://cdnpro.console.wangsu.com/v2/index/#/certificate", - "workflow_node.deploy.form.webhook_data.label": "Webhook 回调数据(JSON 格式)", + "workflow_node.deploy.form.webhook_data.label": "Webhook 回调数据(可选)", "workflow_node.deploy.form.webhook_data.placeholder": "请输入 Webhook 回调数据", - "workflow_node.deploy.form.webhook_data.guide": "小贴士:回调数据是一个 JSON 格式的键值对。其中值支持模板变量,将在被发送到指定的 Webhook URL 时被替换为实际值;其他内容将保持原样。

          支持的变量:
          ${DOMAIN}:证书的主域名(即 CommonName
          ${DOMAINS}:证书的多域名列表(即 SubjectAltNames
          ${CERTIFICATE}:证书文件 PEM 格式内容
          ${PRIVATE_KEY}:私钥文件 PEM 格式内容", + "workflow_node.deploy.form.webhook_data.tooltip": "不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。", + "workflow_node.deploy.form.webhook_data.guide": "支持的变量:
          1. ${DOMAIN}:证书的主域名(即 CommonName)。
          2. ${DOMAINS}:证书的多域名列表(即 SubjectAltNames)。
          3. ${CERTIFICATE}:证书文件 PEM 格式内容。
          4. ${PRIVATE_KEY}:私钥文件 PEM 格式内容。

          其他注意事项请前往授权管理页面查看。", "workflow_node.deploy.form.webhook_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串", - "workflow_node.deploy.form.webhook_data_preset.button": "使用预设模板", "workflow_node.deploy.form.strategy_config.label": "执行策略", "workflow_node.deploy.form.skip_on_last_succeeded.label": "重复部署", "workflow_node.deploy.form.skip_on_last_succeeded.prefix": "当上次部署相同证书成功时,", @@ -711,9 +711,32 @@ "workflow_node.notify.form.subject.placeholder": "请输入通知主题", "workflow_node.notify.form.message.label": "通知内容", "workflow_node.notify.form.message.placeholder": "请输入通知内容", - "workflow_node.notify.form.channel.label": "通知渠道", + "workflow_node.notify.form.channel.label": "通知渠道(已废弃,请使用「通知渠道授权」字段)", "workflow_node.notify.form.channel.placeholder": "请选择通知渠道", - "workflow_node.notify.form.channel.button": "去配置", + "workflow_node.notify.form.channel.button": "设置", + "workflow_node.notify.form.provider.label": "通知渠道", + "workflow_node.notify.form.provider.placeholder": "请选择通知渠道", + "workflow_node.notify.form.provider_access.label": "通知渠道授权", + "workflow_node.notify.form.provider_access.placeholder": "请选择通知渠道授权", + "workflow_node.notify.form.provider_access.button": "新建", + "workflow_node.notify.form.params_config.label": "参数设置", + "workflow_node.notify.form.email_sender_address.label": "发送邮箱地址(可选)", + "workflow_node.notify.form.email_sender_address.placeholder": "请输入发送邮箱地址以覆盖默认值", + "workflow_node.notify.form.email_sender_address.tooltip": "不填写时,将使用所选通知渠道授权的默认发送邮箱地址。", + "workflow_node.notify.form.email_receiver_address.label": "接收邮箱地址(可选)", + "workflow_node.notify.form.email_receiver_address.placeholder": "请输入接收邮箱地址以覆盖默认值", + "workflow_node.notify.form.email_receiver_address.tooltip": "不填写时,将使用所选通知渠道授权的默认接收邮箱地址。", + "workflow_node.notify.form.mattermost_channel_id.label": "Mattermost 频道 ID(可选)", + "workflow_node.notify.form.mattermost_channel_id.placeholder": "请输入 Mattermost 频道 ID 以覆盖默认值", + "workflow_node.notify.form.mattermost_channel_id.tooltip": "不填写时,将使用所选通知渠道授权的默认频道 ID。", + "workflow_node.notify.form.telegram_chat_id.label": "Telegram 会话 ID(可选)", + "workflow_node.notify.form.telegram_chat_id.placeholder": "请输入 Telegram 会话 ID 以覆盖默认值", + "workflow_node.notify.form.telegram_chat_id.tooltip": "不填写时,将使用所选通知渠道授权的默认会话 ID。", + "workflow_node.notify.form.webhook_data.label": "Webhook 回调数据(可选)", + "workflow_node.notify.form.webhook_data.placeholder": "请输入 Webhook 回调数据以覆盖默认值", + "workflow_node.notify.form.webhook_data.tooltip": "不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。", + "workflow_node.notify.form.webhook_data.guide": "支持的变量:
          1. ${DOMAIN}:证书的主域名(即 CommonName)。
          2. ${DOMAINS}:证书的多域名列表(即 SubjectAltNames)。
          3. ${CERTIFICATE}:证书文件 PEM 格式内容。
          4. ${PRIVATE_KEY}:私钥文件 PEM 格式内容。

          其他注意事项请前往授权管理页面查看。", + "workflow_node.notify.form.webhook_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串", "workflow_node.end.label": "结束", diff --git a/ui/src/pages/accesses/AccessList.tsx b/ui/src/pages/accesses/AccessList.tsx index 0dd56aac..f815812e 100644 --- a/ui/src/pages/accesses/AccessList.tsx +++ b/ui/src/pages/accesses/AccessList.tsx @@ -21,7 +21,7 @@ import { useZustandShallowSelector } from "@/hooks"; import { useAccessesStore } from "@/stores/access"; import { getErrMsg } from "@/utils/error"; -type AccessRanges = AccessEditDrawerProps["range"]; +type AccessUsageProp = AccessEditDrawerProps["usage"]; const AccessList = () => { const [searchParams] = useSearchParams(); @@ -87,7 +87,7 @@ const AccessList = () => { @@ -98,7 +98,7 @@ const AccessList = () => { @@ -126,7 +126,7 @@ const AccessList = () => { const [filters, setFilters] = useState>(() => { return { - range: "both-dns-hosting" satisfies AccessRanges, + usage: "both-dns-hosting" satisfies AccessUsageProp, keyword: searchParams.get("keyword"), }; }); @@ -160,13 +160,13 @@ const AccessList = () => { }) .filter((e) => { const provider = accessProvidersMap.get(e.provider); - switch (filters["range"] as AccessRanges) { + switch (filters["usage"] as AccessUsageProp) { case "both-dns-hosting": - return provider?.usages?.includes(ACCESS_USAGES.DNS) || provider?.usages?.includes(ACCESS_USAGES.HOSTING); + return !e.reserve && (provider?.usages?.includes(ACCESS_USAGES.DNS) || provider?.usages?.includes(ACCESS_USAGES.HOSTING)); case "ca-only": - return provider?.usages?.includes(ACCESS_USAGES.CA); - case "notify-only": - return provider?.usages?.includes(ACCESS_USAGES.NOTIFICATION); + return e.reserve === "ca" && provider?.usages?.includes(ACCESS_USAGES.CA); + case "notification-only": + return e.reserve === "notification" && provider?.usages?.includes(ACCESS_USAGES.NOTIFICATION); } }); return Promise.resolve({ @@ -184,11 +184,13 @@ const AccessList = () => { ); const handleTabChange = (key: string) => { - setFilters((prev) => ({ ...prev, range: key })); + setFilters((prev) => ({ ...prev, usage: key })); + setPage(1); }; const handleSearch = (value: string) => { setFilters((prev) => ({ ...prev, keyword: value })); + setPage(1); }; const handleReloadClick = () => { @@ -224,7 +226,7 @@ const AccessList = () => { extra={[ }> @@ -245,18 +247,18 @@ const AccessList = () => { tabList={[ { key: "both-dns-hosting", - label: t("access.props.range.both_dns_hosting"), + label: t("access.props.usage.both_dns_hosting"), }, { key: "ca-only", - label: t("access.props.range.ca_only"), + label: t("access.props.usage.ca_only"), + }, + { + key: "notification-only", + label: t("access.props.usage.notification_only"), }, - // { - // key: "notify-only", - // label: t("access.props.range.notify_only"), - // }, ]} - activeTabKey={filters["range"] as string} + activeTabKey={filters["usage"] as string} onTabChange={(key) => handleTabChange(key)} /> diff --git a/ui/src/pages/certificates/CertificateList.tsx b/ui/src/pages/certificates/CertificateList.tsx index 049069a3..6e28459d 100644 --- a/ui/src/pages/certificates/CertificateList.tsx +++ b/ui/src/pages/certificates/CertificateList.tsx @@ -251,6 +251,7 @@ const CertificateList = () => { const handleSearch = (value: string) => { setFilters((prev) => ({ ...prev, keyword: value.trim() })); + setPage(1); }; const handleReloadClick = () => { diff --git a/ui/src/pages/settings/SettingsNotification.tsx b/ui/src/pages/settings/SettingsNotification.tsx index 950149e2..2c9d593a 100644 --- a/ui/src/pages/settings/SettingsNotification.tsx +++ b/ui/src/pages/settings/SettingsNotification.tsx @@ -1,11 +1,14 @@ import { useTranslation } from "react-i18next"; -import { Card, Divider } from "antd"; +import { Alert, Card, Divider } from "antd"; import NotifyChannels from "@/components/notification/NotifyChannels"; import NotifyTemplate from "@/components/notification/NotifyTemplate"; import { useZustandShallowSelector } from "@/hooks"; import { useNotifyChannelsStore } from "@/stores/notify"; +/** + * @deprecated + */ const SettingsNotification = () => { const { t } = useTranslation(); @@ -22,6 +25,7 @@ const SettingsNotification = () => { +
diff --git a/ui/src/pages/settings/SettingsSSLProvider.tsx b/ui/src/pages/settings/SettingsSSLProvider.tsx index f23c2024..2be6162f 100644 --- a/ui/src/pages/settings/SettingsSSLProvider.tsx +++ b/ui/src/pages/settings/SettingsSSLProvider.tsx @@ -7,7 +7,7 @@ import { produce } from "immer"; import { z } from "zod"; import Show from "@/components/Show"; -import { APPLY_CA_PROVIDERS, type ApplyCAProviderType } from "@/domain/provider"; +import { type CAProviderType, CA_PROVIDERS } from "@/domain/provider"; import { SETTINGS_NAMES, type SSLProviderSettingsContent, type SettingsModel } from "@/domain/settings"; import { useAntdForm } from "@/hooks"; import { get as getSettings, save as saveSettings } from "@/repository/settings"; @@ -27,14 +27,14 @@ const SSLProviderEditFormLetsEncryptConfig = () => { const { pending, settings, updateSettings } = useContext(SSLProviderContext); const { form: formInst, formProps } = useAntdForm>({ - initialValues: settings?.content?.config?.[APPLY_CA_PROVIDERS.LETSENCRYPT], + initialValues: settings?.content?.config?.[CA_PROVIDERS.LETSENCRYPT], onSubmit: async (values) => { const newSettings = produce(settings, (draft) => { draft.content ??= {} as SSLProviderSettingsContent; - draft.content.provider = APPLY_CA_PROVIDERS.LETSENCRYPT; + draft.content.provider = CA_PROVIDERS.LETSENCRYPT; draft.content.config ??= {} as SSLProviderSettingsContent["config"]; - draft.content.config[APPLY_CA_PROVIDERS.LETSENCRYPT] = values; + draft.content.config[CA_PROVIDERS.LETSENCRYPT] = values; }); await updateSettings(newSettings); @@ -44,7 +44,7 @@ const SSLProviderEditFormLetsEncryptConfig = () => { const [formChanged, setFormChanged] = useState(false); useEffect(() => { - setFormChanged(settings?.content?.provider !== APPLY_CA_PROVIDERS.LETSENCRYPT); + setFormChanged(settings?.content?.provider !== CA_PROVIDERS.LETSENCRYPT); }, [settings?.content?.provider]); const handleFormChange = () => { @@ -68,14 +68,14 @@ const SSLProviderEditFormLetsEncryptStagingConfig = () => { const { pending, settings, updateSettings } = useContext(SSLProviderContext); const { form: formInst, formProps } = useAntdForm>({ - initialValues: settings?.content?.config?.[APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING], + initialValues: settings?.content?.config?.[CA_PROVIDERS.LETSENCRYPTSTAGING], onSubmit: async (values) => { const newSettings = produce(settings, (draft) => { draft.content ??= {} as SSLProviderSettingsContent; - draft.content.provider = APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING; + draft.content.provider = CA_PROVIDERS.LETSENCRYPTSTAGING; draft.content.config ??= {} as SSLProviderSettingsContent["config"]; - draft.content.config[APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING] = values; + draft.content.config[CA_PROVIDERS.LETSENCRYPTSTAGING] = values; }); await updateSettings(newSettings); @@ -85,7 +85,7 @@ const SSLProviderEditFormLetsEncryptStagingConfig = () => { const [formChanged, setFormChanged] = useState(false); useEffect(() => { - setFormChanged(settings?.content?.provider !== APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING); + setFormChanged(settings?.content?.provider !== CA_PROVIDERS.LETSENCRYPTSTAGING); }, [settings?.content?.provider]); const handleFormChange = () => { @@ -113,14 +113,14 @@ const SSLProviderEditFormBuypassConfig = () => { const { pending, settings, updateSettings } = useContext(SSLProviderContext); const { form: formInst, formProps } = useAntdForm>({ - initialValues: settings?.content?.config?.[APPLY_CA_PROVIDERS.BUYPASS], + initialValues: settings?.content?.config?.[CA_PROVIDERS.BUYPASS], onSubmit: async (values) => { const newSettings = produce(settings, (draft) => { draft.content ??= {} as SSLProviderSettingsContent; - draft.content.provider = APPLY_CA_PROVIDERS.BUYPASS; + draft.content.provider = CA_PROVIDERS.BUYPASS; draft.content.config ??= {} as SSLProviderSettingsContent["config"]; - draft.content.config[APPLY_CA_PROVIDERS.BUYPASS] = values; + draft.content.config[CA_PROVIDERS.BUYPASS] = values; }); await updateSettings(newSettings); @@ -130,7 +130,7 @@ const SSLProviderEditFormBuypassConfig = () => { const [formChanged, setFormChanged] = useState(false); useEffect(() => { - setFormChanged(settings?.content?.provider !== APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING); + setFormChanged(settings?.content?.provider !== CA_PROVIDERS.LETSENCRYPTSTAGING); }, [settings?.content?.provider]); const handleFormChange = () => { @@ -165,14 +165,14 @@ const SSLProviderEditFormGoogleTrustServicesConfig = () => { }); const formRule = createSchemaFieldRule(formSchema); const { form: formInst, formProps } = useAntdForm>({ - initialValues: settings?.content?.config?.[APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES], + initialValues: settings?.content?.config?.[CA_PROVIDERS.GOOGLETRUSTSERVICES], onSubmit: async (values) => { const newSettings = produce(settings, (draft) => { draft.content ??= {} as SSLProviderSettingsContent; - draft.content.provider = APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES; + draft.content.provider = CA_PROVIDERS.GOOGLETRUSTSERVICES; draft.content.config ??= {} as SSLProviderSettingsContent["config"]; - draft.content.config[APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES] = values; + draft.content.config[CA_PROVIDERS.GOOGLETRUSTSERVICES] = values; }); await updateSettings(newSettings); @@ -182,7 +182,7 @@ const SSLProviderEditFormGoogleTrustServicesConfig = () => { const [formChanged, setFormChanged] = useState(false); useEffect(() => { - setFormChanged(settings?.content?.provider !== APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES); + setFormChanged(settings?.content?.provider !== CA_PROVIDERS.GOOGLETRUSTSERVICES); }, [settings?.content?.provider]); const handleFormChange = () => { @@ -235,14 +235,14 @@ const SSLProviderEditFormSSLComConfig = () => { }); const formRule = createSchemaFieldRule(formSchema); const { form: formInst, formProps } = useAntdForm>({ - initialValues: settings?.content?.config?.[APPLY_CA_PROVIDERS.SSLCOM], + initialValues: settings?.content?.config?.[CA_PROVIDERS.SSLCOM], onSubmit: async (values) => { const newSettings = produce(settings, (draft) => { draft.content ??= {} as SSLProviderSettingsContent; - draft.content.provider = APPLY_CA_PROVIDERS.SSLCOM; + draft.content.provider = CA_PROVIDERS.SSLCOM; draft.content.config ??= {} as SSLProviderSettingsContent["config"]; - draft.content.config[APPLY_CA_PROVIDERS.SSLCOM] = values; + draft.content.config[CA_PROVIDERS.SSLCOM] = values; }); await updateSettings(newSettings); @@ -252,7 +252,7 @@ const SSLProviderEditFormSSLComConfig = () => { const [formChanged, setFormChanged] = useState(false); useEffect(() => { - setFormChanged(settings?.content?.provider !== APPLY_CA_PROVIDERS.SSLCOM); + setFormChanged(settings?.content?.provider !== CA_PROVIDERS.SSLCOM); }, [settings?.content?.provider]); const handleFormChange = () => { @@ -305,14 +305,14 @@ const SSLProviderEditFormZeroSSLConfig = () => { }); const formRule = createSchemaFieldRule(formSchema); const { form: formInst, formProps } = useAntdForm>({ - initialValues: settings?.content?.config?.[APPLY_CA_PROVIDERS.ZEROSSL], + initialValues: settings?.content?.config?.[CA_PROVIDERS.ZEROSSL], onSubmit: async (values) => { const newSettings = produce(settings, (draft) => { draft.content ??= {} as SSLProviderSettingsContent; - draft.content.provider = APPLY_CA_PROVIDERS.ZEROSSL; + draft.content.provider = CA_PROVIDERS.ZEROSSL; draft.content.config ??= {} as SSLProviderSettingsContent["config"]; - draft.content.config[APPLY_CA_PROVIDERS.ZEROSSL] = values; + draft.content.config[CA_PROVIDERS.ZEROSSL] = values; }); await updateSettings(newSettings); @@ -322,7 +322,7 @@ const SSLProviderEditFormZeroSSLConfig = () => { const [formChanged, setFormChanged] = useState(false); useEffect(() => { - setFormChanged(settings?.content?.provider !== APPLY_CA_PROVIDERS.ZEROSSL); + setFormChanged(settings?.content?.provider !== CA_PROVIDERS.ZEROSSL); }, [settings?.content?.provider]); const handleFormChange = () => { @@ -383,20 +383,20 @@ const SettingsSSLProvider = () => { fetchData(); }, []); - const [providerType, setProviderType] = useState(APPLY_CA_PROVIDERS.LETSENCRYPT); + const [providerType, setProviderType] = useState(CA_PROVIDERS.LETSENCRYPT); const providerFormEl = useMemo(() => { switch (providerType) { - case APPLY_CA_PROVIDERS.LETSENCRYPT: + case CA_PROVIDERS.LETSENCRYPT: return ; - case APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING: + case CA_PROVIDERS.LETSENCRYPTSTAGING: return ; - case APPLY_CA_PROVIDERS.BUYPASS: + case CA_PROVIDERS.BUYPASS: return ; - case APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES: + case CA_PROVIDERS.GOOGLETRUSTSERVICES: return ; - case APPLY_CA_PROVIDERS.SSLCOM: + case CA_PROVIDERS.SSLCOM: return ; - case APPLY_CA_PROVIDERS.ZEROSSL: + case CA_PROVIDERS.ZEROSSL: return ; } }, [providerType]); @@ -431,48 +431,48 @@ const SettingsSSLProvider = () => { }>
- setProviderType(value as ApplyCAProviderType)}> + setProviderType(value as CAProviderType)}> } size="small" title={t("provider.letsencrypt")} description="letsencrypt.org" - value={APPLY_CA_PROVIDERS.LETSENCRYPT} + value={CA_PROVIDERS.LETSENCRYPT} /> } size="small" title={t("provider.letsencryptstaging")} description="letsencrypt.org" - value={APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING} + value={CA_PROVIDERS.LETSENCRYPTSTAGING} /> } size="small" title={t("provider.buypass")} description="buypass.com" - value={APPLY_CA_PROVIDERS.BUYPASS} + value={CA_PROVIDERS.BUYPASS} /> } size="small" title={t("provider.googletrustservices")} description="pki.goog" - value={APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES} + value={CA_PROVIDERS.GOOGLETRUSTSERVICES} /> } size="small" title={t("provider.sslcom")} description="ssl.com" - value={APPLY_CA_PROVIDERS.SSLCOM} + value={CA_PROVIDERS.SSLCOM} /> } size="small" title={t("provider.zerossl")} description="zerossl.com" - value={APPLY_CA_PROVIDERS.ZEROSSL} + value={CA_PROVIDERS.ZEROSSL} /> diff --git a/ui/src/pages/workflows/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx index 09bca7fc..f7a350fd 100644 --- a/ui/src/pages/workflows/WorkflowList.tsx +++ b/ui/src/pages/workflows/WorkflowList.tsx @@ -281,6 +281,7 @@ const WorkflowList = () => { const handleSearch = (value: string) => { setFilters((prev) => ({ ...prev, keyword: value.trim() })); + setPage(1); }; const handleCreateClick = () => { diff --git a/ui/src/stores/notify/index.ts b/ui/src/stores/notify/index.ts index c8f0356f..d144aff4 100644 --- a/ui/src/stores/notify/index.ts +++ b/ui/src/stores/notify/index.ts @@ -4,6 +4,9 @@ import { create } from "zustand"; import { type NotifyChannelsSettingsContent, SETTINGS_NAMES, type SettingsModel } from "@/domain/settings"; import { get as getSettings, save as saveSettings } from "@/repository/settings"; +/** + * @deprecated + */ export interface NotifyChannelsState { channels: NotifyChannelsSettingsContent; loading: boolean; @@ -14,6 +17,9 @@ export interface NotifyChannelsState { setChannels: (channels: NotifyChannelsSettingsContent) => Promise; } +/** + * @deprecated + */ export const useNotifyChannelsStore = create((set, get) => { let fetcher: Promise> | null = null; // 防止多次重复请求 let settings: SettingsModel; // 记录当前设置的其他字段,保存回数据库时用