Merge pull request #573 from fudiwei/main
Support configuring independent CA for each workflow
@ -41,7 +41,7 @@ Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决
|
||||
- 支持 20+ 域名托管商(如阿里云、腾讯云、Cloudflare 等,[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-dns-providers));
|
||||
- 支持 70+ 部署目标(如 Kubernetes、CDN、WAF、负载均衡等,[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-host-providers));
|
||||
- 支持邮件、钉钉、飞书、企业微信、Webhook 等多种通知渠道;
|
||||
- 支持 Let's Encrypt、ZeroSSL、Google Trust Services 等多种 ACME 证书颁发机构;
|
||||
- 支持 Let's Encrypt、Buypass、Google Trust Services、SSL.com、ZeroSSL 等多种 ACME 证书颁发机构;
|
||||
- 更多特性等待探索。
|
||||
|
||||
## ⏱️ 快速启动
|
||||
|
@ -41,7 +41,7 @@ Certimate aims to provide users with a secure and user-friendly SSL certificate
|
||||
- Supports more than 20+ domain registrars (e.g., Alibaba Cloud, Tencent Cloud, Cloudflare, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-dns-providers));
|
||||
- Supports more than 70+ deployment targets (e.g., Kubernetes, CDN, WAF, load balancers, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-host-providers));
|
||||
- Supports multiple notification channels including email, DingTalk, Feishu, WeCom, Webhook, and more;
|
||||
- Supports multiple ACME CAs including Let's Encrypt, ZeroSSL, Google Trust Services, and more;
|
||||
- Supports multiple ACME CAs including Let's Encrypt, Buypass, Google Trust Services,SSL.com, ZeroSSL, and more;
|
||||
- More features waiting to be discovered.
|
||||
|
||||
## ⏱️ Fast Track
|
||||
|
@ -1,38 +1,30 @@
|
||||
package applicant
|
||||
|
||||
const (
|
||||
sslProviderLetsEncrypt = "letsencrypt"
|
||||
sslProviderLetsEncryptStaging = "letsencrypt_staging"
|
||||
sslProviderZeroSSL = "zerossl"
|
||||
sslProviderGoogleTrustServices = "gts"
|
||||
)
|
||||
const defaultSSLProvider = sslProviderLetsEncrypt
|
||||
import "github.com/usual2970/certimate/internal/domain"
|
||||
|
||||
const (
|
||||
letsencryptUrl = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
letsencryptStagingUrl = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
zerosslUrl = "https://acme.zerossl.com/v2/DV90"
|
||||
gtsUrl = "https://dv.acme-v02.api.pki.goog/directory"
|
||||
sslProviderLetsEncrypt = string(domain.ApplyCAProviderTypeLetsEncrypt)
|
||||
sslProviderLetsEncryptStaging = string(domain.ApplyCAProviderTypeLetsEncryptStaging)
|
||||
sslProviderBuypass = string(domain.ApplyCAProviderTypeBuypass)
|
||||
sslProviderGoogleTrustServices = string(domain.ApplyCAProviderTypeGoogleTrustServices)
|
||||
sslProviderSSLCom = string(domain.ApplyCAProviderTypeSSLCom)
|
||||
sslProviderZeroSSL = string(domain.ApplyCAProviderTypeZeroSSL)
|
||||
|
||||
sslProviderDefault = sslProviderLetsEncrypt
|
||||
)
|
||||
|
||||
var sslProviderUrls = map[string]string{
|
||||
sslProviderLetsEncrypt: letsencryptUrl,
|
||||
sslProviderLetsEncryptStaging: letsencryptStagingUrl,
|
||||
sslProviderZeroSSL: zerosslUrl,
|
||||
sslProviderGoogleTrustServices: gtsUrl,
|
||||
sslProviderLetsEncrypt: "https://acme-v02.api.letsencrypt.org/directory",
|
||||
sslProviderLetsEncryptStaging: "https://acme-staging-v02.api.letsencrypt.org/directory",
|
||||
sslProviderBuypass: "https://api.buypass.com/acme/directory",
|
||||
sslProviderGoogleTrustServices: "https://dv.acme-v02.api.pki.goog/directory",
|
||||
sslProviderSSLCom: "https://acme.ssl.com/sslcom-dv-rsa",
|
||||
sslProviderSSLCom + "RSA": "https://acme.ssl.com/sslcom-dv-rsa",
|
||||
sslProviderSSLCom + "ECC": "https://acme.ssl.com/sslcom-dv-ecc",
|
||||
sslProviderZeroSSL: "https://acme.zerossl.com/v2/DV90",
|
||||
}
|
||||
|
||||
type acmeSSLProviderConfig struct {
|
||||
Config acmeSSLProviderConfigContent `json:"config"`
|
||||
Provider string `json:"provider"`
|
||||
}
|
||||
|
||||
type acmeSSLProviderConfigContent struct {
|
||||
ZeroSSL acmeSSLProviderEabConfig `json:"zerossl"`
|
||||
GoogleTrustServices acmeSSLProviderEabConfig `json:"gts"`
|
||||
}
|
||||
|
||||
type acmeSSLProviderEabConfig struct {
|
||||
EabHmacKey string `json:"eabHmacKey"`
|
||||
EabKid string `json:"eabKid"`
|
||||
Config map[domain.ApplyCAProviderType]map[string]any `json:"config"`
|
||||
Provider string `json:"provider"`
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/maputil"
|
||||
"github.com/usual2970/certimate/internal/repository"
|
||||
)
|
||||
|
||||
@ -76,16 +77,11 @@ func (u *acmeUser) getPrivateKeyPEM() string {
|
||||
return u.privkey
|
||||
}
|
||||
|
||||
type acmeAccountRepository interface {
|
||||
GetByCAAndEmail(ca, email string) (*domain.AcmeAccount, error)
|
||||
Save(ca, email, key string, resource *registration.Resource) error
|
||||
}
|
||||
|
||||
var registerGroup singleflight.Group
|
||||
|
||||
func registerAcmeUserWithSingleFlight(client *lego.Client, sslProviderConfig *acmeSSLProviderConfig, user *acmeUser) (*registration.Resource, error) {
|
||||
resp, err, _ := registerGroup.Do(fmt.Sprintf("register_acme_user_%s_%s", sslProviderConfig.Provider, user.GetEmail()), func() (interface{}, error) {
|
||||
return registerAcmeUser(client, sslProviderConfig, user)
|
||||
func registerAcmeUserWithSingleFlight(client *lego.Client, user *acmeUser, userRegisterOptions map[string]any) (*registration.Resource, error) {
|
||||
resp, err, _ := registerGroup.Do(fmt.Sprintf("register_acme_user_%s_%s", user.CA, user.Email), func() (interface{}, error) {
|
||||
return registerAcmeUser(client, user, userRegisterOptions)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@ -95,45 +91,81 @@ func registerAcmeUserWithSingleFlight(client *lego.Client, sslProviderConfig *ac
|
||||
return resp.(*registration.Resource), nil
|
||||
}
|
||||
|
||||
func registerAcmeUser(client *lego.Client, sslProviderConfig *acmeSSLProviderConfig, user *acmeUser) (*registration.Resource, error) {
|
||||
func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions map[string]any) (*registration.Resource, error) {
|
||||
var reg *registration.Resource
|
||||
var err error
|
||||
switch sslProviderConfig.Provider {
|
||||
case sslProviderZeroSSL:
|
||||
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||
TermsOfServiceAgreed: true,
|
||||
Kid: sslProviderConfig.Config.ZeroSSL.EabKid,
|
||||
HmacEncoded: sslProviderConfig.Config.ZeroSSL.EabHmacKey,
|
||||
})
|
||||
case sslProviderGoogleTrustServices:
|
||||
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||
TermsOfServiceAgreed: true,
|
||||
Kid: sslProviderConfig.Config.GoogleTrustServices.EabKid,
|
||||
HmacEncoded: sslProviderConfig.Config.GoogleTrustServices.EabHmacKey,
|
||||
})
|
||||
switch user.CA {
|
||||
case sslProviderLetsEncrypt, sslProviderLetsEncryptStaging:
|
||||
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
|
||||
case sslProviderBuypass:
|
||||
{
|
||||
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
}
|
||||
|
||||
case sslProviderGoogleTrustServices:
|
||||
{
|
||||
access := domain.AccessConfigForGoogleTrustServices{}
|
||||
if err := maputil.Populate(userRegisterOptions, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||
TermsOfServiceAgreed: true,
|
||||
Kid: access.EabKid,
|
||||
HmacEncoded: access.EabHmacKey,
|
||||
})
|
||||
}
|
||||
|
||||
case sslProviderSSLCom:
|
||||
{
|
||||
access := domain.AccessConfigForSSLCom{}
|
||||
if err := maputil.Populate(userRegisterOptions, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||
TermsOfServiceAgreed: true,
|
||||
Kid: access.EabKid,
|
||||
HmacEncoded: access.EabHmacKey,
|
||||
})
|
||||
}
|
||||
|
||||
case sslProviderZeroSSL:
|
||||
{
|
||||
access := domain.AccessConfigForZeroSSL{}
|
||||
if err := maputil.Populate(userRegisterOptions, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||
TermsOfServiceAgreed: true,
|
||||
Kid: access.EabKid,
|
||||
HmacEncoded: access.EabHmacKey,
|
||||
})
|
||||
}
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("unsupported ssl provider: %s", sslProviderConfig.Provider)
|
||||
err = fmt.Errorf("unsupported ca provider: %s", user.CA)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo := repository.NewAcmeAccountRepository()
|
||||
resp, err := repo.GetByCAAndEmail(sslProviderConfig.Provider, user.GetEmail())
|
||||
resp, err := repo.GetByCAAndEmail(user.CA, user.Email)
|
||||
if err == nil {
|
||||
user.privkey = resp.Key
|
||||
return resp.Resource, nil
|
||||
}
|
||||
|
||||
if _, err := repo.Save(context.Background(), &domain.AcmeAccount{
|
||||
CA: sslProviderConfig.Provider,
|
||||
Email: user.GetEmail(),
|
||||
CA: user.CA,
|
||||
Email: user.Email,
|
||||
Key: user.getPrivateKeyPEM(),
|
||||
Resource: reg,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("failed to save registration: %w", err)
|
||||
return nil, fmt.Errorf("failed to save acme account registration: %w", err)
|
||||
}
|
||||
|
||||
return reg, nil
|
||||
|
@ -37,18 +37,21 @@ type Applicant interface {
|
||||
}
|
||||
|
||||
type applicantOptions struct {
|
||||
Domains []string
|
||||
ContactEmail string
|
||||
Provider domain.ApplyDNSProviderType
|
||||
ProviderAccessConfig map[string]any
|
||||
ProviderApplyConfig map[string]any
|
||||
KeyAlgorithm string
|
||||
Nameservers []string
|
||||
DnsPropagationTimeout int32
|
||||
DnsTTL int32
|
||||
DisableFollowCNAME bool
|
||||
ReplacedARIAcctId string
|
||||
ReplacedARICertId string
|
||||
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
|
||||
}
|
||||
|
||||
func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
|
||||
@ -58,22 +61,55 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
|
||||
|
||||
nodeConfig := node.GetConfigForApply()
|
||||
options := &applicantOptions{
|
||||
Domains: sliceutil.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }),
|
||||
ContactEmail: nodeConfig.ContactEmail,
|
||||
Provider: domain.ApplyDNSProviderType(nodeConfig.Provider),
|
||||
ProviderApplyConfig: nodeConfig.ProviderConfig,
|
||||
KeyAlgorithm: nodeConfig.KeyAlgorithm,
|
||||
Nameservers: sliceutil.Filter(strings.Split(nodeConfig.Nameservers, ";"), func(s string) bool { return s != "" }),
|
||||
DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout,
|
||||
DnsTTL: nodeConfig.DnsTTL,
|
||||
DisableFollowCNAME: nodeConfig.DisableFollowCNAME,
|
||||
Domains: sliceutil.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }),
|
||||
ContactEmail: nodeConfig.ContactEmail,
|
||||
Provider: domain.ApplyDNSProviderType(nodeConfig.Provider),
|
||||
ProviderAccessConfig: make(map[string]any),
|
||||
ProviderExtendedConfig: nodeConfig.ProviderConfig,
|
||||
CAProvider: domain.ApplyCAProviderType(nodeConfig.CAProvider),
|
||||
CAProviderAccessConfig: make(map[string]any),
|
||||
CAProviderExtendedConfig: nodeConfig.CAProviderConfig,
|
||||
KeyAlgorithm: nodeConfig.KeyAlgorithm,
|
||||
Nameservers: sliceutil.Filter(strings.Split(nodeConfig.Nameservers, ";"), func(s string) bool { return s != "" }),
|
||||
DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout,
|
||||
DnsTTL: nodeConfig.DnsTTL,
|
||||
DisableFollowCNAME: nodeConfig.DisableFollowCNAME,
|
||||
}
|
||||
|
||||
accessRepo := repository.NewAccessRepository()
|
||||
if access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId); err != nil {
|
||||
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err)
|
||||
} else {
|
||||
options.ProviderAccessConfig = access.Config
|
||||
if nodeConfig.ProviderAccessId != "" {
|
||||
if access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId); err != nil {
|
||||
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err)
|
||||
} else {
|
||||
options.ProviderAccessConfig = access.Config
|
||||
}
|
||||
}
|
||||
if nodeConfig.CAProviderAccessId != "" {
|
||||
if access, err := accessRepo.GetById(context.Background(), nodeConfig.CAProviderAccessId); err != nil {
|
||||
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.CAProviderAccessId, err)
|
||||
} else {
|
||||
options.CAProviderAccessConfig = access.Config
|
||||
}
|
||||
}
|
||||
|
||||
settingsRepo := repository.NewSettingsRepository()
|
||||
if string(options.CAProvider) == "" {
|
||||
settings, _ := settingsRepo.GetByName(context.Background(), "sslProvider")
|
||||
|
||||
sslProviderConfig := &acmeSSLProviderConfig{
|
||||
Config: make(map[domain.ApplyCAProviderType]map[string]any),
|
||||
Provider: sslProviderDefault,
|
||||
}
|
||||
if settings != nil {
|
||||
if err := json.Unmarshal([]byte(settings.Content), sslProviderConfig); err != nil {
|
||||
return nil, err
|
||||
} else if sslProviderConfig.Provider == "" {
|
||||
sslProviderConfig.Provider = sslProviderDefault
|
||||
}
|
||||
}
|
||||
|
||||
options.CAProvider = domain.ApplyCAProviderType(sslProviderConfig.Provider)
|
||||
options.CAProviderAccessConfig = sslProviderConfig.Config[options.CAProvider]
|
||||
}
|
||||
|
||||
certRepo := repository.NewCertificateRepository()
|
||||
@ -88,8 +124,8 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
|
||||
lastCertX509, _ := certcrypto.ParsePEMCertificate([]byte(lastCertificate.Certificate))
|
||||
if lastCertX509 != nil {
|
||||
replacedARICertId, _ := certificate.MakeARICertID(lastCertX509)
|
||||
options.ReplacedARIAcctId = lastCertificate.ACMEAccountUrl
|
||||
options.ReplacedARICertId = replacedARICertId
|
||||
options.ReplacedARIAcct = lastCertificate.ACMEAccountUrl
|
||||
options.ReplacedARICert = replacedARICertId
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -106,24 +142,7 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
|
||||
}
|
||||
|
||||
func apply(challengeProvider challenge.Provider, options *applicantOptions) (*ApplyCertResult, error) {
|
||||
settingsRepo := repository.NewSettingsRepository()
|
||||
settings, _ := settingsRepo.GetByName(context.Background(), "sslProvider")
|
||||
|
||||
sslProviderConfig := &acmeSSLProviderConfig{
|
||||
Config: acmeSSLProviderConfigContent{},
|
||||
Provider: defaultSSLProvider,
|
||||
}
|
||||
if settings != nil {
|
||||
if err := json.Unmarshal([]byte(settings.Content), sslProviderConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if sslProviderConfig.Provider == "" {
|
||||
sslProviderConfig.Provider = defaultSSLProvider
|
||||
}
|
||||
|
||||
acmeUser, err := newAcmeUser(sslProviderConfig.Provider, options.ContactEmail)
|
||||
user, err := newAcmeUser(string(options.CAProvider), options.ContactEmail)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -133,9 +152,16 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
|
||||
os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", strconv.FormatBool(options.DisableFollowCNAME))
|
||||
|
||||
// Create an ACME client config
|
||||
config := lego.NewConfig(acmeUser)
|
||||
config.CADirURL = sslProviderUrls[sslProviderConfig.Provider]
|
||||
config := lego.NewConfig(user)
|
||||
config.Certificate.KeyType = parseKeyAlgorithm(domain.CertificateKeyAlgorithmType(options.KeyAlgorithm))
|
||||
config.CADirURL = sslProviderUrls[user.CA]
|
||||
if user.CA == sslProviderSSLCom {
|
||||
if strings.HasPrefix(options.KeyAlgorithm, "RSA") {
|
||||
config.CADirURL = sslProviderUrls[sslProviderSSLCom+"RSA"]
|
||||
} else if strings.HasPrefix(options.KeyAlgorithm, "EC") {
|
||||
config.CADirURL = sslProviderUrls[sslProviderSSLCom+"ECC"]
|
||||
}
|
||||
}
|
||||
|
||||
// Create an ACME client
|
||||
client, err := lego.NewClient(config)
|
||||
@ -152,12 +178,12 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
|
||||
client.Challenge.SetDNS01Provider(challengeProvider, challengeOptions...)
|
||||
|
||||
// New users need to register first
|
||||
if !acmeUser.hasRegistration() {
|
||||
reg, err := registerAcmeUserWithSingleFlight(client, sslProviderConfig, acmeUser)
|
||||
if !user.hasRegistration() {
|
||||
reg, err := registerAcmeUserWithSingleFlight(client, user, options.CAProviderAccessConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to register: %w", err)
|
||||
}
|
||||
acmeUser.Registration = reg
|
||||
user.Registration = reg
|
||||
}
|
||||
|
||||
// Obtain a certificate
|
||||
@ -165,8 +191,8 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
|
||||
Domains: options.Domains,
|
||||
Bundle: true,
|
||||
}
|
||||
if options.ReplacedARICertId != "" && options.ReplacedARIAcctId != acmeUser.Registration.URI {
|
||||
certRequest.ReplacesCertID = options.ReplacedARICertId
|
||||
if options.ReplacedARIAcct == user.Registration.URI {
|
||||
certRequest.ReplacesCertID = options.ReplacedARICert
|
||||
}
|
||||
certResource, err := client.Certificate.Obtain(certRequest)
|
||||
if err != nil {
|
||||
@ -177,7 +203,7 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
|
||||
CertificateFullChain: strings.TrimSpace(string(certResource.Certificate)),
|
||||
IssuerCertificate: strings.TrimSpace(string(certResource.IssuerCertificate)),
|
||||
PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)),
|
||||
ACMEAccountUrl: acmeUser.Registration.URI,
|
||||
ACMEAccountUrl: user.Registration.URI,
|
||||
ACMECertUrl: certResource.CertURL,
|
||||
ACMECertStableUrl: certResource.CertStableURL,
|
||||
CSR: strings.TrimSpace(string(certResource.CSR)),
|
||||
@ -198,6 +224,8 @@ func parseKeyAlgorithm(algo domain.CertificateKeyAlgorithmType) certcrypto.KeyTy
|
||||
return certcrypto.EC256
|
||||
case domain.CertificateKeyAlgorithmTypeEC384:
|
||||
return certcrypto.EC384
|
||||
case domain.CertificateKeyAlgorithmTypeEC512:
|
||||
return certcrypto.KeyType("P512")
|
||||
}
|
||||
|
||||
return certcrypto.RSA2048
|
||||
|
@ -86,8 +86,8 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
||||
applicant, err := pAWSRoute53.NewChallengeProvider(&pAWSRoute53.ChallengeProviderConfig{
|
||||
AccessKeyId: access.AccessKeyId,
|
||||
SecretAccessKey: access.SecretAccessKey,
|
||||
Region: maputil.GetString(options.ProviderApplyConfig, "region"),
|
||||
HostedZoneId: maputil.GetString(options.ProviderApplyConfig, "hostedZoneId"),
|
||||
Region: maputil.GetString(options.ProviderExtendedConfig, "region"),
|
||||
HostedZoneId: maputil.GetString(options.ProviderExtendedConfig, "hostedZoneId"),
|
||||
DnsPropagationTimeout: options.DnsPropagationTimeout,
|
||||
DnsTTL: options.DnsTTL,
|
||||
})
|
||||
@ -279,7 +279,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
||||
applicant, err := pHuaweiCloud.NewChallengeProvider(&pHuaweiCloud.ChallengeProviderConfig{
|
||||
AccessKeyId: access.AccessKeyId,
|
||||
SecretAccessKey: access.SecretAccessKey,
|
||||
Region: maputil.GetString(options.ProviderApplyConfig, "region"),
|
||||
Region: maputil.GetString(options.ProviderExtendedConfig, "region"),
|
||||
DnsPropagationTimeout: options.DnsPropagationTimeout,
|
||||
DnsTTL: options.DnsTTL,
|
||||
})
|
||||
@ -296,7 +296,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
||||
applicant, err := pJDCloud.NewChallengeProvider(&pJDCloud.ChallengeProviderConfig{
|
||||
AccessKeyId: access.AccessKeyId,
|
||||
AccessKeySecret: access.AccessKeySecret,
|
||||
RegionId: maputil.GetString(options.ProviderApplyConfig, "regionId"),
|
||||
RegionId: maputil.GetString(options.ProviderExtendedConfig, "regionId"),
|
||||
DnsPropagationTimeout: options.DnsPropagationTimeout,
|
||||
DnsTTL: options.DnsTTL,
|
||||
})
|
||||
@ -433,7 +433,7 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
|
||||
applicant, err := pTencentCloudEO.NewChallengeProvider(&pTencentCloudEO.ChallengeProviderConfig{
|
||||
SecretId: access.SecretId,
|
||||
SecretKey: access.SecretKey,
|
||||
ZoneId: maputil.GetString(options.ProviderApplyConfig, "zoneId"),
|
||||
ZoneId: maputil.GetString(options.ProviderExtendedConfig, "zoneId"),
|
||||
DnsPropagationTimeout: options.DnsPropagationTimeout,
|
||||
DnsTTL: options.DnsTTL,
|
||||
})
|
||||
|
@ -32,18 +32,23 @@ func NewWithDeployNode(node *domain.WorkflowNode, certdata struct {
|
||||
}
|
||||
|
||||
nodeConfig := node.GetConfigForDeploy()
|
||||
|
||||
accessRepo := repository.NewAccessRepository()
|
||||
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)
|
||||
options := &deployerOptions{
|
||||
Provider: domain.DeployProviderType(nodeConfig.Provider),
|
||||
ProviderAccessConfig: make(map[string]any),
|
||||
ProviderDeployConfig: nodeConfig.ProviderConfig,
|
||||
}
|
||||
|
||||
deployer, err := createDeployer(&deployerOptions{
|
||||
Provider: domain.DeployProviderType(nodeConfig.Provider),
|
||||
ProviderAccessConfig: access.Config,
|
||||
ProviderDeployConfig: 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
|
||||
}
|
||||
}
|
||||
|
||||
deployer, err := createDeployer(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -126,6 +126,11 @@ type AccessConfigForGoDaddy struct {
|
||||
ApiSecret string `json:"apiSecret"`
|
||||
}
|
||||
|
||||
type AccessConfigForGoogleTrustServices struct {
|
||||
EabKid string `json:"eabKid"`
|
||||
EabHmacKey string `json:"eabHmacKey"`
|
||||
}
|
||||
|
||||
type AccessConfigForHuaweiCloud struct {
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
SecretAccessKey string `json:"secretAccessKey"`
|
||||
@ -140,8 +145,6 @@ type AccessConfigForKubernetes struct {
|
||||
KubeConfig string `json:"kubeConfig,omitempty"`
|
||||
}
|
||||
|
||||
type AccessConfigForLocal struct{}
|
||||
|
||||
type AccessConfigForNamecheap struct {
|
||||
Username string `json:"username"`
|
||||
ApiKey string `json:"apiKey"`
|
||||
@ -194,6 +197,11 @@ type AccessConfigForSSH struct {
|
||||
KeyPassphrase string `json:"keyPassphrase,omitempty"`
|
||||
}
|
||||
|
||||
type AccessConfigForSSLCom struct {
|
||||
EabKid string `json:"eabKid"`
|
||||
EabHmacKey string `json:"eabHmacKey"`
|
||||
}
|
||||
|
||||
type AccessConfigForTencentCloud struct {
|
||||
SecretId string `json:"secretId"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
@ -229,3 +237,8 @@ type AccessConfigForWestcn struct {
|
||||
Username string `json:"username"`
|
||||
ApiPassword string `json:"password"`
|
||||
}
|
||||
|
||||
type AccessConfigForZeroSSL struct {
|
||||
EabKid string `json:"eabKid"`
|
||||
EabHmacKey string `json:"eabHmacKey"`
|
||||
}
|
||||
|
@ -9,55 +9,79 @@ type AccessProviderType string
|
||||
NOTICE: If you add new constant, please keep ASCII order.
|
||||
*/
|
||||
const (
|
||||
AccessProviderType1Panel = AccessProviderType("1panel")
|
||||
AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq")
|
||||
AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai(预留)
|
||||
AccessProviderTypeAliyun = AccessProviderType("aliyun")
|
||||
AccessProviderTypeAWS = AccessProviderType("aws")
|
||||
AccessProviderTypeAzure = AccessProviderType("azure")
|
||||
AccessProviderTypeBaiduCloud = AccessProviderType("baiducloud")
|
||||
AccessProviderTypeBaishan = AccessProviderType("baishan")
|
||||
AccessProviderTypeBaotaPanel = AccessProviderType("baotapanel")
|
||||
AccessProviderTypeBytePlus = AccessProviderType("byteplus")
|
||||
AccessProviderTypeCacheFly = AccessProviderType("cachefly")
|
||||
AccessProviderTypeCdnfly = AccessProviderType("cdnfly")
|
||||
AccessProviderTypeCloudflare = AccessProviderType("cloudflare")
|
||||
AccessProviderTypeClouDNS = AccessProviderType("cloudns")
|
||||
AccessProviderTypeCMCCCloud = AccessProviderType("cmcccloud")
|
||||
AccessProviderTypeCTCCCloud = AccessProviderType("ctcccloud") // 联通云(预留)
|
||||
AccessProviderTypeCUCCCloud = AccessProviderType("cucccloud") // 天翼云(预留)
|
||||
AccessProviderTypeDeSEC = AccessProviderType("desec")
|
||||
AccessProviderTypeDNSLA = AccessProviderType("dnsla")
|
||||
AccessProviderTypeDogeCloud = AccessProviderType("dogecloud")
|
||||
AccessProviderTypeDynv6 = AccessProviderType("dynv6")
|
||||
AccessProviderTypeEdgio = AccessProviderType("edgio")
|
||||
AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留)
|
||||
AccessProviderTypeGname = AccessProviderType("gname")
|
||||
AccessProviderTypeGcore = AccessProviderType("gcore")
|
||||
AccessProviderTypeGoDaddy = AccessProviderType("godaddy")
|
||||
AccessProviderTypeGoEdge = AccessProviderType("goedge") // GoEdge(预留)
|
||||
AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud")
|
||||
AccessProviderTypeJDCloud = AccessProviderType("jdcloud")
|
||||
AccessProviderTypeKubernetes = AccessProviderType("k8s")
|
||||
AccessProviderTypeLocal = AccessProviderType("local")
|
||||
AccessProviderTypeNamecheap = AccessProviderType("namecheap")
|
||||
AccessProviderTypeNameDotCom = AccessProviderType("namedotcom")
|
||||
AccessProviderTypeNameSilo = AccessProviderType("namesilo")
|
||||
AccessProviderTypeNS1 = AccessProviderType("ns1")
|
||||
AccessProviderTypePorkbun = AccessProviderType("porkbun")
|
||||
AccessProviderTypePowerDNS = AccessProviderType("powerdns")
|
||||
AccessProviderTypeQiniu = AccessProviderType("qiniu")
|
||||
AccessProviderTypeQingCloud = AccessProviderType("qingcloud") // 青云(预留)
|
||||
AccessProviderTypeRainYun = AccessProviderType("rainyun")
|
||||
AccessProviderTypeSafeLine = AccessProviderType("safeline")
|
||||
AccessProviderTypeSSH = AccessProviderType("ssh")
|
||||
AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud")
|
||||
AccessProviderTypeUCloud = AccessProviderType("ucloud")
|
||||
AccessProviderTypeUpyun = AccessProviderType("upyun")
|
||||
AccessProviderTypeVercel = AccessProviderType("vercel")
|
||||
AccessProviderTypeVolcEngine = AccessProviderType("volcengine")
|
||||
AccessProviderTypeWebhook = AccessProviderType("webhook")
|
||||
AccessProviderTypeWestcn = AccessProviderType("westcn")
|
||||
AccessProviderType1Panel = AccessProviderType("1panel")
|
||||
AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq")
|
||||
AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai(预留)
|
||||
AccessProviderTypeAliyun = AccessProviderType("aliyun")
|
||||
AccessProviderTypeAWS = AccessProviderType("aws")
|
||||
AccessProviderTypeAzure = AccessProviderType("azure")
|
||||
AccessProviderTypeBaiduCloud = AccessProviderType("baiducloud")
|
||||
AccessProviderTypeBaishan = AccessProviderType("baishan")
|
||||
AccessProviderTypeBaotaPanel = AccessProviderType("baotapanel")
|
||||
AccessProviderTypeBytePlus = AccessProviderType("byteplus")
|
||||
AccessProviderTypeBuypass = AccessProviderType("buypass")
|
||||
AccessProviderTypeCacheFly = AccessProviderType("cachefly")
|
||||
AccessProviderTypeCdnfly = AccessProviderType("cdnfly")
|
||||
AccessProviderTypeCloudflare = AccessProviderType("cloudflare")
|
||||
AccessProviderTypeClouDNS = AccessProviderType("cloudns")
|
||||
AccessProviderTypeCMCCCloud = AccessProviderType("cmcccloud")
|
||||
AccessProviderTypeCTCCCloud = AccessProviderType("ctcccloud") // 联通云(预留)
|
||||
AccessProviderTypeCUCCCloud = AccessProviderType("cucccloud") // 天翼云(预留)
|
||||
AccessProviderTypeDeSEC = AccessProviderType("desec")
|
||||
AccessProviderTypeDNSLA = AccessProviderType("dnsla")
|
||||
AccessProviderTypeDogeCloud = AccessProviderType("dogecloud")
|
||||
AccessProviderTypeDynv6 = AccessProviderType("dynv6")
|
||||
AccessProviderTypeEdgio = AccessProviderType("edgio")
|
||||
AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留)
|
||||
AccessProviderTypeGname = AccessProviderType("gname")
|
||||
AccessProviderTypeGcore = AccessProviderType("gcore")
|
||||
AccessProviderTypeGoDaddy = AccessProviderType("godaddy")
|
||||
AccessProviderTypeGoEdge = AccessProviderType("goedge") // GoEdge(预留)
|
||||
AccessProviderTypeGoogleTrustServices = AccessProviderType("googletrustservices")
|
||||
AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud")
|
||||
AccessProviderTypeJDCloud = AccessProviderType("jdcloud")
|
||||
AccessProviderTypeKubernetes = AccessProviderType("k8s")
|
||||
AccessProviderTypeLetsEncrypt = AccessProviderType("letsencrypt")
|
||||
AccessProviderTypeLetsEncryptStaging = AccessProviderType("letsencryptstaging")
|
||||
AccessProviderTypeLocal = AccessProviderType("local")
|
||||
AccessProviderTypeNamecheap = AccessProviderType("namecheap")
|
||||
AccessProviderTypeNameDotCom = AccessProviderType("namedotcom")
|
||||
AccessProviderTypeNameSilo = AccessProviderType("namesilo")
|
||||
AccessProviderTypeNS1 = AccessProviderType("ns1")
|
||||
AccessProviderTypePorkbun = AccessProviderType("porkbun")
|
||||
AccessProviderTypePowerDNS = AccessProviderType("powerdns")
|
||||
AccessProviderTypeQiniu = AccessProviderType("qiniu")
|
||||
AccessProviderTypeQingCloud = AccessProviderType("qingcloud") // 青云(预留)
|
||||
AccessProviderTypeRainYun = AccessProviderType("rainyun")
|
||||
AccessProviderTypeSafeLine = AccessProviderType("safeline")
|
||||
AccessProviderTypeSSH = AccessProviderType("ssh")
|
||||
AccessProviderTypeSSLCOM = AccessProviderType("sslcom")
|
||||
AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud")
|
||||
AccessProviderTypeUCloud = AccessProviderType("ucloud")
|
||||
AccessProviderTypeUpyun = AccessProviderType("upyun")
|
||||
AccessProviderTypeVercel = AccessProviderType("vercel")
|
||||
AccessProviderTypeVolcEngine = AccessProviderType("volcengine")
|
||||
AccessProviderTypeWebhook = AccessProviderType("webhook")
|
||||
AccessProviderTypeWestcn = AccessProviderType("westcn")
|
||||
AccessProviderTypeZeroSSL = AccessProviderType("zerossl")
|
||||
)
|
||||
|
||||
type ApplyCAProviderType 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
|
||||
@ -111,7 +135,7 @@ const (
|
||||
type DeployProviderType string
|
||||
|
||||
/*
|
||||
部署目标提供商常量值。
|
||||
部署证书主机提供商常量值。
|
||||
短横线前的部分始终等于授权提供商类型。
|
||||
|
||||
注意:如果追加新的常量值,请保持以 ASCII 排序。
|
||||
|
@ -62,19 +62,22 @@ type WorkflowNode struct {
|
||||
}
|
||||
|
||||
type WorkflowNodeConfigForApply struct {
|
||||
Domains string `json:"domains"` // 域名列表,以半角分号分隔
|
||||
ContactEmail string `json:"contactEmail"` // 联系邮箱
|
||||
ChallengeType string `json:"challengeType"` // TODO: 验证方式。目前仅支持 dns-01
|
||||
Provider string `json:"provider"` // DNS 提供商
|
||||
ProviderAccessId string `json:"providerAccessId"` // DNS 提供商授权记录 ID
|
||||
ProviderConfig map[string]any `json:"providerConfig"` // DNS 提供商额外配置
|
||||
KeyAlgorithm string `json:"keyAlgorithm"` // 密钥算法
|
||||
Nameservers string `json:"nameservers"` // DNS 服务器列表,以半角分号分隔
|
||||
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout"` // DNS 传播超时时间(零值取决于提供商的默认值)
|
||||
DnsTTL int32 `json:"dnsTTL"` // DNS TTL(零值取决于提供商的默认值)
|
||||
DisableFollowCNAME bool `json:"disableFollowCNAME"` // 是否关闭 CNAME 跟随
|
||||
DisableARI bool `json:"disableARI"` // 是否关闭 ARI
|
||||
SkipBeforeExpiryDays int32 `json:"skipBeforeExpiryDays"` // 证书到期前多少天前跳过续期(零值将使用默认值 30)
|
||||
Domains string `json:"domains"` // 域名列表,以半角分号分隔
|
||||
ContactEmail string `json:"contactEmail"` // 联系邮箱
|
||||
ChallengeType string `json:"challengeType"` // TODO: 验证方式。目前仅支持 dns-01
|
||||
Provider string `json:"provider"` // DNS 提供商
|
||||
ProviderAccessId string `json:"providerAccessId"` // DNS 提供商授权记录 ID
|
||||
ProviderConfig map[string]any `json:"providerConfig"` // DNS 提供商额外配置
|
||||
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"` // 密钥算法
|
||||
Nameservers string `json:"nameservers,omitempty"` // DNS 服务器列表,以半角分号分隔
|
||||
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` // DNS 传播超时时间(零值取决于提供商的默认值)
|
||||
DnsTTL int32 `json:"dnsTTL,omitempty"` // DNS TTL(零值取决于提供商的默认值)
|
||||
DisableFollowCNAME bool `json:"disableFollowCNAME,omitempty"` // 是否关闭 CNAME 跟随
|
||||
DisableARI bool `json:"disableARI,omitempty"` // 是否关闭 ARI
|
||||
SkipBeforeExpiryDays int32 `json:"skipBeforeExpiryDays,omitempty"` // 证书到期前多少天前跳过续期(零值将使用默认值 30)
|
||||
}
|
||||
|
||||
type WorkflowNodeConfigForUpload struct {
|
||||
@ -84,11 +87,11 @@ type WorkflowNodeConfigForUpload struct {
|
||||
}
|
||||
|
||||
type WorkflowNodeConfigForDeploy struct {
|
||||
Certificate string `json:"certificate"` // 前序节点输出的证书,形如“${NodeId}#certificate”
|
||||
Provider string `json:"provider"` // 主机提供商
|
||||
ProviderAccessId string `json:"providerAccessId"` // 主机提供商授权记录 ID
|
||||
ProviderConfig map[string]any `json:"providerConfig"` // 主机提供商额外配置
|
||||
SkipOnLastSucceeded bool `json:"skipOnLastSucceeded"` // 上次部署成功时是否跳过
|
||||
Certificate string `json:"certificate"` // 前序节点输出的证书,形如“${NodeId}#certificate”
|
||||
Provider string `json:"provider"` // 主机提供商
|
||||
ProviderAccessId string `json:"providerAccessId,omitempty"` // 主机提供商授权记录 ID
|
||||
ProviderConfig map[string]any `json:"providerConfig,omitempty"` // 主机提供商额外配置
|
||||
SkipOnLastSucceeded bool `json:"skipOnLastSucceeded"` // 上次部署成功时是否跳过
|
||||
}
|
||||
|
||||
type WorkflowNodeConfigForNotify struct {
|
||||
@ -97,73 +100,54 @@ type WorkflowNodeConfigForNotify struct {
|
||||
Message string `json:"message"` // 通知内容
|
||||
}
|
||||
|
||||
func (n *WorkflowNode) getConfigString(key string) string {
|
||||
return maputil.GetString(n.Config, key)
|
||||
}
|
||||
|
||||
func (n *WorkflowNode) getConfigBool(key string) bool {
|
||||
return maputil.GetBool(n.Config, key)
|
||||
}
|
||||
|
||||
func (n *WorkflowNode) getConfigInt32(key string) int32 {
|
||||
return maputil.GetInt32(n.Config, key)
|
||||
}
|
||||
|
||||
func (n *WorkflowNode) getConfigMap(key string) map[string]any {
|
||||
if val, ok := n.Config[key]; ok {
|
||||
if result, ok := val.(map[string]any); ok {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return make(map[string]any)
|
||||
}
|
||||
|
||||
func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply {
|
||||
skipBeforeExpiryDays := n.getConfigInt32("skipBeforeExpiryDays")
|
||||
skipBeforeExpiryDays := maputil.GetInt32(n.Config, "skipBeforeExpiryDays")
|
||||
if skipBeforeExpiryDays == 0 {
|
||||
skipBeforeExpiryDays = 30
|
||||
}
|
||||
|
||||
return WorkflowNodeConfigForApply{
|
||||
Domains: n.getConfigString("domains"),
|
||||
ContactEmail: n.getConfigString("contactEmail"),
|
||||
Provider: n.getConfigString("provider"),
|
||||
ProviderAccessId: n.getConfigString("providerAccessId"),
|
||||
ProviderConfig: n.getConfigMap("providerConfig"),
|
||||
KeyAlgorithm: n.getConfigString("keyAlgorithm"),
|
||||
Nameservers: n.getConfigString("nameservers"),
|
||||
DnsPropagationTimeout: n.getConfigInt32("dnsPropagationTimeout"),
|
||||
DnsTTL: n.getConfigInt32("dnsTTL"),
|
||||
DisableFollowCNAME: n.getConfigBool("disableFollowCNAME"),
|
||||
DisableARI: n.getConfigBool("disableARI"),
|
||||
Domains: maputil.GetString(n.Config, "domains"),
|
||||
ContactEmail: maputil.GetString(n.Config, "contactEmail"),
|
||||
Provider: maputil.GetString(n.Config, "provider"),
|
||||
ProviderAccessId: maputil.GetString(n.Config, "providerAccessId"),
|
||||
ProviderConfig: maputil.GetAnyMap(n.Config, "providerConfig"),
|
||||
CAProvider: maputil.GetString(n.Config, "caProvider"),
|
||||
CAProviderAccessId: maputil.GetString(n.Config, "caProviderAccessId"),
|
||||
CAProviderConfig: maputil.GetAnyMap(n.Config, "caProviderConfig"),
|
||||
KeyAlgorithm: maputil.GetString(n.Config, "keyAlgorithm"),
|
||||
Nameservers: maputil.GetString(n.Config, "nameservers"),
|
||||
DnsPropagationTimeout: maputil.GetInt32(n.Config, "dnsPropagationTimeout"),
|
||||
DnsTTL: maputil.GetInt32(n.Config, "dnsTTL"),
|
||||
DisableFollowCNAME: maputil.GetBool(n.Config, "disableFollowCNAME"),
|
||||
DisableARI: maputil.GetBool(n.Config, "disableARI"),
|
||||
SkipBeforeExpiryDays: skipBeforeExpiryDays,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *WorkflowNode) GetConfigForUpload() WorkflowNodeConfigForUpload {
|
||||
return WorkflowNodeConfigForUpload{
|
||||
Certificate: n.getConfigString("certificate"),
|
||||
PrivateKey: n.getConfigString("privateKey"),
|
||||
Domains: n.getConfigString("domains"),
|
||||
Certificate: maputil.GetString(n.Config, "certificate"),
|
||||
PrivateKey: maputil.GetString(n.Config, "privateKey"),
|
||||
Domains: maputil.GetString(n.Config, "domains"),
|
||||
}
|
||||
}
|
||||
|
||||
func (n *WorkflowNode) GetConfigForDeploy() WorkflowNodeConfigForDeploy {
|
||||
return WorkflowNodeConfigForDeploy{
|
||||
Certificate: n.getConfigString("certificate"),
|
||||
Provider: n.getConfigString("provider"),
|
||||
ProviderAccessId: n.getConfigString("providerAccessId"),
|
||||
ProviderConfig: n.getConfigMap("providerConfig"),
|
||||
SkipOnLastSucceeded: n.getConfigBool("skipOnLastSucceeded"),
|
||||
Certificate: maputil.GetString(n.Config, "certificate"),
|
||||
Provider: maputil.GetString(n.Config, "provider"),
|
||||
ProviderAccessId: maputil.GetString(n.Config, "providerAccessId"),
|
||||
ProviderConfig: maputil.GetAnyMap(n.Config, "providerConfig"),
|
||||
SkipOnLastSucceeded: maputil.GetBool(n.Config, "skipOnLastSucceeded"),
|
||||
}
|
||||
}
|
||||
|
||||
func (n *WorkflowNode) GetConfigForNotify() WorkflowNodeConfigForNotify {
|
||||
return WorkflowNodeConfigForNotify{
|
||||
Channel: n.getConfigString("channel"),
|
||||
Subject: n.getConfigString("subject"),
|
||||
Message: n.getConfigString("message"),
|
||||
Channel: maputil.GetString(n.Config, "channel"),
|
||||
Subject: maputil.GetString(n.Config, "subject"),
|
||||
Message: maputil.GetString(n.Config, "message"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,3 +180,25 @@ func GetOrDefaultBool(dict map[string]any, key string, defaultValue bool) bool {
|
||||
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// 以 `map[string]any` 形式从字典中获取指定键的值。
|
||||
//
|
||||
// 入参:
|
||||
// - dict: 字典。
|
||||
// - key: 键。
|
||||
//
|
||||
// 出参:
|
||||
// - 字典中键对应的 `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)
|
||||
}
|
||||
|
@ -109,12 +109,24 @@ func (n *applyNode) checkCanSkip(ctx context.Context, lastOutput *domain.Workflo
|
||||
if currentNodeConfig.ContactEmail != lastNodeConfig.ContactEmail {
|
||||
return false, "the configuration item 'ContactEmail' changed"
|
||||
}
|
||||
if currentNodeConfig.Provider != lastNodeConfig.Provider {
|
||||
return false, "the configuration item 'Provider' changed"
|
||||
}
|
||||
if currentNodeConfig.ProviderAccessId != lastNodeConfig.ProviderAccessId {
|
||||
return false, "the configuration item 'ProviderAccessId' changed"
|
||||
}
|
||||
if !maps.Equal(currentNodeConfig.ProviderConfig, lastNodeConfig.ProviderConfig) {
|
||||
return false, "the configuration item 'ProviderConfig' changed"
|
||||
}
|
||||
if currentNodeConfig.CAProvider != lastNodeConfig.CAProvider {
|
||||
return false, "the configuration item 'CAProvider' changed"
|
||||
}
|
||||
if currentNodeConfig.CAProviderAccessId != lastNodeConfig.CAProviderAccessId {
|
||||
return false, "the configuration item 'CAProviderAccessId' changed"
|
||||
}
|
||||
if !maps.Equal(currentNodeConfig.CAProviderConfig, lastNodeConfig.CAProviderConfig) {
|
||||
return false, "the configuration item 'CAProviderConfig' changed"
|
||||
}
|
||||
if currentNodeConfig.KeyAlgorithm != lastNodeConfig.KeyAlgorithm {
|
||||
return false, "the configuration item 'KeyAlgorithm' changed"
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ import (
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -179,20 +177,20 @@ func init() {
|
||||
}
|
||||
|
||||
for _, workflowRun := range workflowRuns {
|
||||
type oldWorkflowRunLogRecord struct {
|
||||
type dWorkflowRunLogRecord struct {
|
||||
Time string `json:"time"`
|
||||
Level string `json:"level"`
|
||||
Content string `json:"content"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
type oldWorkflowRunLog struct {
|
||||
NodeId string `json:"nodeId"`
|
||||
NodeName string `json:"nodeName"`
|
||||
Records []oldWorkflowRunLogRecord `json:"records"`
|
||||
Error string `json:"error"`
|
||||
type dWorkflowRunLog struct {
|
||||
NodeId string `json:"nodeId"`
|
||||
NodeName string `json:"nodeName"`
|
||||
Records []dWorkflowRunLogRecord `json:"records"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
logs := make([]oldWorkflowRunLog, 0)
|
||||
logs := make([]dWorkflowRunLog, 0)
|
||||
if err := workflowRun.UnmarshalJSONField("logs", &logs); err != nil {
|
||||
continue
|
||||
}
|
||||
@ -259,8 +257,20 @@ func init() {
|
||||
return err
|
||||
}
|
||||
|
||||
type dWorkflowNode struct {
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Config map[string]any `json:"config"`
|
||||
Inputs map[string]any `json:"inputs"`
|
||||
Outputs map[string]any `json:"outputs"`
|
||||
Next *dWorkflowNode `json:"next,omitempty"`
|
||||
Branches []dWorkflowNode `json:"branches,omitempty"`
|
||||
Validated bool `json:"validated"`
|
||||
}
|
||||
|
||||
for _, workflowRun := range workflowRuns {
|
||||
node := &domain.WorkflowNode{}
|
||||
node := &dWorkflowNode{}
|
||||
for _, workflowOutput := range workflowOutputs {
|
||||
if workflowOutput.GetString("runId") != workflowRun.Get("id") {
|
||||
continue
|
||||
@ -270,8 +280,8 @@ func init() {
|
||||
continue
|
||||
}
|
||||
|
||||
if node.Type != domain.WorkflowNodeTypeApply {
|
||||
node = &domain.WorkflowNode{}
|
||||
if node.Type != "apply" {
|
||||
node = &dWorkflowNode{}
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -286,7 +296,7 @@ func init() {
|
||||
} else {
|
||||
workflow, _ := app.FindRecordById("workflow", workflowRun.GetString("workflowId"))
|
||||
if workflow != nil {
|
||||
rootNode := &domain.WorkflowNode{}
|
||||
rootNode := &dWorkflowNode{}
|
||||
if err := workflow.UnmarshalJSONField("content", rootNode); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -294,9 +304,9 @@ func init() {
|
||||
rootNode.Next = node
|
||||
workflowRun.Set("detail", rootNode)
|
||||
} else {
|
||||
rootNode := &domain.WorkflowNode{
|
||||
rootNode := &dWorkflowNode{
|
||||
Id: core.GenerateDefaultRandomId(),
|
||||
Type: domain.WorkflowNodeTypeStart,
|
||||
Type: "start",
|
||||
Name: "开始",
|
||||
Config: map[string]any{
|
||||
"trigger": "manual",
|
||||
|
173
migrations/1743264000_upgrade.go
Normal file
@ -0,0 +1,173 @@
|
||||
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 `settings`
|
||||
{
|
||||
collection, err := app.FindCollectionByNameOrId("dy6ccjb60spfy6p")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
records, err := app.FindRecordsByFilter(collection, "name='sslProvider'", "-created", 1, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(records) == 1 {
|
||||
record := records[0]
|
||||
|
||||
content := make(map[string]any)
|
||||
if err := record.UnmarshalJSONField("content", &content); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if provider, ok := content["provider"]; ok {
|
||||
if providerStr, ok := provider.(string); ok {
|
||||
if providerStr == "letsencrypt_staging" {
|
||||
content["provider"] = "letsencryptstaging"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config, ok := content["config"]; ok {
|
||||
if configMap, ok := config.(map[string]any); ok {
|
||||
if _, ok := configMap["letsencrypt_staging"]; ok {
|
||||
configMap["letsencryptstaging"] = configMap["letsencrypt_staging"]
|
||||
delete(configMap, "letsencrypt_staging")
|
||||
}
|
||||
if _, ok := configMap["gts"]; ok {
|
||||
configMap["googletrustservices"] = configMap["gts"]
|
||||
delete(configMap, "gts")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
record.Set("content", content)
|
||||
if err := app.Save(record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update collection `access`
|
||||
{
|
||||
collection, err := app.FindCollectionByNameOrId("4yzbv8urny5ja1e")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update field
|
||||
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
|
||||
"hidden": false,
|
||||
"id": "hwy7m03o",
|
||||
"maxSelect": 1,
|
||||
"name": "provider",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "select",
|
||||
"values": [
|
||||
"1panel",
|
||||
"acmehttpreq",
|
||||
"akamai",
|
||||
"aliyun",
|
||||
"aws",
|
||||
"azure",
|
||||
"baiducloud",
|
||||
"baishan",
|
||||
"baotapanel",
|
||||
"byteplus",
|
||||
"buypass",
|
||||
"cachefly",
|
||||
"cdnfly",
|
||||
"cloudflare",
|
||||
"cloudns",
|
||||
"cmcccloud",
|
||||
"ctcccloud",
|
||||
"cucccloud",
|
||||
"desec",
|
||||
"dnsla",
|
||||
"dogecloud",
|
||||
"dynv6",
|
||||
"edgio",
|
||||
"fastly",
|
||||
"gname",
|
||||
"gcore",
|
||||
"godaddy",
|
||||
"goedge",
|
||||
"googletrustservices",
|
||||
"huaweicloud",
|
||||
"jdcloud",
|
||||
"k8s",
|
||||
"letsencrypt",
|
||||
"letsencryptstaging",
|
||||
"local",
|
||||
"namecheap",
|
||||
"namedotcom",
|
||||
"namesilo",
|
||||
"ns1",
|
||||
"porkbun",
|
||||
"powerdns",
|
||||
"qiniu",
|
||||
"qingcloud",
|
||||
"rainyun",
|
||||
"safeline",
|
||||
"ssh",
|
||||
"sslcom",
|
||||
"tencentcloud",
|
||||
"ucloud",
|
||||
"upyun",
|
||||
"vercel",
|
||||
"volcengine",
|
||||
"webhook",
|
||||
"westcn",
|
||||
"zerossl"
|
||||
]
|
||||
}`)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := app.Save(collection); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// update collection `acme_accounts`
|
||||
{
|
||||
collection, err := app.FindCollectionByNameOrId("012d7abbod1hwvr")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
records, err := app.FindRecordsByFilter(collection, "ca='letsencrypt_staging' || ca='gts'", "-created", 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
ca := record.GetString("ca")
|
||||
if ca == "letsencrypt_staging" {
|
||||
record.Set("ca", "letsencryptstaging")
|
||||
} else if ca == "gts" {
|
||||
record.Set("ca", "googletrustservices")
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := app.Save(record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}, func(app core.App) error {
|
||||
return nil
|
||||
})
|
||||
}
|
@ -1 +0,0 @@
|
||||
<svg width="200" height="200" viewBox="-0.5 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Color-" transform="translate(-401.000000, -860.000000)"><g id="Google" transform="translate(401.000000, 860.000000)"><path d="M9.82727273,24 C9.82727273,22.4757333 10.0804318,21.0144 10.5322727,19.6437333 L2.62345455,13.6042667 C1.08206818,16.7338667 0.213636364,20.2602667 0.213636364,24 C0.213636364,27.7365333 1.081,31.2608 2.62025,34.3882667 L10.5247955,28.3370667 C10.0772273,26.9728 9.82727273,25.5168 9.82727273,24" id="Fill-1" fill="#FBBC05"></path><path d="M23.7136364,10.1333333 C27.025,10.1333333 30.0159091,11.3066667 32.3659091,13.2266667 L39.2022727,6.4 C35.0363636,2.77333333 29.6954545,0.533333333 23.7136364,0.533333333 C14.4268636,0.533333333 6.44540909,5.84426667 2.62345455,13.6042667 L10.5322727,19.6437333 C12.3545909,14.112 17.5491591,10.1333333 23.7136364,10.1333333" id="Fill-2" fill="#EB4335"></path><path d="M23.7136364,37.8666667 C17.5491591,37.8666667 12.3545909,33.888 10.5322727,28.3562667 L2.62345455,34.3946667 C6.44540909,42.1557333 14.4268636,47.4666667 23.7136364,47.4666667 C29.4455,47.4666667 34.9177955,45.4314667 39.0249545,41.6181333 L31.5177727,35.8144 C29.3995682,37.1488 26.7323182,37.8666667 23.7136364,37.8666667" id="Fill-3" fill="#34A853"></path><path d="M46.1454545,24 C46.1454545,22.6133333 45.9318182,21.12 45.6113636,19.7333333 L23.7136364,19.7333333 L23.7136364,28.8 L36.3181818,28.8 C35.6879545,31.8912 33.9724545,34.2677333 31.5177727,35.8144 L39.0249545,41.6181333 C43.3393409,37.6138667 46.1454545,31.6490667 46.1454545,24" id="Fill-4" fill="#4285F4"></path></g></g></g></svg>
|
Before Width: | Height: | Size: 1.7 KiB |
BIN
ui/public/imgs/providers/buypass.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
1
ui/public/imgs/providers/google.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="M925.915429 521.545143c0.512-29.622857-2.56-59.282286-9.033143-88.100572H512v160.036572h237.714286a210.468571 210.468571 0 0 1-88.137143 139.885714l-0.804572 5.302857 127.963429 99.181715 8.850286 0.841142c81.298286-75.154286 128.365714-185.782857 128.365714-317.147428" fill="#4285F4"></path><path d="M512 943.177143c116.370286 0 214.198857-38.363429 285.622857-104.484572l-136.045714-105.472a254.829714 254.829714 0 0 1-149.430857 43.117715 259.949714 259.949714 0 0 1-245.394286-179.273143l-5.12 0.512-133.083429 102.912-1.682285 4.754286a429.860571 429.860571 0 0 0 385.097143 237.897142" fill="#34A853"></path><path d="M266.605714 597.211429a264.045714 264.045714 0 0 1-14.336-85.357715c0.182857-28.964571 4.937143-57.782857 13.970286-85.394285l-0.146286-5.632-134.802285-104.594286-4.425143 2.011428a430.262857 430.262857 0 0 0 0 387.181715l139.702857-108.214857" fill="#FBBC05"></path><path d="M512 247.515429a237.933714 237.933714 0 0 1 166.656 64.256l121.673143-118.784a414.646857 414.646857 0 0 0-288.512-112.128 430.811429 430.811429 0 0 0-385.316572 237.714285l139.410286 108.251429A260.973714 260.973714 0 0 1 512 247.478857" fill="#EB4335"></path></svg>
|
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
1
ui/public/imgs/providers/sslcom.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 72 72" fill="none"><circle cx="36" cy="36" r="35" stroke="#A0CC40" stroke-width="2"></circle><path d="M13.4924 16.2017H20.2845L18.8546 22.9937H12.0625L13.4924 16.2017Z" fill="#A0CC40"></path><path d="M19.8362 25.943H26.6282L25.1983 32.7351H18.4062L19.8362 25.943Z" fill="#A0CC40"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M21.9812 22.9613C21.8448 22.8247 22.8688 17.4717 23.1888 16.6485C23.3515 16.2297 24.34 16.2017 38.924 16.2017C52.2002 16.2017 54.5064 16.2555 54.6262 16.5678C54.7033 16.769 54.5273 18.1065 54.2349 19.5397C53.8001 21.6709 53.5664 22.5316 53.8871 22.8758C54.1437 23.1511 54.755 23.0959 55.902 23.0959C58.0542 23.0959 58.0979 23.1081 57.9587 23.6704C57.8806 23.9865 56.3682 31.0817 54.5978 39.4377C52.8278 47.7937 51.32 54.8314 51.2472 55.0773C51.1231 55.4958 50.2388 55.5242 37.3542 55.5242C29.7857 55.5242 23.5459 55.4381 23.4879 55.3327C23.4302 55.2275 24.902 48.0172 26.7589 39.31C28.6161 30.6029 30.0866 23.3926 30.027 23.2874C29.9675 23.1819 28.1632 23.0959 26.0174 23.0959C23.8716 23.0959 22.0553 23.0354 21.9812 22.9613ZM35.6711 44.6994C36.8749 45.2482 38.2735 45.5226 39.8669 45.5226C40.9646 45.5226 42.0445 45.3898 43.1067 45.1243C44.1867 44.8587 45.2224 44.3895 46.2138 43.7168L43.7175 40.0786C43.2572 40.415 42.7527 40.6717 42.2038 40.8487C41.655 41.0081 41.1062 41.0877 40.5574 41.0877C39.6191 41.0877 38.902 40.9019 38.4063 40.5301C38.0832 40.2668 37.8693 39.8861 37.7646 39.3882H47.9399C48.0285 38.9987 48.0993 38.5915 48.1524 38.1666C48.2232 37.7417 48.2586 37.3079 48.2586 36.8653C48.2586 35.6084 47.9665 34.5019 47.3823 33.5458C46.798 32.5898 45.9571 31.8374 44.8594 31.2886C43.7618 30.7398 42.434 30.4653 40.876 30.4653C39.0702 30.4653 37.4857 30.8283 36.1225 31.5541C34.777 32.28 33.7325 33.2714 32.9889 34.5284C32.2453 35.7854 31.8736 37.2106 31.8736 38.8039C31.8736 40.1671 32.2099 41.3533 32.8827 42.3624C33.5554 43.3716 34.4849 44.1505 35.6711 44.6994ZM38.0084 36.4936H42.7816C42.7785 36.2493 42.7364 36.028 42.6553 35.8297C42.5137 35.4225 42.2658 35.1126 41.9117 34.9002C41.5576 34.67 41.1239 34.555 40.6105 34.555C39.8846 34.555 39.3092 34.7497 38.8843 35.1392C38.4965 35.4948 38.2045 35.9462 38.0084 36.4936Z" fill="#A0CC40"></path></svg>
|
After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
118
ui/src/components/access/AccessEditDrawer.tsx
Normal file
@ -0,0 +1,118 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useControllableValue } from "ahooks";
|
||||
import { Button, Drawer, Space, notification } from "antd";
|
||||
|
||||
import { type AccessModel } from "@/domain/access";
|
||||
import { useTriggerElement, useZustandShallowSelector } from "@/hooks";
|
||||
import { useAccessesStore } from "@/stores/access";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
|
||||
import AccessForm, { type AccessFormInstance, type AccessFormProps } from "./AccessForm";
|
||||
|
||||
export type AccessEditDrawerProps = {
|
||||
data?: AccessFormProps["initialValues"];
|
||||
loading?: boolean;
|
||||
open?: boolean;
|
||||
range?: AccessFormProps["range"];
|
||||
scene: AccessFormProps["scene"];
|
||||
trigger?: React.ReactNode;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
afterSubmit?: (record: AccessModel) => void;
|
||||
};
|
||||
|
||||
const AccessEditDrawer = ({ data, loading, trigger, scene, range, afterSubmit, ...props }: AccessEditDrawerProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||
|
||||
const { createAccess, updateAccess } = useAccessesStore(useZustandShallowSelector(["createAccess", "updateAccess"]));
|
||||
|
||||
const [open, setOpen] = useControllableValue<boolean>(props, {
|
||||
valuePropName: "open",
|
||||
defaultValuePropName: "defaultOpen",
|
||||
trigger: "onOpenChange",
|
||||
});
|
||||
|
||||
const triggerEl = useTriggerElement(trigger, { onClick: () => setOpen(true) });
|
||||
|
||||
const formRef = useRef<AccessFormInstance>(null);
|
||||
const [formPending, setFormPending] = useState(false);
|
||||
|
||||
const handleOkClick = async () => {
|
||||
setFormPending(true);
|
||||
try {
|
||||
await formRef.current!.validateFields();
|
||||
} catch (err) {
|
||||
setFormPending(false);
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
let values: AccessModel = formRef.current!.getFieldsValue();
|
||||
|
||||
if (scene === "add") {
|
||||
if (data?.id) {
|
||||
throw "Invalid props: `data`";
|
||||
}
|
||||
|
||||
values = await createAccess(values);
|
||||
} else if (scene === "edit") {
|
||||
if (!data?.id) {
|
||||
throw "Invalid props: `data`";
|
||||
}
|
||||
|
||||
values = await updateAccess({ ...data, ...values });
|
||||
} else {
|
||||
throw "Invalid props: `scene`";
|
||||
}
|
||||
|
||||
afterSubmit?.(values);
|
||||
setOpen(false);
|
||||
} catch (err) {
|
||||
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
|
||||
|
||||
throw err;
|
||||
} finally {
|
||||
setFormPending(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancelClick = () => {
|
||||
if (formPending) return;
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationContextHolder}
|
||||
|
||||
{triggerEl}
|
||||
|
||||
<Drawer
|
||||
afterOpenChange={setOpen}
|
||||
closable={!formPending}
|
||||
destroyOnClose
|
||||
footer={
|
||||
<Space className="w-full justify-end">
|
||||
<Button onClick={handleCancelClick}>{t("common.button.cancel")}</Button>
|
||||
<Button loading={formPending} type="primary" onClick={handleOkClick}>
|
||||
{scene === "edit" ? t("common.button.save") : t("common.button.submit")}
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
loading={loading}
|
||||
maskClosable={!formPending}
|
||||
open={open}
|
||||
title={t(`access.action.${scene}`)}
|
||||
width={720}
|
||||
onClose={() => setOpen(false)}
|
||||
>
|
||||
<AccessForm ref={formRef} initialValues={data} range={range} scene={scene === "add" ? "add" : "edit"} />
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessEditDrawer;
|
@ -14,13 +14,14 @@ export type AccessEditModalProps = {
|
||||
data?: AccessFormProps["initialValues"];
|
||||
loading?: boolean;
|
||||
open?: boolean;
|
||||
preset: AccessFormProps["preset"];
|
||||
range?: AccessFormProps["range"];
|
||||
scene: AccessFormProps["scene"];
|
||||
trigger?: React.ReactNode;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
afterSubmit?: (record: AccessModel) => void;
|
||||
};
|
||||
|
||||
const AccessEditModal = ({ data, loading, trigger, preset, afterSubmit, ...props }: AccessEditModalProps) => {
|
||||
const AccessEditModal = ({ data, loading, trigger, scene, range, afterSubmit, ...props }: AccessEditModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||
@ -50,13 +51,13 @@ const AccessEditModal = ({ data, loading, trigger, preset, afterSubmit, ...props
|
||||
try {
|
||||
let values: AccessModel = formRef.current!.getFieldsValue();
|
||||
|
||||
if (preset === "add") {
|
||||
if (scene === "add") {
|
||||
if (data?.id) {
|
||||
throw "Invalid props: `data`";
|
||||
}
|
||||
|
||||
values = await createAccess(values);
|
||||
} else if (preset === "edit") {
|
||||
} else if (scene === "edit") {
|
||||
if (!data?.id) {
|
||||
throw "Invalid props: `data`";
|
||||
}
|
||||
@ -96,15 +97,15 @@ const AccessEditModal = ({ data, loading, trigger, preset, afterSubmit, ...props
|
||||
confirmLoading={formPending}
|
||||
destroyOnClose
|
||||
loading={loading}
|
||||
okText={preset === "edit" ? t("common.button.save") : t("common.button.submit")}
|
||||
okText={scene === "edit" ? t("common.button.save") : t("common.button.submit")}
|
||||
open={open}
|
||||
title={t(`access.action.${preset}`)}
|
||||
title={t(`access.action.${scene}`)}
|
||||
width={480}
|
||||
onOk={handleOkClick}
|
||||
onCancel={handleCancelClick}
|
||||
>
|
||||
<div className="pb-2 pt-4">
|
||||
<AccessForm ref={formRef} initialValues={data} preset={preset === "add" ? "add" : "edit"} />
|
||||
<AccessForm ref={formRef} initialValues={data} range={range} scene={scene === "add" ? "add" : "edit"} />
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
|
@ -6,7 +6,7 @@ import { z } from "zod";
|
||||
|
||||
import AccessProviderSelect from "@/components/provider/AccessProviderSelect";
|
||||
import { type AccessModel } from "@/domain/access";
|
||||
import { ACCESS_PROVIDERS } from "@/domain/provider";
|
||||
import { ACCESS_PROVIDERS, ACCESS_USAGES } from "@/domain/provider";
|
||||
import { useAntdForm, useAntdFormName } from "@/hooks";
|
||||
|
||||
import AccessForm1PanelConfig from "./AccessForm1PanelConfig";
|
||||
@ -31,10 +31,10 @@ import AccessFormEdgioConfig from "./AccessFormEdgioConfig";
|
||||
import AccessFormGcoreConfig from "./AccessFormGcoreConfig";
|
||||
import AccessFormGnameConfig from "./AccessFormGnameConfig";
|
||||
import AccessFormGoDaddyConfig from "./AccessFormGoDaddyConfig";
|
||||
import AccessFormGoogleTrustServicesConfig from "./AccessFormGoogleTrustServicesConfig";
|
||||
import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig";
|
||||
import AccessFormJDCloudConfig from "./AccessFormJDCloudConfig";
|
||||
import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig";
|
||||
import AccessFormLocalConfig from "./AccessFormLocalConfig";
|
||||
import AccessFormNamecheapConfig from "./AccessFormNamecheapConfig";
|
||||
import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig";
|
||||
import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig";
|
||||
@ -45,6 +45,7 @@ import AccessFormQiniuConfig from "./AccessFormQiniuConfig";
|
||||
import AccessFormRainYunConfig from "./AccessFormRainYunConfig";
|
||||
import AccessFormSafeLineConfig from "./AccessFormSafeLineConfig";
|
||||
import AccessFormSSHConfig from "./AccessFormSSHConfig";
|
||||
import AccessFormSSLComConfig from "./AccessFormSSLComConfig";
|
||||
import AccessFormTencentCloudConfig from "./AccessFormTencentCloudConfig";
|
||||
import AccessFormUCloudConfig from "./AccessFormUCloudConfig";
|
||||
import AccessFormUpyunConfig from "./AccessFormUpyunConfig";
|
||||
@ -52,16 +53,19 @@ import AccessFormVercelConfig from "./AccessFormVercelConfig";
|
||||
import AccessFormVolcEngineConfig from "./AccessFormVolcEngineConfig";
|
||||
import AccessFormWebhookConfig from "./AccessFormWebhookConfig";
|
||||
import AccessFormWestcnConfig from "./AccessFormWestcnConfig";
|
||||
import AccessFormZeroSSLConfig from "./AccessFormZeroSSLConfig";
|
||||
|
||||
type AccessFormFieldValues = Partial<MaybeModelRecord<AccessModel>>;
|
||||
type AccessFormPresets = "add" | "edit";
|
||||
type AccessFormRanges = "both-dns-hosting" | "ca-only" | "notify-only";
|
||||
type AccessFormScenes = "add" | "edit";
|
||||
|
||||
export type AccessFormProps = {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
disabled?: boolean;
|
||||
initialValues?: AccessFormFieldValues;
|
||||
preset: AccessFormPresets;
|
||||
range?: AccessFormRanges;
|
||||
scene: AccessFormScenes;
|
||||
onValuesChange?: (values: AccessFormFieldValues) => void;
|
||||
};
|
||||
|
||||
@ -71,7 +75,7 @@ export type AccessFormInstance = {
|
||||
validateFields: FormInstance<AccessFormFieldValues>["validateFields"];
|
||||
};
|
||||
|
||||
const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className, style, disabled, initialValues, preset, onValuesChange }, ref) => {
|
||||
const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className, style, disabled, initialValues, range, scene, onValuesChange }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
@ -80,7 +84,14 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
.min(1, t("access.form.name.placeholder"))
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim(),
|
||||
provider: z.nativeEnum(ACCESS_PROVIDERS, { message: t("access.form.provider.placeholder") }),
|
||||
provider: z.nativeEnum(ACCESS_PROVIDERS, {
|
||||
message:
|
||||
range === "ca-only"
|
||||
? t("access.form.certificate_authority.placeholder")
|
||||
: range === "notify-only"
|
||||
? t("access.form.notification_channel.placeholder")
|
||||
: t("access.form.provider.placeholder"),
|
||||
}),
|
||||
config: z.any(),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
@ -88,6 +99,35 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
initialValues: initialValues,
|
||||
});
|
||||
|
||||
const providerLabel = useMemo(() => {
|
||||
switch (range) {
|
||||
case "ca-only":
|
||||
return t("access.form.certificate_authority.label");
|
||||
case "notify-only":
|
||||
return t("access.form.notification_channel.label");
|
||||
}
|
||||
|
||||
return t("access.form.provider.label");
|
||||
}, [range]);
|
||||
const providerPlaceholder = useMemo(() => {
|
||||
switch (range) {
|
||||
case "ca-only":
|
||||
return t("access.form.certificate_authority.placeholder");
|
||||
case "notify-only":
|
||||
return t("access.form.notification_channel.placeholder");
|
||||
}
|
||||
|
||||
return t("access.form.provider.placeholder");
|
||||
}, [range]);
|
||||
const providerTooltip = useMemo(() => {
|
||||
switch (range) {
|
||||
case "both-dns-hosting":
|
||||
return <span dangerouslySetInnerHTML={{ __html: t("access.form.provider.tooltip") }}></span>;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [range]);
|
||||
|
||||
const fieldProvider = Form.useWatch("provider", formInst);
|
||||
|
||||
const [nestedFormInst] = Form.useForm();
|
||||
@ -147,6 +187,8 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
return <AccessFormGnameConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.GODADDY:
|
||||
return <AccessFormGoDaddyConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.GOOGLETRUSTSERVICES:
|
||||
return <AccessFormGoogleTrustServicesConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.EDGIO:
|
||||
return <AccessFormEdgioConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.HUAWEICLOUD:
|
||||
@ -155,8 +197,6 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
return <AccessFormJDCloudConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.KUBERNETES:
|
||||
return <AccessFormKubernetesConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.LOCAL:
|
||||
return <AccessFormLocalConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.NAMECHEAP:
|
||||
return <AccessFormNamecheapConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.NAMEDOTCOM:
|
||||
@ -177,6 +217,8 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
return <AccessFormSafeLineConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.SSH:
|
||||
return <AccessFormSSHConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.SSLCOM:
|
||||
return <AccessFormSSLComConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.TENCENTCLOUD:
|
||||
return <AccessFormTencentCloudConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.UCLOUD:
|
||||
@ -191,6 +233,8 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
return <AccessFormWebhookConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.WESTCN:
|
||||
return <AccessFormWestcnConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.ZEROSSL:
|
||||
return <AccessFormZeroSSLConfig {...nestedFormProps} />;
|
||||
}
|
||||
}, [disabled, initialValues?.config, fieldProvider, nestedFormInst, nestedFormName]);
|
||||
|
||||
@ -235,13 +279,25 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
<Input placeholder={t("access.form.name.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="provider"
|
||||
label={t("access.form.provider.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.provider.tooltip") }}></span>}
|
||||
>
|
||||
<AccessProviderSelect disabled={preset !== "add"} placeholder={t("access.form.provider.placeholder")} showSearch={!disabled} />
|
||||
<Form.Item name="provider" label={providerLabel} rules={[formRule]} tooltip={providerTooltip}>
|
||||
<AccessProviderSelect
|
||||
filter={(record) => {
|
||||
if (range == null) return true;
|
||||
|
||||
switch (range) {
|
||||
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":
|
||||
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)}
|
||||
showSearch={!disabled}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
|
@ -0,0 +1,82 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, type FormInstance, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { type AccessConfigForGoogleTrustServices } from "@/domain/access";
|
||||
|
||||
type AccessFormGoogleTrustServicesConfigFieldValues = Nullish<AccessConfigForGoogleTrustServices>;
|
||||
|
||||
export type AccessFormGoogleTrustServicesConfigProps = {
|
||||
form: FormInstance;
|
||||
formName: string;
|
||||
disabled?: boolean;
|
||||
initialValues?: AccessFormGoogleTrustServicesConfigFieldValues;
|
||||
onValuesChange?: (values: AccessFormGoogleTrustServicesConfigFieldValues) => void;
|
||||
};
|
||||
|
||||
const initFormModel = (): AccessFormGoogleTrustServicesConfigFieldValues => {
|
||||
return {
|
||||
eabKid: "",
|
||||
eabHmacKey: "",
|
||||
};
|
||||
};
|
||||
|
||||
const AccessFormGoogleTrustServicesConfig = ({
|
||||
form: formInst,
|
||||
formName,
|
||||
disabled,
|
||||
initialValues,
|
||||
onValuesChange,
|
||||
}: AccessFormGoogleTrustServicesConfigProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
eabKid: z
|
||||
.string()
|
||||
.min(1, t("access.form.googletrustservices_eab_kid.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim(),
|
||||
eabHmacKey: z
|
||||
.string()
|
||||
.min(1, t("access.form.googletrustservices_eab_hmac_key.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||
.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="eabKid"
|
||||
label={t("access.form.googletrustservices_eab_kid.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.googletrustservices_eab_kid.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.googletrustservices_eab_kid.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="eabHmacKey"
|
||||
label={t("access.form.googletrustservices_eab_hmac_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.googletrustservices_eab_hmac_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.googletrustservices_eab_hmac_key.placeholder")} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessFormGoogleTrustServicesConfig;
|
@ -1,36 +0,0 @@
|
||||
import { Form, type FormInstance } from "antd";
|
||||
|
||||
import { type AccessConfigForLocal } from "@/domain/access";
|
||||
|
||||
type AccessFormLocalConfigFieldValues = Nullish<AccessConfigForLocal>;
|
||||
|
||||
export type AccessFormLocalConfigProps = {
|
||||
form: FormInstance;
|
||||
formName: string;
|
||||
disabled?: boolean;
|
||||
initialValues?: AccessFormLocalConfigFieldValues;
|
||||
onValuesChange?: (values: AccessFormLocalConfigFieldValues) => void;
|
||||
};
|
||||
|
||||
const initFormModel = (): AccessFormLocalConfigFieldValues => {
|
||||
return {};
|
||||
};
|
||||
|
||||
const AccessFormLocalConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormLocalConfigProps) => {
|
||||
const handleFormChange = (_: unknown, values: any) => {
|
||||
onValuesChange?.(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={formInst}
|
||||
disabled={disabled}
|
||||
initialValues={initialValues ?? initFormModel()}
|
||||
layout="vertical"
|
||||
name={formName}
|
||||
onValuesChange={handleFormChange}
|
||||
></Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessFormLocalConfig;
|
@ -7,7 +7,7 @@ import { z } from "zod";
|
||||
|
||||
import { type AccessConfigForSSH } from "@/domain/access";
|
||||
import { readFileContent } from "@/utils/file";
|
||||
import { validDomainName, validIPv4Address, validIPv6Address } from "@/utils/validators";
|
||||
import { validDomainName, validIPv4Address, validIPv6Address, validPortNumber } from "@/utils/validators";
|
||||
|
||||
type AccessFormSSHConfigFieldValues = Nullish<AccessConfigForSSH>;
|
||||
|
||||
@ -34,11 +34,13 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
|
||||
host: z
|
||||
.string({ message: t("access.form.ssh_host.placeholder") })
|
||||
.refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")),
|
||||
port: z
|
||||
.number({ message: t("access.form.ssh_port.placeholder") })
|
||||
.int()
|
||||
.gte(1, t("common.errmsg.port_invalid"))
|
||||
.lte(65535, t("common.errmsg.port_invalid")),
|
||||
port: z.preprocess(
|
||||
(v) => Number(v),
|
||||
z
|
||||
.number({ message: t("access.form.ssh_port.placeholder") })
|
||||
.int(t("access.form.ssh_port.placeholder"))
|
||||
.refine((v) => validPortNumber(v), t("common.errmsg.port_invalid"))
|
||||
),
|
||||
username: z
|
||||
.string()
|
||||
.min(1, "access.form.ssh_username.placeholder")
|
||||
|
76
ui/src/components/access/AccessFormSSLComConfig.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, type FormInstance, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { type AccessConfigForSSLCom } from "@/domain/access";
|
||||
|
||||
type AccessFormSSLComConfigFieldValues = Nullish<AccessConfigForSSLCom>;
|
||||
|
||||
export type AccessFormSSLComConfigProps = {
|
||||
form: FormInstance;
|
||||
formName: string;
|
||||
disabled?: boolean;
|
||||
initialValues?: AccessFormSSLComConfigFieldValues;
|
||||
onValuesChange?: (values: AccessFormSSLComConfigFieldValues) => void;
|
||||
};
|
||||
|
||||
const initFormModel = (): AccessFormSSLComConfigFieldValues => {
|
||||
return {
|
||||
eabKid: "",
|
||||
eabHmacKey: "",
|
||||
};
|
||||
};
|
||||
|
||||
const AccessFormSSLComConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormSSLComConfigProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
eabKid: z
|
||||
.string()
|
||||
.min(1, t("access.form.sslcom_eab_kid.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim(),
|
||||
eabHmacKey: z
|
||||
.string()
|
||||
.min(1, t("access.form.sslcom_eab_hmac_key.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||
.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="eabKid"
|
||||
label={t("access.form.sslcom_eab_kid.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.sslcom_eab_kid.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.sslcom_eab_kid.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="eabHmacKey"
|
||||
label={t("access.form.sslcom_eab_hmac_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.sslcom_eab_hmac_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.sslcom_eab_hmac_key.placeholder")} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessFormSSLComConfig;
|
76
ui/src/components/access/AccessFormZeroSSLConfig.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, type FormInstance, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { type AccessConfigForZeroSSL } from "@/domain/access";
|
||||
|
||||
type AccessFormZeroSSLConfigFieldValues = Nullish<AccessConfigForZeroSSL>;
|
||||
|
||||
export type AccessFormZeroSSLConfigProps = {
|
||||
form: FormInstance;
|
||||
formName: string;
|
||||
disabled?: boolean;
|
||||
initialValues?: AccessFormZeroSSLConfigFieldValues;
|
||||
onValuesChange?: (values: AccessFormZeroSSLConfigFieldValues) => void;
|
||||
};
|
||||
|
||||
const initFormModel = (): AccessFormZeroSSLConfigFieldValues => {
|
||||
return {
|
||||
eabKid: "",
|
||||
eabHmacKey: "",
|
||||
};
|
||||
};
|
||||
|
||||
const AccessFormZeroSSLConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormZeroSSLConfigProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
eabKid: z
|
||||
.string()
|
||||
.min(1, t("access.form.zerossl_eab_kid.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim(),
|
||||
eabHmacKey: z
|
||||
.string()
|
||||
.min(1, t("access.form.zerossl_eab_hmac_key.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||
.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="eabKid"
|
||||
label={t("access.form.zerossl_eab_kid.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.zerossl_eab_kid.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.zerossl_eab_kid.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="eabHmacKey"
|
||||
label={t("access.form.zerossl_eab_hmac_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.zerossl_eab_hmac_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.zerossl_eab_hmac_key.placeholder")} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessFormZeroSSLConfig;
|
@ -29,7 +29,6 @@ const CertificateDetailDrawer = ({ data, loading, trigger, ...props }: Certifica
|
||||
|
||||
<Drawer
|
||||
afterOpenChange={setOpen}
|
||||
closable
|
||||
destroyOnClose
|
||||
open={open}
|
||||
loading={loading}
|
||||
|
@ -3,6 +3,8 @@ import { Form, Input, InputNumber, Switch } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validPortNumber } from "@/utils/validators";
|
||||
|
||||
const NotifyChannelEditFormEmailFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -11,11 +13,13 @@ const NotifyChannelEditFormEmailFields = () => {
|
||||
.string({ message: t("settings.notification.channel.form.email_smtp_host.placeholder") })
|
||||
.min(1, t("settings.notification.channel.form.email_smtp_host.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
smtpPort: z
|
||||
.number({ message: t("settings.notification.channel.form.email_smtp_port.placeholder") })
|
||||
.int()
|
||||
.gte(1, t("common.errmsg.port_invalid"))
|
||||
.lte(65535, t("common.errmsg.port_invalid")),
|
||||
smtpPort: z.preprocess(
|
||||
(v) => Number(v),
|
||||
z
|
||||
.number({ message: t("settings.notification.channel.form.email_smtp_port.placeholder") })
|
||||
.int(t("settings.notification.channel.form.email_smtp_port.placeholder"))
|
||||
.refine((v) => validPortNumber(v), t("common.errmsg.port_invalid"))
|
||||
),
|
||||
smtpTLS: z.boolean().nullish(),
|
||||
username: z
|
||||
.string({ message: t("settings.notification.channel.form.email_username.placeholder") })
|
||||
|
@ -1,17 +1,19 @@
|
||||
import { memo, useEffect, useState } from "react";
|
||||
import { memo, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Avatar, Select, type SelectProps, Space, Tag, Typography } from "antd";
|
||||
|
||||
import { ACCESS_USAGES, type AccessProvider, accessProvidersMap } from "@/domain/provider";
|
||||
import Show from "@/components/Show";
|
||||
import { ACCESS_USAGES, type AccessProvider, type AccessUsageType, accessProvidersMap } from "@/domain/provider";
|
||||
|
||||
export type AccessProviderSelectProps = Omit<
|
||||
SelectProps,
|
||||
"filterOption" | "filterSort" | "labelRender" | "options" | "optionFilterProp" | "optionLabelProp" | "optionRender"
|
||||
> & {
|
||||
filter?: (record: AccessProvider) => boolean;
|
||||
showOptionTags?: boolean | { [key in AccessUsageType]?: boolean };
|
||||
};
|
||||
|
||||
const AccessProviderSelect = ({ filter, ...props }: AccessProviderSelectProps) => {
|
||||
const AccessProviderSelect = ({ filter, showOptionTags, ...props }: AccessProviderSelectProps = { showOptionTags: true }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [options, setOptions] = useState<Array<{ key: string; value: string; label: string; data: AccessProvider }>>([]);
|
||||
@ -23,33 +25,51 @@ const AccessProviderSelect = ({ filter, ...props }: AccessProviderSelectProps) =
|
||||
key: item.type,
|
||||
value: item.type,
|
||||
label: t(item.name),
|
||||
disabled: item.builtin,
|
||||
data: item,
|
||||
}))
|
||||
);
|
||||
}, [filter]);
|
||||
|
||||
const showOptionTagForDNS = useMemo(() => {
|
||||
return typeof showOptionTags === "object" ? !!showOptionTags[ACCESS_USAGES.DNS] : !!showOptionTags;
|
||||
}, [showOptionTags]);
|
||||
const showOptionTagForHosting = useMemo(() => {
|
||||
return typeof showOptionTags === "object" ? !!showOptionTags[ACCESS_USAGES.HOSTING] : !!showOptionTags;
|
||||
}, [showOptionTags]);
|
||||
const showOptionTagForCA = useMemo(() => {
|
||||
return typeof showOptionTags === "object" ? !!showOptionTags[ACCESS_USAGES.CA] : !!showOptionTags;
|
||||
}, [showOptionTags]);
|
||||
const showOptionTagForNotification = useMemo(() => {
|
||||
return typeof showOptionTags === "object" ? !!showOptionTags[ACCESS_USAGES.NOTIFICATION] : !!showOptionTags;
|
||||
}, [showOptionTags]);
|
||||
|
||||
const renderOption = (key: string) => {
|
||||
const provider = accessProvidersMap.get(key);
|
||||
const provider = accessProvidersMap.get(key) ?? ({ type: "", name: "", icon: "", usages: [] } as unknown as AccessProvider);
|
||||
return (
|
||||
<div className="flex max-w-full items-center justify-between gap-4 overflow-hidden">
|
||||
<Space className="max-w-full grow truncate" size={4}>
|
||||
<Avatar src={provider?.icon} size="small" />
|
||||
<Typography.Text className="leading-loose" ellipsis>
|
||||
{t(provider?.name ?? "")}
|
||||
<Avatar src={provider.icon} size="small" />
|
||||
<Typography.Text className="leading-loose" type={provider.builtin ? "secondary" : undefined} delete={provider.builtin ? true : undefined} ellipsis>
|
||||
{t(provider.name)}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
<div>
|
||||
{provider?.usages?.includes(ACCESS_USAGES.APPLY) && (
|
||||
<>
|
||||
<Tag color="orange">{t("access.props.provider.usage.dns")}</Tag>
|
||||
</>
|
||||
)}
|
||||
{provider?.usages?.includes(ACCESS_USAGES.DEPLOY) && (
|
||||
<>
|
||||
<Tag color="blue">{t("access.props.provider.usage.host")}</Tag>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{showOptionTags && (
|
||||
<div>
|
||||
<Show when={showOptionTagForDNS && provider.usages.includes(ACCESS_USAGES.DNS)}>
|
||||
<Tag color="peru">{t("access.props.provider.usage.dns")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForHosting && provider.usages.includes(ACCESS_USAGES.HOSTING)}>
|
||||
<Tag color="royalblue">{t("access.props.provider.usage.hosting")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForCA && provider.usages.includes(ACCESS_USAGES.CA)}>
|
||||
<Tag color="crimson">{t("access.props.provider.usage.ca")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForNotification && provider.usages.includes(ACCESS_USAGES.NOTIFICATION)}>
|
||||
<Tag color="mediumaquamarine">{t("access.props.provider.usage.notification")}</Tag>
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
83
ui/src/components/provider/ApplyCAProviderSelect.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
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";
|
||||
|
||||
export type ApplyCAProviderSelectProps = Omit<
|
||||
SelectProps,
|
||||
"filterOption" | "filterSort" | "labelRender" | "options" | "optionFilterProp" | "optionLabelProp" | "optionRender"
|
||||
> & {
|
||||
filter?: (record: ApplyCAProvider) => boolean;
|
||||
};
|
||||
|
||||
const ApplyCAProviderSelect = ({ filter, ...props }: ApplyCAProviderSelectProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [options, setOptions] = useState<Array<{ key: string; value: string; label: string; data: ApplyCAProvider }>>([]);
|
||||
useEffect(() => {
|
||||
const allItems = Array.from(applyCAProvidersMap.values());
|
||||
const filteredItems = filter != null ? allItems.filter(filter) : allItems;
|
||||
setOptions([
|
||||
{
|
||||
key: "",
|
||||
value: "",
|
||||
label: "provider.default_ca_provider.label",
|
||||
data: {} as ApplyCAProvider,
|
||||
},
|
||||
...filteredItems.map((item) => ({
|
||||
key: item.type,
|
||||
value: item.type,
|
||||
label: t(item.name),
|
||||
data: item,
|
||||
})),
|
||||
]);
|
||||
}, [filter]);
|
||||
|
||||
const renderOption = (key: string) => {
|
||||
if (key === "") {
|
||||
return (
|
||||
<Space className="max-w-full grow overflow-hidden truncate" size={4}>
|
||||
<Typography.Text className="italic leading-loose" type="secondary" ellipsis italic>
|
||||
{t("provider.default_ca_provider.label")}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
const provider = applyCAProvidersMap.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 || t("provider.default_ca_provider.label")}</Typography.Text>;
|
||||
}
|
||||
|
||||
return renderOption(value as string);
|
||||
}}
|
||||
options={options}
|
||||
optionFilterProp={undefined}
|
||||
optionLabelProp={undefined}
|
||||
optionRender={(option) => renderOption(option.data.value)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ApplyCAProviderSelect);
|
@ -1,4 +1,4 @@
|
||||
import { memo, useEffect, useRef, useState } from "react";
|
||||
import { memo, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Avatar, Card, Col, Empty, Flex, Input, type InputRef, Row, Typography } from "antd";
|
||||
|
||||
@ -24,15 +24,16 @@ const ApplyDNSProviderPicker = ({ className, style, autoFocus, placeholder, onSe
|
||||
}
|
||||
}, []);
|
||||
|
||||
const providers = Array.from(applyDNSProvidersMap.values());
|
||||
const filteredProviders = providers.filter((provider) => {
|
||||
if (keyword) {
|
||||
const value = keyword.toLowerCase();
|
||||
return provider.type.toLowerCase().includes(value) || t(provider.name).toLowerCase().includes(value);
|
||||
}
|
||||
const providers = useMemo(() => {
|
||||
return Array.from(applyDNSProvidersMap.values()).filter((provider) => {
|
||||
if (keyword) {
|
||||
const value = keyword.toLowerCase();
|
||||
return provider.type.toLowerCase().includes(value) || t(provider.name).toLowerCase().includes(value);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}, [keyword]);
|
||||
|
||||
const handleProviderTypeSelect = (value: string) => {
|
||||
onSelect?.(value);
|
||||
@ -40,12 +41,12 @@ const ApplyDNSProviderPicker = ({ className, style, autoFocus, placeholder, onSe
|
||||
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<Input.Search ref={keywordInputRef} placeholder={placeholder} onChange={(e) => setKeyword(e.target.value.trim())} />
|
||||
<Input.Search ref={keywordInputRef} placeholder={placeholder ?? t("common.text.search")} onChange={(e) => setKeyword(e.target.value.trim())} />
|
||||
|
||||
<div className="mt-4">
|
||||
<Show when={filteredProviders.length > 0} fallback={<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}>
|
||||
<Show when={providers.length > 0} fallback={<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}>
|
||||
<Row gutter={[16, 16]}>
|
||||
{filteredProviders.map((provider, index) => {
|
||||
{providers.map((provider, index) => {
|
||||
return (
|
||||
<Col key={index} xs={24} md={12} span={12}>
|
||||
<Card
|
||||
|
@ -16,6 +16,8 @@ export type DeployProviderPickerProps = {
|
||||
const DeployProviderPicker = ({ className, style, autoFocus, placeholder, onSelect }: DeployProviderPickerProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [category, setCategory] = useState<string>(DEPLOY_CATEGORIES.ALL);
|
||||
|
||||
const [keyword, setKeyword] = useState<string>();
|
||||
const keywordInputRef = useRef<InputRef>(null);
|
||||
useEffect(() => {
|
||||
@ -24,26 +26,24 @@ const DeployProviderPicker = ({ className, style, autoFocus, placeholder, onSele
|
||||
}
|
||||
}, []);
|
||||
|
||||
const [category, setCategory] = useState<string>(DEPLOY_CATEGORIES.ALL);
|
||||
|
||||
const providers = useMemo(() => {
|
||||
return Array.from(deployProvidersMap.values())
|
||||
.filter((provider) => {
|
||||
if (category && category !== DEPLOY_CATEGORIES.ALL) {
|
||||
return provider.category === category;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.filter((provider) => {
|
||||
if (keyword) {
|
||||
const value = keyword.toLowerCase();
|
||||
return provider.type.toLowerCase().includes(value) || t(provider.name).toLowerCase().includes(value);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.filter((provider) => {
|
||||
if (category && category !== DEPLOY_CATEGORIES.ALL) {
|
||||
return provider.category === category;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}, [keyword, category]);
|
||||
}, [category, keyword]);
|
||||
|
||||
const handleProviderTypeSelect = (value: string) => {
|
||||
onSelect?.(value);
|
||||
@ -51,7 +51,7 @@ const DeployProviderPicker = ({ className, style, autoFocus, placeholder, onSele
|
||||
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<Input.Search ref={keywordInputRef} placeholder={placeholder} onChange={(e) => setKeyword(e.target.value.trim())} />
|
||||
<Input.Search ref={keywordInputRef} placeholder={placeholder ?? t("common.text.search")} onChange={(e) => setKeyword(e.target.value.trim())} />
|
||||
|
||||
<div className="mt-4">
|
||||
<Flex>
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
CheckOutlined as CheckOutlinedIcon,
|
||||
ClockCircleOutlined as ClockCircleOutlinedIcon,
|
||||
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
||||
DownloadOutlined as DownloadOutlinedIcon,
|
||||
RightOutlined as RightOutlinedIcon,
|
||||
SelectOutlined as SelectOutlinedIcon,
|
||||
SettingOutlined as SettingOutlinedIcon,
|
||||
@ -188,6 +189,34 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
|
||||
);
|
||||
};
|
||||
|
||||
const handleDownloadClick = () => {
|
||||
const NEWLINE = "\n";
|
||||
const logstr = listData
|
||||
.map((group) => {
|
||||
return (
|
||||
group.name +
|
||||
NEWLINE +
|
||||
group.records
|
||||
.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}`;
|
||||
})
|
||||
.join(NEWLINE)
|
||||
);
|
||||
})
|
||||
.join(NEWLINE + NEWLINE);
|
||||
const blob = new Blob([logstr], { type: "text/plain" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `certimate_workflow_run_#${runId}_logs.txt`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography.Title level={5}>{t("workflow_run.logs")}</Typography.Title>
|
||||
@ -210,6 +239,15 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
|
||||
icon: <CheckOutlinedIcon className={showWhitespace ? "visible" : "invisible"} />,
|
||||
onClick: () => setShowWhitespace(!showWhitespace),
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
key: "download-logs",
|
||||
label: t("workflow_run.logs.menu.download_logs"),
|
||||
icon: <DownloadOutlinedIcon className="invisible" />,
|
||||
onClick: handleDownloadClick,
|
||||
},
|
||||
],
|
||||
}}
|
||||
trigger={["click"]}
|
||||
|
@ -30,7 +30,6 @@ const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowR
|
||||
|
||||
<Drawer
|
||||
afterOpenChange={setOpen}
|
||||
closable
|
||||
destroyOnClose
|
||||
open={open}
|
||||
loading={loading}
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { forwardRef, memo, useEffect, useImperativeHandle, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormOutlined as FormOutlinedIcon, PlusOutlined as PlusOutlinedIcon, QuestionCircleOutlined as QuestionCircleOutlinedIcon } from "@ant-design/icons";
|
||||
import { Link } from "react-router";
|
||||
import {
|
||||
FormOutlined as FormOutlinedIcon,
|
||||
PlusOutlined as PlusOutlinedIcon,
|
||||
QuestionCircleOutlined as QuestionCircleOutlinedIcon,
|
||||
RightOutlined as RightOutlinedIcon,
|
||||
} from "@ant-design/icons";
|
||||
import { useControllableValue } from "ahooks";
|
||||
import {
|
||||
AutoComplete,
|
||||
@ -25,8 +31,9 @@ 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, applyDNSProvidersMap } from "@/domain/provider";
|
||||
import { ACCESS_USAGES, APPLY_DNS_PROVIDERS, accessProvidersMap, applyCAProvidersMap, applyDNSProvidersMap } from "@/domain/provider";
|
||||
import { type WorkflowNodeConfigForApply } from "@/domain/workflow";
|
||||
import { useAntdForm, useAntdFormName, useZustandShallowSelector } from "@/hooks";
|
||||
import { useAccessesStore } from "@/stores/access";
|
||||
@ -60,7 +67,7 @@ const initFormModel = (): ApplyNodeConfigFormFieldValues => {
|
||||
return {
|
||||
challengeType: "dns-01",
|
||||
keyAlgorithm: "RSA2048",
|
||||
skipBeforeExpiryDays: 20,
|
||||
skipBeforeExpiryDays: 30,
|
||||
};
|
||||
};
|
||||
|
||||
@ -83,7 +90,18 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
providerAccessId: z
|
||||
.string({ message: t("workflow_node.apply.form.provider_access.placeholder") })
|
||||
.min(1, t("workflow_node.apply.form.provider_access.placeholder")),
|
||||
providerConfig: z.any(),
|
||||
providerConfig: z.any().nullish(),
|
||||
caProvider: z.string({ message: t("workflow_node.apply.form.ca_provider.placeholder") }).nullish(),
|
||||
caProviderAccessId: z
|
||||
.string({ message: t("workflow_node.apply.form.ca_provider_access.placeholder") })
|
||||
.nullish()
|
||||
.refine((v) => {
|
||||
if (!fieldCAProvider) return true;
|
||||
|
||||
const provider = applyCAProvidersMap.get(fieldCAProvider);
|
||||
return !!provider?.builtin || !!v;
|
||||
}, t("workflow_node.apply.form.ca_provider_access.placeholder")),
|
||||
caProviderConfig: z.any().nullish(),
|
||||
keyAlgorithm: z
|
||||
.string({ message: t("workflow_node.apply.form.key_algorithm.placeholder") })
|
||||
.nonempty(t("workflow_node.apply.form.key_algorithm.placeholder")),
|
||||
@ -96,24 +114,27 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
.split(MULTIPLE_INPUT_DELIMITER)
|
||||
.every((e) => validIPv4Address(e) || validIPv6Address(e) || validDomainName(e));
|
||||
}, t("common.errmsg.host_invalid")),
|
||||
dnsPropagationTimeout: z
|
||||
.union([
|
||||
z.number().int().gte(1, t("workflow_node.apply.form.dns_propagation_timeout.placeholder")),
|
||||
z.string().refine((v) => !v || /^[1-9]\d*$/.test(v), t("workflow_node.apply.form.dns_propagation_timeout.placeholder")),
|
||||
])
|
||||
.nullish(),
|
||||
dnsTTL: z
|
||||
.union([
|
||||
z.number().int().gte(1, t("workflow_node.apply.form.dns_ttl.placeholder")),
|
||||
z.string().refine((v) => !v || /^[1-9]\d*$/.test(v), t("workflow_node.apply.form.dns_ttl.placeholder")),
|
||||
])
|
||||
.nullish(),
|
||||
dnsPropagationTimeout: z.preprocess(
|
||||
(v) => (v == null || v === "" ? undefined : Number(v)),
|
||||
z
|
||||
.number()
|
||||
.int(t("workflow_node.apply.form.dns_propagation_timeout.placeholder"))
|
||||
.gte(1, t("workflow_node.apply.form.dns_propagation_timeout.placeholder"))
|
||||
.nullish()
|
||||
),
|
||||
dnsTTL: z.preprocess(
|
||||
(v) => (v == null || v === "" ? undefined : Number(v)),
|
||||
z.number().int(t("workflow_node.apply.form.dns_ttl.placeholder")).gte(1, t("workflow_node.apply.form.dns_ttl.placeholder")).nullish()
|
||||
),
|
||||
disableFollowCNAME: z.boolean().nullish(),
|
||||
disableARI: z.boolean().nullish(),
|
||||
skipBeforeExpiryDays: z
|
||||
.number({ message: t("workflow_node.apply.form.skip_before_expiry_days.placeholder") })
|
||||
.int(t("workflow_node.apply.form.skip_before_expiry_days.placeholder"))
|
||||
.gte(1, t("workflow_node.apply.form.skip_before_expiry_days.placeholder")),
|
||||
skipBeforeExpiryDays: z.preprocess(
|
||||
(v) => Number(v),
|
||||
z
|
||||
.number({ message: t("workflow_node.apply.form.skip_before_expiry_days.placeholder") })
|
||||
.int(t("workflow_node.apply.form.skip_before_expiry_days.placeholder"))
|
||||
.gte(1, t("workflow_node.apply.form.skip_before_expiry_days.placeholder"))
|
||||
),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const { form: formInst, formProps } = useAntdForm({
|
||||
@ -121,9 +142,10 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
initialValues: initialValues ?? initFormModel(),
|
||||
});
|
||||
|
||||
const fieldDomains = Form.useWatch<string>("domains", formInst);
|
||||
const fieldProvider = Form.useWatch<string>("provider", { form: formInst, preserve: true });
|
||||
const fieldProviderAccessId = Form.useWatch<string>("providerAccessId", formInst);
|
||||
const fieldDomains = Form.useWatch<string>("domains", formInst);
|
||||
const fieldCAProvider = Form.useWatch<string>("caProvider", formInst);
|
||||
const fieldNameservers = Form.useWatch<string>("nameservers", formInst);
|
||||
|
||||
const [showProvider, setShowProvider] = useState(false);
|
||||
@ -139,6 +161,17 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
}
|
||||
}, [accesses, fieldProviderAccessId]);
|
||||
|
||||
const [showCAProviderAccess, setShowCAProviderAccess] = useState(false);
|
||||
useEffect(() => {
|
||||
// 内置的 CA 提供商(如 Let's Encrypt)无需显示授权信息字段
|
||||
if (fieldCAProvider) {
|
||||
const provider = applyCAProvidersMap.get(fieldCAProvider);
|
||||
setShowCAProviderAccess(!provider?.builtin);
|
||||
} else {
|
||||
setShowCAProviderAccess(false);
|
||||
}
|
||||
}, [fieldCAProvider]);
|
||||
|
||||
const [nestedFormInst] = Form.useForm();
|
||||
const nestedFormName = useAntdFormName({ form: nestedFormInst, name: "workflowNodeApplyConfigFormProviderConfigForm" });
|
||||
const nestedFormEl = useMemo(() => {
|
||||
@ -195,6 +228,27 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
}
|
||||
};
|
||||
|
||||
const handleCAProviderSelect = (value?: string | undefined) => {
|
||||
if (fieldCAProvider === value) return;
|
||||
|
||||
// 切换 CA 提供商时联动授权信息
|
||||
if (value === "") {
|
||||
setTimeout(() => {
|
||||
formInst.setFieldValue("caProvider", undefined);
|
||||
formInst.setFieldValue("caProviderAccessId", undefined);
|
||||
onValuesChange?.(formInst.getFieldsValue(true));
|
||||
}, 1);
|
||||
} else if (initialValues?.caProvider === value) {
|
||||
formInst.setFieldValue("caProviderAccessId", initialValues?.caProviderAccessId);
|
||||
onValuesChange?.(formInst.getFieldsValue(true));
|
||||
} else {
|
||||
if (applyCAProvidersMap.get(fieldCAProvider)?.provider !== applyCAProvidersMap.get(value!)?.provider) {
|
||||
formInst.setFieldValue("caProviderAccessId", undefined);
|
||||
onValuesChange?.(formInst.getFieldsValue(true));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleFormProviderChange = (name: string) => {
|
||||
if (name === nestedFormName) {
|
||||
formInst.setFieldValue("providerConfig", nestedFormInst.getFieldsValue());
|
||||
@ -301,16 +355,17 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<AccessEditModal
|
||||
preset="add"
|
||||
range="both-dns-hosting"
|
||||
scene="add"
|
||||
trigger={
|
||||
<Button size="small" type="link">
|
||||
<PlusOutlinedIcon />
|
||||
{t("workflow_node.apply.form.provider_access.button")}
|
||||
<PlusOutlinedIcon className="text-xs" />
|
||||
</Button>
|
||||
}
|
||||
afterSubmit={(record) => {
|
||||
const provider = accessProvidersMap.get(record.provider);
|
||||
if (provider?.usages?.includes(ACCESS_USAGES.APPLY)) {
|
||||
if (provider?.usages?.includes(ACCESS_USAGES.DNS)) {
|
||||
formInst.setFieldValue("providerAccessId", record.id);
|
||||
}
|
||||
}}
|
||||
@ -322,7 +377,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
<AccessSelect
|
||||
filter={(record) => {
|
||||
const provider = accessProvidersMap.get(record.provider);
|
||||
return !!provider?.usages?.includes(ACCESS_USAGES.APPLY);
|
||||
return !!provider?.usages?.includes(ACCESS_USAGES.DNS);
|
||||
}}
|
||||
placeholder={t("workflow_node.apply.form.provider_access.placeholder")}
|
||||
onChange={handleProviderAccessSelect}
|
||||
@ -340,6 +395,73 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
</Divider>
|
||||
|
||||
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
||||
<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="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>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<Form.Item name="caProvider" rules={[formRule]}>
|
||||
<ApplyCAProviderSelect
|
||||
allowClear
|
||||
placeholder={t("workflow_node.apply.form.ca_provider.placeholder")}
|
||||
showSearch
|
||||
onSelect={handleCAProviderSelect}
|
||||
onClear={handleCAProviderSelect}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0" hidden={!showCAProviderAccess}>
|
||||
<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("workflow_node.apply.form.ca_provider_access.label")}</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<AccessEditModal
|
||||
data={{ provider: applyCAProvidersMap.get(fieldCAProvider!)?.provider }}
|
||||
range="ca-only"
|
||||
scene="add"
|
||||
trigger={
|
||||
<Button size="small" type="link">
|
||||
{t("workflow_node.apply.form.ca_provider_access.button")}
|
||||
<PlusOutlinedIcon className="text-xs" />
|
||||
</Button>
|
||||
}
|
||||
afterSubmit={(record) => {
|
||||
const provider = accessProvidersMap.get(record.provider);
|
||||
if (provider?.usages?.includes(ACCESS_USAGES.CA)) {
|
||||
formInst.setFieldValue("caProviderAccessId", record.id);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<Form.Item name="caProviderAccessId" rules={[formRule]}>
|
||||
<AccessSelect
|
||||
filter={(record) => {
|
||||
if (fieldCAProvider) {
|
||||
return applyCAProvidersMap.get(fieldCAProvider)?.provider === record.provider;
|
||||
}
|
||||
|
||||
const provider = accessProvidersMap.get(record.provider);
|
||||
return !!provider?.usages?.includes(ACCESS_USAGES.CA);
|
||||
}}
|
||||
placeholder={t("workflow_node.apply.form.ca_provider_access.placeholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="keyAlgorithm" label={t("workflow_node.apply.form.key_algorithm.label")} rules={[formRule]}>
|
||||
<Select
|
||||
options={["RSA2048", "RSA3072", "RSA4096", "RSA8192", "EC256", "EC384"].map((e) => ({
|
||||
@ -364,6 +486,9 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
onChange={(e) => {
|
||||
formInst.setFieldValue("nameservers", e.target.value);
|
||||
}}
|
||||
onClear={() => {
|
||||
formInst.setFieldValue("nameservers", undefined);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<NameserversModalInput
|
||||
@ -448,7 +573,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
<InputNumber
|
||||
className="w-36"
|
||||
min={1}
|
||||
max={90}
|
||||
max={365}
|
||||
placeholder={t("workflow_node.apply.form.skip_before_expiry_days.placeholder")}
|
||||
addonAfter={t("workflow_node.apply.form.skip_before_expiry_days.unit")}
|
||||
/>
|
||||
|
@ -7,8 +7,8 @@ import { z } from "zod";
|
||||
|
||||
import AccessEditModal from "@/components/access/AccessEditModal";
|
||||
import AccessSelect from "@/components/access/AccessSelect";
|
||||
import DeployProviderPicker from "@/components/provider/DeployProviderPicker";
|
||||
import DeployProviderSelect from "@/components/provider/DeployProviderSelect";
|
||||
import DeployProviderPicker from "@/components/provider/DeployProviderPicker.tsx";
|
||||
import DeployProviderSelect from "@/components/provider/DeployProviderSelect.tsx";
|
||||
import Show from "@/components/Show";
|
||||
import { ACCESS_USAGES, DEPLOY_PROVIDERS, accessProvidersMap, deployProvidersMap } from "@/domain/provider";
|
||||
import { type WorkflowNode, type WorkflowNodeConfigForDeploy } from "@/domain/workflow";
|
||||
@ -125,8 +125,14 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
provider: z.string({ message: t("workflow_node.deploy.form.provider.placeholder") }).nonempty(t("workflow_node.deploy.form.provider.placeholder")),
|
||||
providerAccessId: z
|
||||
.string({ message: t("workflow_node.deploy.form.provider_access.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.provider_access.placeholder")),
|
||||
providerConfig: z.any(),
|
||||
.nullish()
|
||||
.refine((v) => {
|
||||
if (!fieldProvider) return true;
|
||||
|
||||
const provider = deployProvidersMap.get(fieldProvider);
|
||||
return !!provider?.builtin || !!v;
|
||||
}, t("workflow_node.deploy.form.provider_access.placeholder")),
|
||||
providerConfig: z.any().nullish(),
|
||||
skipOnLastSucceeded: z.boolean().nullish(),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
@ -137,6 +143,17 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
|
||||
const fieldProvider = Form.useWatch("provider", { form: formInst, preserve: true });
|
||||
|
||||
const [showProviderAccess, setShowProviderAccess] = useState(false);
|
||||
useEffect(() => {
|
||||
// 内置的部署提供商(如本地部署)无需显示授权信息字段
|
||||
if (fieldProvider) {
|
||||
const provider = deployProvidersMap.get(fieldProvider);
|
||||
setShowProviderAccess(!provider?.builtin);
|
||||
} else {
|
||||
setShowProviderAccess(false);
|
||||
}
|
||||
}, [fieldProvider]);
|
||||
|
||||
const [nestedFormInst] = Form.useForm();
|
||||
const nestedFormName = useAntdFormName({ form: nestedFormInst, name: "workflowNodeDeployConfigFormProviderConfigForm" });
|
||||
const nestedFormEl = useMemo(() => {
|
||||
@ -292,7 +309,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
onValuesChange?.(formInst.getFieldsValue(true));
|
||||
};
|
||||
|
||||
const handleProviderSelect = (value: string) => {
|
||||
const handleProviderSelect = (value?: string | undefined) => {
|
||||
if (fieldProvider === value) return;
|
||||
|
||||
// 切换部署目标时重置表单,避免其他部署目标的配置字段影响当前部署目标
|
||||
@ -310,7 +327,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
}
|
||||
formInst.setFieldsValue(newValues);
|
||||
|
||||
if (deployProvidersMap.get(fieldProvider)?.provider !== deployProvidersMap.get(value)?.provider) {
|
||||
if (deployProvidersMap.get(fieldProvider)?.provider !== deployProvidersMap.get(value!)?.provider) {
|
||||
formInst.setFieldValue("providerAccessId", undefined);
|
||||
onValuesChange?.(formInst.getFieldsValue(true));
|
||||
}
|
||||
@ -364,10 +381,11 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
placeholder={t("workflow_node.deploy.form.provider.placeholder")}
|
||||
showSearch
|
||||
onSelect={handleProviderSelect}
|
||||
onClear={handleProviderSelect}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0">
|
||||
<Form.Item className="mb-0" hidden={!showProviderAccess}>
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">
|
||||
@ -381,16 +399,17 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
<div className="text-right">
|
||||
<AccessEditModal
|
||||
data={{ provider: deployProvidersMap.get(fieldProvider!)?.provider }}
|
||||
preset="add"
|
||||
range="both-dns-hosting"
|
||||
scene="add"
|
||||
trigger={
|
||||
<Button size="small" type="link">
|
||||
<PlusOutlinedIcon />
|
||||
{t("workflow_node.deploy.form.provider_access.button")}
|
||||
<PlusOutlinedIcon className="text-xs" />
|
||||
</Button>
|
||||
}
|
||||
afterSubmit={(record) => {
|
||||
const provider = accessProvidersMap.get(record.provider);
|
||||
if (provider?.usages?.includes(ACCESS_USAGES.DEPLOY)) {
|
||||
if (provider?.usages?.includes(ACCESS_USAGES.HOSTING)) {
|
||||
formInst.setFieldValue("providerAccessId", record.id);
|
||||
}
|
||||
}}
|
||||
@ -406,7 +425,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
}
|
||||
|
||||
const provider = accessProvidersMap.get(record.provider);
|
||||
return !!provider?.usages?.includes(ACCESS_USAGES.DEPLOY);
|
||||
return !!provider?.usages?.includes(ACCESS_USAGES.HOSTING);
|
||||
}}
|
||||
placeholder={t("workflow_node.deploy.form.provider_access.placeholder")}
|
||||
/>
|
||||
|
@ -100,6 +100,9 @@ const DeployNodeConfigFormAliyunCASDeployConfig = ({
|
||||
onChange={(e) => {
|
||||
formInst.setFieldValue("resourceIds", e.target.value);
|
||||
}}
|
||||
onClear={() => {
|
||||
formInst.setFieldValue("resourceIds", "");
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<ResourceIdsModalInput
|
||||
@ -130,6 +133,9 @@ const DeployNodeConfigFormAliyunCASDeployConfig = ({
|
||||
onChange={(e) => {
|
||||
formInst.setFieldValue("contactIds", e.target.value);
|
||||
}}
|
||||
onClear={() => {
|
||||
formInst.setFieldValue("contactIds", "");
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<ContactIdsModalInput
|
||||
|
@ -10,7 +10,7 @@ type DeployNodeConfigFormAliyunCLBConfigFieldValues = Nullish<{
|
||||
resourceType: string;
|
||||
region: string;
|
||||
loadbalancerId?: string;
|
||||
listenerPort?: string | number;
|
||||
listenerPort?: number;
|
||||
domain?: string;
|
||||
}>;
|
||||
|
||||
@ -53,10 +53,13 @@ const DeployNodeConfigFormAliyunCLBConfig = ({
|
||||
.min(1, t("workflow_node.deploy.form.aliyun_clb_loadbalancer_id.placeholder"))
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim(),
|
||||
listenerPort: z
|
||||
.union([z.number(), z.string()])
|
||||
.refine((v) => fieldResourceType === RESOURCE_TYPE_LISTENER && validPortNumber(v), t("workflow_node.deploy.form.aliyun_clb_listener_port.placeholder"))
|
||||
.nullish(),
|
||||
listenerPort: z.preprocess(
|
||||
(v) => (v == null || v === "" ? undefined : Number(v)),
|
||||
z
|
||||
.number()
|
||||
.nullish()
|
||||
.refine((v) => fieldResourceType === RESOURCE_TYPE_LISTENER && validPortNumber(v!), t("workflow_node.deploy.form.aliyun_clb_listener_port.placeholder"))
|
||||
),
|
||||
domain: z
|
||||
.string()
|
||||
.nullish()
|
||||
|
@ -10,7 +10,7 @@ type DeployNodeConfigFormBaiduCloudAppBLBConfigFieldValues = Nullish<{
|
||||
resourceType: string;
|
||||
region: string;
|
||||
loadbalancerId?: string;
|
||||
listenerPort?: string | number;
|
||||
listenerPort?: number;
|
||||
domain?: string;
|
||||
}>;
|
||||
|
||||
@ -53,13 +53,16 @@ const DeployNodeConfigFormBaiduCloudAppBLBConfig = ({
|
||||
.min(1, t("workflow_node.deploy.form.baiducloud_appblb_loadbalancer_id.placeholder"))
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim(),
|
||||
listenerPort: z
|
||||
.union([z.number(), z.string()])
|
||||
.refine(
|
||||
(v) => fieldResourceType === RESOURCE_TYPE_LISTENER && validPortNumber(v),
|
||||
t("workflow_node.deploy.form.baiducloud_appblb_listener_port.placeholder")
|
||||
)
|
||||
.nullish(),
|
||||
listenerPort: z.preprocess(
|
||||
(v) => (v == null || v === "" ? undefined : Number(v)),
|
||||
z
|
||||
.number()
|
||||
.refine(
|
||||
(v) => fieldResourceType === RESOURCE_TYPE_LISTENER && validPortNumber(v!),
|
||||
t("workflow_node.deploy.form.baiducloud_appblb_listener_port.placeholder")
|
||||
)
|
||||
.nullish()
|
||||
),
|
||||
domain: z
|
||||
.string()
|
||||
.nullish()
|
||||
|
@ -10,7 +10,7 @@ type DeployNodeConfigFormBaiduCloudBLBConfigFieldValues = Nullish<{
|
||||
resourceType: string;
|
||||
region: string;
|
||||
loadbalancerId?: string;
|
||||
listenerPort?: string | number;
|
||||
listenerPort?: number;
|
||||
domain?: string;
|
||||
}>;
|
||||
|
||||
@ -53,13 +53,16 @@ const DeployNodeConfigFormBaiduCloudBLBConfig = ({
|
||||
.min(1, t("workflow_node.deploy.form.baiducloud_blb_loadbalancer_id.placeholder"))
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim(),
|
||||
listenerPort: z
|
||||
.union([z.number(), z.string()])
|
||||
.refine(
|
||||
(v) => fieldResourceType === RESOURCE_TYPE_LISTENER && validPortNumber(v),
|
||||
t("workflow_node.deploy.form.baiducloud_blb_listener_port.placeholder")
|
||||
)
|
||||
.nullish(),
|
||||
listenerPort: z.preprocess(
|
||||
(v) => (v == null || v === "" ? undefined : Number(v)),
|
||||
z
|
||||
.number()
|
||||
.nullish()
|
||||
.refine(
|
||||
(v) => fieldResourceType === RESOURCE_TYPE_LISTENER && validPortNumber(v!),
|
||||
t("workflow_node.deploy.form.baiducloud_blb_listener_port.placeholder")
|
||||
)
|
||||
),
|
||||
domain: z
|
||||
.string()
|
||||
.nullish()
|
||||
|
@ -123,6 +123,9 @@ const DeployNodeConfigFormBaotaPanelSiteConfig = ({
|
||||
onChange={(e) => {
|
||||
formInst.setFieldValue("siteNames", e.target.value);
|
||||
}}
|
||||
onClear={() => {
|
||||
formInst.setFieldValue("siteNames", "");
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<SiteNamesModalInput
|
||||
|
@ -107,6 +107,9 @@ const DeployNodeConfigFormTencentCloudSSLDeployConfig = ({
|
||||
onChange={(e) => {
|
||||
formInst.setFieldValue("resourceIds", e.target.value);
|
||||
}}
|
||||
onClear={() => {
|
||||
formInst.setFieldValue("resourceIds", "");
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<ResourceIdsModalInput
|
||||
|
@ -283,7 +283,8 @@ const SharedNodeConfigDrawer = ({
|
||||
{ModelContextHolder}
|
||||
|
||||
<Drawer
|
||||
afterOpenChange={(open) => setOpen(open)}
|
||||
afterOpenChange={setOpen}
|
||||
closable={!pending}
|
||||
destroyOnClose
|
||||
extra={
|
||||
<SharedNodeMenu
|
||||
@ -306,6 +307,7 @@ const SharedNodeConfigDrawer = ({
|
||||
)
|
||||
}
|
||||
loading={loading}
|
||||
maskClosable={!pending}
|
||||
open={open}
|
||||
title={<div className="max-w-[480px] truncate">{node.name}</div>}
|
||||
width={720}
|
||||
|
@ -28,10 +28,10 @@ export interface AccessModel extends BaseModel {
|
||||
| AccessConfigForGcore
|
||||
| AccessConfigForGname
|
||||
| AccessConfigForGoDaddy
|
||||
| AccessConfigForGoogleTrustServices
|
||||
| AccessConfigForHuaweiCloud
|
||||
| AccessConfigForJDCloud
|
||||
| AccessConfigForKubernetes
|
||||
| AccessConfigForLocal
|
||||
| AccessConfigForNamecheap
|
||||
| AccessConfigForNameDotCom
|
||||
| AccessConfigForNameSilo
|
||||
@ -41,6 +41,7 @@ export interface AccessModel extends BaseModel {
|
||||
| AccessConfigForRainYun
|
||||
| AccessConfigForSafeLine
|
||||
| AccessConfigForSSH
|
||||
| AccessConfigForSSLCom
|
||||
| AccessConfigForTencentCloud
|
||||
| AccessConfigForUCloud
|
||||
| AccessConfigForUpyun
|
||||
@ -48,6 +49,7 @@ export interface AccessModel extends BaseModel {
|
||||
| AccessConfigForVolcEngine
|
||||
| AccessConfigForWebhook
|
||||
| AccessConfigForWestcn
|
||||
| AccessConfigForZeroSSL
|
||||
);
|
||||
}
|
||||
|
||||
@ -164,6 +166,11 @@ export type AccessConfigForGoDaddy = {
|
||||
apiSecret: string;
|
||||
};
|
||||
|
||||
export type AccessConfigForGoogleTrustServices = {
|
||||
eabKid: string;
|
||||
eabHmacKey: string;
|
||||
};
|
||||
|
||||
export type AccessConfigForHuaweiCloud = {
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
@ -178,8 +185,6 @@ export type AccessConfigForKubernetes = {
|
||||
kubeConfig?: string;
|
||||
};
|
||||
|
||||
export type AccessConfigForLocal = NonNullable<unknown>;
|
||||
|
||||
export type AccessConfigForNamecheap = {
|
||||
username: string;
|
||||
apiKey: string;
|
||||
@ -232,6 +237,11 @@ export type AccessConfigForSSH = {
|
||||
keyPassphrase?: string;
|
||||
};
|
||||
|
||||
export type AccessConfigForSSLCom = {
|
||||
eabKid: string;
|
||||
eabHmacKey: string;
|
||||
};
|
||||
|
||||
export type AccessConfigForTencentCloud = {
|
||||
secretId: string;
|
||||
secretKey: string;
|
||||
@ -267,4 +277,9 @@ export type AccessConfigForWestcn = {
|
||||
username: string;
|
||||
apiPassword: string;
|
||||
};
|
||||
|
||||
export type AccessConfigForZeroSSL = {
|
||||
eabKid: string;
|
||||
eabHmacKey: string;
|
||||
};
|
||||
// #endregion
|
||||
|
@ -13,6 +13,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
|
||||
BAISHAN: "baishan",
|
||||
BAOTAPANEL: "baotapanel",
|
||||
BYTEPLUS: "byteplus",
|
||||
BUYPASS: "buypass",
|
||||
CACHEFLY: "cachefly",
|
||||
CDNFLY: "cdnfly",
|
||||
CLOUDFLARE: "cloudflare",
|
||||
@ -22,13 +23,16 @@ export const ACCESS_PROVIDERS = Object.freeze({
|
||||
DNSLA: "dnsla",
|
||||
DOGECLOUD: "dogecloud",
|
||||
DYNV6: "dynv6",
|
||||
EDGIO: "edgio",
|
||||
GCORE: "gcore",
|
||||
GNAME: "gname",
|
||||
GODADDY: "godaddy",
|
||||
EDGIO: "edgio",
|
||||
GOOGLETRUSTSERVICES: "googletrustservices",
|
||||
HUAWEICLOUD: "huaweicloud",
|
||||
JDCLOUD: "jdcloud",
|
||||
KUBERNETES: "k8s",
|
||||
LETSENCRYPT: "letsencrypt",
|
||||
LETSENCRYPTSTAGING: "letsencryptstaging",
|
||||
LOCAL: "local",
|
||||
NAMECHEAP: "namecheap",
|
||||
NAMEDOTCOM: "namedotcom",
|
||||
@ -40,6 +44,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
|
||||
RAINYUN: "rainyun",
|
||||
SAFELINE: "safeline",
|
||||
SSH: "ssh",
|
||||
SSLCOM: "sslcom",
|
||||
TENCENTCLOUD: "tencentcloud",
|
||||
UCLOUD: "ucloud",
|
||||
UPYUN: "upyun",
|
||||
@ -47,13 +52,16 @@ export const ACCESS_PROVIDERS = Object.freeze({
|
||||
VOLCENGINE: "volcengine",
|
||||
WEBHOOK: "webhook",
|
||||
WESTCN: "westcn",
|
||||
ZEROSSL: "zerossl",
|
||||
} as const);
|
||||
|
||||
export type AccessProviderType = (typeof ACCESS_PROVIDERS)[keyof typeof ACCESS_PROVIDERS];
|
||||
|
||||
export const ACCESS_USAGES = Object.freeze({
|
||||
APPLY: "apply",
|
||||
DEPLOY: "deploy",
|
||||
DNS: "dns",
|
||||
HOSTING: "hosting",
|
||||
CA: "ca",
|
||||
NOTIFICATION: "notification",
|
||||
} as const);
|
||||
|
||||
export type AccessUsageType = (typeof ACCESS_USAGES)[keyof typeof ACCESS_USAGES];
|
||||
@ -63,59 +71,68 @@ export type AccessProvider = {
|
||||
name: string;
|
||||
icon: string;
|
||||
usages: AccessUsageType[];
|
||||
builtin: boolean;
|
||||
};
|
||||
|
||||
export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProvider> = new Map(
|
||||
/*
|
||||
注意:此处的顺序决定显示在前端的顺序。
|
||||
NOTICE: The following order determines the order displayed at the frontend.
|
||||
*/
|
||||
注意:此处的顺序决定显示在前端的顺序。
|
||||
NOTICE: The following order determines the order displayed at the frontend.
|
||||
*/
|
||||
[
|
||||
[ACCESS_PROVIDERS.LOCAL, "provider.local", "/imgs/providers/local.svg", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.SSH, "provider.ssh", "/imgs/providers/ssh.svg", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.WEBHOOK, "provider.webhook", "/imgs/providers/webhook.svg", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.KUBERNETES, "provider.kubernetes", "/imgs/providers/kubernetes.svg", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.ALIYUN, "provider.aliyun", "/imgs/providers/aliyun.svg", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.TENCENTCLOUD, "provider.tencentcloud", "/imgs/providers/tencentcloud.svg", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.BAIDUCLOUD, "provider.baiducloud", "/imgs/providers/baiducloud.svg", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.HUAWEICLOUD, "provider.huaweicloud", "/imgs/providers/huaweicloud.svg", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.VOLCENGINE, "provider.volcengine", "/imgs/providers/volcengine.svg", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.JDCLOUD, "provider.jdcloud", "/imgs/providers/jdcloud.svg", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.AWS, "provider.aws", "/imgs/providers/aws.svg", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.AZURE, "provider.azure", "/imgs/providers/azure.svg", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.GCORE, "provider.gcore", "/imgs/providers/gcore.png", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.LOCAL, "provider.local", "/imgs/providers/local.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.SSH, "provider.ssh", "/imgs/providers/ssh.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.WEBHOOK, "provider.webhook", "/imgs/providers/webhook.svg", [ACCESS_USAGES.HOSTING, ACCESS_USAGES.NOTIFICATION]],
|
||||
[ACCESS_PROVIDERS.KUBERNETES, "provider.kubernetes", "/imgs/providers/kubernetes.svg", [ACCESS_USAGES.HOSTING]],
|
||||
|
||||
[ACCESS_PROVIDERS.QINIU, "provider.qiniu", "/imgs/providers/qiniu.svg", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.UPYUN, "provider.upyun", "/imgs/providers/upyun.svg", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.BAISHAN, "provider.baishan", "/imgs/providers/baishan.png", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.DOGECLOUD, "provider.dogecloud", "/imgs/providers/dogecloud.png", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.BYTEPLUS, "provider.byteplus", "/imgs/providers/byteplus.svg", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.UCLOUD, "provider.ucloud", "/imgs/providers/ucloud.svg", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.SAFELINE, "provider.safeline", "/imgs/providers/safeline.svg", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS["1PANEL"], "provider.1panel", "/imgs/providers/1panel.svg", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.BAOTAPANEL, "provider.baotapanel", "/imgs/providers/baotapanel.svg", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.CACHEFLY, "provider.cachefly", "/imgs/providers/cachefly.png", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.CDNFLY, "provider.cdnfly", "/imgs/providers/cdnfly.png", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.EDGIO, "provider.edgio", "/imgs/providers/edgio.svg", [ACCESS_USAGES.DEPLOY]],
|
||||
[ACCESS_PROVIDERS.ALIYUN, "provider.aliyun", "/imgs/providers/aliyun.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.TENCENTCLOUD, "provider.tencentcloud", "/imgs/providers/tencentcloud.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.BAIDUCLOUD, "provider.baiducloud", "/imgs/providers/baiducloud.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.HUAWEICLOUD, "provider.huaweicloud", "/imgs/providers/huaweicloud.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.VOLCENGINE, "provider.volcengine", "/imgs/providers/volcengine.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.JDCLOUD, "provider.jdcloud", "/imgs/providers/jdcloud.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.AWS, "provider.aws", "/imgs/providers/aws.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.AZURE, "provider.azure", "/imgs/providers/azure.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.GCORE, "provider.gcore", "/imgs/providers/gcore.png", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||
|
||||
[ACCESS_PROVIDERS.CLOUDFLARE, "provider.cloudflare", "/imgs/providers/cloudflare.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.CLOUDNS, "provider.cloudns", "/imgs/providers/cloudns.png", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.DESEC, "provider.desec", "/imgs/providers/desec.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.DNSLA, "provider.dnsla", "/imgs/providers/dnsla.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.DYNV6, "provider.dynv6", "/imgs/providers/dynv6.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.GNAME, "provider.gname", "/imgs/providers/gname.png", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.GODADDY, "provider.godaddy", "/imgs/providers/godaddy.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.NAMECHEAP, "provider.namecheap", "/imgs/providers/namecheap.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.NAMEDOTCOM, "provider.namedotcom", "/imgs/providers/namedotcom.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.NAMESILO, "provider.namesilo", "/imgs/providers/namesilo.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.NS1, "provider.ns1", "/imgs/providers/ns1.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.PORKBUN, "provider.porkbun", "/imgs/providers/porkbun.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.VERCEL, "provider.vercel", "/imgs/providers/vercel.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.CMCCCLOUD, "provider.cmcccloud", "/imgs/providers/cmcccloud.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.RAINYUN, "provider.rainyun", "/imgs/providers/rainyun.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.WESTCN, "provider.westcn", "/imgs/providers/westcn.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.POWERDNS, "provider.powerdns", "/imgs/providers/powerdns.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.ACMEHTTPREQ, "provider.acmehttpreq", "/imgs/providers/acmehttpreq.svg", [ACCESS_USAGES.APPLY]],
|
||||
[ACCESS_PROVIDERS.QINIU, "provider.qiniu", "/imgs/providers/qiniu.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.UPYUN, "provider.upyun", "/imgs/providers/upyun.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.BAISHAN, "provider.baishan", "/imgs/providers/baishan.png", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.DOGECLOUD, "provider.dogecloud", "/imgs/providers/dogecloud.png", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.BYTEPLUS, "provider.byteplus", "/imgs/providers/byteplus.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.UCLOUD, "provider.ucloud", "/imgs/providers/ucloud.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.SAFELINE, "provider.safeline", "/imgs/providers/safeline.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS["1PANEL"], "provider.1panel", "/imgs/providers/1panel.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.BAOTAPANEL, "provider.baotapanel", "/imgs/providers/baotapanel.svg", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.CACHEFLY, "provider.cachefly", "/imgs/providers/cachefly.png", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.CDNFLY, "provider.cdnfly", "/imgs/providers/cdnfly.png", [ACCESS_USAGES.HOSTING]],
|
||||
[ACCESS_PROVIDERS.EDGIO, "provider.edgio", "/imgs/providers/edgio.svg", [ACCESS_USAGES.HOSTING]],
|
||||
|
||||
[ACCESS_PROVIDERS.CLOUDFLARE, "provider.cloudflare", "/imgs/providers/cloudflare.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.CLOUDNS, "provider.cloudns", "/imgs/providers/cloudns.png", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.DESEC, "provider.desec", "/imgs/providers/desec.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.DNSLA, "provider.dnsla", "/imgs/providers/dnsla.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.DYNV6, "provider.dynv6", "/imgs/providers/dynv6.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.GNAME, "provider.gname", "/imgs/providers/gname.png", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.GODADDY, "provider.godaddy", "/imgs/providers/godaddy.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.NAMECHEAP, "provider.namecheap", "/imgs/providers/namecheap.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.NAMEDOTCOM, "provider.namedotcom", "/imgs/providers/namedotcom.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.NAMESILO, "provider.namesilo", "/imgs/providers/namesilo.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.NS1, "provider.ns1", "/imgs/providers/ns1.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.PORKBUN, "provider.porkbun", "/imgs/providers/porkbun.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.VERCEL, "provider.vercel", "/imgs/providers/vercel.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.CMCCCLOUD, "provider.cmcccloud", "/imgs/providers/cmcccloud.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.RAINYUN, "provider.rainyun", "/imgs/providers/rainyun.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.WESTCN, "provider.westcn", "/imgs/providers/westcn.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.POWERDNS, "provider.powerdns", "/imgs/providers/powerdns.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.ACMEHTTPREQ, "provider.acmehttpreq", "/imgs/providers/acmehttpreq.svg", [ACCESS_USAGES.DNS]],
|
||||
|
||||
[ACCESS_PROVIDERS.LETSENCRYPT, "provider.letsencrypt", "/imgs/providers/letsencrypt.svg", [ACCESS_USAGES.CA]],
|
||||
[ACCESS_PROVIDERS.LETSENCRYPTSTAGING, "provider.letsencryptstaging", "/imgs/providers/letsencrypt.svg", [ACCESS_USAGES.CA]],
|
||||
[ACCESS_PROVIDERS.BUYPASS, "provider.buypass", "/imgs/providers/buypass.png", [ACCESS_USAGES.CA]],
|
||||
[ACCESS_PROVIDERS.GOOGLETRUSTSERVICES, "provider.googletrustservices", "/imgs/providers/google.svg", [ACCESS_USAGES.CA]],
|
||||
[ACCESS_PROVIDERS.SSLCOM, "provider.sslcom", "/imgs/providers/sslcom.svg", [ACCESS_USAGES.CA]],
|
||||
[ACCESS_PROVIDERS.ZEROSSL, "provider.zerossl", "/imgs/providers/zerossl.svg", [ACCESS_USAGES.CA]],
|
||||
].map((e) => [
|
||||
e[0] as string,
|
||||
{
|
||||
@ -123,12 +140,62 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
|
||||
name: e[1] as string,
|
||||
icon: e[2] as string,
|
||||
usages: e[3] as AccessUsageType[],
|
||||
builtin: ([ACCESS_PROVIDERS.LOCAL, ACCESS_PROVIDERS.LETSENCRYPT, ACCESS_PROVIDERS.LETSENCRYPTSTAGING] as string[]).includes(e[0] as string),
|
||||
},
|
||||
])
|
||||
);
|
||||
// #endregion
|
||||
|
||||
// #region ApplyProvider
|
||||
// #region ApplyCAProvider
|
||||
/*
|
||||
注意:如果追加新的常量值,请保持以 ASCII 排序。
|
||||
NOTICE: If you add new constant, please keep ASCII order.
|
||||
*/
|
||||
export const APPLY_CA_PROVIDERS = Object.freeze({
|
||||
BUYPASS: `${ACCESS_PROVIDERS.BUYPASS}`,
|
||||
GOOGLETRUSTSERVICES: `${ACCESS_PROVIDERS.GOOGLETRUSTSERVICES}`,
|
||||
LETSENCRYPT: `${ACCESS_PROVIDERS.LETSENCRYPT}`,
|
||||
LETSENCRYPTSTAGING: `${ACCESS_PROVIDERS.LETSENCRYPTSTAGING}`,
|
||||
SSLCOM: `${ACCESS_PROVIDERS.SSLCOM}`,
|
||||
ZEROSSL: `${ACCESS_PROVIDERS.ZEROSSL}`,
|
||||
} as const);
|
||||
|
||||
export type ApplyCAProviderType = (typeof APPLY_CA_PROVIDERS)[keyof typeof APPLY_CA_PROVIDERS];
|
||||
|
||||
export type ApplyCAProvider = {
|
||||
type: ApplyCAProviderType;
|
||||
name: string;
|
||||
icon: string;
|
||||
provider: AccessProviderType;
|
||||
builtin: boolean;
|
||||
};
|
||||
|
||||
export const applyCAProvidersMap: Map<ApplyCAProvider["type"] | string, ApplyCAProvider> = new Map(
|
||||
/*
|
||||
注意:此处的顺序决定显示在前端的顺序。
|
||||
NOTICE: The following order determines the order displayed at the frontend.
|
||||
*/
|
||||
[
|
||||
[APPLY_CA_PROVIDERS.LETSENCRYPT, "builtin"],
|
||||
[APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING, "builtin"],
|
||||
[APPLY_CA_PROVIDERS.BUYPASS],
|
||||
[APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES],
|
||||
[APPLY_CA_PROVIDERS.SSLCOM],
|
||||
[APPLY_CA_PROVIDERS.ZEROSSL],
|
||||
].map(([type, builtin]) => [
|
||||
type,
|
||||
{
|
||||
type: type as ApplyCAProviderType,
|
||||
name: accessProvidersMap.get(type.split("-")[0])!.name,
|
||||
icon: accessProvidersMap.get(type.split("-")[0])!.icon,
|
||||
provider: type.split("-")[0] as AccessProviderType,
|
||||
builtin: builtin === "builtin",
|
||||
},
|
||||
])
|
||||
);
|
||||
// #endregion
|
||||
|
||||
// #region ApplyDNSProvider
|
||||
/*
|
||||
注意:如果追加新的常量值,请保持以 ASCII 排序。
|
||||
NOTICE: If you add new constant, please keep ASCII order.
|
||||
@ -327,6 +394,7 @@ export type DeployProvider = {
|
||||
icon: string;
|
||||
provider: AccessProviderType;
|
||||
category: DeployCategoryType;
|
||||
builtin: boolean;
|
||||
};
|
||||
|
||||
export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProvider> = new Map(
|
||||
@ -335,7 +403,7 @@ export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProv
|
||||
NOTICE: The following order determines the order displayed at the frontend.
|
||||
*/
|
||||
[
|
||||
[DEPLOY_PROVIDERS.LOCAL, "provider.local", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.LOCAL, "provider.local", DEPLOY_CATEGORIES.OTHER, "builtin"],
|
||||
[DEPLOY_PROVIDERS.SSH, "provider.ssh", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.WEBHOOK, "provider.webhook", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.KUBERNETES_SECRET, "provider.kubernetes.secret", DEPLOY_CATEGORIES.OTHER],
|
||||
@ -405,7 +473,7 @@ export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProv
|
||||
[DEPLOY_PROVIDERS.BAOTAPANEL_SITE, "provider.baotapanel.site", DEPLOY_CATEGORIES.WEBSITE],
|
||||
[DEPLOY_PROVIDERS.BAOTAPANEL_CONSOLE, "provider.baotapanel.console", DEPLOY_CATEGORIES.OTHER],
|
||||
[DEPLOY_PROVIDERS.SAFELINE, "provider.safeline", DEPLOY_CATEGORIES.FIREWALL],
|
||||
].map(([type, name, category]) => [
|
||||
].map(([type, name, category, builtin]) => [
|
||||
type,
|
||||
{
|
||||
type: type as DeployProviderType,
|
||||
@ -413,6 +481,7 @@ export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProv
|
||||
icon: accessProvidersMap.get(type.split("-")[0])!.icon,
|
||||
provider: type.split("-")[0] as AccessProviderType,
|
||||
category: category as DeployCategoryType,
|
||||
builtin: builtin === "builtin",
|
||||
},
|
||||
])
|
||||
);
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { type ApplyCAProviderType } from "./provider";
|
||||
|
||||
export const SETTINGS_NAMES = Object.freeze({
|
||||
EMAILS: "emails",
|
||||
NOTIFY_TEMPLATES: "notifyTemplates",
|
||||
@ -152,37 +154,12 @@ export const notifyChannelsMap: Map<NotifyChannel["type"], NotifyChannel> = new
|
||||
// #endregion
|
||||
|
||||
// #region Settings: SSLProvider
|
||||
export const SSLPROVIDERS = Object.freeze({
|
||||
LETS_ENCRYPT: "letsencrypt",
|
||||
LETS_ENCRYPT_STAGING: "letsencrypt_staging",
|
||||
ZERO_SSL: "zerossl",
|
||||
GOOGLE_TRUST_SERVICES: "gts",
|
||||
} as const);
|
||||
|
||||
export type SSLProviders = (typeof SSLPROVIDERS)[keyof typeof SSLPROVIDERS];
|
||||
|
||||
export type SSLProviderSettingsContent = {
|
||||
provider: (typeof SSLPROVIDERS)[keyof typeof SSLPROVIDERS];
|
||||
provider: ApplyCAProviderType;
|
||||
config: {
|
||||
[key: string]: Record<string, unknown> | undefined;
|
||||
[SSLPROVIDERS.LETS_ENCRYPT]?: SSLProviderLetsEncryptConfig;
|
||||
[SSLPROVIDERS.LETS_ENCRYPT_STAGING]?: SSLProviderLetsEncryptConfig;
|
||||
[SSLPROVIDERS.ZERO_SSL]?: SSLProviderZeroSSLConfig;
|
||||
[SSLPROVIDERS.GOOGLE_TRUST_SERVICES]?: SSLProviderGoogleTrustServicesConfig;
|
||||
};
|
||||
};
|
||||
|
||||
export type SSLProviderLetsEncryptConfig = NonNullable<unknown>;
|
||||
|
||||
export type SSLProviderZeroSSLConfig = {
|
||||
eabKid: string;
|
||||
eabHmacKey: string;
|
||||
};
|
||||
|
||||
export type SSLProviderGoogleTrustServicesConfig = {
|
||||
eabKid: string;
|
||||
eabHmacKey: string;
|
||||
};
|
||||
// #endregion
|
||||
|
||||
// #region Settings: Persistence
|
||||
|
@ -126,6 +126,9 @@ export type WorkflowNodeConfigForApply = {
|
||||
provider: string;
|
||||
providerAccessId: string;
|
||||
providerConfig?: Record<string, unknown>;
|
||||
caProvider?: string;
|
||||
caProviderAccessId?: string;
|
||||
caProviderConfig?: Record<string, unknown>;
|
||||
keyAlgorithm: string;
|
||||
nameservers?: string;
|
||||
dnsPropagationTimeout?: number;
|
||||
@ -145,8 +148,8 @@ export type WorkflowNodeConfigForUpload = {
|
||||
export type WorkflowNodeConfigForDeploy = {
|
||||
certificate: string;
|
||||
provider: string;
|
||||
providerAccessId: string;
|
||||
providerConfig: Record<string, unknown>;
|
||||
providerAccessId?: string;
|
||||
providerConfig?: Record<string, unknown>;
|
||||
skipOnLastSucceeded: boolean;
|
||||
};
|
||||
|
||||
|
@ -14,7 +14,12 @@
|
||||
"access.props.name": "Name",
|
||||
"access.props.provider": "Provider",
|
||||
"access.props.provider.usage.dns": "DNS provider",
|
||||
"access.props.provider.usage.host": "Host provider",
|
||||
"access.props.provider.usage.hosting": "Hosting provider",
|
||||
"access.props.provider.usage.ca": "Certificate authority",
|
||||
"access.props.provider.usage.notification": "Notification channel",
|
||||
"access.props.range.both_dns_hosting": "Provider",
|
||||
"access.props.range.ca_only": "Certificate authority",
|
||||
"access.props.range.notify_only": "Notification channel",
|
||||
"access.props.created_at": "Created at",
|
||||
"access.props.updated_at": "Updated at",
|
||||
|
||||
@ -23,6 +28,10 @@
|
||||
"access.form.provider.label": "Provider",
|
||||
"access.form.provider.placeholder": "Please select a provider",
|
||||
"access.form.provider.tooltip": "DNS provider: The provider that hosts your domain names and manages your DNS records.<br>Host provider: The provider that hosts your servers or cloud services for deploying certificates.<br><br><i>Cannot be edited after saving.</i>",
|
||||
"access.form.certificate_authority.label": "Certificate authority",
|
||||
"access.form.certificate_authority.placeholder": "Please select a certificate authority",
|
||||
"access.form.notification_channel.label": "Notification channel",
|
||||
"access.form.notification_channel.placeholder": "Please select a notification channel",
|
||||
"access.form.1panel_api_url.label": "1Panel URL",
|
||||
"access.form.1panel_api_url.placeholder": "Please enter 1Panel URL",
|
||||
"access.form.1panel_api_url.tooltip": "For more information, see <a href=\"https://docs.1panel.pro/dev_manual/api_manual/\" target=\"_blank\">https://docs.1panel.pro/dev_manual/api_manual/</a>",
|
||||
@ -39,10 +48,10 @@
|
||||
"access.form.acmehttpreq_mode.label": "Mode",
|
||||
"access.form.acmehttpreq_mode.placeholder": "Please select mode",
|
||||
"access.form.acmehttpreq_mode.tooltip": "For more information, see <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
|
||||
"access.form.acmehttpreq_username.label": "HTTP Basic Auth username",
|
||||
"access.form.acmehttpreq_username.label": "HTTP Basic Auth username (Optional)",
|
||||
"access.form.acmehttpreq_username.placeholder": "Please enter HTTP Basic Auth username",
|
||||
"access.form.acmehttpreq_username.tooltip": "For more information, see <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
|
||||
"access.form.acmehttpreq_password.label": "HTTP Basic Auth password",
|
||||
"access.form.acmehttpreq_password.label": "HTTP Basic Auth password (Optional)",
|
||||
"access.form.acmehttpreq_password.placeholder": "Please enter HTTP Basic Auth password",
|
||||
"access.form.acmehttpreq_password.tooltip": "For more information, see <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
|
||||
"access.form.aliyun_access_key_id.label": "Aliyun AccessKeyId",
|
||||
@ -168,6 +177,12 @@
|
||||
"access.form.godaddy_api_secret.label": "GoDaddy API secret",
|
||||
"access.form.godaddy_api_secret.placeholder": "Please enter GoDaddy API secret",
|
||||
"access.form.godaddy_api_secret.tooltip": "For more information, see <a href=\"https://developer.godaddy.com/\" target=\"_blank\">https://developer.godaddy.com/</a>",
|
||||
"access.form.googletrustservices_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.googletrustservices_eab_kid.placeholder": "Please enter ACME EAB KID",
|
||||
"access.form.googletrustservices_eab_kid.tooltip": "For more information, see <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"access.form.googletrustservices_eab_hmac_key.label": "ACME EAB HMAC key",
|
||||
"access.form.googletrustservices_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key",
|
||||
"access.form.googletrustservices_eab_hmac_key.tooltip": "For more information, see <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"access.form.huaweicloud_access_key_id.label": "Huawei Cloud AccessKeyId",
|
||||
"access.form.huaweicloud_access_key_id.placeholder": "Please enter Huawei Cloud AccessKeyId",
|
||||
"access.form.huaweicloud_access_key_id.tooltip": "For more information, see <a href=\"https://support.huaweicloud.com/intl/en-us/usermanual-ca/ca_01_0003.html\" target=\"_blank\">https://support.huaweicloud.com/intl/en-us/usermanual-ca/ca_01_0003.html</a>",
|
||||
@ -239,16 +254,22 @@
|
||||
"access.form.ssh_port.placeholder": "Please enter server port",
|
||||
"access.form.ssh_username.label": "Username",
|
||||
"access.form.ssh_username.placeholder": "Please enter username",
|
||||
"access.form.ssh_password.label": "Password",
|
||||
"access.form.ssh_password.label": "Password (Optional)",
|
||||
"access.form.ssh_password.placeholder": "Please enter password",
|
||||
"access.form.ssh_password.tooltip": "Required when using password to connect to SSH.",
|
||||
"access.form.ssh_key.label": "SSH key",
|
||||
"access.form.ssh_key.label": "SSH key (Optional)",
|
||||
"access.form.ssh_key.placeholder": "Please enter SSH key",
|
||||
"access.form.ssh_key.upload": "Choose file ...",
|
||||
"access.form.ssh_key.tooltip": "Required when using key to connect to SSH.",
|
||||
"access.form.ssh_key_passphrase.label": "SSH key passphrase",
|
||||
"access.form.ssh_key_passphrase.label": "SSH key passphrase (Optional)",
|
||||
"access.form.ssh_key_passphrase.placeholder": "Please enter SSH key passphrase",
|
||||
"access.form.ssh_key_passphrase.tooltip": "Optional when using key to connect to SSH.",
|
||||
"access.form.sslcom_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.sslcom_eab_kid.placeholder": "Please enter ACME EAB KID",
|
||||
"access.form.sslcom_eab_kid.tooltip": "For more information, see <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||
"access.form.sslcom_eab_hmac_key.label": "ACME EAB HMAC key",
|
||||
"access.form.sslcom_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key",
|
||||
"access.form.sslcom_eab_hmac_key.tooltip": "For more information, see <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||
"access.form.tencentcloud_secret_id.label": "Tencent Cloud SecretId",
|
||||
"access.form.tencentcloud_secret_id.placeholder": "Please enter Tencent Cloud SecretId",
|
||||
"access.form.tencentcloud_secret_id.tooltip": "For more information, see <a href=\"https://cloud.tencent.com/document/product/598/40488?lang=en\" target=\"_blank\">https://cloud.tencent.com/document/product/598/40488?lang=en</a>",
|
||||
@ -287,5 +308,11 @@
|
||||
"access.form.westcn_username.tooltip": "For more information, see <a href=\"https://www.west.cn/CustomerCenter/doc/apiv2.html#12u3001u8eabu4efdu9a8cu8bc10a3ca20id3d12u3001u8eabu4efdu9a8cu8bc13e203ca3e\" target=\"_blank\">https://www.west.cn/CustomerCenter/doc/apiv2.html</a>",
|
||||
"access.form.westcn_api_password.label": "West.cn API password",
|
||||
"access.form.westcn_api_password.placeholder": "Please enter West.cn API password",
|
||||
"access.form.westcn_api_password.tooltip": "For more information, see <a href=\"https://www.west.cn/CustomerCenter/doc/apiv2.html#12u3001u8eabu4efdu9a8cu8bc10a3ca20id3d12u3001u8eabu4efdu9a8cu8bc13e203ca3e\" target=\"_blank\">https://www.west.cn/CustomerCenter/doc/apiv2.html</a>"
|
||||
"access.form.westcn_api_password.tooltip": "For more information, see <a href=\"https://www.west.cn/CustomerCenter/doc/apiv2.html#12u3001u8eabu4efdu9a8cu8bc10a3ca20id3d12u3001u8eabu4efdu9a8cu8bc13e203ca3e\" target=\"_blank\">https://www.west.cn/CustomerCenter/doc/apiv2.html</a>",
|
||||
"access.form.zerossl_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.zerossl_eab_kid.placeholder": "Please enter ACME EAB KID",
|
||||
"access.form.zerossl_eab_kid.tooltip": "For more information, see <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>",
|
||||
"access.form.zerossl_eab_hmac_key.label": "ACME EAB HMAC key",
|
||||
"access.form.zerossl_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key",
|
||||
"access.form.zerossl_eab_hmac_key.tooltip": "For more information, see <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>"
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
"common.text.operation_succeeded": "Operation succeeded",
|
||||
"common.text.operation_failed": "Operation failed",
|
||||
"common.text.request_error": "Request error",
|
||||
"common.text.search": "Search ...",
|
||||
|
||||
"common.menu.theme": "Change theme",
|
||||
"common.menu.locale": "Change language",
|
||||
|
@ -40,6 +40,7 @@
|
||||
"provider.baotapanel.site": "aaPanel (aka BaoTaPanel) - Website",
|
||||
"provider.byteplus": "BytePlus",
|
||||
"provider.byteplus.cdn": "BytePlus - CDN (Content Delivery Network)",
|
||||
"provider.buypass": "Buypass AS",
|
||||
"provider.cachefly": "CacheFly",
|
||||
"provider.cdnfly": "Cdnfly",
|
||||
"provider.cloudflare": "Cloudflare",
|
||||
@ -61,6 +62,7 @@
|
||||
"provider.godaddy": "GoDaddy",
|
||||
"provider.goedge": "GoEdge",
|
||||
"provider.goedge.cdn": "GoEdge - CDN (Content Delivery Network)",
|
||||
"provider.googletrustservices": "Google Trust Services",
|
||||
"provider.huaweicloud": "Huawei Cloud",
|
||||
"provider.huaweicloud.cdn": "Huawei Cloud - CDN (Content Delivery Network)",
|
||||
"provider.huaweicloud.dns": "Huawei Cloud - DNS (Domain Name Service)",
|
||||
@ -75,6 +77,8 @@
|
||||
"provider.jdcloud.vod": "JD Cloud - VOD (Video on Demand)",
|
||||
"provider.kubernetes": "Kubernetes",
|
||||
"provider.kubernetes.secret": "Kubernetes - Secret",
|
||||
"provider.letsencrypt": "Let's Encrypt",
|
||||
"provider.letsencryptstaging": "Let's Encrypt Staging Environment",
|
||||
"provider.local": "Local deployment",
|
||||
"provider.namecheap": "Namecheap",
|
||||
"provider.namedotcom": "Name.com",
|
||||
@ -89,6 +93,7 @@
|
||||
"provider.rainyun": "Rain Yun",
|
||||
"provider.safeline": "SafeLine",
|
||||
"provider.ssh": "SSH deployment",
|
||||
"provider.sslcom": "SSL.com",
|
||||
"provider.tencentcloud": "Tencent Cloud",
|
||||
"provider.tencentcloud.cdn": "Tencent Cloud - CDN (Content Delivery Network)",
|
||||
"provider.tencentcloud.clb": "Tencent Cloud - CLB (Cloud Load Balancer)",
|
||||
@ -121,6 +126,7 @@
|
||||
"provider.volcengine.tos": "Volcengine - TOS (Tinder Object Storage)",
|
||||
"provider.webhook": "Webhook",
|
||||
"provider.westcn": "West.cn",
|
||||
"provider.zerossl": "ZeroSSL",
|
||||
|
||||
"provider.category.all": "All",
|
||||
"provider.category.cdn": "CDN",
|
||||
@ -130,5 +136,7 @@
|
||||
"provider.category.av": "Audio/Video",
|
||||
"provider.category.serverless": "Serverless",
|
||||
"provider.category.website": "Website",
|
||||
"provider.category.other": "Other"
|
||||
"provider.category.other": "Other",
|
||||
|
||||
"provider.default_ca_provider.label": "(Default) Follow global settings"
|
||||
}
|
||||
|
@ -85,25 +85,27 @@
|
||||
"settings.notification.channel.form.wecom_webhook_url.tooltip": "For more information, see <a href=\"https://open.work.weixin.qq.com/help2/pc/18401#%E5%85%AD%E3%80%81%E7%BE%A4%E6%9C%BA%E5%99%A8%E4%BA%BAWebhook%E5%9C%B0%E5%9D%80\" target=\"_blank\">https://open.work.weixin.qq.com/help2/pc/18401</a>",
|
||||
|
||||
"settings.sslprovider.tab": "Certificate authority",
|
||||
"settings.sslprovider.form.provider.label": "ACME provider",
|
||||
"settings.sslprovider.form.provider.option.letsencrypt.label": "Let's Encrypt",
|
||||
"settings.sslprovider.form.provider.option.letsencrypt_staging.label": "Let's Encrypt Staging Environment",
|
||||
"settings.sslprovider.form.provider.option.zerossl.label": "ZeroSSL",
|
||||
"settings.sslprovider.form.provider.option.gts.label": "Google Trust Services",
|
||||
"settings.sslprovider.form.provider.label": "ACME provider (system-wide)",
|
||||
"settings.sslprovider.form.provider.alert": "Attention: The certificate validity lifetime, certificate algorithm, domain names count, and support for wildcard domain names are allowed may vary among different providers. After switching service providers, please check whether the configuration of the workflows needs to be adjusted.",
|
||||
"settings.sslprovider.form.letsencrypt_staging_alert": "The staging environment can reduce the chance of your running up against rate limits.<br><br>Learn more:<br><a href=\"https://letsencrypt.org/docs/staging-environment/\" target=\"_blank\">https://letsencrypt.org/docs/staging-environment/</a>",
|
||||
"settings.sslprovider.form.letsencryptstaging_alert": "The staging environment can reduce the chance of your running up against rate limits.<br><br>Learn more:<br><a href=\"https://letsencrypt.org/docs/staging-environment/\" target=\"_blank\">https://letsencrypt.org/docs/staging-environment/</a>",
|
||||
"settings.sslprovider.form.zerossl_eab_kid.label": "EAB KID",
|
||||
"settings.sslprovider.form.zerossl_eab_kid.placeholder": "Please enter EAB KID",
|
||||
"settings.sslprovider.form.zerossl_eab_kid.tooltip": "For more information, see <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>",
|
||||
"settings.sslprovider.form.zerossl_eab_hmac_key.label": "EAB HMAC Key",
|
||||
"settings.sslprovider.form.zerossl_eab_hmac_key.placeholder": "Please enter EAB HMAC Key",
|
||||
"settings.sslprovider.form.zerossl_eab_hmac_key.label": "EAB HMAC key",
|
||||
"settings.sslprovider.form.zerossl_eab_hmac_key.placeholder": "Please enter EAB HMAC key",
|
||||
"settings.sslprovider.form.zerossl_eab_hmac_key.tooltip": "For more information, see <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>",
|
||||
"settings.sslprovider.form.gts_eab_kid.label": "EAB KID",
|
||||
"settings.sslprovider.form.gts_eab_kid.placeholder": "Please enter EAB KID",
|
||||
"settings.sslprovider.form.gts_eab_kid.tooltip": "For more information, see <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"settings.sslprovider.form.gts_eab_hmac_key.label": "EAB HMAC Key",
|
||||
"settings.sslprovider.form.gts_eab_hmac_key.placeholder": "Please enter EAB HMAC Key",
|
||||
"settings.sslprovider.form.gts_eab_hmac_key.tooltip": "For more information, see <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"settings.sslprovider.form.googletrustservices_eab_kid.label": "EAB KID",
|
||||
"settings.sslprovider.form.googletrustservices_eab_kid.placeholder": "Please enter EAB KID",
|
||||
"settings.sslprovider.form.googletrustservices_eab_kid.tooltip": "For more information, see <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"settings.sslprovider.form.googletrustservices_eab_hmac_key.label": "EAB HMAC key",
|
||||
"settings.sslprovider.form.googletrustservices_eab_hmac_key.placeholder": "Please enter EAB HMAC key",
|
||||
"settings.sslprovider.form.googletrustservices_eab_hmac_key.tooltip": "For more information, see <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"settings.sslprovider.form.sslcom_eab_kid.label": "EAB KID",
|
||||
"settings.sslprovider.form.sslcom_eab_kid.placeholder": "Please enter EAB KID",
|
||||
"settings.sslprovider.form.sslcom_eab_kid.tooltip": "For more information, see <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||
"settings.sslprovider.form.sslcom_eab_hmac_key.label": "EAB HMAC key",
|
||||
"settings.sslprovider.form.sslcom_eab_hmac_key.placeholder": "Please enter EAB HMAC key",
|
||||
"settings.sslprovider.form.sslcom_eab_hmac_key.tooltip": "For more information, see <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||
|
||||
"settings.persistence.tab": "Persistence",
|
||||
"settings.persistence.form.workflow_runs_max_days.label": "Max days retention of workflow history runs",
|
||||
|
@ -55,6 +55,13 @@
|
||||
"workflow_node.apply.form.tencentcloud_eo_zone_id.placeholder": "Please enter Tencent Cloud EdgeOne zone ID",
|
||||
"workflow_node.apply.form.tencentcloud_eo_zone_id.tooltip": "For more information, see <a href=\"https://console.tencentcloud.com/edgeone\" target=\"_blank\">https://console.tencentcloud.com/edgeone</a>",
|
||||
"workflow_node.apply.form.advanced_config.label": "Advanced settings",
|
||||
"workflow_node.apply.form.ca_provider.label": "Certificate authority (Optional)",
|
||||
"workflow_node.apply.form.ca_provider.placeholder": "Please select a certificate authority",
|
||||
"workflow_node.apply.form.ca_provider.tooltip": "Used to issue SSL certificates.",
|
||||
"workflow_node.apply.form.ca_provider.button": "Configure",
|
||||
"workflow_node.apply.form.ca_provider_access.label": "Certificate authority authorization",
|
||||
"workflow_node.apply.form.ca_provider_access.placeholder": "Please select an authorization of the certificate authority",
|
||||
"workflow_node.apply.form.ca_provider_access.button": "Create",
|
||||
"workflow_node.apply.form.key_algorithm.label": "Certificate key algorithm",
|
||||
"workflow_node.apply.form.key_algorithm.placeholder": "Please select certificate key algorithm",
|
||||
"workflow_node.apply.form.nameservers.label": "DNS recursive nameservers (Optional)",
|
||||
@ -90,7 +97,7 @@
|
||||
"workflow_node.deploy.form.provider_access.placeholder": "Please select an authorization of host provider",
|
||||
"workflow_node.deploy.form.provider_access.tooltip": "Used to deploy certificates.",
|
||||
"workflow_node.deploy.form.provider_access.button": "Create",
|
||||
"workflow_node.deploy.form.provider_access.guide_for_local": "Tips: Due to the form validations, youe need to select an authorization for local deployment also, even if it means nothing.",
|
||||
"workflow_node.deploy.form.provider_access.guide_for_local": "Tips: If you are running Certimate in Docker, the \"Local\" refers to the container rather than the host.",
|
||||
"workflow_node.deploy.form.certificate.label": "Certificate",
|
||||
"workflow_node.deploy.form.certificate.placeholder": "Please select certificate",
|
||||
"workflow_node.deploy.form.certificate.tooltip": "The certificate to be deployed comes from the previous nodes of application or upload.",
|
||||
|
@ -21,6 +21,7 @@
|
||||
"workflow_run.logs": "Logs",
|
||||
"workflow_run.logs.menu.show_timestamps": "Show timestamps",
|
||||
"workflow_run.logs.menu.show_whitespaces": "Show whitespaces",
|
||||
"workflow_run.logs.menu.download_logs": "Download logs",
|
||||
|
||||
"workflow_run.artifacts": "Artifacts",
|
||||
"workflow_run_artifact.props.type": "Type",
|
||||
|
@ -14,7 +14,12 @@
|
||||
"access.props.name": "名称",
|
||||
"access.props.provider": "提供商",
|
||||
"access.props.provider.usage.dns": "DNS 提供商",
|
||||
"access.props.provider.usage.host": "主机提供商",
|
||||
"access.props.provider.usage.hosting": "主机提供商",
|
||||
"access.props.provider.usage.ca": "证书颁发机构",
|
||||
"access.props.provider.usage.notification": "通知渠道",
|
||||
"access.props.range.both_dns_hosting": "提供商",
|
||||
"access.props.range.ca_only": "证书颁发机构",
|
||||
"access.props.range.notify_only": "通知渠道",
|
||||
"access.props.created_at": "创建时间",
|
||||
"access.props.updated_at": "更新时间",
|
||||
|
||||
@ -23,6 +28,10 @@
|
||||
"access.form.provider.label": "提供商",
|
||||
"access.form.provider.placeholder": "请选择提供商",
|
||||
"access.form.provider.tooltip": "提供商分为两种类型:<br>【DNS 提供商】你的 DNS 托管方,通常等同于域名注册商,用于在申请证书时管理您的域名解析记录。<br>【主机提供商】你的服务器或云服务的托管方,用于部署签发的证书。<br><br>该字段保存后不可修改。",
|
||||
"access.form.certificate_authority.label": "证书颁发机构",
|
||||
"access.form.certificate_authority.placeholder": "请选择证书颁发机构",
|
||||
"access.form.notification_channel.label": "通知渠道",
|
||||
"access.form.notification_channel.placeholder": "请选择通知渠道",
|
||||
"access.form.1panel_api_url.label": "1Panel URL",
|
||||
"access.form.1panel_api_url.placeholder": "请输入 1Panel URL",
|
||||
"access.form.1panel_api_url.tooltip": "这是什么?请参阅 <a href=\"https://1panel.cn/docs/dev_manual/api_manual/\" target=\"_blank\">https://1panel.cn/docs/dev_manual/api_manual/</a>",
|
||||
@ -39,10 +48,10 @@
|
||||
"access.form.acmehttpreq_mode.label": "模式",
|
||||
"access.form.acmehttpreq_mode.placeholder": "请选择模式",
|
||||
"access.form.acmehttpreq_mode.tooltip": "这是什么?请参阅 <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
|
||||
"access.form.acmehttpreq_username.label": "HTTP 基本认证用户名",
|
||||
"access.form.acmehttpreq_username.label": "HTTP 基本认证用户名(可选)",
|
||||
"access.form.acmehttpreq_username.placeholder": "请输入 HTTP 基本认证用户名",
|
||||
"access.form.acmehttpreq_username.tooltip": "这是什么?请参阅 <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
|
||||
"access.form.acmehttpreq_password.label": "HTTP 基本认证密码",
|
||||
"access.form.acmehttpreq_password.label": "HTTP 基本认证密码(可选)",
|
||||
"access.form.acmehttpreq_password.placeholder": "请输入 HTTP 基本认证密码",
|
||||
"access.form.acmehttpreq_password.tooltip": "这是什么?请参阅 <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
|
||||
"access.form.aliyun_access_key_id.label": "阿里云 AccessKeyId",
|
||||
@ -162,6 +171,12 @@
|
||||
"access.form.godaddy_api_secret.label": "GoDaddy API Secret",
|
||||
"access.form.godaddy_api_secret.placeholder": "请输入 GoDaddy API Secret",
|
||||
"access.form.godaddy_api_secret.tooltip": "这是什么?请参阅 <a href=\"https://developer.godaddy.com/\" target=\"_blank\">https://developer.godaddy.com/</a>",
|
||||
"access.form.googletrustservices_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.googletrustservices_eab_kid.placeholder": "请输入 ACME EAB KID",
|
||||
"access.form.googletrustservices_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"access.form.googletrustservices_eab_hmac_key.label": "ACME EAB HMAC Key",
|
||||
"access.form.googletrustservices_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key",
|
||||
"access.form.googletrustservices_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"access.form.huaweicloud_access_key_id.label": "华为云 AccessKeyId",
|
||||
"access.form.huaweicloud_access_key_id.placeholder": "请输入华为云 AccessKeyId",
|
||||
"access.form.huaweicloud_access_key_id.tooltip": "这是什么?请参阅 <a href=\"https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html\" target=\"_blank\">https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html</a>",
|
||||
@ -233,16 +248,22 @@
|
||||
"access.form.ssh_port.placeholder": "请输入服务器端口",
|
||||
"access.form.ssh_username.label": "用户名",
|
||||
"access.form.ssh_username.placeholder": "请输入用户名",
|
||||
"access.form.ssh_password.label": "密码",
|
||||
"access.form.ssh_password.label": "密码(可选)",
|
||||
"access.form.ssh_password.placeholder": "请输入密码",
|
||||
"access.form.ssh_password.tooltip": "使用密码连接到 SSH 时必填。<br>该字段与密钥文件字段二选一,如果同时填写优先使用 SSH 密钥登录。",
|
||||
"access.form.ssh_key.label": "SSH 密钥",
|
||||
"access.form.ssh_key.label": "SSH 密钥(可选)",
|
||||
"access.form.ssh_key.placeholder": "请输入 SSH 密钥文件",
|
||||
"access.form.ssh_key.upload": "选择文件",
|
||||
"access.form.ssh_key.tooltip": "使用 SSH 密钥连接到 SSH 时必填。<br>该字段与密码字段二选一,如果同时填写优先使用 SSH 密钥登录。",
|
||||
"access.form.ssh_key_passphrase.label": "SSH 密钥口令",
|
||||
"access.form.ssh_key_passphrase.label": "SSH 密钥口令(可选)",
|
||||
"access.form.ssh_key_passphrase.placeholder": "请输入 SSH 密钥口令",
|
||||
"access.form.ssh_key_passphrase.tooltip": "使用 SSH 密钥连接到 SSH 时选填。",
|
||||
"access.form.sslcom_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.sslcom_eab_kid.placeholder": "请输入 ACME EAB KID",
|
||||
"access.form.sslcom_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||
"access.form.sslcom_eab_hmac_key.label": "ACME EAB HMAC key",
|
||||
"access.form.sslcom_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC key",
|
||||
"access.form.sslcom_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||
"access.form.tencentcloud_secret_id.label": "腾讯云 SecretId",
|
||||
"access.form.tencentcloud_secret_id.placeholder": "请输入腾讯云 SecretId",
|
||||
"access.form.tencentcloud_secret_id.tooltip": "这是什么?请参阅 <a href=\"https://cloud.tencent.com/document/product/598/40488\" target=\"_blank\">https://cloud.tencent.com/document/product/598/40488</a>",
|
||||
@ -287,5 +308,11 @@
|
||||
"access.form.westcn_username.tooltip": "这是什么?请参阅 <a href=\"https://www.west.cn/CustomerCenter/doc/apiv2.html#12u3001u8eabu4efdu9a8cu8bc10a3ca20id3d12u3001u8eabu4efdu9a8cu8bc13e203ca3e\" target=\"_blank\">https://www.west.cn/CustomerCenter/doc/apiv2.html</a>",
|
||||
"access.form.westcn_api_password.label": "西部数码 API 密码",
|
||||
"access.form.westcn_api_password.placeholder": "请输入西部数码 API 密码",
|
||||
"access.form.westcn_api_password.tooltip": "这是什么?请参阅 <a href=\"https://www.west.cn/CustomerCenter/doc/apiv2.html#12u3001u8eabu4efdu9a8cu8bc10a3ca20id3d12u3001u8eabu4efdu9a8cu8bc13e203ca3e\" target=\"_blank\">https://www.west.cn/CustomerCenter/doc/apiv2.html</a>"
|
||||
"access.form.westcn_api_password.tooltip": "这是什么?请参阅 <a href=\"https://www.west.cn/CustomerCenter/doc/apiv2.html#12u3001u8eabu4efdu9a8cu8bc10a3ca20id3d12u3001u8eabu4efdu9a8cu8bc13e203ca3e\" target=\"_blank\">https://www.west.cn/CustomerCenter/doc/apiv2.html</a>",
|
||||
"access.form.zerossl_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.zerossl_eab_kid.placeholder": "请输入 ACME EAB KID",
|
||||
"access.form.zerossl_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>",
|
||||
"access.form.zerossl_eab_hmac_key.label": "ACME EAB HMAC Key",
|
||||
"access.form.zerossl_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key",
|
||||
"access.form.zerossl_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>"
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
"common.text.operation_succeeded": "操作成功",
|
||||
"common.text.operation_failed": "操作失败",
|
||||
"common.text.request_error": "请求错误",
|
||||
"common.text.search": "搜索 ……",
|
||||
|
||||
"common.menu.theme": "切换主题",
|
||||
"common.menu.locale": "切换语言",
|
||||
|
@ -40,6 +40,7 @@
|
||||
"provider.baotapanel.site": "宝塔面板 - 网站",
|
||||
"provider.byteplus": "BytePlus",
|
||||
"provider.byteplus.cdn": "BytePlus - 内容分发网络 CDN",
|
||||
"provider.buypass": "Buypass AS",
|
||||
"provider.cachefly": "CacheFly",
|
||||
"provider.cdnfly": "Cdnfly",
|
||||
"provider.cloudflare": "Cloudflare",
|
||||
@ -61,6 +62,7 @@
|
||||
"provider.godaddy": "GoDaddy",
|
||||
"provider.goedge": "GoEdge",
|
||||
"provider.goedge.cdn": "GoEdge - 内容分发网络 CDN",
|
||||
"provider.googletrustservices": "Google Trust Services",
|
||||
"provider.huaweicloud": "华为云",
|
||||
"provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN",
|
||||
"provider.huaweicloud.dns": "华为云 - 云解析 DNS",
|
||||
@ -75,11 +77,13 @@
|
||||
"provider.jdcloud.vod": "京东云 - 视频点播",
|
||||
"provider.kubernetes": "Kubernetes",
|
||||
"provider.kubernetes.secret": "Kubernetes - Secret",
|
||||
"provider.letsencrypt": "Let's Encrypt",
|
||||
"provider.letsencryptstaging": "Let's Encrypt 测试环境",
|
||||
"provider.local": "本地部署",
|
||||
"provider.namecheap": "Namecheap",
|
||||
"provider.namedotcom": "Name.com",
|
||||
"provider.namesilo": "NameSilo",
|
||||
"provider.ns1": "NS1(IBM NS1 Connect)",
|
||||
"provider.ns1": "NS1 (IBM NS1 Connect)",
|
||||
"provider.porkbun": "Porkbun",
|
||||
"provider.powerdns": "PowerDNS",
|
||||
"provider.qiniu": "七牛云",
|
||||
@ -89,6 +93,7 @@
|
||||
"provider.rainyun": "雨云",
|
||||
"provider.safeline": "雷池",
|
||||
"provider.ssh": "SSH 部署",
|
||||
"provider.sslcom": "SSL.com",
|
||||
"provider.tencentcloud": "腾讯云",
|
||||
"provider.tencentcloud.cdn": "腾讯云 - 内容分发网络 CDN",
|
||||
"provider.tencentcloud.clb": "腾讯云 - 负载均衡 CLB",
|
||||
@ -121,6 +126,7 @@
|
||||
"provider.volcengine.tos": "火山引擎 - 对象存储 TOS",
|
||||
"provider.webhook": "Webhook",
|
||||
"provider.westcn": "西部数码",
|
||||
"provider.zerossl": "ZeroSSL",
|
||||
|
||||
"provider.category.all": "全部",
|
||||
"provider.category.cdn": "CDN",
|
||||
@ -130,5 +136,7 @@
|
||||
"provider.category.av": "音视频",
|
||||
"provider.category.serverless": "Serverless",
|
||||
"provider.category.website": "网站托管",
|
||||
"provider.category.other": "其他"
|
||||
"provider.category.other": "其他",
|
||||
|
||||
"provider.default_ca_provider.label": "(默认)不指定,跟随全局设置"
|
||||
}
|
||||
|
@ -85,25 +85,27 @@
|
||||
"settings.notification.channel.form.wecom_webhook_url.tooltip": "这是什么?请参阅 <a href=\"https://open.work.weixin.qq.com/help2/pc/18401#%E5%85%AD%E3%80%81%E7%BE%A4%E6%9C%BA%E5%99%A8%E4%BA%BAWebhook%E5%9C%B0%E5%9D%80\" target=\"_blank\">https://open.work.weixin.qq.com/help2/pc/18401</a>",
|
||||
|
||||
"settings.sslprovider.tab": "证书颁发机构",
|
||||
"settings.sslprovider.form.provider.label": "ACME 服务商",
|
||||
"settings.sslprovider.form.provider.option.letsencrypt.label": "Let's Encrypt",
|
||||
"settings.sslprovider.form.provider.option.letsencrypt_staging.label": "Let's Encrypt 测试环境",
|
||||
"settings.sslprovider.form.provider.option.zerossl.label": "ZeroSSL",
|
||||
"settings.sslprovider.form.provider.option.gts.label": "Google Trust Services",
|
||||
"settings.sslprovider.form.provider.label": "ACME 服务商(全局)",
|
||||
"settings.sslprovider.form.provider.alert": "注意:不同服务商所支持的证书有效期、证书算法、多域名数量上限、是否允许泛域名等可能不同,切换服务商后请注意检查已有工作流的配置是否需要调整。",
|
||||
"settings.sslprovider.form.letsencrypt_staging_alert": "测试环境比生产环境有更宽松的速率限制,可进行测试性部署。<br><br>点击下方链接了解更多:<br><a href=\"https://letsencrypt.org/zh-cn/docs/staging-environment/\" target=\"_blank\">https://letsencrypt.org/zh-cn/docs/staging-environment/</a>",
|
||||
"settings.sslprovider.form.letsencryptstaging_alert": "测试环境比生产环境有更宽松的速率限制,可进行测试性部署。<br><br>点击下方链接了解更多:<br><a href=\"https://letsencrypt.org/zh-cn/docs/staging-environment/\" target=\"_blank\">https://letsencrypt.org/zh-cn/docs/staging-environment/</a>",
|
||||
"settings.sslprovider.form.zerossl_eab_kid.label": "EAB KID",
|
||||
"settings.sslprovider.form.zerossl_eab_kid.placeholder": "请输入 EAB KID",
|
||||
"settings.sslprovider.form.zerossl_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>",
|
||||
"settings.sslprovider.form.zerossl_eab_hmac_key.label": "EAB HMAC Key",
|
||||
"settings.sslprovider.form.zerossl_eab_hmac_key.placeholder": "请输入 EAB HMAC Key",
|
||||
"settings.sslprovider.form.zerossl_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>",
|
||||
"settings.sslprovider.form.gts_eab_kid.label": "EAB KID",
|
||||
"settings.sslprovider.form.gts_eab_kid.placeholder": "请输入 EAB KID",
|
||||
"settings.sslprovider.form.gts_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"settings.sslprovider.form.gts_eab_hmac_key.label": "EAB HMAC Key",
|
||||
"settings.sslprovider.form.gts_eab_hmac_key.placeholder": "请输入 EAB HMAC Key",
|
||||
"settings.sslprovider.form.gts_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"settings.sslprovider.form.googletrustservices_eab_kid.label": "EAB KID",
|
||||
"settings.sslprovider.form.googletrustservices_eab_kid.placeholder": "请输入 EAB KID",
|
||||
"settings.sslprovider.form.googletrustservices_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"settings.sslprovider.form.googletrustservices_eab_hmac_key.label": "EAB HMAC Key",
|
||||
"settings.sslprovider.form.googletrustservices_eab_hmac_key.placeholder": "请输入 EAB HMAC Key",
|
||||
"settings.sslprovider.form.googletrustservices_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"settings.sslprovider.form.sslcom_eab_kid.label": "EAB KID",
|
||||
"settings.sslprovider.form.sslcom_eab_kid.placeholder": "请输入 EAB KID",
|
||||
"settings.sslprovider.form.sslcom_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||
"settings.sslprovider.form.sslcom_eab_hmac_key.label": "EAB HMAC Key",
|
||||
"settings.sslprovider.form.sslcom_eab_hmac_key.placeholder": "请输入 EAB HMAC Key",
|
||||
"settings.sslprovider.form.sslcom_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||
|
||||
"settings.persistence.tab": "数据持久化",
|
||||
"settings.persistence.form.workflow_runs_max_days.label": "工作流执行历史保留天数",
|
||||
|
@ -55,6 +55,12 @@
|
||||
"workflow_node.apply.form.tencentcloud_eo_zone_id.placeholder": "请输入腾讯云 EdgeOne 站点 ID",
|
||||
"workflow_node.apply.form.tencentcloud_eo_zone_id.tooltip": "这是什么?请参阅 <a href=\"https://console.cloud.tencent.com/edgeone\" target=\"_blank\">https://console.cloud.tencent.com/edgeone</a>",
|
||||
"workflow_node.apply.form.advanced_config.label": "高级设置",
|
||||
"workflow_node.apply.form.ca_provider.label": "证书颁发机构(可选)",
|
||||
"workflow_node.apply.form.ca_provider.placeholder": "请选择证书颁发机构",
|
||||
"workflow_node.apply.form.ca_provider.button": "去配置",
|
||||
"workflow_node.apply.form.ca_provider_access.label": "证书颁发机构授权",
|
||||
"workflow_node.apply.form.ca_provider_access.placeholder": "请选择证书颁发机构授权",
|
||||
"workflow_node.apply.form.ca_provider_access.button": "新建",
|
||||
"workflow_node.apply.form.key_algorithm.label": "数字证书算法",
|
||||
"workflow_node.apply.form.key_algorithm.placeholder": "请选择数字证书算法",
|
||||
"workflow_node.apply.form.nameservers.label": "DNS 递归服务器(可选)",
|
||||
@ -70,15 +76,15 @@
|
||||
"workflow_node.apply.form.dns_ttl.placeholder": "请输入 DNS 解析 TTL",
|
||||
"workflow_node.apply.form.dns_ttl.unit": "秒",
|
||||
"workflow_node.apply.form.dns_ttl.tooltip": "在 ACME DNS-01 质询时 DNS 解析记录的 TTL。如果你不了解此选项的用途,保持默认即可。<br><br>不填写时,将使用提供商提供的默认值。",
|
||||
"workflow_node.apply.form.disable_follow_cname.label": "关闭 CNAME 跟随",
|
||||
"workflow_node.apply.form.disable_follow_cname.tooltip": "在 ACME DNS-01 质询时是否关闭 CNAME 跟随。如果你不了解该选项的用途,保持默认即可。<a href=\"https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme/#the-advantages-of-a-cname\" target=\"_blank\">点此了解更多</a>。",
|
||||
"workflow_node.apply.form.disable_ari.label": "关闭 ARI 续期",
|
||||
"workflow_node.apply.form.disable_ari.tooltip": "在 ACME 证书续期时是否关闭 ARI(ACME Renewal Information)。如果你不了解该选项的用途,保持默认即可。<a href=\"https://letsencrypt.org/2023/03/23/improving-resliiency-and-reliability-with-ari/\" target=\"_blank\">点此了解更多</a>。",
|
||||
"workflow_node.apply.form.disable_follow_cname.label": "阻止 CNAME 跟随",
|
||||
"workflow_node.apply.form.disable_follow_cname.tooltip": "在 ACME DNS-01 质询时是否阻止 CNAME 跟随。如果你不了解该选项的用途,保持默认即可。<a href=\"https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme/#the-advantages-of-a-cname\" target=\"_blank\">点此了解更多</a>。",
|
||||
"workflow_node.apply.form.disable_ari.label": "阻止 ARI 续期",
|
||||
"workflow_node.apply.form.disable_ari.tooltip": "在 ACME 证书续期时是否阻止 ARI(ACME Renewal Information)。如果你不了解该选项的用途,保持默认即可。<a href=\"https://letsencrypt.org/2023/03/23/improving-resliiency-and-reliability-with-ari/\" target=\"_blank\">点此了解更多</a>。",
|
||||
"workflow_node.apply.form.strategy_config.label": "执行策略",
|
||||
"workflow_node.apply.form.skip_before_expiry_days.label": "续期间隔",
|
||||
"workflow_node.apply.form.skip_before_expiry_days.placeholder": "请输入续期间隔",
|
||||
"workflow_node.apply.form.skip_before_expiry_days.prefix": "当上次签发的证书距到期时间超过",
|
||||
"workflow_node.apply.form.skip_before_expiry_days.suffix": "时,跳过重新申请。",
|
||||
"workflow_node.apply.form.skip_before_expiry_days.prefix": "当上次签发的证书有效期不足",
|
||||
"workflow_node.apply.form.skip_before_expiry_days.suffix": "时,重新申请证书。",
|
||||
"workflow_node.apply.form.skip_before_expiry_days.unit": "天",
|
||||
"workflow_node.apply.form.skip_before_expiry_days.tooltip": "注意不要超过颁发的证书最大有效期,否则证书可能永远不会续期。",
|
||||
|
||||
@ -90,7 +96,7 @@
|
||||
"workflow_node.deploy.form.provider_access.placeholder": "请选择主机提供商授权",
|
||||
"workflow_node.deploy.form.provider_access.tooltip": "用于部署证书,注意与申请阶段所需的 DNS 提供商相区分。",
|
||||
"workflow_node.deploy.form.provider_access.button": "新建",
|
||||
"workflow_node.deploy.form.provider_access.guide_for_local": "小贴士:由于表单限制,你同样需要为本地部署选择一个授权 —— 即使它是空白的。<br>请注意,如果你使用 Docker 安装 Certimate,“本地部署”将会部署到容器内而非宿主机上。",
|
||||
"workflow_node.deploy.form.provider_access.guide_for_local": "小贴士:如果你正在使用 Docker 运行 Certimate,“本地”指的是容器内而非宿主机。",
|
||||
"workflow_node.deploy.form.certificate.label": "待部署证书",
|
||||
"workflow_node.deploy.form.certificate.placeholder": "请选择待部署证书",
|
||||
"workflow_node.deploy.form.certificate.tooltip": "待部署证书来自之前的申请或上传节点。如果选项为空请先确保前序节点配置正确。",
|
||||
@ -633,7 +639,7 @@
|
||||
"workflow_node.deploy.form.webhook_data_preset.button": "使用预设模板",
|
||||
"workflow_node.deploy.form.strategy_config.label": "执行策略",
|
||||
"workflow_node.deploy.form.skip_on_last_succeeded.label": "重复部署",
|
||||
"workflow_node.deploy.form.skip_on_last_succeeded.prefix": "当上次部署已成功时",
|
||||
"workflow_node.deploy.form.skip_on_last_succeeded.prefix": "当上次部署相同证书已成功时",
|
||||
"workflow_node.deploy.form.skip_on_last_succeeded.suffix": "重新部署。",
|
||||
"workflow_node.deploy.form.skip_on_last_succeeded.switch.on": "跳过",
|
||||
"workflow_node.deploy.form.skip_on_last_succeeded.switch.off": "不跳过",
|
||||
|
@ -21,6 +21,7 @@
|
||||
"workflow_run.logs": "日志",
|
||||
"workflow_run.logs.menu.show_timestamps": "显示日期时间",
|
||||
"workflow_run.logs.menu.show_whitespaces": "显示转义换行符",
|
||||
"workflow_run.logs.menu.download_logs": "下载日志",
|
||||
|
||||
"workflow_run.artifacts": "输出产物",
|
||||
"workflow_run_artifact.props.type": "类型",
|
||||
|
@ -14,13 +14,15 @@ import { Avatar, Button, Card, Empty, Flex, Input, Modal, Space, Table, type Tab
|
||||
import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import AccessEditModal from "@/components/access/AccessEditModal";
|
||||
import AccessEditDrawer, { type AccessEditDrawerProps } from "@/components/access/AccessEditDrawer";
|
||||
import { type AccessModel } from "@/domain/access";
|
||||
import { accessProvidersMap } from "@/domain/provider";
|
||||
import { ACCESS_USAGES, accessProvidersMap } from "@/domain/provider";
|
||||
import { useZustandShallowSelector } from "@/hooks";
|
||||
import { useAccessesStore } from "@/stores/access";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
|
||||
type AccessRanges = AccessEditDrawerProps["range"];
|
||||
|
||||
const AccessList = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
@ -83,9 +85,10 @@ const AccessList = () => {
|
||||
width: 120,
|
||||
render: (_, record) => (
|
||||
<Space.Compact>
|
||||
<AccessEditModal
|
||||
<AccessEditDrawer
|
||||
data={record}
|
||||
preset="edit"
|
||||
range={filters["range"] as AccessRanges}
|
||||
scene="edit"
|
||||
trigger={
|
||||
<Tooltip title={t("access.action.edit")}>
|
||||
<Button color="primary" icon={<EditOutlinedIcon />} variant="text" />
|
||||
@ -93,9 +96,10 @@ const AccessList = () => {
|
||||
}
|
||||
/>
|
||||
|
||||
<AccessEditModal
|
||||
<AccessEditDrawer
|
||||
data={{ ...record, id: undefined, name: `${record.name}-copy` }}
|
||||
preset="add"
|
||||
range={filters["range"] as AccessRanges}
|
||||
scene="add"
|
||||
trigger={
|
||||
<Tooltip title={t("access.action.duplicate")}>
|
||||
<Button color="primary" icon={<SnippetsOutlinedIcon />} variant="text" />
|
||||
@ -122,6 +126,7 @@ const AccessList = () => {
|
||||
|
||||
const [filters, setFilters] = useState<Record<string, unknown>>(() => {
|
||||
return {
|
||||
range: "both-dns-hosting" satisfies AccessRanges,
|
||||
keyword: searchParams.get("keyword"),
|
||||
};
|
||||
});
|
||||
@ -144,14 +149,26 @@ const AccessList = () => {
|
||||
() => {
|
||||
const startIndex = (page - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
const list = accesses.filter((e) => {
|
||||
const keyword = (filters["keyword"] as string | undefined)?.trim();
|
||||
if (keyword) {
|
||||
return e.name.includes(keyword);
|
||||
}
|
||||
const list = accesses
|
||||
.filter((e) => {
|
||||
const keyword = (filters["keyword"] as string | undefined)?.trim();
|
||||
if (keyword) {
|
||||
return e.name.includes(keyword);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
})
|
||||
.filter((e) => {
|
||||
const provider = accessProvidersMap.get(e.provider);
|
||||
switch (filters["range"] as AccessRanges) {
|
||||
case "both-dns-hosting":
|
||||
return provider?.usages?.includes(ACCESS_USAGES.DNS) || provider?.usages?.includes(ACCESS_USAGES.HOSTING);
|
||||
case "ca-only":
|
||||
return provider?.usages?.includes(ACCESS_USAGES.CA);
|
||||
case "notify-only":
|
||||
return provider?.usages?.includes(ACCESS_USAGES.NOTIFICATION);
|
||||
}
|
||||
});
|
||||
return Promise.resolve({
|
||||
items: list.slice(startIndex, endIndex),
|
||||
totalItems: list.length,
|
||||
@ -166,6 +183,10 @@ const AccessList = () => {
|
||||
}
|
||||
);
|
||||
|
||||
const handleTabChange = (key: string) => {
|
||||
setFilters((prev) => ({ ...prev, range: key }));
|
||||
};
|
||||
|
||||
const handleSearch = (value: string) => {
|
||||
setFilters((prev) => ({ ...prev, keyword: value }));
|
||||
};
|
||||
@ -201,9 +222,10 @@ const AccessList = () => {
|
||||
<PageHeader
|
||||
title={t("access.page.title")}
|
||||
extra={[
|
||||
<AccessEditModal
|
||||
<AccessEditDrawer
|
||||
key="create"
|
||||
preset="add"
|
||||
range={filters["range"] as AccessRanges}
|
||||
scene="add"
|
||||
trigger={
|
||||
<Button type="primary" icon={<PlusOutlinedIcon />}>
|
||||
{t("access.action.add")}
|
||||
@ -213,7 +235,32 @@ const AccessList = () => {
|
||||
]}
|
||||
/>
|
||||
|
||||
<Card size="small">
|
||||
<Card
|
||||
className="rounded-b-none"
|
||||
styles={{
|
||||
body: {
|
||||
padding: 0,
|
||||
},
|
||||
}}
|
||||
tabList={[
|
||||
{
|
||||
key: "both-dns-hosting",
|
||||
label: t("access.props.range.both_dns_hosting"),
|
||||
},
|
||||
{
|
||||
key: "ca-only",
|
||||
label: t("access.props.range.ca_only"),
|
||||
},
|
||||
// {
|
||||
// key: "notify-only",
|
||||
// label: t("access.props.range.notify_only"),
|
||||
// },
|
||||
]}
|
||||
activeTabKey={filters["range"] as string}
|
||||
onTabChange={(key) => handleTabChange(key)}
|
||||
/>
|
||||
|
||||
<Card className="rounded-t-none " size="small">
|
||||
<div className="mb-4">
|
||||
<Flex gap="small">
|
||||
<div className="flex-1">
|
||||
|
@ -7,7 +7,8 @@ import { produce } from "immer";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { SETTINGS_NAMES, SSLPROVIDERS, type SSLProviderSettingsContent, type SSLProviders, type SettingsModel } from "@/domain/settings";
|
||||
import { APPLY_CA_PROVIDERS, type ApplyCAProviderType } from "@/domain/provider";
|
||||
import { SETTINGS_NAMES, type SSLProviderSettingsContent, type SettingsModel } from "@/domain/settings";
|
||||
import { useAntdForm } from "@/hooks";
|
||||
import { get as getSettings, save as saveSettings } from "@/repository/settings";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
@ -26,14 +27,14 @@ const SSLProviderEditFormLetsEncryptConfig = () => {
|
||||
const { pending, settings, updateSettings } = useContext(SSLProviderContext);
|
||||
|
||||
const { form: formInst, formProps } = useAntdForm<NonNullable<unknown>>({
|
||||
initialValues: settings?.content?.config?.[SSLPROVIDERS.LETS_ENCRYPT],
|
||||
initialValues: settings?.content?.config?.[APPLY_CA_PROVIDERS.LETSENCRYPT],
|
||||
onSubmit: async (values) => {
|
||||
const newSettings = produce(settings, (draft) => {
|
||||
draft.content ??= {} as SSLProviderSettingsContent;
|
||||
draft.content.provider = SSLPROVIDERS.LETS_ENCRYPT;
|
||||
draft.content.provider = APPLY_CA_PROVIDERS.LETSENCRYPT;
|
||||
|
||||
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
|
||||
draft.content.config[SSLPROVIDERS.LETS_ENCRYPT] = values;
|
||||
draft.content.config[APPLY_CA_PROVIDERS.LETSENCRYPT] = values;
|
||||
});
|
||||
await updateSettings(newSettings);
|
||||
|
||||
@ -43,7 +44,7 @@ const SSLProviderEditFormLetsEncryptConfig = () => {
|
||||
|
||||
const [formChanged, setFormChanged] = useState(false);
|
||||
useEffect(() => {
|
||||
setFormChanged(settings?.content?.provider !== SSLPROVIDERS.LETS_ENCRYPT);
|
||||
setFormChanged(settings?.content?.provider !== APPLY_CA_PROVIDERS.LETSENCRYPT);
|
||||
}, [settings?.content?.provider]);
|
||||
|
||||
const handleFormChange = () => {
|
||||
@ -67,14 +68,14 @@ const SSLProviderEditFormLetsEncryptStagingConfig = () => {
|
||||
const { pending, settings, updateSettings } = useContext(SSLProviderContext);
|
||||
|
||||
const { form: formInst, formProps } = useAntdForm<NonNullable<unknown>>({
|
||||
initialValues: settings?.content?.config?.[SSLPROVIDERS.LETS_ENCRYPT_STAGING],
|
||||
initialValues: settings?.content?.config?.[APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING],
|
||||
onSubmit: async (values) => {
|
||||
const newSettings = produce(settings, (draft) => {
|
||||
draft.content ??= {} as SSLProviderSettingsContent;
|
||||
draft.content.provider = SSLPROVIDERS.LETS_ENCRYPT_STAGING;
|
||||
draft.content.provider = APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING;
|
||||
|
||||
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
|
||||
draft.content.config[SSLPROVIDERS.LETS_ENCRYPT_STAGING] = values;
|
||||
draft.content.config[APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING] = values;
|
||||
});
|
||||
await updateSettings(newSettings);
|
||||
|
||||
@ -84,7 +85,7 @@ const SSLProviderEditFormLetsEncryptStagingConfig = () => {
|
||||
|
||||
const [formChanged, setFormChanged] = useState(false);
|
||||
useEffect(() => {
|
||||
setFormChanged(settings?.content?.provider !== SSLPROVIDERS.LETS_ENCRYPT_STAGING);
|
||||
setFormChanged(settings?.content?.provider !== APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING);
|
||||
}, [settings?.content?.provider]);
|
||||
|
||||
const handleFormChange = () => {
|
||||
@ -94,7 +95,188 @@ const SSLProviderEditFormLetsEncryptStagingConfig = () => {
|
||||
return (
|
||||
<Form {...formProps} form={formInst} disabled={pending} layout="vertical" onValuesChange={handleFormChange}>
|
||||
<Form.Item>
|
||||
<Alert type="info" message={<span dangerouslySetInnerHTML={{ __html: t("settings.sslprovider.form.letsencrypt_staging_alert") }}></span>} />
|
||||
<Alert type="info" message={<span dangerouslySetInnerHTML={{ __html: t("settings.sslprovider.form.letsencryptstaging_alert") }}></span>} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" disabled={!formChanged} loading={pending}>
|
||||
{t("common.button.save")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
const SSLProviderEditFormBuypassConfig = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { pending, settings, updateSettings } = useContext(SSLProviderContext);
|
||||
|
||||
const { form: formInst, formProps } = useAntdForm<NonNullable<unknown>>({
|
||||
initialValues: settings?.content?.config?.[APPLY_CA_PROVIDERS.BUYPASS],
|
||||
onSubmit: async (values) => {
|
||||
const newSettings = produce(settings, (draft) => {
|
||||
draft.content ??= {} as SSLProviderSettingsContent;
|
||||
draft.content.provider = APPLY_CA_PROVIDERS.BUYPASS;
|
||||
|
||||
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
|
||||
draft.content.config[APPLY_CA_PROVIDERS.BUYPASS] = values;
|
||||
});
|
||||
await updateSettings(newSettings);
|
||||
|
||||
setFormChanged(false);
|
||||
},
|
||||
});
|
||||
|
||||
const [formChanged, setFormChanged] = useState(false);
|
||||
useEffect(() => {
|
||||
setFormChanged(settings?.content?.provider !== APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING);
|
||||
}, [settings?.content?.provider]);
|
||||
|
||||
const handleFormChange = () => {
|
||||
setFormChanged(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...formProps} form={formInst} disabled={pending} layout="vertical" onValuesChange={handleFormChange}>
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" disabled={!formChanged} loading={pending}>
|
||||
{t("common.button.save")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
const SSLProviderEditFormGoogleTrustServicesConfig = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { pending, settings, updateSettings } = useContext(SSLProviderContext);
|
||||
|
||||
const formSchema = z.object({
|
||||
eabKid: z
|
||||
.string({ message: t("settings.sslprovider.form.googletrustservices_eab_kid.placeholder") })
|
||||
.min(1, t("settings.sslprovider.form.googletrustservices_eab_kid.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
eabHmacKey: z
|
||||
.string({ message: t("settings.sslprovider.form.googletrustservices_eab_hmac_key.placeholder") })
|
||||
.min(1, t("settings.sslprovider.form.googletrustservices_eab_hmac_key.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const { form: formInst, formProps } = useAntdForm<z.infer<typeof formSchema>>({
|
||||
initialValues: settings?.content?.config?.[APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES],
|
||||
onSubmit: async (values) => {
|
||||
const newSettings = produce(settings, (draft) => {
|
||||
draft.content ??= {} as SSLProviderSettingsContent;
|
||||
draft.content.provider = APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES;
|
||||
|
||||
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
|
||||
draft.content.config[APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES] = values;
|
||||
});
|
||||
await updateSettings(newSettings);
|
||||
|
||||
setFormChanged(false);
|
||||
},
|
||||
});
|
||||
|
||||
const [formChanged, setFormChanged] = useState(false);
|
||||
useEffect(() => {
|
||||
setFormChanged(settings?.content?.provider !== APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES);
|
||||
}, [settings?.content?.provider]);
|
||||
|
||||
const handleFormChange = () => {
|
||||
setFormChanged(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...formProps} form={formInst} disabled={pending} layout="vertical" onValuesChange={handleFormChange}>
|
||||
<Form.Item
|
||||
name="eabKid"
|
||||
label={t("settings.sslprovider.form.googletrustservices_eab_kid.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.sslprovider.form.googletrustservices_eab_kid.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("settings.sslprovider.form.googletrustservices_eab_kid.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="eabHmacKey"
|
||||
label={t("settings.sslprovider.form.googletrustservices_eab_hmac_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.sslprovider.form.googletrustservices_eab_hmac_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("settings.sslprovider.form.googletrustservices_eab_hmac_key.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" disabled={!formChanged} loading={pending}>
|
||||
{t("common.button.save")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
const SSLProviderEditFormSSLComConfig = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { pending, settings, updateSettings } = useContext(SSLProviderContext);
|
||||
|
||||
const formSchema = z.object({
|
||||
eabKid: z
|
||||
.string({ message: t("settings.sslprovider.form.sslcom_eab_kid.placeholder") })
|
||||
.min(1, t("settings.sslprovider.form.sslcom_eab_kid.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
eabHmacKey: z
|
||||
.string({ message: t("settings.sslprovider.form.sslcom_eab_hmac_key.placeholder") })
|
||||
.min(1, t("settings.sslprovider.form.sslcom_eab_hmac_key.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const { form: formInst, formProps } = useAntdForm<z.infer<typeof formSchema>>({
|
||||
initialValues: settings?.content?.config?.[APPLY_CA_PROVIDERS.SSLCOM],
|
||||
onSubmit: async (values) => {
|
||||
const newSettings = produce(settings, (draft) => {
|
||||
draft.content ??= {} as SSLProviderSettingsContent;
|
||||
draft.content.provider = APPLY_CA_PROVIDERS.SSLCOM;
|
||||
|
||||
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
|
||||
draft.content.config[APPLY_CA_PROVIDERS.SSLCOM] = values;
|
||||
});
|
||||
await updateSettings(newSettings);
|
||||
|
||||
setFormChanged(false);
|
||||
},
|
||||
});
|
||||
|
||||
const [formChanged, setFormChanged] = useState(false);
|
||||
useEffect(() => {
|
||||
setFormChanged(settings?.content?.provider !== APPLY_CA_PROVIDERS.SSLCOM);
|
||||
}, [settings?.content?.provider]);
|
||||
|
||||
const handleFormChange = () => {
|
||||
setFormChanged(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...formProps} form={formInst} disabled={pending} layout="vertical" onValuesChange={handleFormChange}>
|
||||
<Form.Item
|
||||
name="eabKid"
|
||||
label={t("settings.sslprovider.form.sslcom_eab_kid.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.sslprovider.form.sslcom_eab_kid.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("settings.sslprovider.form.sslcom_eab_kid.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="eabHmacKey"
|
||||
label={t("settings.sslprovider.form.sslcom_eab_hmac_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.sslprovider.form.sslcom_eab_hmac_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("settings.sslprovider.form.sslcom_eab_hmac_key.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
@ -123,14 +305,14 @@ const SSLProviderEditFormZeroSSLConfig = () => {
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const { form: formInst, formProps } = useAntdForm<z.infer<typeof formSchema>>({
|
||||
initialValues: settings?.content?.config?.[SSLPROVIDERS.ZERO_SSL],
|
||||
initialValues: settings?.content?.config?.[APPLY_CA_PROVIDERS.ZEROSSL],
|
||||
onSubmit: async (values) => {
|
||||
const newSettings = produce(settings, (draft) => {
|
||||
draft.content ??= {} as SSLProviderSettingsContent;
|
||||
draft.content.provider = SSLPROVIDERS.ZERO_SSL;
|
||||
draft.content.provider = APPLY_CA_PROVIDERS.ZEROSSL;
|
||||
|
||||
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
|
||||
draft.content.config[SSLPROVIDERS.ZERO_SSL] = values;
|
||||
draft.content.config[APPLY_CA_PROVIDERS.ZEROSSL] = values;
|
||||
});
|
||||
await updateSettings(newSettings);
|
||||
|
||||
@ -140,7 +322,7 @@ const SSLProviderEditFormZeroSSLConfig = () => {
|
||||
|
||||
const [formChanged, setFormChanged] = useState(false);
|
||||
useEffect(() => {
|
||||
setFormChanged(settings?.content?.provider !== SSLPROVIDERS.ZERO_SSL);
|
||||
setFormChanged(settings?.content?.provider !== APPLY_CA_PROVIDERS.ZEROSSL);
|
||||
}, [settings?.content?.provider]);
|
||||
|
||||
const handleFormChange = () => {
|
||||
@ -176,76 +358,6 @@ const SSLProviderEditFormZeroSSLConfig = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const SSLProviderEditFormGoogleTrustServicesConfig = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { pending, settings, updateSettings } = useContext(SSLProviderContext);
|
||||
|
||||
const formSchema = z.object({
|
||||
eabKid: z
|
||||
.string({ message: t("settings.sslprovider.form.gts_eab_kid.placeholder") })
|
||||
.min(1, t("settings.sslprovider.form.gts_eab_kid.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
eabHmacKey: z
|
||||
.string({ message: t("settings.sslprovider.form.gts_eab_hmac_key.placeholder") })
|
||||
.min(1, t("settings.sslprovider.form.gts_eab_hmac_key.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const { form: formInst, formProps } = useAntdForm<z.infer<typeof formSchema>>({
|
||||
initialValues: settings?.content?.config?.[SSLPROVIDERS.GOOGLE_TRUST_SERVICES],
|
||||
onSubmit: async (values) => {
|
||||
const newSettings = produce(settings, (draft) => {
|
||||
draft.content ??= {} as SSLProviderSettingsContent;
|
||||
draft.content.provider = SSLPROVIDERS.GOOGLE_TRUST_SERVICES;
|
||||
|
||||
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
|
||||
draft.content.config[SSLPROVIDERS.GOOGLE_TRUST_SERVICES] = values;
|
||||
});
|
||||
await updateSettings(newSettings);
|
||||
|
||||
setFormChanged(false);
|
||||
},
|
||||
});
|
||||
|
||||
const [formChanged, setFormChanged] = useState(false);
|
||||
useEffect(() => {
|
||||
setFormChanged(settings?.content?.provider !== SSLPROVIDERS.GOOGLE_TRUST_SERVICES);
|
||||
}, [settings?.content?.provider]);
|
||||
|
||||
const handleFormChange = () => {
|
||||
setFormChanged(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...formProps} form={formInst} disabled={pending} layout="vertical" onValuesChange={handleFormChange}>
|
||||
<Form.Item
|
||||
name="eabKid"
|
||||
label={t("settings.sslprovider.form.gts_eab_kid.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.sslprovider.form.gts_eab_kid.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("settings.sslprovider.form.gts_eab_kid.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="eabHmacKey"
|
||||
label={t("settings.sslprovider.form.gts_eab_hmac_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.sslprovider.form.gts_eab_hmac_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("settings.sslprovider.form.gts_eab_hmac_key.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" disabled={!formChanged} loading={pending}>
|
||||
{t("common.button.save")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
const SettingsSSLProvider = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -271,17 +383,21 @@ const SettingsSSLProvider = () => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const [providerType, setProviderType] = useState<SSLProviders>(SSLPROVIDERS.LETS_ENCRYPT);
|
||||
const [providerType, setProviderType] = useState<ApplyCAProviderType>(APPLY_CA_PROVIDERS.LETSENCRYPT);
|
||||
const providerFormEl = useMemo(() => {
|
||||
switch (providerType) {
|
||||
case SSLPROVIDERS.LETS_ENCRYPT:
|
||||
case APPLY_CA_PROVIDERS.LETSENCRYPT:
|
||||
return <SSLProviderEditFormLetsEncryptConfig />;
|
||||
case SSLPROVIDERS.LETS_ENCRYPT_STAGING:
|
||||
case APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING:
|
||||
return <SSLProviderEditFormLetsEncryptStagingConfig />;
|
||||
case SSLPROVIDERS.ZERO_SSL:
|
||||
return <SSLProviderEditFormZeroSSLConfig />;
|
||||
case SSLPROVIDERS.GOOGLE_TRUST_SERVICES:
|
||||
case APPLY_CA_PROVIDERS.BUYPASS:
|
||||
return <SSLProviderEditFormBuypassConfig />;
|
||||
case APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES:
|
||||
return <SSLProviderEditFormGoogleTrustServicesConfig />;
|
||||
case APPLY_CA_PROVIDERS.SSLCOM:
|
||||
return <SSLProviderEditFormSSLComConfig />;
|
||||
case APPLY_CA_PROVIDERS.ZEROSSL:
|
||||
return <SSLProviderEditFormZeroSSLConfig />;
|
||||
}
|
||||
}, [providerType]);
|
||||
|
||||
@ -315,34 +431,48 @@ const SettingsSSLProvider = () => {
|
||||
<Show when={!loading} fallback={<Skeleton active />}>
|
||||
<Form form={formInst} disabled={formPending} layout="vertical" initialValues={{ provider: providerType }}>
|
||||
<Form.Item className="mb-2" name="provider" label={t("settings.sslprovider.form.provider.label")}>
|
||||
<CheckCard.Group className="w-full" onChange={(value) => setProviderType(value as SSLProviders)}>
|
||||
<CheckCard.Group className="w-full" onChange={(value) => setProviderType(value as ApplyCAProviderType)}>
|
||||
<CheckCard
|
||||
avatar={<img src={"/imgs/acme/letsencrypt.svg"} className="size-8" />}
|
||||
avatar={<img src={"/imgs/providers/letsencrypt.svg"} className="size-8" />}
|
||||
size="small"
|
||||
title={t("settings.sslprovider.form.provider.option.letsencrypt.label")}
|
||||
title={t("provider.letsencrypt")}
|
||||
description="letsencrypt.org"
|
||||
value={SSLPROVIDERS.LETS_ENCRYPT}
|
||||
value={APPLY_CA_PROVIDERS.LETSENCRYPT}
|
||||
/>
|
||||
<CheckCard
|
||||
avatar={<img src={"/imgs/acme/letsencrypt.svg"} className="size-8" />}
|
||||
avatar={<img src={"/imgs/providers/letsencrypt.svg"} className="size-8" />}
|
||||
size="small"
|
||||
title={t("settings.sslprovider.form.provider.option.letsencrypt_staging.label")}
|
||||
title={t("provider.letsencryptstaging")}
|
||||
description="letsencrypt.org"
|
||||
value={SSLPROVIDERS.LETS_ENCRYPT_STAGING}
|
||||
value={APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING}
|
||||
/>
|
||||
<CheckCard
|
||||
avatar={<img src={"/imgs/acme/zerossl.svg"} className="size-8" />}
|
||||
avatar={<img src={"/imgs/providers/buypass.png"} className="size-8" />}
|
||||
size="small"
|
||||
title={t("settings.sslprovider.form.provider.option.zerossl.label")}
|
||||
description="zerossl.com"
|
||||
value={SSLPROVIDERS.ZERO_SSL}
|
||||
title={t("provider.buypass")}
|
||||
description="buypass.com"
|
||||
value={APPLY_CA_PROVIDERS.BUYPASS}
|
||||
/>
|
||||
<CheckCard
|
||||
avatar={<img src={"/imgs/acme/google.svg"} className="size-8" />}
|
||||
avatar={<img src={"/imgs/providers/google.svg"} className="size-8" />}
|
||||
size="small"
|
||||
title={t("settings.sslprovider.form.provider.option.gts.label")}
|
||||
title={t("provider.googletrustservices")}
|
||||
description="pki.goog"
|
||||
value={SSLPROVIDERS.GOOGLE_TRUST_SERVICES}
|
||||
value={APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES}
|
||||
/>
|
||||
<CheckCard
|
||||
avatar={<img src={"/imgs/providers/sslcom.svg"} className="size-8" />}
|
||||
size="small"
|
||||
title={t("provider.sslcom")}
|
||||
description="ssl.com"
|
||||
value={APPLY_CA_PROVIDERS.SSLCOM}
|
||||
/>
|
||||
<CheckCard
|
||||
avatar={<img src={"/imgs/providers/zerossl.svg"} className="size-8" />}
|
||||
size="small"
|
||||
title={t("provider.zerossl")}
|
||||
description="zerossl.com"
|
||||
value={APPLY_CA_PROVIDERS.ZEROSSL}
|
||||
/>
|
||||
</CheckCard.Group>
|
||||
</Form.Item>
|
||||
|