mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-07 21:19:51 +00:00
Merge pull request #643 from fudiwei/main
Support configuring independent notification channel for each workflow
This commit is contained in:
commit
fc064aa9f8
2
go.mod
2
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
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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 {
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
)
|
||||
|
@ -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 {
|
||||
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
72
internal/notify/notifier.go
Normal file
72
internal/notify/notifier.go
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
108
internal/notify/providers_deprecated.go
Normal file
108
internal/notify/providers_deprecated.go
Normal file
@ -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)
|
||||
}
|
@ -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 {
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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{
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)),
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -32,6 +32,7 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
|
||||
|
||||
return &NotifierProvider{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
})
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
|
||||
|
||||
return &NotifierProvider{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -29,6 +29,7 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
|
||||
|
||||
return &NotifierProvider{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
33
internal/pkg/utils/http/parser.go
Normal file
33
internal/pkg/utils/http/parser.go
Normal file
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
// 生成证书实体
|
||||
|
88
migrations/1745726400_upgrade.go
Normal file
88
migrations/1745726400_upgrade.go
Normal file
@ -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
|
||||
})
|
||||
}
|
1
ui/public/imgs/providers/dingtalk.svg
Normal file
1
ui/public/imgs/providers/dingtalk.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512.003 79C272.855 79 79 272.855 79 512.003 79 751.145 272.855 945 512.003 945 751.145 945 945 751.145 945 512.003 945 272.855 751.145 79 512.003 79z m200.075 375.014c-0.867 3.764-3.117 9.347-6.234 16.012h0.087l-0.347 0.648c-18.183 38.86-65.631 115.108-65.631 115.108l-0.215-0.52-13.856 24.147h66.8L565.063 779l29.002-115.368h-52.598l18.27-76.29c-14.76 3.55-32.253 8.436-52.945 15.1 0 0-27.967 16.36-80.607-31.5 0 0-35.501-31.29-14.891-39.078 8.744-3.33 42.466-7.573 69.004-11.122 35.93-4.845 57.965-7.441 57.965-7.441s-110.607 1.643-136.841-2.468c-26.237-4.11-59.525-47.905-66.626-86.377 0 0-10.953-21.117 23.595-11.122 34.547 10 177.535 38.95 177.535 38.95s-185.933-56.992-198.36-70.929c-12.381-13.846-36.406-75.902-33.289-113.981 0 0 1.343-9.521 11.127-6.926 0 0 137.49 62.75 231.475 97.152 94.028 34.403 175.76 51.885 165.2 96.414z" fill="#3AA2EB"></path></svg>
|
After Width: | Height: | Size: 1022 B |
1
ui/public/imgs/providers/email.svg
Normal file
1
ui/public/imgs/providers/email.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M832 128H192a128 128 0 0 0-128 128v512a128 128 0 0 0 128 128h384v-64H192a64 64 0 0 1-64-64V310.4l384 206.08 384-206.72V768h64V256a128 128 0 0 0-128-128zM512 443.52L131.2 240A64 64 0 0 1 192 192h640a64 64 0 0 1 60.8 46.72z" fill="#616971"></path><path d="M640 896h64v-64h-64z m256-64v64h64v-64z m-128 64h64v-64h-64z" fill="#FF8910"></path></svg>
|
After Width: | Height: | Size: 500 B |
1
ui/public/imgs/providers/lark.svg
Normal file
1
ui/public/imgs/providers/lark.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 24 KiB |
1
ui/public/imgs/providers/mattermost.svg
Normal file
1
ui/public/imgs/providers/mattermost.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M515.465911 0.01242C300.718451-1.427543 99.826634 133.353979 27.187509 347.877444c-90.654661 267.771091 52.898635 558.325595 320.665726 648.947257 267.771091 90.654661 558.325595-52.898635 648.947257-320.665726 73.698099-217.594386-7.327811-450.199385-183.932255-578.421077l5.375862 108.670197c88.097727 97.374488 122.87783 235.163933 79.006961 364.760588C831.811749 764.541694 615.912319 866.204071 415.1165 798.204826c-200.860818-67.966246-310.617986-279.83378-245.115676-473.23879 43.998865-129.949647 155.932977-218.494363 285.592631-241.915759L525.641648 0.268413A211.822535 211.822535 0 0 0 516.199892 0.044419h-0.79998zM665.996027 46.700215h-0.351991a12.193685 12.193685 0 0 0-4.959872 1.055973l0.063998-0.031999-0.159996 0.031999a14.076637 14.076637 0 0 0-4.63988 3.039921c-6.175841 6.047844-28.031277 35.327089-28.031277 35.327089L580.298238 145.097676l-55.454569 67.614256-95.231543 118.429944s-43.678873 54.526593-34.047122 121.629862c9.631751 67.166267 59.550464 99.838424 98.297464 112.990085 38.687002 13.087662 98.206466 17.407551 146.685215-30.047225C688.967434 488.258823 687.395475 418.372626 687.395475 418.372626l-3.711905-151.901081-2.975923-87.454744-2.011948-75.742046s0.415989-36.511058-0.863978-45.086836a13.914641 13.914641 0 0 0-1.53596-4.639881l0.031999 0.063999-0.255993-0.511987-0.287993-0.479988a11.878694 11.878694 0 0 0-9.759748-5.983845H665.996027z" fill="#0072C6"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
1
ui/public/imgs/providers/telegram.svg
Normal file
1
ui/public/imgs/providers/telegram.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M679.424 746.862l84.005-395.996c7.424-34.852-12.581-48.567-35.438-40.009L234.277 501.138c-33.72 13.13-33.134 32-5.706 40.558l126.282 39.424 293.156-184.576c13.714-9.143 26.295-3.986 16.018 5.157L426.898 615.973l-9.143 130.304c13.13 0 18.871-5.706 25.71-12.581l61.696-59.429 128 94.282c23.442 13.129 40.01 6.29 46.3-21.724zM1024 512c0 282.843-229.157 512-512 512S0 794.843 0 512 229.157 0 512 0s512 229.157 512 512z" fill="#1296DB"></path></svg>
|
1
ui/public/imgs/providers/wecom.svg
Normal file
1
ui/public/imgs/providers/wecom.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M803.6 859.2c0 26.6-20.4 49.2-48.4 51.8-21.2 2-48.6-11-54.8-39.8-5.6-26-13.2-50.8-29-72.4-6-8.2-12.8-16-20-23.4-7.6-8-9.4-14-4.4-19.8 5-5.8 12.8-5 20.8 3.2 20.8 21 45.4 35.2 73.6 43.6 7.2 2.2 14.8 3.4 22.2 5.2 24.6 6.2 40 26.2 40 51.6z" fill="#FC6401"></path><path d="M698.2 549.8c0.2-28.4 20.8-50.2 49.6-52.6 25.6-2.2 50.6 17.6 55 45.2 6 36.2 22.8 66.2 48.4 92 3.2 3.2 5.6 9.2 5.2 13.8-0.4 7.2-9.8 10.6-16.2 6.4-3.4-2.2-6.2-5-9-7.8-25.4-24.6-55.6-39.4-90.4-45.4-24.8-4.6-42.6-26.2-42.6-51.6z" fill="#2DBD00"></path><path d="M595.4 765.2c-26.6 0-49.2-20.4-51.8-48.4-2-21.2 11-48.6 39.8-54.8 26-5.6 50.8-13.2 72.4-29 8.2-6 16-12.8 23.4-20 8-7.6 14-9.4 19.8-4.4 5.8 5 5 12.8-3.2 20.8-21 20.8-35.2 45.4-43.6 73.6-2.2 7.2-3.4 14.8-5.2 22.2-6.2 24.6-26.2 40-51.6 40z" fill="#FFCD00"></path><path d="M898.8 650c28.4 0.2 50.2 20.8 52.6 49.6 2.2 25.6-17.6 50.6-45.2 55-36.2 6-66.2 22.8-92 48.4-3.2 3.2-9.2 5.6-13.8 5.2-7.2-0.4-10.6-9.8-6.4-16.2 2.2-3.4 5-6.2 7.8-9 24.6-25.4 39.4-55.6 45.4-90.4 4.6-25 26.2-42.8 51.6-42.6z" fill="#0084F0"></path><path d="M734 208.6c-110.4-108.4-244.8-139.8-392.4-100C81.6 178.8 1.2 449.2 132.6 625.6c6.4 8.6 7.6 24.4 5.2 35.4-7 32.4-17.4 64.2-26 96.2-4.6 17.2-7.4 34.6 8 48.4 16.6 14.8 34.2 11.8 52.2 2.6 29.6-15 59.8-29.2 89-45 19-10.4 36.2-10.8 57.6-4.8 42.8 11.8 87.2 18.4 109.6 23 43.8-0.8 83.6-5.2 120.2-13.6-13.8-12-23-29.2-24.8-49-0.4-5.4-0.2-10.8 0.6-16.2-57.8 12.4-119.2 7.4-183.2-14-42.2-14.2-76.8-17.8-113.4 7-3.4 2.2-7.8 2.8-24.6 8.2 33.8-58.4 8.8-95-19.6-136.6-63.4-92-50.4-210.8 24.6-296.4 122.8-139.8 363-139.8 485.8 0.2 52.8 60.2 73.6 135.2 61.8 206.2 28 1.6 52.8 20.6 63 47.2 32-108.8 4-228.8-84.6-315.8z" fill="#0083EF"></path></svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -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)}
|
||||
>
|
||||
<AccessForm ref={formRef} initialValues={data} range={range} scene={scene === "add" ? "add" : "edit"} />
|
||||
<AccessForm ref={formRef} initialValues={data} scene={scene === "add" ? "add" : "edit"} usage={usage} />
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
|
@ -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}
|
||||
>
|
||||
<div className="pb-2 pt-4">
|
||||
<AccessForm ref={formRef} initialValues={data} range={range} scene={scene === "add" ? "add" : "edit"} />
|
||||
<AccessForm ref={formRef} initialValues={data} scene={scene === "add" ? "add" : "edit"} usage={usage} />
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
|
@ -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<MaybeModelRecord<AccessModel>>;
|
||||
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<AccessFormFieldValues>["validateFields"];
|
||||
};
|
||||
|
||||
const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className, style, disabled, initialValues, range, scene, onValuesChange }, ref) => {
|
||||
const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className, style, disabled, initialValues, usage, scene, onValuesChange }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
@ -88,13 +94,14 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ 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<AccessFormInstance, AccessFormProps>(({ 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 <span dangerouslySetInnerHTML={{ __html: t("access.form.provider.tooltip") }}></span>;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [range]);
|
||||
}, [usage]);
|
||||
|
||||
const fieldProvider = Form.useWatch("provider", formInst);
|
||||
|
||||
@ -179,6 +186,8 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
return <AccessFormCMCCCloudConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.DESEC:
|
||||
return <AccessFormDeSECConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.DINGTALKBOT:
|
||||
return <AccessFormDingTalkBotConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.DNSLA:
|
||||
return <AccessFormDNSLAConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.DOGECLOUD:
|
||||
@ -195,12 +204,18 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
return <AccessFormGoogleTrustServicesConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.EDGIO:
|
||||
return <AccessFormEdgioConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.EMAIL:
|
||||
return <AccessFormEmailConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.HUAWEICLOUD:
|
||||
return <AccessFormHuaweiCloudConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.JDCLOUD:
|
||||
return <AccessFormJDCloudConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.KUBERNETES:
|
||||
return <AccessFormKubernetesConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.LARKBOT:
|
||||
return <AccessFormLarkBotConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.MATTERMOST:
|
||||
return <AccessFormMattermostConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.NAMECHEAP:
|
||||
return <AccessFormNamecheapConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.NAMEDOTCOM:
|
||||
@ -221,6 +236,8 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
return <AccessFormSafeLineConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.SSH:
|
||||
return <AccessFormSSHConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.TELEGRAM:
|
||||
return <AccessFormTelegramConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.SSLCOM:
|
||||
return <AccessFormSSLComConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.TENCENTCLOUD:
|
||||
@ -236,7 +253,14 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
case ACCESS_PROVIDERS.WANGSU:
|
||||
return <AccessFormWangsuConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.WEBHOOK:
|
||||
return <AccessFormWebhookConfig {...nestedFormProps} />;
|
||||
return (
|
||||
<AccessFormWebhookConfig
|
||||
usage={usage === "notification-only" ? "notification" : usage === "both-dns-hosting" ? "deployment" : "none"}
|
||||
{...nestedFormProps}
|
||||
/>
|
||||
);
|
||||
case ACCESS_PROVIDERS.WECOMBOT:
|
||||
return <AccessFormWeComBotConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.WESTCN:
|
||||
return <AccessFormWestcnConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.ZEROSSL:
|
||||
@ -260,6 +284,7 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ 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<AccessFormInstance, AccessFormProps>(({ className,
|
||||
<Form.Item name="provider" label={providerLabel} rules={[formRule]} tooltip={providerTooltip}>
|
||||
<AccessProviderSelect
|
||||
filter={(record) => {
|
||||
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}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
68
ui/src/components/access/AccessFormDingTalkBotConfig.tsx
Normal file
68
ui/src/components/access/AccessFormDingTalkBotConfig.tsx
Normal file
@ -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<AccessConfigForDingTalkBot>;
|
||||
|
||||
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<typeof formSchema>) => {
|
||||
onValuesChange?.(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={formInst}
|
||||
disabled={disabled}
|
||||
initialValues={initialValues ?? initFormModel()}
|
||||
layout="vertical"
|
||||
name={formName}
|
||||
onValuesChange={handleFormChange}
|
||||
>
|
||||
<Form.Item
|
||||
name="webhookUrl"
|
||||
label={t("access.form.dingtalkbot_webhook_url.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.dingtalkbot_webhook_url.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("access.form.dingtalkbot_webhook_url.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="secret"
|
||||
label={t("access.form.dingtalkbot_secret.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.dingtalkbot_secret.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.dingtalkbot_secret.placeholder")} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessFormDingTalkBotConfig;
|
125
ui/src/components/access/AccessFormEmailConfig.tsx
Normal file
125
ui/src/components/access/AccessFormEmailConfig.tsx
Normal file
@ -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<AccessConfigForEmail>;
|
||||
|
||||
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<typeof formSchema>) => {
|
||||
onValuesChange?.(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={formInst}
|
||||
disabled={disabled}
|
||||
initialValues={initialValues ?? initFormModel()}
|
||||
layout="vertical"
|
||||
name={formName}
|
||||
onValuesChange={handleFormChange}
|
||||
>
|
||||
<div className="flex space-x-2">
|
||||
<div className="w-3/5">
|
||||
<Form.Item name="smtpHost" label={t("access.form.email_smtp_host.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("access.form.email_smtp_host.placeholder")} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
<div className="w-2/5">
|
||||
<Form.Item name="smtpPort" label={t("access.form.email_smtp_port.label")} rules={[formRule]}>
|
||||
<InputNumber className="w-full" placeholder={t("access.form.email_smtp_port.placeholder")} min={1} max={65535} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Form.Item name="smtpTls" label={t("access.form.email_smtp_tls.label")} rules={[formRule]}>
|
||||
<Switch onChange={handleTlsSwitchChange} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="username" label={t("access.form.email_username.label")} rules={[formRule]}>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.email_username.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="password" label={t("access.form.email_password.label")} rules={[formRule]}>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.email_password.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="defaultSenderAddress" label={t("access.form.email_default_sender_address.label")} rules={[formRule]}>
|
||||
<Input type="email" allowClear placeholder={t("access.form.email_default_sender_address.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="defaultReceiverAddress" label={t("access.form.email_default_receiver_address.label")} rules={[formRule]}>
|
||||
<Input type="email" allowClear placeholder={t("access.form.email_default_receiver_address.placeholder")} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessFormEmailConfig;
|
57
ui/src/components/access/AccessFormLarkBotConfig.tsx
Normal file
57
ui/src/components/access/AccessFormLarkBotConfig.tsx
Normal file
@ -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<AccessConfigForLarkBot>;
|
||||
|
||||
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<typeof formSchema>) => {
|
||||
onValuesChange?.(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={formInst}
|
||||
disabled={disabled}
|
||||
initialValues={initialValues ?? initFormModel()}
|
||||
layout="vertical"
|
||||
name={formName}
|
||||
onValuesChange={handleFormChange}
|
||||
>
|
||||
<Form.Item
|
||||
name="webhookUrl"
|
||||
label={t("access.form.larkbot_webhook_url.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.larkbot_webhook_url.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("access.form.larkbot_webhook_url.placeholder")} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessFormLarkBotConfig;
|
79
ui/src/components/access/AccessFormMattermostConfig.tsx
Normal file
79
ui/src/components/access/AccessFormMattermostConfig.tsx
Normal file
@ -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<AccessConfigForMattermost>;
|
||||
|
||||
export type AccessFormMattermostConfigProps = {
|
||||
form: FormInstance;
|
||||
formName: string;
|
||||
disabled?: boolean;
|
||||
initialValues?: AccessFormMattermostConfigFieldValues;
|
||||
onValuesChange?: (values: AccessFormMattermostConfigFieldValues) => void;
|
||||
};
|
||||
|
||||
const initFormModel = (): AccessFormMattermostConfigFieldValues => {
|
||||
return {
|
||||
serverUrl: "http://<your-host-addr>: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<typeof formSchema>) => {
|
||||
onValuesChange?.(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={formInst}
|
||||
disabled={disabled}
|
||||
initialValues={initialValues ?? initFormModel()}
|
||||
layout="vertical"
|
||||
name={formName}
|
||||
onValuesChange={handleFormChange}
|
||||
>
|
||||
<Form.Item
|
||||
name="serverUrl"
|
||||
label={t("access.form.mattermost_server_url.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.mattermost_server_url.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("access.form.mattermost_server_url.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="username" label={t("access.form.mattermost_username.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("access.form.mattermost_username.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="password" label={t("access.form.mattermost_password.label")} rules={[formRule]}>
|
||||
<Input.Password placeholder={t("access.form.mattermost_password.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="defaultChannelId"
|
||||
label={t("access.form.mattermost_default_channel_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.mattermost_default_channel_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input allowClear placeholder={t("access.form.mattermost_default_channel_id.placeholder")} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessFormMattermostConfig;
|
@ -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"))
|
||||
),
|
||||
|
81
ui/src/components/access/AccessFormTelegramConfig.tsx
Normal file
81
ui/src/components/access/AccessFormTelegramConfig.tsx
Normal file
@ -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<AccessConfigForTelegram>;
|
||||
|
||||
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<typeof formSchema>) => {
|
||||
onValuesChange?.(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={formInst}
|
||||
disabled={disabled}
|
||||
initialValues={initialValues ?? initFormModel()}
|
||||
layout="vertical"
|
||||
name={formName}
|
||||
onValuesChange={handleFormChange}
|
||||
>
|
||||
<Form.Item
|
||||
name="botToken"
|
||||
label={t("access.form.telegram_bot_token.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.telegram_bot_token.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.telegram_bot_token.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="defaultChatId"
|
||||
label={t("access.form.telegram_default_chat_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.telegram_default_chat_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input type="number" allowClear placeholder={t("access.form.telegram_default_chat_id.placeholder")} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessFormTelegramConfig;
|
57
ui/src/components/access/AccessFormWeComBotConfig.tsx
Normal file
57
ui/src/components/access/AccessFormWeComBotConfig.tsx
Normal file
@ -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<AccessConfigForWeComBot>;
|
||||
|
||||
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<typeof formSchema>) => {
|
||||
onValuesChange?.(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={formInst}
|
||||
disabled={disabled}
|
||||
initialValues={initialValues ?? initFormModel()}
|
||||
layout="vertical"
|
||||
name={formName}
|
||||
onValuesChange={handleFormChange}
|
||||
>
|
||||
<Form.Item
|
||||
name="webhookUrl"
|
||||
label={t("access.form.wecombot_webhook_url.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.wecombot_webhook_url.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("access.form.wecombot_webhook_url.placeholder")} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessFormWeComBotConfig;
|
@ -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<AccessConfigForWebhook>;
|
||||
@ -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<HTMLTextAreaElement>) => {
|
||||
let value = e.target.value;
|
||||
value = value.trim();
|
||||
value = value.replace(/(?<!\r)\n/g, "\r\n");
|
||||
formInst.setFieldValue("headers", value);
|
||||
};
|
||||
|
||||
const handleWebhookDataForDeploymentBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||
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<HTMLTextAreaElement>) => {
|
||||
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 <your-gotify-token>");
|
||||
formInst.setFieldValue(
|
||||
"defaultDataForNotification",
|
||||
JSON.stringify(
|
||||
{
|
||||
title: "${SUBJECT}",
|
||||
body: "${MESSAGE}",
|
||||
group: "<your-bark-group>",
|
||||
device_keys: "<your-bark-device-key>",
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
break;
|
||||
|
||||
case "gotify":
|
||||
formInst.setFieldValue("url", "https://<your-gotify-server>/");
|
||||
formInst.setFieldValue("method", "POST");
|
||||
formInst.setFieldValue("headers", "Content-Type: application/json\r\nAuthorization: Bearer <your-gotify-token>");
|
||||
formInst.setFieldValue(
|
||||
"defaultDataForNotification",
|
||||
JSON.stringify(
|
||||
{
|
||||
title: "${SUBJECT}",
|
||||
message: "${MESSAGE}",
|
||||
priority: 1,
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
break;
|
||||
|
||||
case "ntfy":
|
||||
formInst.setFieldValue("url", "https://<your-ntfy-server>/");
|
||||
formInst.setFieldValue("method", "POST");
|
||||
formInst.setFieldValue("headers", "Content-Type: application/json");
|
||||
formInst.setFieldValue(
|
||||
"defaultDataForNotification",
|
||||
JSON.stringify(
|
||||
{
|
||||
topic: "<your-ntfy-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: "<your-pushover-token>",
|
||||
user: "<your-pushover-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: "<your-pushplus-token>",
|
||||
title: "${SUBJECT}",
|
||||
content: "${MESSAGE}",
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
break;
|
||||
|
||||
case "serverchan":
|
||||
formInst.setFieldValue("url", "https://sctapi.ftqq.com/<your-serverchan-key>.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<typeof formSchema>) => {
|
||||
onValuesChange?.(values);
|
||||
};
|
||||
@ -47,6 +266,92 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa
|
||||
<Input placeholder={t("access.form.webhook_url.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="method" label={t("access.form.webhook_method.label")} rules={[formRule]}>
|
||||
<Select
|
||||
options={["GET", "POST", "PUT", "PATCH", "DELETE"].map((s) => ({ label: s, value: s }))}
|
||||
placeholder={t("access.form.webhook_method.placeholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="headers"
|
||||
label={t("access.form.webhook_headers.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.webhook_headers.tooltip") }}></span>}
|
||||
>
|
||||
<Input.TextArea autoSize={{ minRows: 3, maxRows: 5 }} placeholder={t("access.form.webhook_headers.placeholder")} onBlur={handleWebhookHeadersBlur} />
|
||||
</Form.Item>
|
||||
|
||||
<Show when={!usage || usage === "deployment"}>
|
||||
<Form.Item className="mb-0">
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">
|
||||
<span>{t("access.form.webhook_default_data_for_deployment.label")}</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<Button size="small" type="link" onClick={handlePresetDataForDeploymentClick}>
|
||||
{t("access.form.webhook_preset_data.button")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<Form.Item name="defaultDataForDeployment" rules={[formRule]}>
|
||||
<Input.TextArea
|
||||
allowClear
|
||||
autoSize={{ minRows: 3, maxRows: 10 }}
|
||||
placeholder={t("access.form.webhook_default_data_for_deployment.placeholder")}
|
||||
onBlur={handleWebhookDataForDeploymentBlur}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Alert type="info" message={<span dangerouslySetInnerHTML={{ __html: t("access.form.webhook_default_data_for_deployment.guide") }}></span>} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={!usage || usage === "notification"}>
|
||||
<Form.Item className="mb-0">
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">
|
||||
<span>{t("access.form.webhook_default_data_for_notification.label")}</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: ["bark", "ntfy", "gotify", "pushover", "pushplus", "serverchan", "common"].map((key) => ({
|
||||
key,
|
||||
label: t(`access.form.webhook_preset_data.option.${key}.label`),
|
||||
onClick: () => handlePresetDataForNotificationClick(key),
|
||||
})),
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button size="small" type="link">
|
||||
{t("access.form.webhook_preset_data.button")}
|
||||
<DownOutlinedIcon />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<Form.Item name="defaultDataForNotification" rules={[formRule]}>
|
||||
<Input.TextArea
|
||||
allowClear
|
||||
autoSize={{ minRows: 3, maxRows: 10 }}
|
||||
placeholder={t("access.form.webhook_default_data_for_notification.placeholder")}
|
||||
onBlur={handleWebhookDataForNotificationBlur}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Alert type="info" message={<span dangerouslySetInnerHTML={{ __html: t("access.form.webhook_default_data_for_notification.guide") }}></span>} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Form.Item
|
||||
name="allowInsecureConnections"
|
||||
label={t("access.form.webhook_allow_insecure_conns.label")}
|
||||
|
@ -34,6 +34,9 @@ export type NotifyChannelEditFormInstance = {
|
||||
validateFields: FormInstance<NotifyChannelEditFormFieldValues>["validateFields"];
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
const NotifyChannelEditForm = forwardRef<NotifyChannelEditFormInstance, NotifyChannelEditFormProps>(
|
||||
({ className, style, channel, disabled, initialValues, onValuesChange }, ref) => {
|
||||
const { form: formInst, formProps } = useAntdForm({
|
||||
|
@ -7,9 +7,7 @@ const NotifyChannelEditFormMattermostFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
serverUrl: z
|
||||
.string({ message: t("settings.notification.channel.form.mattermost_server_url.placeholder") })
|
||||
.url(t("common.errmsg.url_invalid")),
|
||||
serverUrl: z.string({ message: t("settings.notification.channel.form.mattermost_server_url.placeholder") }).url(t("common.errmsg.url_invalid")),
|
||||
channelId: z
|
||||
.string({ message: t("settings.notification.channel.form.mattermost_channel_id.placeholder") })
|
||||
.nonempty(t("settings.notification.channel.form.mattermost_channel_id.placeholder")),
|
||||
@ -42,19 +40,11 @@ const NotifyChannelEditFormMattermostFields = () => {
|
||||
<Input placeholder={t("settings.notification.channel.form.mattermost_channel_id.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="username"
|
||||
label={t("settings.notification.channel.form.mattermost_username.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Form.Item name="username" label={t("settings.notification.channel.form.mattermost_username.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("settings.notification.channel.form.mattermost_username.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="password"
|
||||
label={t("settings.notification.channel.form.mattermost_password.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Form.Item name="password" label={t("settings.notification.channel.form.mattermost_password.label")} rules={[formRule]}>
|
||||
<Input.Password placeholder={t("settings.notification.channel.form.mattermost_password.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
|
@ -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<string>();
|
||||
@ -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);
|
@ -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<Array<{ key: string; value: string; label: string; data: ApplyDNSProvider }>>([]);
|
||||
const [options, setOptions] = useState<Array<{ key: string; value: string; label: string; data: ACMEDns01Provider }>>([]);
|
||||
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 (
|
||||
<Space className="max-w-full grow overflow-hidden truncate" size={4}>
|
||||
<Avatar src={provider?.icon} size="small" />
|
||||
@ -64,4 +64,4 @@ const ApplyDNSProviderSelect = ({ filter, ...props }: ApplyDNSProviderSelectProp
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ApplyDNSProviderSelect);
|
||||
export default memo(ACMEDns01ProviderSelect);
|
@ -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<Array<{ key: string; value: string; label: string; data: ApplyCAProvider }>>([]);
|
||||
const [options, setOptions] = useState<Array<{ key: string; value: string; label: string; data: CAProvider }>>([]);
|
||||
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 (
|
||||
<Space className="max-w-full grow overflow-hidden truncate" size={4}>
|
||||
<Avatar src={provider?.icon} size="small" />
|
||||
@ -80,4 +80,4 @@ const ApplyCAProviderSelect = ({ filter, ...props }: ApplyCAProviderSelectProps)
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ApplyCAProviderSelect);
|
||||
export default memo(CAProviderSelect);
|
@ -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<string>(DEPLOY_CATEGORIES.ALL);
|
||||
const [category, setCategory] = useState<string>(DEPLOYMENT_CATEGORIES.ALL);
|
||||
|
||||
const [keyword, setKeyword] = useState<string>();
|
||||
const keywordInputRef = useRef<InputRef>(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
|
||||
<div className="mt-4">
|
||||
<Flex>
|
||||
<Tabs
|
||||
defaultActiveKey={DEPLOY_CATEGORIES.ALL}
|
||||
defaultActiveKey={DEPLOYMENT_CATEGORIES.ALL}
|
||||
items={[
|
||||
DEPLOY_CATEGORIES.ALL,
|
||||
DEPLOY_CATEGORIES.CDN,
|
||||
DEPLOY_CATEGORIES.STORAGE,
|
||||
DEPLOY_CATEGORIES.LOADBALANCE,
|
||||
DEPLOY_CATEGORIES.FIREWALL,
|
||||
DEPLOY_CATEGORIES.AV,
|
||||
DEPLOY_CATEGORIES.SERVERLESS,
|
||||
DEPLOY_CATEGORIES.WEBSITE,
|
||||
DEPLOY_CATEGORIES.OTHER,
|
||||
DEPLOYMENT_CATEGORIES.ALL,
|
||||
DEPLOYMENT_CATEGORIES.CDN,
|
||||
DEPLOYMENT_CATEGORIES.STORAGE,
|
||||
DEPLOYMENT_CATEGORIES.LOADBALANCE,
|
||||
DEPLOYMENT_CATEGORIES.FIREWALL,
|
||||
DEPLOYMENT_CATEGORIES.AV,
|
||||
DEPLOYMENT_CATEGORIES.SERVERLESS,
|
||||
DEPLOYMENT_CATEGORIES.WEBSITE,
|
||||
DEPLOYMENT_CATEGORIES.OTHER,
|
||||
].map((key) => ({
|
||||
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);
|
@ -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<Array<{ key: string; value: string; label: string; data: DeployProvider }>>([]);
|
||||
const [options, setOptions] = useState<Array<{ key: string; value: string; label: string; data: DeploymentProvider }>>([]);
|
||||
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 (
|
||||
<Space className="max-w-full grow overflow-hidden truncate" size={4}>
|
||||
<Avatar src={provider?.icon} size="small" />
|
||||
@ -64,4 +64,4 @@ const DeployProviderSelect = ({ filter, ...props }: DeployProviderSelectProps) =
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(DeployProviderSelect);
|
||||
export default memo(DeploymentProviderSelect);
|
67
ui/src/components/provider/NotificationProviderSelect.tsx
Normal file
67
ui/src/components/provider/NotificationProviderSelect.tsx
Normal file
@ -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<Array<{ key: string; value: string; label: string; data: NotificationProvider }>>([]);
|
||||
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 (
|
||||
<Space className="max-w-full grow overflow-hidden truncate" size={4}>
|
||||
<Avatar src={provider?.icon} size="small" />
|
||||
<Typography.Text className="leading-loose" ellipsis>
|
||||
{t(provider?.name ?? "")}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
{...props}
|
||||
filterOption={(inputValue, option) => {
|
||||
if (!option) return false;
|
||||
|
||||
const value = inputValue.toLowerCase();
|
||||
return option.value.toLowerCase().includes(value) || option.label.toLowerCase().includes(value);
|
||||
}}
|
||||
labelRender={({ label, value }) => {
|
||||
if (!label) {
|
||||
return <Typography.Text type="secondary">{props.placeholder}</Typography.Text>;
|
||||
}
|
||||
|
||||
return renderOption(value as string);
|
||||
}}
|
||||
options={options}
|
||||
optionFilterProp={undefined}
|
||||
optionLabelProp={undefined}
|
||||
optionRender={(option) => renderOption(option.data.value)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(NotificationProviderSelect);
|
@ -193,6 +193,7 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
|
||||
const NEWLINE = "\n";
|
||||
const logstr = listData
|
||||
.map((group) => {
|
||||
const escape = (str: string) => str.replaceAll("\r", "\\r").replaceAll("\n", "\\n");
|
||||
return (
|
||||
group.name +
|
||||
NEWLINE +
|
||||
@ -200,8 +201,9 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
|
||||
.map((record) => {
|
||||
const datetime = dayjs(record.timestamp).format("YYYY-MM-DDTHH:mm:ss.SSSZ");
|
||||
const level = record.level;
|
||||
const message = record.message.trim().replaceAll("\r", "\\r").replaceAll("\n", "\\n");
|
||||
return `[${datetime}] [${level}] ${message}`;
|
||||
const message = record.message;
|
||||
const data = record.data && Object.keys(record.data).length > 0 ? JSON.stringify(record.data) : "";
|
||||
return `[${datetime}] [${level}] ${escape(message)} ${escape(data)}`.trim();
|
||||
})
|
||||
.join(NEWLINE)
|
||||
);
|
||||
|
@ -31,9 +31,10 @@ import AccessEditModal from "@/components/access/AccessEditModal";
|
||||
import AccessSelect from "@/components/access/AccessSelect";
|
||||
import ModalForm from "@/components/ModalForm";
|
||||
import MultipleInput from "@/components/MultipleInput";
|
||||
import ApplyCAProviderSelect from "@/components/provider/ApplyCAProviderSelect";
|
||||
import ApplyDNSProviderSelect from "@/components/provider/ApplyDNSProviderSelect";
|
||||
import { ACCESS_USAGES, APPLY_DNS_PROVIDERS, accessProvidersMap, applyCAProvidersMap, applyDNSProvidersMap } from "@/domain/provider";
|
||||
import ACMEDns01ProviderSelect from "@/components/provider/ACMEDns01ProviderSelect";
|
||||
import CAProviderSelect from "@/components/provider/CAProviderSelect";
|
||||
import Show from "@/components/Show";
|
||||
import { ACCESS_USAGES, ACME_DNS01_PROVIDERS, accessProvidersMap, acmeDns01ProvidersMap, caProvidersMap } from "@/domain/provider";
|
||||
import { type WorkflowNodeConfigForApply } from "@/domain/workflow";
|
||||
import { useAntdForm, useAntdFormName, useZustandShallowSelector } from "@/hooks";
|
||||
import { useAccessesStore } from "@/stores/access";
|
||||
@ -98,7 +99,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
.refine((v) => {
|
||||
if (!fieldCAProvider) return true;
|
||||
|
||||
const provider = applyCAProvidersMap.get(fieldCAProvider);
|
||||
const provider = caProvidersMap.get(fieldCAProvider);
|
||||
return !!provider?.builtin || !!v;
|
||||
}, t("workflow_node.apply.form.ca_provider_access.placeholder")),
|
||||
caProviderConfig: z.any().nullish(),
|
||||
@ -154,7 +155,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
// 如果对应多个(如 AWS 的 Route53、Lightsail,腾讯云的 DNS、EdgeOne 等),则显示。
|
||||
if (fieldProviderAccessId) {
|
||||
const access = accesses.find((e) => e.id === fieldProviderAccessId);
|
||||
const providers = Array.from(applyDNSProvidersMap.values()).filter((e) => e.provider === access?.provider);
|
||||
const providers = Array.from(acmeDns01ProvidersMap.values()).filter((e) => e.provider === access?.provider);
|
||||
setShowProvider(providers.length > 1);
|
||||
} else {
|
||||
setShowProvider(false);
|
||||
@ -165,7 +166,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
useEffect(() => {
|
||||
// 内置的 CA 提供商(如 Let's Encrypt)无需显示授权信息字段
|
||||
if (fieldCAProvider) {
|
||||
const provider = applyCAProvidersMap.get(fieldCAProvider);
|
||||
const provider = caProvidersMap.get(fieldCAProvider);
|
||||
setShowCAProviderAccess(!provider?.builtin);
|
||||
} else {
|
||||
setShowCAProviderAccess(false);
|
||||
@ -187,16 +188,16 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
NOTICE: If you add new child component, please keep ASCII order.
|
||||
*/
|
||||
switch (fieldProvider) {
|
||||
case APPLY_DNS_PROVIDERS.AWS:
|
||||
case APPLY_DNS_PROVIDERS.AWS_ROUTE53:
|
||||
case ACME_DNS01_PROVIDERS.AWS:
|
||||
case ACME_DNS01_PROVIDERS.AWS_ROUTE53:
|
||||
return <ApplyNodeConfigFormAWSRoute53Config {...nestedFormProps} />;
|
||||
case APPLY_DNS_PROVIDERS.HUAWEICLOUD:
|
||||
case APPLY_DNS_PROVIDERS.HUAWEICLOUD_DNS:
|
||||
case ACME_DNS01_PROVIDERS.HUAWEICLOUD:
|
||||
case ACME_DNS01_PROVIDERS.HUAWEICLOUD_DNS:
|
||||
return <ApplyNodeConfigFormHuaweiCloudDNSConfig {...nestedFormProps} />;
|
||||
case APPLY_DNS_PROVIDERS.JDCLOUD:
|
||||
case APPLY_DNS_PROVIDERS.JDCLOUD_DNS:
|
||||
case ACME_DNS01_PROVIDERS.JDCLOUD:
|
||||
case ACME_DNS01_PROVIDERS.JDCLOUD_DNS:
|
||||
return <ApplyNodeConfigFormJDCloudDNSConfig {...nestedFormProps} />;
|
||||
case APPLY_DNS_PROVIDERS.TENCENTCLOUD_EO:
|
||||
case ACME_DNS01_PROVIDERS.TENCENTCLOUD_EO:
|
||||
return <ApplyNodeConfigFormTencentCloudEOConfig {...nestedFormProps} />;
|
||||
}
|
||||
}, [disabled, initialValues?.providerConfig, fieldProvider, nestedFormInst, nestedFormName]);
|
||||
@ -209,7 +210,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
formInst.setFieldValue("providerAccessId", initialValues?.providerAccessId);
|
||||
onValuesChange?.(formInst.getFieldsValue(true));
|
||||
} else {
|
||||
if (applyDNSProvidersMap.get(fieldProvider)?.provider !== applyDNSProvidersMap.get(value)?.provider) {
|
||||
if (acmeDns01ProvidersMap.get(fieldProvider)?.provider !== acmeDns01ProvidersMap.get(value)?.provider) {
|
||||
formInst.setFieldValue("providerAccessId", undefined);
|
||||
onValuesChange?.(formInst.getFieldsValue(true));
|
||||
}
|
||||
@ -217,11 +218,9 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
};
|
||||
|
||||
const handleProviderAccessSelect = (value: string) => {
|
||||
if (fieldProviderAccessId === value) return;
|
||||
|
||||
// 切换授权信息时联动 DNS 提供商
|
||||
const access = accesses.find((access) => access.id === value);
|
||||
const provider = Array.from(applyDNSProvidersMap.values()).find((provider) => provider.provider === access?.provider);
|
||||
const provider = Array.from(acmeDns01ProvidersMap.values()).find((provider) => provider.provider === access?.provider);
|
||||
if (fieldProvider !== provider?.type) {
|
||||
formInst.setFieldValue("provider", provider?.type);
|
||||
onValuesChange?.(formInst.getFieldsValue(true));
|
||||
@ -229,8 +228,6 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
};
|
||||
|
||||
const handleCAProviderSelect = (value?: string | undefined) => {
|
||||
if (fieldCAProvider === value) return;
|
||||
|
||||
// 切换 CA 提供商时联动授权信息
|
||||
if (value === "") {
|
||||
setTimeout(() => {
|
||||
@ -242,7 +239,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
formInst.setFieldValue("caProviderAccessId", initialValues?.caProviderAccessId);
|
||||
onValuesChange?.(formInst.getFieldsValue(true));
|
||||
} else {
|
||||
if (applyCAProvidersMap.get(fieldCAProvider)?.provider !== applyCAProvidersMap.get(value!)?.provider) {
|
||||
if (caProvidersMap.get(fieldCAProvider)?.provider !== caProvidersMap.get(value!)?.provider) {
|
||||
formInst.setFieldValue("caProviderAccessId", undefined);
|
||||
onValuesChange?.(formInst.getFieldsValue(true));
|
||||
}
|
||||
@ -327,7 +324,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="provider" label={t("workflow_node.apply.form.provider.label")} hidden={!showProvider} rules={[formRule]}>
|
||||
<ApplyDNSProviderSelect
|
||||
<ACMEDns01ProviderSelect
|
||||
disabled={!showProvider}
|
||||
filter={(record) => {
|
||||
if (fieldProviderAccessId) {
|
||||
@ -355,7 +352,6 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<AccessEditModal
|
||||
range="both-dns-hosting"
|
||||
scene="add"
|
||||
trigger={
|
||||
<Button size="small" type="link">
|
||||
@ -363,10 +359,12 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
<PlusOutlinedIcon className="text-xs" />
|
||||
</Button>
|
||||
}
|
||||
usage="both-dns-hosting"
|
||||
afterSubmit={(record) => {
|
||||
const provider = accessProvidersMap.get(record.provider);
|
||||
if (provider?.usages?.includes(ACCESS_USAGES.DNS)) {
|
||||
formInst.setFieldValue("providerAccessId", record.id);
|
||||
handleProviderAccessSelect(record.id);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@ -376,6 +374,8 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
<Form.Item name="providerAccessId" rules={[formRule]}>
|
||||
<AccessSelect
|
||||
filter={(record) => {
|
||||
if (record.reserve) return false;
|
||||
|
||||
const provider = accessProvidersMap.get(record.provider);
|
||||
return !!provider?.usages?.includes(ACCESS_USAGES.DNS);
|
||||
}}
|
||||
@ -398,19 +398,23 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
<Form.Item className="mb-0">
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">{t("workflow_node.apply.form.ca_provider.label")}</div>
|
||||
<div className="max-w-full grow truncate">
|
||||
<span>{t("workflow_node.apply.form.ca_provider.label")}</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<Link className="ant-typography" to="/settings/ssl-provider" target="_blank">
|
||||
<Button size="small" type="link">
|
||||
{t("workflow_node.apply.form.ca_provider.button")}
|
||||
<RightOutlinedIcon className="text-xs" />
|
||||
</Button>
|
||||
</Link>
|
||||
<Show when={!fieldCAProvider}>
|
||||
<Link className="ant-typography" to="/settings/ssl-provider" target="_blank">
|
||||
<Button size="small" type="link">
|
||||
{t("workflow_node.apply.form.ca_provider.button")}
|
||||
<RightOutlinedIcon className="text-xs" />
|
||||
</Button>
|
||||
</Link>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<Form.Item name="caProvider" rules={[formRule]}>
|
||||
<ApplyCAProviderSelect
|
||||
<CAProviderSelect
|
||||
allowClear
|
||||
placeholder={t("workflow_node.apply.form.ca_provider.placeholder")}
|
||||
showSearch
|
||||
@ -428,8 +432,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<AccessEditModal
|
||||
data={{ provider: applyCAProvidersMap.get(fieldCAProvider!)?.provider }}
|
||||
range="ca-only"
|
||||
data={{ provider: caProvidersMap.get(fieldCAProvider!)?.provider }}
|
||||
scene="add"
|
||||
trigger={
|
||||
<Button size="small" type="link">
|
||||
@ -437,6 +440,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
<PlusOutlinedIcon className="text-xs" />
|
||||
</Button>
|
||||
}
|
||||
usage="ca-only"
|
||||
afterSubmit={(record) => {
|
||||
const provider = accessProvidersMap.get(record.provider);
|
||||
if (provider?.usages?.includes(ACCESS_USAGES.CA)) {
|
||||
@ -450,9 +454,8 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
<Form.Item name="caProviderAccessId" rules={[formRule]}>
|
||||
<AccessSelect
|
||||
filter={(record) => {
|
||||
if (fieldCAProvider) {
|
||||
return applyCAProvidersMap.get(fieldCAProvider)?.provider === record.provider;
|
||||
}
|
||||
if (record.reserve !== "ca") return false;
|
||||
if (fieldCAProvider) return caProvidersMap.get(fieldCAProvider)?.provider === record.provider;
|
||||
|
||||
const provider = accessProvidersMap.get(record.provider);
|
||||
return !!provider?.usages?.includes(ACCESS_USAGES.CA);
|
||||
|
@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Avatar, Flex, Typography } from "antd";
|
||||
import { produce } from "immer";
|
||||
|
||||
import { deployProvidersMap } from "@/domain/provider";
|
||||
import { deploymentProvidersMap } from "@/domain/provider";
|
||||
import { type WorkflowNodeConfigForDeploy, WorkflowNodeType } from "@/domain/workflow";
|
||||
import { useZustandShallowSelector } from "@/hooks";
|
||||
import { useWorkflowStore } from "@/stores/workflow";
|
||||
@ -43,7 +43,7 @@ const DeployNode = ({ node, disabled }: DeployNodeProps) => {
|
||||
}
|
||||
|
||||
const config = (node.config as WorkflowNodeConfigForDeploy) ?? {};
|
||||
const provider = deployProvidersMap.get(config.provider);
|
||||
const provider = deploymentProvidersMap.get(config.provider);
|
||||
return (
|
||||
<Flex className="size-full overflow-hidden" align="center" gap={8}>
|
||||
<Avatar src={provider?.icon} size="small" />
|
||||
|
@ -7,10 +7,10 @@ import { z } from "zod";
|
||||
|
||||
import AccessEditModal from "@/components/access/AccessEditModal";
|
||||
import AccessSelect from "@/components/access/AccessSelect";
|
||||
import DeployProviderPicker from "@/components/provider/DeployProviderPicker.tsx";
|
||||
import DeployProviderSelect from "@/components/provider/DeployProviderSelect.tsx";
|
||||
import DeploymentProviderPicker from "@/components/provider/DeploymentProviderPicker.tsx";
|
||||
import DeploymentProviderSelect from "@/components/provider/DeploymentProviderSelect.tsx";
|
||||
import Show from "@/components/Show";
|
||||
import { ACCESS_USAGES, DEPLOY_PROVIDERS, accessProvidersMap, deployProvidersMap } from "@/domain/provider";
|
||||
import { ACCESS_USAGES, DEPLOYMENT_PROVIDERS, accessProvidersMap, deploymentProvidersMap } from "@/domain/provider";
|
||||
import { type WorkflowNode, type WorkflowNodeConfigForDeploy } from "@/domain/workflow";
|
||||
import { useAntdForm, useAntdFormName, useZustandShallowSelector } from "@/hooks";
|
||||
import { useWorkflowStore } from "@/stores/workflow";
|
||||
@ -133,7 +133,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
.refine((v) => {
|
||||
if (!fieldProvider) return true;
|
||||
|
||||
const provider = deployProvidersMap.get(fieldProvider);
|
||||
const provider = deploymentProvidersMap.get(fieldProvider);
|
||||
return !!provider?.builtin || !!v;
|
||||
}, t("workflow_node.deploy.form.provider_access.placeholder")),
|
||||
providerConfig: z.any().nullish(),
|
||||
@ -151,7 +151,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
useEffect(() => {
|
||||
// 内置的部署提供商(如本地部署)无需显示授权信息字段
|
||||
if (fieldProvider) {
|
||||
const provider = deployProvidersMap.get(fieldProvider);
|
||||
const provider = deploymentProvidersMap.get(fieldProvider);
|
||||
setShowProviderAccess(!provider?.builtin);
|
||||
} else {
|
||||
setShowProviderAccess(false);
|
||||
@ -173,145 +173,145 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
NOTICE: If you add new child component, please keep ASCII order.
|
||||
*/
|
||||
switch (fieldProvider) {
|
||||
case DEPLOY_PROVIDERS["1PANEL_CONSOLE"]:
|
||||
case DEPLOYMENT_PROVIDERS["1PANEL_CONSOLE"]:
|
||||
return <DeployNodeConfigForm1PanelConsoleConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS["1PANEL_SITE"]:
|
||||
case DEPLOYMENT_PROVIDERS["1PANEL_SITE"]:
|
||||
return <DeployNodeConfigForm1PanelSiteConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.ALIYUN_ALB:
|
||||
case DEPLOYMENT_PROVIDERS.ALIYUN_ALB:
|
||||
return <DeployNodeConfigFormAliyunALBConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.ALIYUN_APIGW:
|
||||
case DEPLOYMENT_PROVIDERS.ALIYUN_APIGW:
|
||||
return <DeployNodeConfigFormAliyunAPIGWConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.ALIYUN_CAS:
|
||||
case DEPLOYMENT_PROVIDERS.ALIYUN_CAS:
|
||||
return <DeployNodeConfigFormAliyunCASConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.ALIYUN_CAS_DEPLOY:
|
||||
case DEPLOYMENT_PROVIDERS.ALIYUN_CAS_DEPLOY:
|
||||
return <DeployNodeConfigFormAliyunCASDeployConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.ALIYUN_CLB:
|
||||
case DEPLOYMENT_PROVIDERS.ALIYUN_CLB:
|
||||
return <DeployNodeConfigFormAliyunCLBConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.ALIYUN_CDN:
|
||||
case DEPLOYMENT_PROVIDERS.ALIYUN_CDN:
|
||||
return <DeployNodeConfigFormAliyunCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.ALIYUN_DCDN:
|
||||
case DEPLOYMENT_PROVIDERS.ALIYUN_DCDN:
|
||||
return <DeployNodeConfigFormAliyunDCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.ALIYUN_ESA:
|
||||
case DEPLOYMENT_PROVIDERS.ALIYUN_ESA:
|
||||
return <DeployNodeConfigFormAliyunESAConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.ALIYUN_FC:
|
||||
case DEPLOYMENT_PROVIDERS.ALIYUN_FC:
|
||||
return <DeployNodeConfigFormAliyunFCConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.ALIYUN_LIVE:
|
||||
case DEPLOYMENT_PROVIDERS.ALIYUN_LIVE:
|
||||
return <DeployNodeConfigFormAliyunLiveConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.ALIYUN_NLB:
|
||||
case DEPLOYMENT_PROVIDERS.ALIYUN_NLB:
|
||||
return <DeployNodeConfigFormAliyunNLBConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.ALIYUN_OSS:
|
||||
case DEPLOYMENT_PROVIDERS.ALIYUN_OSS:
|
||||
return <DeployNodeConfigFormAliyunOSSConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.ALIYUN_VOD:
|
||||
case DEPLOYMENT_PROVIDERS.ALIYUN_VOD:
|
||||
return <DeployNodeConfigFormAliyunVODConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.ALIYUN_WAF:
|
||||
case DEPLOYMENT_PROVIDERS.ALIYUN_WAF:
|
||||
return <DeployNodeConfigFormAliyunWAFConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.AWS_ACM:
|
||||
case DEPLOYMENT_PROVIDERS.AWS_ACM:
|
||||
return <DeployNodeConfigFormAWSACMConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.AWS_CLOUDFRONT:
|
||||
case DEPLOYMENT_PROVIDERS.AWS_CLOUDFRONT:
|
||||
return <DeployNodeConfigFormAWSCloudFrontConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.AZURE_KEYVAULT:
|
||||
case DEPLOYMENT_PROVIDERS.AZURE_KEYVAULT:
|
||||
return <DeployNodeConfigFormAzureKeyVaultConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.BAIDUCLOUD_APPBLB:
|
||||
case DEPLOYMENT_PROVIDERS.BAIDUCLOUD_APPBLB:
|
||||
return <DeployNodeConfigFormBaiduCloudAppBLBConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.BAIDUCLOUD_BLB:
|
||||
case DEPLOYMENT_PROVIDERS.BAIDUCLOUD_BLB:
|
||||
return <DeployNodeConfigFormBaiduCloudBLBConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.BAIDUCLOUD_CDN:
|
||||
case DEPLOYMENT_PROVIDERS.BAIDUCLOUD_CDN:
|
||||
return <DeployNodeConfigFormBaiduCloudCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.BAISHAN_CDN:
|
||||
case DEPLOYMENT_PROVIDERS.BAISHAN_CDN:
|
||||
return <DeployNodeConfigFormBaishanCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.BAOTAPANEL_CONSOLE:
|
||||
case DEPLOYMENT_PROVIDERS.BAOTAPANEL_CONSOLE:
|
||||
return <DeployNodeConfigFormBaotaPanelConsoleConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.BAOTAPANEL_SITE:
|
||||
case DEPLOYMENT_PROVIDERS.BAOTAPANEL_SITE:
|
||||
return <DeployNodeConfigFormBaotaPanelSiteConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.BUNNY_CDN:
|
||||
case DEPLOYMENT_PROVIDERS.BUNNY_CDN:
|
||||
return <DeployNodeConfigFormBunnyCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.BYTEPLUS_CDN:
|
||||
case DEPLOYMENT_PROVIDERS.BYTEPLUS_CDN:
|
||||
return <DeployNodeConfigFormBytePlusCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.CDNFLY:
|
||||
case DEPLOYMENT_PROVIDERS.CDNFLY:
|
||||
return <DeployNodeConfigFormCdnflyConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.DOGECLOUD_CDN:
|
||||
case DEPLOYMENT_PROVIDERS.DOGECLOUD_CDN:
|
||||
return <DeployNodeConfigFormDogeCloudCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.EDGIO_APPLICATIONS:
|
||||
case DEPLOYMENT_PROVIDERS.EDGIO_APPLICATIONS:
|
||||
return <DeployNodeConfigFormEdgioApplicationsConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.GCORE_CDN:
|
||||
case DEPLOYMENT_PROVIDERS.GCORE_CDN:
|
||||
return <DeployNodeConfigFormGcoreCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.HUAWEICLOUD_CDN:
|
||||
case DEPLOYMENT_PROVIDERS.HUAWEICLOUD_CDN:
|
||||
return <DeployNodeConfigFormHuaweiCloudCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.HUAWEICLOUD_ELB:
|
||||
case DEPLOYMENT_PROVIDERS.HUAWEICLOUD_ELB:
|
||||
return <DeployNodeConfigFormHuaweiCloudELBConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.HUAWEICLOUD_WAF:
|
||||
case DEPLOYMENT_PROVIDERS.HUAWEICLOUD_WAF:
|
||||
return <DeployNodeConfigFormHuaweiCloudWAFConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.JDCLOUD_ALB:
|
||||
case DEPLOYMENT_PROVIDERS.JDCLOUD_ALB:
|
||||
return <DeployNodeConfigFormJDCloudALBConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.JDCLOUD_CDN:
|
||||
case DEPLOYMENT_PROVIDERS.JDCLOUD_CDN:
|
||||
return <DeployNodeConfigFormJDCloudCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.JDCLOUD_LIVE:
|
||||
case DEPLOYMENT_PROVIDERS.JDCLOUD_LIVE:
|
||||
return <DeployNodeConfigFormJDCloudLiveConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.JDCLOUD_VOD:
|
||||
case DEPLOYMENT_PROVIDERS.JDCLOUD_VOD:
|
||||
return <DeployNodeConfigFormJDCloudVODConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.KUBERNETES_SECRET:
|
||||
case DEPLOYMENT_PROVIDERS.KUBERNETES_SECRET:
|
||||
return <DeployNodeConfigFormKubernetesSecretConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.LOCAL:
|
||||
case DEPLOYMENT_PROVIDERS.LOCAL:
|
||||
return <DeployNodeConfigFormLocalConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.QINIU_CDN:
|
||||
case DEPLOYMENT_PROVIDERS.QINIU_CDN:
|
||||
return <DeployNodeConfigFormQiniuCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.QINIU_KODO:
|
||||
case DEPLOYMENT_PROVIDERS.QINIU_KODO:
|
||||
return <DeployNodeConfigFormQiniuKodoConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.QINIU_PILI:
|
||||
case DEPLOYMENT_PROVIDERS.QINIU_PILI:
|
||||
return <DeployNodeConfigFormQiniuPiliConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.RAINYUN_RCDN:
|
||||
case DEPLOYMENT_PROVIDERS.RAINYUN_RCDN:
|
||||
return <DeployNodeConfigFormRainYunRCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.SAFELINE:
|
||||
case DEPLOYMENT_PROVIDERS.SAFELINE:
|
||||
return <DeployNodeConfigFormSafeLineConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.SSH:
|
||||
case DEPLOYMENT_PROVIDERS.SSH:
|
||||
return <DeployNodeConfigFormSSHConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.TENCENTCLOUD_CDN:
|
||||
case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_CDN:
|
||||
return <DeployNodeConfigFormTencentCloudCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.TENCENTCLOUD_CLB:
|
||||
case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_CLB:
|
||||
return <DeployNodeConfigFormTencentCloudCLBConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.TENCENTCLOUD_COS:
|
||||
case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_COS:
|
||||
return <DeployNodeConfigFormTencentCloudCOSConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.TENCENTCLOUD_CSS:
|
||||
case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_CSS:
|
||||
return <DeployNodeConfigFormTencentCloudCSSConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.TENCENTCLOUD_ECDN:
|
||||
case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_ECDN:
|
||||
return <DeployNodeConfigFormTencentCloudECDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.TENCENTCLOUD_EO:
|
||||
case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_EO:
|
||||
return <DeployNodeConfigFormTencentCloudEOConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.TENCENTCLOUD_SCF:
|
||||
case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_SCF:
|
||||
return <DeployNodeConfigFormTencentCloudSCFConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.TENCENTCLOUD_SSL_DEPLOY:
|
||||
case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_SSL_DEPLOY:
|
||||
return <DeployNodeConfigFormTencentCloudSSLDeployConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.TENCENTCLOUD_VOD:
|
||||
case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_VOD:
|
||||
return <DeployNodeConfigFormTencentCloudVODConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.TENCENTCLOUD_WAF:
|
||||
case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_WAF:
|
||||
return <DeployNodeConfigFormTencentCloudWAFConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.UCLOUD_UCDN:
|
||||
case DEPLOYMENT_PROVIDERS.UCLOUD_UCDN:
|
||||
return <DeployNodeConfigFormUCloudUCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.UCLOUD_US3:
|
||||
case DEPLOYMENT_PROVIDERS.UCLOUD_US3:
|
||||
return <DeployNodeConfigFormUCloudUS3Config {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.UPYUN_CDN:
|
||||
case DEPLOYMENT_PROVIDERS.UPYUN_CDN:
|
||||
return <DeployNodeConfigFormUpyunCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.UPYUN_FILE:
|
||||
case DEPLOYMENT_PROVIDERS.UPYUN_FILE:
|
||||
return <DeployNodeConfigFormUpyunFileConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.VOLCENGINE_ALB:
|
||||
case DEPLOYMENT_PROVIDERS.VOLCENGINE_ALB:
|
||||
return <DeployNodeConfigFormVolcEngineALBConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.VOLCENGINE_CDN:
|
||||
case DEPLOYMENT_PROVIDERS.VOLCENGINE_CDN:
|
||||
return <DeployNodeConfigFormVolcEngineCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.VOLCENGINE_CERTCENTER:
|
||||
case DEPLOYMENT_PROVIDERS.VOLCENGINE_CERTCENTER:
|
||||
return <DeployNodeConfigFormVolcEngineCertCenterConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.VOLCENGINE_CLB:
|
||||
case DEPLOYMENT_PROVIDERS.VOLCENGINE_CLB:
|
||||
return <DeployNodeConfigFormVolcEngineCLBConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.VOLCENGINE_DCDN:
|
||||
case DEPLOYMENT_PROVIDERS.VOLCENGINE_DCDN:
|
||||
return <DeployNodeConfigFormVolcEngineDCDNConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.VOLCENGINE_IMAGEX:
|
||||
case DEPLOYMENT_PROVIDERS.VOLCENGINE_IMAGEX:
|
||||
return <DeployNodeConfigFormVolcEngineImageXConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.VOLCENGINE_LIVE:
|
||||
case DEPLOYMENT_PROVIDERS.VOLCENGINE_LIVE:
|
||||
return <DeployNodeConfigFormVolcEngineLiveConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.VOLCENGINE_TOS:
|
||||
case DEPLOYMENT_PROVIDERS.VOLCENGINE_TOS:
|
||||
return <DeployNodeConfigFormVolcEngineTOSConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.WANGSU_CDNPRO:
|
||||
case DEPLOYMENT_PROVIDERS.WANGSU_CDNPRO:
|
||||
return <DeployNodeConfigFormWangsuCDNProConfig {...nestedFormProps} />;
|
||||
case DEPLOY_PROVIDERS.WEBHOOK:
|
||||
case DEPLOYMENT_PROVIDERS.WEBHOOK:
|
||||
return <DeployNodeConfigFormWebhookConfig {...nestedFormProps} />;
|
||||
}
|
||||
}, [disabled, initialValues?.providerConfig, fieldProvider, nestedFormInst, nestedFormName]);
|
||||
@ -322,8 +322,6 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
};
|
||||
|
||||
const handleProviderSelect = (value?: string | undefined) => {
|
||||
if (fieldProvider === value) return;
|
||||
|
||||
// 切换部署目标时重置表单,避免其他部署目标的配置字段影响当前部署目标
|
||||
if (initialValues?.provider === value) {
|
||||
formInst.resetFields();
|
||||
@ -339,7 +337,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
}
|
||||
formInst.setFieldsValue(newValues);
|
||||
|
||||
if (deployProvidersMap.get(fieldProvider)?.provider !== deployProvidersMap.get(value!)?.provider) {
|
||||
if (deploymentProvidersMap.get(fieldProvider)?.provider !== deploymentProvidersMap.get(value!)?.provider) {
|
||||
formInst.setFieldValue("providerAccessId", undefined);
|
||||
onValuesChange?.(formInst.getFieldsValue(true));
|
||||
}
|
||||
@ -384,10 +382,10 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
||||
<Show
|
||||
when={!!fieldProvider}
|
||||
fallback={<DeployProviderPicker autoFocus placeholder={t("workflow_node.deploy.search.provider.placeholder")} onSelect={handleProviderPick} />}
|
||||
fallback={<DeploymentProviderPicker autoFocus placeholder={t("workflow_node.deploy.search.provider.placeholder")} onSelect={handleProviderPick} />}
|
||||
>
|
||||
<Form.Item name="provider" label={t("workflow_node.deploy.form.provider.label")} rules={[formRule]}>
|
||||
<DeployProviderSelect
|
||||
<DeploymentProviderSelect
|
||||
allowClear
|
||||
disabled={!!initialValues?.provider}
|
||||
placeholder={t("workflow_node.deploy.form.provider.placeholder")}
|
||||
@ -410,8 +408,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<AccessEditModal
|
||||
data={{ provider: deployProvidersMap.get(fieldProvider!)?.provider }}
|
||||
range="both-dns-hosting"
|
||||
data={{ provider: deploymentProvidersMap.get(fieldProvider!)?.provider }}
|
||||
scene="add"
|
||||
trigger={
|
||||
<Button size="small" type="link">
|
||||
@ -419,6 +416,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
<PlusOutlinedIcon className="text-xs" />
|
||||
</Button>
|
||||
}
|
||||
usage="both-dns-hosting"
|
||||
afterSubmit={(record) => {
|
||||
const provider = accessProvidersMap.get(record.provider);
|
||||
if (provider?.usages?.includes(ACCESS_USAGES.HOSTING)) {
|
||||
@ -432,9 +430,8 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
<Form.Item name="providerAccessId" rules={[formRule]}>
|
||||
<AccessSelect
|
||||
filter={(record) => {
|
||||
if (fieldProvider) {
|
||||
return deployProvidersMap.get(fieldProvider)?.provider === record.provider;
|
||||
}
|
||||
if (record.reserve) return false;
|
||||
if (fieldProvider) return deploymentProvidersMap.get(fieldProvider)?.provider === record.provider;
|
||||
|
||||
const provider = accessProvidersMap.get(record.provider);
|
||||
return !!provider?.usages?.includes(ACCESS_USAGES.HOSTING);
|
||||
@ -444,7 +441,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Show when={fieldProvider === DEPLOY_PROVIDERS.LOCAL}>
|
||||
<Show when={fieldProvider === DEPLOYMENT_PROVIDERS.LOCAL}>
|
||||
<Form.Item>
|
||||
<Alert
|
||||
type="info"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user