mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-08 13:39:53 +00:00
fix conflict
This commit is contained in:
commit
f799740d70
1
go.mod
1
go.mod
@ -46,6 +46,7 @@ require (
|
|||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.6 // indirect
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.6 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.46.0 // indirect
|
github.com/aws/aws-sdk-go-v2/service/route53 v1.46.0 // indirect
|
||||||
github.com/blinkbean/dingtalk v1.1.3 // indirect
|
github.com/blinkbean/dingtalk v1.1.3 // indirect
|
||||||
|
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.35 // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
github.com/go-lark/lark v1.14.1 // indirect
|
github.com/go-lark/lark v1.14.1 // indirect
|
||||||
|
4
go.sum
4
go.sum
@ -225,6 +225,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
|||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/blinkbean/dingtalk v1.1.3 h1:MbidFZYom7DTFHD/YIs+eaI7kRy52kmWE/sy0xjo6E4=
|
github.com/blinkbean/dingtalk v1.1.3 h1:MbidFZYom7DTFHD/YIs+eaI7kRy52kmWE/sy0xjo6E4=
|
||||||
github.com/blinkbean/dingtalk v1.1.3/go.mod h1:9BaLuGSBqY3vT5hstValh48DbsKO7vaHaJnG9pXwbto=
|
github.com/blinkbean/dingtalk v1.1.3/go.mod h1:9BaLuGSBqY3vT5hstValh48DbsKO7vaHaJnG9pXwbto=
|
||||||
|
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.35 h1:bM18V4iw9ylRc2LahQaq3k3gjEVJdyQYvptLVZaCa54=
|
||||||
|
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.35/go.mod h1:7iCaE+dR9EycrJU0GQyMhptbInLbQhsKXiDKDjNi8Vs=
|
||||||
github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI=
|
github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI=
|
||||||
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||||
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||||
@ -751,6 +753,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
@ -1265,6 +1268,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
|||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
|
116
internal/deployer/byteplus_cdn.go
Normal file
116
internal/deployer/byteplus_cdn.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
bytepluscdn "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/byteplus-cdn"
|
||||||
|
|
||||||
|
"github.com/byteplus-sdk/byteplus-sdk-golang/service/cdn"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ByteplusCDNDeployer struct {
|
||||||
|
option *DeployerOption
|
||||||
|
infos []string
|
||||||
|
sdkClient *cdn.CDN
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewByteplusCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
access := &domain.ByteplusAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
|
}
|
||||||
|
client := cdn.NewInstance()
|
||||||
|
client.Client.SetAccessKey(access.AccessKey)
|
||||||
|
client.Client.SetSecretKey(access.SecretKey)
|
||||||
|
uploader, err := bytepluscdn.New(&bytepluscdn.ByteplusCDNUploaderConfig{
|
||||||
|
AccessKey: access.AccessKey,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
return &ByteplusCDNDeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ByteplusCDNDeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ByteplusCDNDeployer) GetInfos() []string {
|
||||||
|
return d.infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ByteplusCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
|
apiCtx := context.Background()
|
||||||
|
// 上传证书
|
||||||
|
upres, err := d.sslUploader.Upload(apiCtx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||||
|
|
||||||
|
domains := make([]string, 0)
|
||||||
|
configDomain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||||
|
if strings.HasPrefix(configDomain, "*.") {
|
||||||
|
// 获取证书可以部署的域名
|
||||||
|
// REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-describecertconfig-9ea17
|
||||||
|
describeCertConfigReq := &cdn.DescribeCertConfigRequest{
|
||||||
|
CertId: upres.CertId,
|
||||||
|
}
|
||||||
|
describeCertConfigResp, err := d.sdkClient.DescribeCertConfig(describeCertConfigReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertConfig'")
|
||||||
|
}
|
||||||
|
for i := range describeCertConfigResp.Result.CertNotConfig {
|
||||||
|
// 当前未启用 HTTPS 的加速域名列表。
|
||||||
|
domains = append(domains, describeCertConfigResp.Result.CertNotConfig[i].Domain)
|
||||||
|
}
|
||||||
|
for i := range describeCertConfigResp.Result.OtherCertConfig {
|
||||||
|
// 已启用了 HTTPS 的加速域名列表。这些加速域名关联的证书不是您指定的证书。
|
||||||
|
domains = append(domains, describeCertConfigResp.Result.OtherCertConfig[i].Domain)
|
||||||
|
}
|
||||||
|
for i := range describeCertConfigResp.Result.SpecifiedCertConfig {
|
||||||
|
// 已启用了 HTTPS 的加速域名列表。这些加速域名关联了您指定的证书。
|
||||||
|
d.infos = append(d.infos, fmt.Sprintf("%s域名已配置该证书", describeCertConfigResp.Result.SpecifiedCertConfig[i].Domain))
|
||||||
|
}
|
||||||
|
if len(domains) == 0 {
|
||||||
|
if len(describeCertConfigResp.Result.SpecifiedCertConfig) > 0 {
|
||||||
|
// 所有匹配的域名都配置了该证书,跳过部署
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return xerrors.Errorf("未查询到匹配的域名: %s", configDomain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
domains = append(domains, configDomain)
|
||||||
|
}
|
||||||
|
// 部署证书
|
||||||
|
// REF: https://github.com/byteplus-sdk/byteplus-sdk-golang/blob/master/service/cdn/api_list.go#L306
|
||||||
|
for i := range domains {
|
||||||
|
batchDeployCertReq := &cdn.BatchDeployCertRequest{
|
||||||
|
CertId: upres.CertId,
|
||||||
|
Domain: domains[i],
|
||||||
|
}
|
||||||
|
batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.BatchDeployCert'")
|
||||||
|
} else {
|
||||||
|
d.infos = append(d.infos, toStr(fmt.Sprintf("%s域名的证书已修改", domains[i]), batchDeployCertResp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -42,6 +42,7 @@ const (
|
|||||||
targetK8sSecret = "k8s-secret"
|
targetK8sSecret = "k8s-secret"
|
||||||
targetVolcengineLive = "volcengine-live"
|
targetVolcengineLive = "volcengine-live"
|
||||||
targetVolcengineCDN = "volcengine-cdn"
|
targetVolcengineCDN = "volcengine-cdn"
|
||||||
|
targetByteplusCDN = "byteplus-cdn"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeployerOption struct {
|
type DeployerOption struct {
|
||||||
@ -156,6 +157,8 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
|
|||||||
return NewVolcengineLiveDeployer(option)
|
return NewVolcengineLiveDeployer(option)
|
||||||
case targetVolcengineCDN:
|
case targetVolcengineCDN:
|
||||||
return NewVolcengineCDNDeployer(option)
|
return NewVolcengineCDNDeployer(option)
|
||||||
|
case targetByteplusCDN:
|
||||||
|
return NewByteplusCDNDeployer(option)
|
||||||
}
|
}
|
||||||
return nil, errors.New("unsupported deploy target")
|
return nil, errors.New("unsupported deploy target")
|
||||||
}
|
}
|
||||||
|
@ -101,9 +101,9 @@ func (d *TencentCDNDeployer) Deploy(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
temp := make([]string, 0)
|
temp := make([]string, 0)
|
||||||
for _, aliInstanceId := range tcInstanceIds {
|
for _, tcInstanceId := range tcInstanceIds {
|
||||||
if !slices.Contains(deployedDomains, aliInstanceId) {
|
if !slices.Contains(deployedDomains, tcInstanceId) {
|
||||||
temp = append(temp, aliInstanceId)
|
temp = append(temp, tcInstanceId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tcInstanceIds = temp
|
tcInstanceIds = temp
|
||||||
|
@ -16,6 +16,11 @@ type AliyunAccess struct {
|
|||||||
AccessKeySecret string `json:"accessKeySecret"`
|
AccessKeySecret string `json:"accessKeySecret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ByteplusAccess struct {
|
||||||
|
AccessKey string
|
||||||
|
SecretKey string
|
||||||
|
}
|
||||||
|
|
||||||
type TencentAccess struct {
|
type TencentAccess struct {
|
||||||
SecretId string `json:"secretId"`
|
SecretId string `json:"secretId"`
|
||||||
SecretKey string `json:"secretKey"`
|
SecretKey string `json:"secretKey"`
|
||||||
|
@ -2,14 +2,22 @@ package domains
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/applicant"
|
"github.com/usual2970/certimate/internal/applicant"
|
||||||
"github.com/usual2970/certimate/internal/deployer"
|
"github.com/usual2970/certimate/internal/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/utils/app"
|
"github.com/usual2970/certimate/internal/utils/app"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Phase string
|
type Phase string
|
||||||
@ -20,6 +28,8 @@ const (
|
|||||||
deployPhase Phase = "deploy"
|
deployPhase Phase = "deploy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const validityDuration = time.Hour * 24 * 10
|
||||||
|
|
||||||
func deploy(ctx context.Context, record *models.Record) error {
|
func deploy(ctx context.Context, record *models.Record) error {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@ -45,7 +55,10 @@ func deploy(ctx context.Context, record *models.Record) error {
|
|||||||
cert := currRecord.GetString("certificate")
|
cert := currRecord.GetString("certificate")
|
||||||
expiredAt := currRecord.GetDateTime("expiredAt").Time()
|
expiredAt := currRecord.GetDateTime("expiredAt").Time()
|
||||||
|
|
||||||
if cert != "" && time.Until(expiredAt) > time.Hour*24*10 && currRecord.GetBool("deployed") {
|
// 检查证书是否包含设置的所有域名
|
||||||
|
changed := isCertChanged(cert, currRecord)
|
||||||
|
|
||||||
|
if cert != "" && time.Until(expiredAt) > validityDuration && currRecord.GetBool("deployed") && !changed {
|
||||||
app.GetApp().Logger().Info("证书在有效期内")
|
app.GetApp().Logger().Info("证书在有效期内")
|
||||||
history.record(checkPhase, "证书在有效期内且已部署,跳过", &RecordInfo{
|
history.record(checkPhase, "证书在有效期内且已部署,跳过", &RecordInfo{
|
||||||
Info: []string{fmt.Sprintf("证书有效期至 %s", expiredAt.Format("2006-01-02"))},
|
Info: []string{fmt.Sprintf("证书有效期至 %s", expiredAt.Format("2006-01-02"))},
|
||||||
@ -60,7 +73,7 @@ func deploy(ctx context.Context, record *models.Record) error {
|
|||||||
// ############2.申请证书
|
// ############2.申请证书
|
||||||
history.record(applyPhase, "开始申请", nil)
|
history.record(applyPhase, "开始申请", nil)
|
||||||
|
|
||||||
if cert != "" && time.Until(expiredAt) > time.Hour*24 {
|
if cert != "" && time.Until(expiredAt) > validityDuration && !changed {
|
||||||
history.record(applyPhase, "证书在有效期内,跳过", &RecordInfo{
|
history.record(applyPhase, "证书在有效期内,跳过", &RecordInfo{
|
||||||
Info: []string{fmt.Sprintf("证书有效期至 %s", expiredAt.Format("2006-01-02"))},
|
Info: []string{fmt.Sprintf("证书有效期至 %s", expiredAt.Format("2006-01-02"))},
|
||||||
})
|
})
|
||||||
@ -121,3 +134,80 @@ func deploy(ctx context.Context, record *models.Record) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isCertChanged(certificate string, record *models.Record) bool {
|
||||||
|
// 如果证书为空,直接返回true
|
||||||
|
if certificate == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析证书
|
||||||
|
cert, err := x509.ParseCertificateFromPEM(certificate)
|
||||||
|
if err != nil {
|
||||||
|
app.GetApp().Logger().Error("解析证书失败", "err", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历域名列表,检查是否都在证书中,找到第一个不存在证书中域名时提前返回true
|
||||||
|
for _, domain := range strings.Split(record.GetString("domain"), ";") {
|
||||||
|
if !slices.Contains(cert.DNSNames, domain) && !slices.Contains(cert.DNSNames, "*."+removeLastSubdomain(domain)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析applyConfig
|
||||||
|
applyConfig := &domain.ApplyConfig{}
|
||||||
|
record.UnmarshalJSONField("applyConfig", applyConfig)
|
||||||
|
|
||||||
|
// 检查证书加密算法是否变更
|
||||||
|
switch pubkey := cert.PublicKey.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
bitSize := pubkey.N.BitLen()
|
||||||
|
switch bitSize {
|
||||||
|
case 2048:
|
||||||
|
// RSA2048
|
||||||
|
if applyConfig.KeyAlgorithm != "" && applyConfig.KeyAlgorithm != "RSA2048" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case 3072:
|
||||||
|
// RSA3072
|
||||||
|
if applyConfig.KeyAlgorithm != "RSA3072" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case 4096:
|
||||||
|
// RSA4096
|
||||||
|
if applyConfig.KeyAlgorithm != "RSA4096" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case 8192:
|
||||||
|
// RSA8192
|
||||||
|
if applyConfig.KeyAlgorithm != "RSA8192" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
bitSize := pubkey.Curve.Params().BitSize
|
||||||
|
switch bitSize {
|
||||||
|
case 256:
|
||||||
|
// EC256
|
||||||
|
if applyConfig.KeyAlgorithm != "EC256" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case 384:
|
||||||
|
// EC384
|
||||||
|
if applyConfig.KeyAlgorithm != "EC384" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeLastSubdomain(domain string) string {
|
||||||
|
parts := strings.Split(domain, ".")
|
||||||
|
if len(parts) > 1 {
|
||||||
|
return strings.Join(parts[1:], ".")
|
||||||
|
}
|
||||||
|
return domain
|
||||||
|
}
|
||||||
|
@ -0,0 +1,111 @@
|
|||||||
|
package bytepluscdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/byteplus-sdk/byteplus-sdk-golang/service/cdn"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ByteplusCDNUploaderConfig struct {
|
||||||
|
AccessKey string `json:"accessKey"`
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ByteplusCDNUploader struct {
|
||||||
|
config *ByteplusCDNUploaderConfig
|
||||||
|
sdkClient *cdn.CDN
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ uploader.Uploader = (*ByteplusCDNUploader)(nil)
|
||||||
|
|
||||||
|
func New(config *ByteplusCDNUploaderConfig) (*ByteplusCDNUploader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
instance := cdn.NewInstance()
|
||||||
|
client := instance.Client
|
||||||
|
client.SetAccessKey(config.AccessKey)
|
||||||
|
client.SetSecretKey(config.SecretKey)
|
||||||
|
|
||||||
|
return &ByteplusCDNUploader{
|
||||||
|
config: config,
|
||||||
|
sdkClient: instance,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ByteplusCDNUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
|
// 解析证书内容
|
||||||
|
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 查询证书列表,避免重复上传
|
||||||
|
// REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-listcertinfo
|
||||||
|
pageNum := int64(1)
|
||||||
|
pageSize := int64(100)
|
||||||
|
certSource := "cert_center"
|
||||||
|
listCertInfoReq := &cdn.ListCertInfoRequest{
|
||||||
|
PageNum: &pageNum,
|
||||||
|
PageSize: &pageSize,
|
||||||
|
Source: &certSource,
|
||||||
|
}
|
||||||
|
searchTotal := 0
|
||||||
|
for {
|
||||||
|
listCertInfoResp, err := u.sdkClient.ListCertInfo(listCertInfoReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.ListCertInfo'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if listCertInfoResp.Result.CertInfo != nil {
|
||||||
|
for _, certDetail := range listCertInfoResp.Result.CertInfo {
|
||||||
|
hash := sha256.Sum256(certX509.Raw)
|
||||||
|
isSameCert := strings.EqualFold(hex.EncodeToString(hash[:]), certDetail.CertFingerprint.Sha256)
|
||||||
|
// 如果已存在相同证书,直接返回已有的证书信息
|
||||||
|
if isSameCert {
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: certDetail.CertId,
|
||||||
|
CertName: certDetail.Desc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
searchTotal += len(listCertInfoResp.Result.CertInfo)
|
||||||
|
if int(listCertInfoResp.Result.Total) > searchTotal {
|
||||||
|
pageNum++
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
var certId, certName string
|
||||||
|
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
||||||
|
// 上传新证书
|
||||||
|
// REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-addcertificate
|
||||||
|
addCertificateReq := &cdn.AddCertificateRequest{
|
||||||
|
Certificate: certPem,
|
||||||
|
PrivateKey: privkeyPem,
|
||||||
|
Source: &certSource,
|
||||||
|
Desc: &certName,
|
||||||
|
}
|
||||||
|
addCertificateResp, err := u.sdkClient.AddCertificate(addCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.AddCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
certId = addCertificateResp.Result.CertId
|
||||||
|
return &uploader.UploadResult{
|
||||||
|
CertId: certId,
|
||||||
|
CertName: certName,
|
||||||
|
}, nil
|
||||||
|
}
|
107
migrations/1731872250_add_byteplus.go
Normal file
107
migrations/1731872250_add_byteplus.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
|
"github.com/pocketbase/pocketbase/daos"
|
||||||
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
|
"github.com/pocketbase/pocketbase/models/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db)
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update
|
||||||
|
edit_configType := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "hwy7m03o",
|
||||||
|
"name": "configType",
|
||||||
|
"type": "select",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSelect": 1,
|
||||||
|
"values": [
|
||||||
|
"aliyun",
|
||||||
|
"tencent",
|
||||||
|
"huaweicloud",
|
||||||
|
"qiniu",
|
||||||
|
"aws",
|
||||||
|
"cloudflare",
|
||||||
|
"namesilo",
|
||||||
|
"godaddy",
|
||||||
|
"pdns",
|
||||||
|
"httpreq",
|
||||||
|
"local",
|
||||||
|
"ssh",
|
||||||
|
"webhook",
|
||||||
|
"k8s",
|
||||||
|
"baiducloud",
|
||||||
|
"dogecloud",
|
||||||
|
"volcengine",
|
||||||
|
"byteplus"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`), edit_configType); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(edit_configType)
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
}, func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db)
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update
|
||||||
|
edit_configType := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "hwy7m03o",
|
||||||
|
"name": "configType",
|
||||||
|
"type": "select",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSelect": 1,
|
||||||
|
"values": [
|
||||||
|
"aliyun",
|
||||||
|
"tencent",
|
||||||
|
"huaweicloud",
|
||||||
|
"qiniu",
|
||||||
|
"aws",
|
||||||
|
"cloudflare",
|
||||||
|
"namesilo",
|
||||||
|
"godaddy",
|
||||||
|
"pdns",
|
||||||
|
"httpreq",
|
||||||
|
"local",
|
||||||
|
"ssh",
|
||||||
|
"webhook",
|
||||||
|
"k8s",
|
||||||
|
"baiducloud",
|
||||||
|
"dogecloud",
|
||||||
|
"volcengine"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`), edit_configType); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(edit_configType)
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
})
|
||||||
|
}
|
4
ui/public/imgs/providers/byteplus.svg
Normal file
4
ui/public/imgs/providers/byteplus.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200">
|
||||||
|
<path d="M0 0 C0.33 12.87 0.66 25.74 1 39 C4.63 36.03 8.26 33.06 12 30 C12.99 29.67 13.98 29.34 15 29 C15 28.34 15 27.68 15 27 C16.6953125 25.4765625 16.6953125 25.4765625 19.125 23.625 C24.36818245 19.55364629 29.38002727 15.25969115 34.35205078 10.86181641 C38.78276777 7 38.78276777 7 41 7 C41 6.34 41 5.68 41 5 C45.35294118 1 45.35294118 1 49 1 C49.02312269 12.19573244 49.04091471 23.39145924 49.05181217 34.58721066 C49.05704116 39.78528115 49.0641391 44.98333536 49.07543945 50.18139648 C49.08626776 55.19342268 49.09227671 60.20543318 49.09487724 65.21747017 C49.09673159 67.13412282 49.1003524 69.05077457 49.10573006 70.96742058 C49.11293975 73.64228072 49.11399097 76.31707514 49.11352539 78.99194336 C49.11712067 79.7925116 49.12071594 80.59307983 49.12442017 81.41790771 C49.11405216 86.88594784 49.11405216 86.88594784 48 88 C46.23140807 88.09946453 44.45888549 88.13080217 42.6875 88.1328125 C41.61242187 88.13410156 40.53734375 88.13539063 39.4296875 88.13671875 C38.29789062 88.13285156 37.16609375 88.12898438 36 88.125 C34.86820312 88.12886719 33.73640625 88.13273437 32.5703125 88.13671875 C31.49523437 88.13542969 30.42015625 88.13414062 29.3125 88.1328125 C28.31863281 88.13168457 27.32476562 88.13055664 26.30078125 88.12939453 C24 88 24 88 23 87 C22.84174742 85.00217874 22.74880829 82.99911166 22.68359375 80.99609375 C22.64169922 79.78115234 22.59980469 78.56621094 22.55664062 77.31445312 C22.51732422 76.03505859 22.47800781 74.75566406 22.4375 73.4375 C22.39431641 72.15423828 22.35113281 70.87097656 22.30664062 69.54882812 C22.200152 66.36600147 22.09814038 63.18309326 22 60 C21.24074219 60.63808594 20.48148437 61.27617188 19.69921875 61.93359375 C18.70535156 62.75988281 17.71148437 63.58617187 16.6875 64.4375 C15.70136719 65.26121094 14.71523437 66.08492188 13.69921875 66.93359375 C11 69 11 69 8 70 C8 70.66 8 71.32 8 72 C6.51261765 73.35382149 4.95569449 74.63139886 3.375 75.875 C-1.2643158 79.58405196 -5.81455578 83.3707187 -10.3125 87.25 C-11.99985624 88.70517299 -13.68754171 90.15975891 -15.38134766 91.60742188 C-16.47861169 92.55143304 -17.5666265 93.50628271 -18.64599609 94.47070312 C-22.67291886 98 -22.67291886 98 -26 98 C-26 84.8 -26 71.6 -26 58 C-29.3 60.64 -32.6 63.28 -36 66 C-36.66 66 -37.32 66 -38 66 C-38 66.66 -38 67.32 -38 68 C-39.71332754 69.58533702 -41.49689047 71.09541215 -43.3125 72.5625 C-44.29863281 73.36816406 -45.28476563 74.17382812 -46.30078125 75.00390625 C-49 77 -49 77 -52 78 C-52 78.66 -52 79.32 -52 80 C-53.42720409 81.26928798 -54.91983776 82.46538593 -56.4375 83.625 C-60.85617551 87.04712316 -64.96051883 90.6632284 -68.9921875 94.53125 C-71 96 -71 96 -75 96 C-75.02312283 84.93465142 -75.04091478 73.86930855 -75.05181217 62.80394077 C-75.05704113 57.66640434 -75.06413901 52.52888437 -75.07543945 47.39135742 C-75.08626787 42.4376859 -75.09227674 37.48403026 -75.09487724 32.53034782 C-75.09673157 30.63602384 -75.10035234 28.74170076 -75.10573006 26.8473835 C-75.11293989 24.20365614 -75.11399097 21.55999529 -75.11352539 18.91625977 C-75.11712067 18.12504227 -75.12071594 17.33382477 -75.12442017 16.51863098 C-75.11405216 11.11405216 -75.11405216 11.11405216 -74 10 C-72.21928754 9.91273777 -70.4351686 9.89300959 -68.65234375 9.90234375 C-67.57275391 9.90556641 -66.49316406 9.90878906 -65.38085938 9.91210938 C-63.67639648 9.92467773 -63.67639648 9.92467773 -61.9375 9.9375 C-60.22723633 9.94426758 -60.22723633 9.94426758 -58.48242188 9.95117188 C-55.65489649 9.96300253 -52.8274736 9.9794859 -50 10 C-48.65893595 12.6821281 -48.78549581 14.88398032 -48.68359375 17.8828125 C-48.64169922 19.04941406 -48.59980469 20.21601562 -48.55664062 21.41796875 C-48.51732422 22.64128906 -48.47800781 23.86460938 -48.4375 25.125 C-48.39431641 26.35605469 -48.35113281 27.58710937 -48.30664062 28.85546875 C-48.20031296 31.90352853 -48.09828219 34.9516719 -48 38 C-46.515 37.505 -46.515 37.505 -45 37 C-45 36.34 -45 35.68 -45 35 C-43.28667246 33.41466298 -41.50310953 31.90458785 -39.6875 30.4375 C-38.70136719 29.63183594 -37.71523437 28.82617188 -36.69921875 27.99609375 C-34 26 -34 26 -31 25 C-31 24.34 -31 23.68 -31 23 C-29.3046875 21.4765625 -29.3046875 21.4765625 -26.875 19.625 C-21.63181755 15.55364629 -16.61997273 11.25969115 -11.64794922 6.86181641 C-7.21723223 3 -7.21723223 3 -5 3 C-5 2.34 -5 1.68 -5 1 C-3 0 -3 0 0 0 Z " fill="#0260F9" transform="translate(113,51)"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.4 KiB |
194
ui/src/components/certimate/AccessByteplusForm.tsx
Normal file
194
ui/src/components/certimate/AccessByteplusForm.tsx
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import z from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { PbErrorData } from "@/domain/base";
|
||||||
|
import { accessProvidersMap, accessTypeFormSchema, type Access, type ByteplusConfig } from "@/domain/access";
|
||||||
|
import { save } from "@/repository/access";
|
||||||
|
import { useConfigContext } from "@/providers/config";
|
||||||
|
|
||||||
|
type AccessByteplusFormProps = {
|
||||||
|
op: "add" | "edit" | "copy";
|
||||||
|
data?: Access;
|
||||||
|
onAfterReq: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AccessByteplusForm = ({ data, op, onAfterReq }: AccessByteplusFormProps) => {
|
||||||
|
const { addAccess, updateAccess } = useConfigContext();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const formSchema = z.object({
|
||||||
|
id: z.string().optional(),
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
.min(1, "access.authorization.form.name.placeholder")
|
||||||
|
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||||
|
configType: accessTypeFormSchema,
|
||||||
|
accessKey: z
|
||||||
|
.string()
|
||||||
|
.min(1, "access.authorization.form.access_key.placeholder")
|
||||||
|
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||||
|
secretKey: z
|
||||||
|
.string()
|
||||||
|
.min(1, "access.authorization.form.secret_key.placeholder")
|
||||||
|
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||||
|
});
|
||||||
|
|
||||||
|
let config: ByteplusConfig = {
|
||||||
|
accessKey: "",
|
||||||
|
secretKey: "",
|
||||||
|
};
|
||||||
|
if (data) config = data.config as ByteplusConfig;
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
id: data?.id,
|
||||||
|
name: data?.name || "",
|
||||||
|
configType: "byteplus",
|
||||||
|
accessKey: config.accessKey,
|
||||||
|
secretKey: config.secretKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||||
|
const req: Access = {
|
||||||
|
id: data.id as string,
|
||||||
|
name: data.name,
|
||||||
|
configType: data.configType,
|
||||||
|
usage: accessProvidersMap.get(data.configType)!.usage,
|
||||||
|
config: {
|
||||||
|
accessKey: data.accessKey,
|
||||||
|
secretKey: data.secretKey,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
req.id = op == "copy" ? "" : req.id;
|
||||||
|
const rs = await save(req);
|
||||||
|
|
||||||
|
onAfterReq();
|
||||||
|
|
||||||
|
req.id = rs.id;
|
||||||
|
req.created = rs.created;
|
||||||
|
req.updated = rs.updated;
|
||||||
|
if (data.id && op == "edit") {
|
||||||
|
updateAccess(req);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addAccess(req);
|
||||||
|
} catch (e) {
|
||||||
|
const err = e as ClientResponseError;
|
||||||
|
|
||||||
|
Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => {
|
||||||
|
form.setError(key as keyof z.infer<typeof formSchema>, {
|
||||||
|
type: "manual",
|
||||||
|
message: value.message,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
form.handleSubmit(onSubmit)(e);
|
||||||
|
}}
|
||||||
|
className="space-y-8"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="id"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="hidden">
|
||||||
|
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="configType"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="hidden">
|
||||||
|
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="accessKey"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("access.authorization.form.access_key.label")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder={t("access.authorization.form.access_key.placeholder")} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="secretKey"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("access.authorization.form.secret_key.label")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder={t("access.authorization.form.secret_key.placeholder")} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button type="submit">{t("common.save")}</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccessByteplusForm;
|
@ -22,6 +22,7 @@ import AccessSSHForm from "./AccessSSHForm";
|
|||||||
import AccessWebhookForm from "./AccessWebhookForm";
|
import AccessWebhookForm from "./AccessWebhookForm";
|
||||||
import AccessKubernetesForm from "./AccessKubernetesForm";
|
import AccessKubernetesForm from "./AccessKubernetesForm";
|
||||||
import AccessVolcengineForm from "./AccessVolcengineForm";
|
import AccessVolcengineForm from "./AccessVolcengineForm";
|
||||||
|
import AccessByteplusForm from "./AccessByteplusForm";
|
||||||
import { Access } from "@/domain/access";
|
import { Access } from "@/domain/access";
|
||||||
import { AccessTypeSelect } from "./AccessTypeSelect";
|
import { AccessTypeSelect } from "./AccessTypeSelect";
|
||||||
|
|
||||||
@ -235,6 +236,17 @@ const AccessEditDialog = ({ trigger, op, data, className, outConfigType }: Acces
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case "byteplus":
|
||||||
|
childComponent = (
|
||||||
|
<AccessByteplusForm
|
||||||
|
data={data}
|
||||||
|
op={op}
|
||||||
|
onAfterReq={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
@ -29,6 +28,7 @@ import DeployToWebhook from "./DeployToWebhook";
|
|||||||
import DeployToKubernetesSecret from "./DeployToKubernetesSecret";
|
import DeployToKubernetesSecret from "./DeployToKubernetesSecret";
|
||||||
import DeployToVolcengineLive from "./DeployToVolcengineLive";
|
import DeployToVolcengineLive from "./DeployToVolcengineLive";
|
||||||
import DeployToVolcengineCDN from "./DeployToVolcengineCDN";
|
import DeployToVolcengineCDN from "./DeployToVolcengineCDN";
|
||||||
|
import DeployToByteplusCDN from "./DeployToByteplusCDN";
|
||||||
import { deployTargetsMap, type DeployConfig } from "@/domain/domain";
|
import { deployTargetsMap, type DeployConfig } from "@/domain/domain";
|
||||||
import { accessProvidersMap } from "@/domain/access";
|
import { accessProvidersMap } from "@/domain/access";
|
||||||
import { useConfigContext } from "@/providers/config";
|
import { useConfigContext } from "@/providers/config";
|
||||||
@ -182,6 +182,9 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
|
|||||||
case "volcengine-cdn":
|
case "volcengine-cdn":
|
||||||
childComponent = <DeployToVolcengineCDN />;
|
childComponent = <DeployToVolcengineCDN />;
|
||||||
break;
|
break;
|
||||||
|
case "byteplus-cdn":
|
||||||
|
childComponent = <DeployToByteplusCDN />;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
68
ui/src/components/certimate/DeployToByteplusCDN.tsx
Normal file
68
ui/src/components/certimate/DeployToByteplusCDN.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { produce } from "immer";
|
||||||
|
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { useDeployEditContext } from "./DeployEdit";
|
||||||
|
|
||||||
|
type DeployToByteplusCDNConfigParams = {
|
||||||
|
domain?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeployToByteplusCDN = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToByteplusCDNConfigParams>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!config.id) {
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
config: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setErrors({});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||||
|
message: t("common.errmsg.domain_invalid"),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const res = formSchema.safeParse(config.config);
|
||||||
|
setErrors({
|
||||||
|
...errors,
|
||||||
|
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
|
||||||
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col space-y-8">
|
||||||
|
<div>
|
||||||
|
<Label>{t("domain.deployment.form.domain.label.wildsupported")}</Label>
|
||||||
|
<Input
|
||||||
|
placeholder={t("domain.deployment.form.domain.placeholder")}
|
||||||
|
className="w-full mt-1"
|
||||||
|
value={config?.config?.domain}
|
||||||
|
onChange={(e) => {
|
||||||
|
const nv = produce(config, (draft) => {
|
||||||
|
draft.config ??= {};
|
||||||
|
draft.config.domain = e.target.value?.trim();
|
||||||
|
});
|
||||||
|
setConfig(nv);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeployToByteplusCDN;
|
@ -18,6 +18,7 @@ export const accessProviders = [
|
|||||||
["qiniu", "common.provider.qiniu", "/imgs/providers/qiniu.svg", "deploy", "七牛云:qiniu"],
|
["qiniu", "common.provider.qiniu", "/imgs/providers/qiniu.svg", "deploy", "七牛云:qiniu"],
|
||||||
["dogecloud", "common.provider.dogecloud", "/imgs/providers/dogecloud.svg", "deploy", "多吉云:doge cloud"],
|
["dogecloud", "common.provider.dogecloud", "/imgs/providers/dogecloud.svg", "deploy", "多吉云:doge cloud"],
|
||||||
["volcengine", "common.provider.volcengine", "/imgs/providers/volcengine.svg", "all", "火山引擎:volcengine"],
|
["volcengine", "common.provider.volcengine", "/imgs/providers/volcengine.svg", "all", "火山引擎:volcengine"],
|
||||||
|
["byteplus", "common.provider.byteplus", "/imgs/providers/byteplus.svg", "all", "BytePlus"],
|
||||||
["aws", "common.provider.aws", "/imgs/providers/aws.svg", "apply", "亚马逊:amazon:aws"],
|
["aws", "common.provider.aws", "/imgs/providers/aws.svg", "apply", "亚马逊:amazon:aws"],
|
||||||
["cloudflare", "common.provider.cloudflare", "/imgs/providers/cloudflare.svg", "apply", "cloudflare:cf:cloud flare"],
|
["cloudflare", "common.provider.cloudflare", "/imgs/providers/cloudflare.svg", "apply", "cloudflare:cf:cloud flare"],
|
||||||
["namesilo", "common.provider.namesilo", "/imgs/providers/namesilo.svg", "apply", "namesilo"],
|
["namesilo", "common.provider.namesilo", "/imgs/providers/namesilo.svg", "apply", "namesilo"],
|
||||||
@ -53,6 +54,7 @@ export const accessTypeFormSchema = z.union(
|
|||||||
z.literal("webhook"),
|
z.literal("webhook"),
|
||||||
z.literal("k8s"),
|
z.literal("k8s"),
|
||||||
z.literal("volcengine"),
|
z.literal("volcengine"),
|
||||||
|
z.literal("byteplus"),
|
||||||
],
|
],
|
||||||
{ message: "access.authorization.form.type.placeholder" }
|
{ message: "access.authorization.form.type.placeholder" }
|
||||||
);
|
);
|
||||||
@ -79,7 +81,8 @@ export type Access = {
|
|||||||
| SSHConfig
|
| SSHConfig
|
||||||
| WebhookConfig
|
| WebhookConfig
|
||||||
| KubernetesConfig
|
| KubernetesConfig
|
||||||
| VolcengineConfig;
|
| VolcengineConfig
|
||||||
|
| ByteplusConfig;
|
||||||
deleted?: string;
|
deleted?: string;
|
||||||
created?: string;
|
created?: string;
|
||||||
updated?: string;
|
updated?: string;
|
||||||
@ -172,3 +175,8 @@ export type VolcengineConfig = {
|
|||||||
accessKeyId: string;
|
accessKeyId: string;
|
||||||
secretAccessKey: string;
|
secretAccessKey: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ByteplusConfig = {
|
||||||
|
accessKey: string;
|
||||||
|
secretKey: string;
|
||||||
|
};
|
||||||
|
@ -93,6 +93,7 @@ export const deployTargetList: string[][] = [
|
|||||||
["k8s-secret", "common.provider.kubernetes.secret", "/imgs/providers/k8s.svg"],
|
["k8s-secret", "common.provider.kubernetes.secret", "/imgs/providers/k8s.svg"],
|
||||||
["volcengine-live", "common.provider.volcengine.live", "/imgs/providers/volcengine.svg"],
|
["volcengine-live", "common.provider.volcengine.live", "/imgs/providers/volcengine.svg"],
|
||||||
["volcengine-cdn", "common.provider.volcengine.cdn", "/imgs/providers/volcengine.svg"],
|
["volcengine-cdn", "common.provider.volcengine.cdn", "/imgs/providers/volcengine.svg"],
|
||||||
|
["byteplus-cdn", "common.provider.byteplus.cdn", "/imgs/providers/byteplus.svg"],
|
||||||
];
|
];
|
||||||
|
|
||||||
export const deployTargetsMap: Map<DeployTarget["type"], DeployTarget> = new Map(
|
export const deployTargetsMap: Map<DeployTarget["type"], DeployTarget> = new Map(
|
||||||
|
@ -93,5 +93,7 @@
|
|||||||
"common.provider.bark": "Bark",
|
"common.provider.bark": "Bark",
|
||||||
"common.provider.volcengine": "Volcengine",
|
"common.provider.volcengine": "Volcengine",
|
||||||
"common.provider.volcengine.live": "Volcengine - Live",
|
"common.provider.volcengine.live": "Volcengine - Live",
|
||||||
"common.provider.volcengine.cdn": "Volcengine - CDN"
|
"common.provider.volcengine.cdn": "Volcengine - CDN",
|
||||||
|
"common.provider.byteplus": "BytePlus",
|
||||||
|
"common.provider.byteplus.cdn": "BytePlus - CDN"
|
||||||
}
|
}
|
||||||
|
@ -93,5 +93,7 @@
|
|||||||
"common.provider.bark": "Bark",
|
"common.provider.bark": "Bark",
|
||||||
"common.provider.volcengine": "火山引擎",
|
"common.provider.volcengine": "火山引擎",
|
||||||
"common.provider.volcengine.live": "火山引擎 - 视频直播",
|
"common.provider.volcengine.live": "火山引擎 - 视频直播",
|
||||||
"common.provider.volcengine.cdn": "火山引擎 - CDN"
|
"common.provider.volcengine.cdn": "火山引擎 - CDN",
|
||||||
|
"common.provider.byteplus": "BytePlus",
|
||||||
|
"common.provider.byteplus.cdn": "BytePlus - CDN"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user