Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c853f2976f | ||
|
|
b66931003f | ||
|
|
42c5aea3f7 | ||
|
|
e2fd9c4cee | ||
|
|
f847b7ff62 | ||
|
|
9eae8f5077 | ||
|
|
2bacf76664 | ||
|
|
b2030caedc | ||
|
|
956c975c6d | ||
|
|
c298f8b952 | ||
|
|
e2562a5251 | ||
|
|
dbdb40baf9 | ||
|
|
2ff923dd1b | ||
|
|
f4f13f91f2 | ||
|
|
034aa980e6 | ||
|
|
6ac7a51ce0 | ||
|
|
cf0c0e3e2c | ||
|
|
1b899575e0 | ||
|
|
23e5cb5669 | ||
|
|
e4ba4c9b37 | ||
|
|
9ed64bdc9a | ||
|
|
e9b6fb55ff | ||
|
|
80caf881ae | ||
|
|
c36db3545f | ||
|
|
a367585ab4 | ||
|
|
2994cb5c65 | ||
|
|
1bedb31a3c | ||
|
|
8fecebc254 | ||
|
|
44497a0969 | ||
|
|
5362371bda | ||
|
|
8b04e96a7d | ||
|
|
5d93334426 | ||
|
|
150b666d4b | ||
|
|
94579d65c4 | ||
|
|
551b06b4e8 | ||
|
|
76fc47a274 | ||
|
|
35e1bfcd7f | ||
|
|
24df7913fe | ||
|
|
83674e4b35 | ||
|
|
22d3aeb7b5 | ||
|
|
cf005711c0 | ||
|
|
0a00d0c52f | ||
|
|
9aa17a0395 | ||
|
|
65ecdf7dc2 | ||
|
|
0dfa5994cc | ||
|
|
5d2844fdb6 | ||
|
|
44332b9d07 | ||
|
|
20a23e148c | ||
|
|
0bcb6206f4 | ||
|
|
943b9827ee | ||
|
|
741f3ec212 | ||
|
|
8549a17675 | ||
|
|
718cfccbea | ||
|
|
2458fa26d8 | ||
|
|
ac24684d2b | ||
|
|
106dbd9538 | ||
|
|
f9efb2b800 | ||
|
|
897d124d5b | ||
|
|
34daf9ccac | ||
|
|
269a97e81e | ||
|
|
2fd57621d8 | ||
|
|
76de837214 | ||
|
|
1e41020728 | ||
|
|
8a78e49bf0 |
20
README.md
20
README.md
@@ -75,8 +75,11 @@ make local.run
|
||||
| :--------: | :----------: | :----------: | ----------------------------------------------------------------- |
|
||||
| 阿里云 | √ | √ | 可签发在阿里云注册的域名;可部署到阿里云 OSS、CDN、SLB |
|
||||
| 腾讯云 | √ | √ | 可签发在腾讯云注册的域名;可部署到腾讯云 COS、CDN、ECDN、CLB、TEO |
|
||||
| 百度智能云 | | √ | 可部署到百度智能云 CDN |
|
||||
| 华为云 | √ | √ | 可签发在华为云注册的域名;可部署到华为云 CDN、ELB |
|
||||
| 七牛云 | | √ | 可部署到七牛云 CDN |
|
||||
| 多吉云 | | √ | 可部署到多吉云 CDN |
|
||||
| 火山引擎 | √ | √ | 可签发在火山引擎注册的域名;可部署到火山引擎 Live、CDN |
|
||||
| AWS | √ | | 可签发在 AWS Route53 托管的域名 |
|
||||
| CloudFlare | √ | | 可签发在 CloudFlare 注册的域名;CloudFlare 服务自带 SSL 证书 |
|
||||
| GoDaddy | √ | | 可签发在 GoDaddy 注册的域名 |
|
||||
@@ -90,15 +93,13 @@ make local.run
|
||||
|
||||
## 四、系统截图
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
<div align="center">
|
||||
<img src="https://i.imgur.com/SYjjbql.jpeg" title="Login page" width="95%"/>
|
||||
<img src="https://i.imgur.com/WMVbBId.jpeg" title="Dashboard page" width="47%"/>
|
||||
<img src="https://i.imgur.com/8wit3ZA.jpeg" title="Domains page" width="47%"/>
|
||||
<img src="https://i.imgur.com/EWtOoJ0.jpeg" title="Accesses page" width="47%"/>
|
||||
<img src="https://i.imgur.com/aaPtSW7.jpeg" title="History page" width="47%"/>
|
||||
</div>
|
||||
|
||||
## 五、概念
|
||||
|
||||
@@ -188,3 +189,4 @@ Certimate 是一个免费且开源的项目,采用 [MIT 开源协议](LICENSE.
|
||||
## 十、Star 趋势图
|
||||
|
||||
[](https://starchart.cc/usual2970/certimate)
|
||||
|
||||
|
||||
51
README_EN.md
51
README_EN.md
@@ -70,34 +70,35 @@ password:1234567890
|
||||
|
||||
## List of Supported Providers
|
||||
|
||||
| Provider | Registration | Deployment | Remarks |
|
||||
| :-----------: | :----------: | :--------: | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| Alibaba Cloud | √ | √ | Supports domains registered on Alibaba Cloud; supports deployment to Alibaba Cloud OSS, CDN,SLB |
|
||||
| Tencent Cloud | √ | √ | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud COS, CDN, ECDN, CLB, TEO |
|
||||
| Huawei Cloud | √ | √ | Supports domains registered on Huawei Cloud; supports deployment to Huawei Cloud CDN, ELB |
|
||||
| Qiniu Cloud | | √ | Supports deployment to Qiniu Cloud CDN |
|
||||
| AWS | √ | | Supports domains managed on AWS Route53 |
|
||||
| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
|
||||
| GoDaddy | √ | | Supports domains registered on GoDaddy |
|
||||
| Namesilo | √ | | Supports domains registered on Namesilo |
|
||||
| PowerDNS | √ | | Supports domains managed on PowerDNS |
|
||||
| HTTP Request | √ | | Supports domains which allow managing DNS by HTTP request |
|
||||
| Local Deploy | | √ | Supports deployment to local servers |
|
||||
| SSH | | √ | Supports deployment to SSH servers |
|
||||
| Webhook | | √ | Supports callback to Webhook |
|
||||
| Kubernetes | | √ | Supports deployment to Kubernetes Secret |
|
||||
| Provider | Registration | Deployment | Remarks |
|
||||
| :-----------: | :----------: | :--------: |-------------------------------------------------------------------------------------------------------------|
|
||||
| Alibaba Cloud | √ | √ | Supports domains registered on Alibaba Cloud; supports deployment to Alibaba Cloud OSS, CDN,SLB |
|
||||
| Tencent Cloud | √ | √ | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud COS, CDN, ECDN, CLB, TEO |
|
||||
| Baidu Cloud | | √ | Supports deployment to Baidu Cloud CDN |
|
||||
| Huawei Cloud | √ | √ | Supports domains registered on Huawei Cloud; supports deployment to Huawei Cloud CDN, ELB |
|
||||
| Qiniu Cloud | | √ | Supports deployment to Qiniu Cloud CDN |
|
||||
| Doge Cloud | | √ | Supports deployment to Doge Cloud CDN |
|
||||
| Volcengine | √ | √ | Supports domains registered on Volcengine; supports deployment to Volcengine Live, CDN |
|
||||
| AWS | √ | | Supports domains managed on AWS Route53 |
|
||||
| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
|
||||
| GoDaddy | √ | | Supports domains registered on GoDaddy |
|
||||
| Namesilo | √ | | Supports domains registered on Namesilo |
|
||||
| PowerDNS | √ | | Supports domains managed on PowerDNS |
|
||||
| HTTP Request | √ | | Supports domains which allow managing DNS by HTTP request |
|
||||
| Local Deploy | | √ | Supports deployment to local servers |
|
||||
| SSH | | √ | Supports deployment to SSH servers |
|
||||
| Webhook | | √ | Supports callback to Webhook |
|
||||
| Kubernetes | | √ | Supports deployment to Kubernetes Secret |
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
<div align="center">
|
||||
<img src="https://i.imgur.com/SYjjbql.jpeg" title="Login page" width="95%"/>
|
||||
<img src="https://i.imgur.com/WMVbBId.jpeg" title="Dashboard page" width="47%"/>
|
||||
<img src="https://i.imgur.com/8wit3ZA.jpeg" title="Domains page" width="47%"/>
|
||||
<img src="https://i.imgur.com/EWtOoJ0.jpeg" title="Accesses page" width="47%"/>
|
||||
<img src="https://i.imgur.com/aaPtSW7.jpeg" title="History page" width="47%"/>
|
||||
</div>
|
||||
|
||||
## Concepts
|
||||
|
||||
|
||||
58
go.mod
58
go.mod
@@ -13,9 +13,10 @@ require (
|
||||
github.com/alibabacloud-go/slb-20140515/v4 v4.0.9
|
||||
github.com/alibabacloud-go/tea v1.2.2
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/go-acme/lego/v4 v4.19.2
|
||||
github.com/baidubce/bce-sdk-go v0.9.197
|
||||
github.com/go-acme/lego/v4 v4.20.2
|
||||
github.com/gojek/heimdall/v7 v7.0.3
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.120
|
||||
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
|
||||
github.com/nikoksr/notify v1.0.0
|
||||
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0
|
||||
@@ -25,9 +26,10 @@ require (
|
||||
github.com/qiniu/go-sdk/v7 v7.22.0
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1031
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1031
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1034
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1030
|
||||
github.com/volcengine/volc-sdk-golang v1.0.184
|
||||
golang.org/x/crypto v0.28.0
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
k8s.io/api v0.31.1
|
||||
@@ -42,7 +44,7 @@ require (
|
||||
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect
|
||||
github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.43.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.46.0 // indirect
|
||||
github.com/blinkbean/dingtalk v1.1.3 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
@@ -85,34 +87,34 @@ require (
|
||||
github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
|
||||
github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.15 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.47 // indirect
|
||||
github.com/aliyun/credentials-go v1.3.10 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.33 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.42 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 // indirect
|
||||
github.com/aws/smithy-go v1.20.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.32.3 // indirect
|
||||
github.com/aws/smithy-go v1.22.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.5.6 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.104.0 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.108.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/disintegration/imaging v1.6.2 // indirect
|
||||
github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
|
||||
github.com/domodwyer/mailyak/v3 v3.6.2
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
||||
@@ -121,7 +123,7 @@ require (
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
@@ -149,7 +151,7 @@ require (
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.9.0 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1034 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
@@ -166,8 +168,8 @@ require (
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
golang.org/x/tools v0.25.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
|
||||
google.golang.org/api v0.202.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||
google.golang.org/api v0.204.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
|
||||
google.golang.org/grpc v1.67.1 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
|
||||
@@ -35,6 +35,7 @@ const (
|
||||
configTypeGodaddy = "godaddy"
|
||||
configTypePdns = "pdns"
|
||||
configTypeHttpreq = "httpreq"
|
||||
configTypeVolcengine = "volcengine"
|
||||
)
|
||||
|
||||
const defaultSSLProvider = "letsencrypt"
|
||||
@@ -188,6 +189,8 @@ func Get(record *models.Record) (Applicant, error) {
|
||||
return NewPdns(option), nil
|
||||
case configTypeHttpreq:
|
||||
return NewHttpreq(option), nil
|
||||
case configTypeVolcengine:
|
||||
return NewVolcengine(option), nil
|
||||
default:
|
||||
return nil, errors.New("unknown config type")
|
||||
}
|
||||
|
||||
35
internal/applicant/volcengine.go
Normal file
35
internal/applicant/volcengine.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package applicant
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
volcengineDns "github.com/go-acme/lego/v4/providers/dns/volcengine"
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
)
|
||||
|
||||
type volcengine struct {
|
||||
option *ApplyOption
|
||||
}
|
||||
|
||||
func NewVolcengine(option *ApplyOption) Applicant {
|
||||
return &volcengine{
|
||||
option: option,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *volcengine) Apply() (*Certificate, error) {
|
||||
access := &domain.VolcengineAccess{}
|
||||
json.Unmarshal([]byte(a.option.Access), access)
|
||||
|
||||
os.Setenv("VOLC_ACCESSKEY", access.AccessKeyID)
|
||||
os.Setenv("VOLC_SECRETKEY", access.SecretAccessKey)
|
||||
os.Setenv("VOLC_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
|
||||
dnsProvider, err := volcengineDns.NewDNSProvider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return apply(a.option, dnsProvider)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
aliyunAlb "github.com/alibabacloud-go/alb-20200616/v2/client"
|
||||
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
@@ -39,10 +40,21 @@ func NewAliyunALBDeployer(option *DeployerOption) (Deployer, error) {
|
||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||
}
|
||||
|
||||
aliCasRegion := option.DeployConfig.GetConfigAsString("region")
|
||||
if aliCasRegion != "" {
|
||||
// 阿里云 CAS 服务接入点是独立于 ALB 服务的
|
||||
// 国内版接入点:华东一杭州
|
||||
// 国际版接入点:亚太东南一新加坡
|
||||
if !strings.HasPrefix(aliCasRegion, "cn-") {
|
||||
aliCasRegion = "ap-southeast-1"
|
||||
} else {
|
||||
aliCasRegion = "cn-hangzhou"
|
||||
}
|
||||
}
|
||||
uploader, err := uploaderAliyunCas.New(&uploaderAliyunCas.AliyunCASUploaderConfig{
|
||||
AccessKeyId: access.AccessKeyId,
|
||||
AccessKeySecret: access.AccessKeySecret,
|
||||
Region: option.DeployConfig.GetConfigAsString("region"),
|
||||
Region: aliCasRegion,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||
|
||||
@@ -93,10 +93,11 @@ func (d *AliyunCLBDeployer) createSdkClient(accessKeyId, accessKeySecret, region
|
||||
|
||||
var endpoint string
|
||||
switch region {
|
||||
case "cn-hangzhou":
|
||||
case "cn-hangzhou-finance":
|
||||
case "cn-shanghai-finance-1":
|
||||
case "cn-shenzhen-finance-1":
|
||||
case
|
||||
"cn-hangzhou",
|
||||
"cn-hangzhou-finance",
|
||||
"cn-shanghai-finance-1",
|
||||
"cn-shenzhen-finance-1":
|
||||
endpoint = "slb.aliyuncs.com"
|
||||
default:
|
||||
endpoint = fmt.Sprintf("slb.%s.aliyuncs.com", region)
|
||||
@@ -246,10 +247,10 @@ func (d *AliyunCLBDeployer) updateListenerCertificate(ctx context.Context, aliLo
|
||||
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setdomainextensionattribute
|
||||
//
|
||||
// 这里仅修改跟被替换证书一致的扩展域名
|
||||
if describeDomainExtensionsResp.Body.DomainExtensions == nil && describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension == nil {
|
||||
if describeDomainExtensionsResp.Body.DomainExtensions != nil && describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension != nil {
|
||||
for _, domainExtension := range describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension {
|
||||
if *domainExtension.ServerCertificateId == *describeLoadBalancerHTTPSListenerAttributeResp.Body.ServerCertificateId {
|
||||
break
|
||||
if *domainExtension.ServerCertificateId != *describeLoadBalancerHTTPSListenerAttributeResp.Body.ServerCertificateId {
|
||||
continue
|
||||
}
|
||||
|
||||
setDomainExtensionAttributeReq := &aliyunSlb.SetDomainExtensionAttributeRequest{
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
aliyunNlb "github.com/alibabacloud-go/nlb-20220430/v2/client"
|
||||
@@ -39,10 +40,21 @@ func NewAliyunNLBDeployer(option *DeployerOption) (Deployer, error) {
|
||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||
}
|
||||
|
||||
aliCasRegion := option.DeployConfig.GetConfigAsString("region")
|
||||
if aliCasRegion != "" {
|
||||
// 阿里云 CAS 服务接入点是独立于 NLB 服务的
|
||||
// 国内版接入点:华东一杭州
|
||||
// 国际版接入点:亚太东南一新加坡
|
||||
if !strings.HasPrefix(aliCasRegion, "cn-") {
|
||||
aliCasRegion = "ap-southeast-1"
|
||||
} else {
|
||||
aliCasRegion = "cn-hangzhou"
|
||||
}
|
||||
}
|
||||
uploader, err := uploaderAliyunCas.New(&uploaderAliyunCas.AliyunCASUploaderConfig{
|
||||
AccessKeyId: access.AccessKeyId,
|
||||
AccessKeySecret: access.AccessKeySecret,
|
||||
Region: option.DeployConfig.GetConfigAsString("region"),
|
||||
Region: aliCasRegion,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||
|
||||
80
internal/deployer/baiducloud_cdn.go
Normal file
80
internal/deployer/baiducloud_cdn.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package deployer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
bceCdn "github.com/baidubce/bce-sdk-go/services/cdn"
|
||||
bceCdnApi "github.com/baidubce/bce-sdk-go/services/cdn/api"
|
||||
xerrors "github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
)
|
||||
|
||||
type BaiduCloudCDNDeployer struct {
|
||||
option *DeployerOption
|
||||
infos []string
|
||||
|
||||
sdkClient *bceCdn.Client
|
||||
}
|
||||
|
||||
func NewBaiduCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||
access := &domain.BaiduCloudAccess{}
|
||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to get access")
|
||||
}
|
||||
|
||||
client, err := (&BaiduCloudCDNDeployer{}).createSdkClient(
|
||||
access.AccessKeyId,
|
||||
access.SecretAccessKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||
}
|
||||
|
||||
return &BaiduCloudCDNDeployer{
|
||||
option: option,
|
||||
infos: make([]string, 0),
|
||||
sdkClient: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *BaiduCloudCDNDeployer) GetID() string {
|
||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||
}
|
||||
|
||||
func (d *BaiduCloudCDNDeployer) GetInfos() []string {
|
||||
return d.infos
|
||||
}
|
||||
|
||||
func (d *BaiduCloudCDNDeployer) Deploy(ctx context.Context) error {
|
||||
// 修改域名证书
|
||||
// REF: https://cloud.baidu.com/doc/CDN/s/qjzuz2hp8
|
||||
putCertResp, err := d.sdkClient.PutCert(
|
||||
d.option.DeployConfig.GetConfigAsString("domain"),
|
||||
&bceCdnApi.UserCertificate{
|
||||
CertName: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()),
|
||||
ServerData: d.option.Certificate.Certificate,
|
||||
PrivateData: d.option.Certificate.PrivateKey,
|
||||
},
|
||||
"ON",
|
||||
)
|
||||
if err != nil {
|
||||
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.PutCert'")
|
||||
}
|
||||
|
||||
d.infos = append(d.infos, toStr("已修改域名证书", putCertResp))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *BaiduCloudCDNDeployer) createSdkClient(accessKeyId, secretAccessKey string) (*bceCdn.Client, error) {
|
||||
client, err := bceCdn.NewClient(accessKeyId, secretAccessKey, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
@@ -33,11 +33,15 @@ const (
|
||||
targetTencentTEO = "tencent-teo"
|
||||
targetHuaweiCloudCDN = "huaweicloud-cdn"
|
||||
targetHuaweiCloudELB = "huaweicloud-elb"
|
||||
targetBaiduCloudCDN = "baiducloud-cdn"
|
||||
targetQiniuCdn = "qiniu-cdn"
|
||||
targetDogeCloudCdn = "dogecloud-cdn"
|
||||
targetLocal = "local"
|
||||
targetSSH = "ssh"
|
||||
targetWebhook = "webhook"
|
||||
targetK8sSecret = "k8s-secret"
|
||||
targetVolcengineLive = "volcengine-live"
|
||||
targetVolcengineCDN = "volcengine-cdn"
|
||||
)
|
||||
|
||||
type DeployerOption struct {
|
||||
@@ -134,8 +138,12 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
|
||||
return NewHuaweiCloudCDNDeployer(option)
|
||||
case targetHuaweiCloudELB:
|
||||
return NewHuaweiCloudELBDeployer(option)
|
||||
case targetBaiduCloudCDN:
|
||||
return NewBaiduCloudCDNDeployer(option)
|
||||
case targetQiniuCdn:
|
||||
return NewQiniuCDNDeployer(option)
|
||||
case targetDogeCloudCdn:
|
||||
return NewDogeCloudCDNDeployer(option)
|
||||
case targetLocal:
|
||||
return NewLocalDeployer(option)
|
||||
case targetSSH:
|
||||
@@ -144,6 +152,10 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
|
||||
return NewWebhookDeployer(option)
|
||||
case targetK8sSecret:
|
||||
return NewK8sSecretDeployer(option)
|
||||
case targetVolcengineLive:
|
||||
return NewVolcengineLiveDeployer(option)
|
||||
case targetVolcengineCDN:
|
||||
return NewVolcengineCDNDeployer(option)
|
||||
}
|
||||
return nil, errors.New("unsupported deploy target")
|
||||
}
|
||||
|
||||
88
internal/deployer/dogecloud_cdn.go
Normal file
88
internal/deployer/dogecloud_cdn.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package deployer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
xerrors "github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||
uploaderDoge "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/dogecloud"
|
||||
doge "github.com/usual2970/certimate/internal/pkg/vendors/dogecloud-sdk"
|
||||
)
|
||||
|
||||
type DogeCloudCDNDeployer struct {
|
||||
option *DeployerOption
|
||||
infos []string
|
||||
|
||||
sdkClient *doge.Client
|
||||
sslUploader uploader.Uploader
|
||||
}
|
||||
|
||||
func NewDogeCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||
access := &domain.DogeCloudAccess{}
|
||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to get access")
|
||||
}
|
||||
|
||||
client, err := (&DogeCloudCDNDeployer{}).createSdkClient(
|
||||
access.AccessKey,
|
||||
access.SecretKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||
}
|
||||
|
||||
uploader, err := uploaderDoge.New(&uploaderDoge.DogeCloudUploaderConfig{
|
||||
AccessKey: access.AccessKey,
|
||||
SecretKey: access.SecretKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||
}
|
||||
|
||||
return &DogeCloudCDNDeployer{
|
||||
option: option,
|
||||
infos: make([]string, 0),
|
||||
sdkClient: client,
|
||||
sslUploader: uploader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *DogeCloudCDNDeployer) GetID() string {
|
||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||
}
|
||||
|
||||
func (d *DogeCloudCDNDeployer) GetInfos() []string {
|
||||
return d.infos
|
||||
}
|
||||
|
||||
func (d *DogeCloudCDNDeployer) Deploy(ctx context.Context) error {
|
||||
// 上传证书到 CDN
|
||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||
|
||||
// 绑定证书
|
||||
// REF: https://docs.dogecloud.com/cdn/api-cert-bind
|
||||
bindCdnCertId, _ := strconv.ParseInt(upres.CertId, 10, 64)
|
||||
bindCdnCertResp, err := d.sdkClient.BindCdnCertWithDomain(bindCdnCertId, d.option.DeployConfig.GetConfigAsString("domain"))
|
||||
if err != nil {
|
||||
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.BindCdnCert'")
|
||||
}
|
||||
|
||||
d.infos = append(d.infos, toStr("已绑定证书", bindCdnCertResp))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DogeCloudCDNDeployer) createSdkClient(accessKey, secretKey string) (*doge.Client, error) {
|
||||
client := doge.NewClient(accessKey, secretKey)
|
||||
return client, nil
|
||||
}
|
||||
@@ -105,6 +105,9 @@ func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
||||
}
|
||||
|
||||
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
||||
|
||||
default:
|
||||
return errors.New("unsupported format")
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
xerrors "github.com/pkg/errors"
|
||||
"github.com/qiniu/go-sdk/v7/auth"
|
||||
@@ -69,9 +70,14 @@ func (d *QiniuCDNDeployer) Deploy(ctx context.Context) error {
|
||||
|
||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
||||
|
||||
// 在七牛 CDN 中泛域名表示为 .example.com,需去除前缀星号
|
||||
domain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||
if strings.HasPrefix(domain, "*") {
|
||||
domain = strings.TrimPrefix(domain, "*")
|
||||
}
|
||||
|
||||
// 获取域名信息
|
||||
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
||||
domain := d.option.DeployConfig.GetConfigAsString("domain")
|
||||
getDomainInfoResp, err := d.sdkClient.GetDomainInfo(domain)
|
||||
if err != nil {
|
||||
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDomainInfo'")
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/fs"
|
||||
)
|
||||
|
||||
type SSHDeployer struct {
|
||||
@@ -105,11 +105,14 @@ func (d *SSHDeployer) Deploy(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fs.WriteFile(d.option.DeployConfig.GetConfigAsString("certPath"), jksData); err != nil {
|
||||
if err := d.writeSftpFile(client, d.option.DeployConfig.GetConfigAsString("certPath"), jksData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
||||
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
||||
|
||||
default:
|
||||
return errors.New("unsupported format")
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
@@ -156,8 +159,8 @@ func (d *SSHDeployer) createSshClient(access *domain.SSHAccess) (*ssh.Client, er
|
||||
})
|
||||
}
|
||||
|
||||
func (d *SSHDeployer) sshExecCommand(client *ssh.Client, command string) (string, string, error) {
|
||||
session, err := client.NewSession()
|
||||
func (d *SSHDeployer) sshExecCommand(sshCli *ssh.Client, command string) (string, string, error) {
|
||||
session, err := sshCli.NewSession()
|
||||
if err != nil {
|
||||
return "", "", xerrors.Wrap(err, "failed to create ssh session")
|
||||
}
|
||||
@@ -175,12 +178,12 @@ func (d *SSHDeployer) sshExecCommand(client *ssh.Client, command string) (string
|
||||
return stdoutBuf.String(), stderrBuf.String(), nil
|
||||
}
|
||||
|
||||
func (d *SSHDeployer) writeSftpFileString(client *ssh.Client, path string, content string) error {
|
||||
return d.writeSftpFile(client, path, []byte(content))
|
||||
func (d *SSHDeployer) writeSftpFileString(sshCli *ssh.Client, path string, content string) error {
|
||||
return d.writeSftpFile(sshCli, path, []byte(content))
|
||||
}
|
||||
|
||||
func (d *SSHDeployer) writeSftpFile(client *ssh.Client, path string, data []byte) error {
|
||||
sftpCli, err := sftp.NewClient(client)
|
||||
func (d *SSHDeployer) writeSftpFile(sshCli *ssh.Client, path string, data []byte) error {
|
||||
sftpCli, err := sftp.NewClient(sshCli)
|
||||
if err != nil {
|
||||
return xerrors.Wrap(err, "failed to create sftp client")
|
||||
}
|
||||
|
||||
@@ -70,8 +70,6 @@ func (d *TencentCLBDeployer) GetInfos() []string {
|
||||
}
|
||||
|
||||
func (d *TencentCLBDeployer) Deploy(ctx context.Context) error {
|
||||
// TODO: 直接部署方式
|
||||
|
||||
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
||||
case "ssl-deploy":
|
||||
// 通过 SSL 服务部署到云资源实例
|
||||
@@ -104,7 +102,7 @@ func (d *TencentCLBDeployer) Deploy(ctx context.Context) error {
|
||||
func (d *TencentCLBDeployer) createSdkClients(secretId, secretKey, region string) (*tencentCLBDeployerSdkClients, error) {
|
||||
credential := common.NewCredential(secretId, secretKey)
|
||||
|
||||
sslClient, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
||||
sslClient, err := tcSsl.NewClient(credential, region, profile.NewClientProfile())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ func NewTencentCOSDeployer(option *DeployerOption) (Deployer, error) {
|
||||
client, err := (&TencentCOSDeployer{}).createSdkClient(
|
||||
access.SecretId,
|
||||
access.SecretKey,
|
||||
option.DeployConfig.GetConfigAsString("region"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||
@@ -95,9 +96,9 @@ func (d *TencentCOSDeployer) Deploy(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *TencentCOSDeployer) createSdkClient(secretId, secretKey string) (*tcSsl.Client, error) {
|
||||
func (d *TencentCOSDeployer) createSdkClient(secretId, secretKey, region string) (*tcSsl.Client, error) {
|
||||
credential := common.NewCredential(secretId, secretKey)
|
||||
client, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
||||
client, err := tcSsl.NewClient(credential, region, profile.NewClientProfile())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
116
internal/deployer/volcengine_cdn.go
Normal file
116
internal/deployer/volcengine_cdn.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package deployer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
volcenginecdn "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-cdn"
|
||||
|
||||
xerrors "github.com/pkg/errors"
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||
"github.com/volcengine/volc-sdk-golang/service/cdn"
|
||||
)
|
||||
|
||||
type VolcengineCDNDeployer struct {
|
||||
option *DeployerOption
|
||||
infos []string
|
||||
sdkClient *cdn.CDN
|
||||
sslUploader uploader.Uploader
|
||||
}
|
||||
|
||||
func NewVolcengineCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||
access := &domain.VolcengineAccess{}
|
||||
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.AccessKeyID)
|
||||
client.Client.SetSecretKey(access.SecretAccessKey)
|
||||
uploader, err := volcenginecdn.New(&volcenginecdn.VolcengineCDNUploaderConfig{
|
||||
AccessKeyId: access.AccessKeyID,
|
||||
AccessKeySecret: access.SecretAccessKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||
}
|
||||
return &VolcengineCDNDeployer{
|
||||
option: option,
|
||||
infos: make([]string, 0),
|
||||
sdkClient: client,
|
||||
sslUploader: uploader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *VolcengineCDNDeployer) GetID() string {
|
||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||
}
|
||||
|
||||
func (d *VolcengineCDNDeployer) GetInfos() []string {
|
||||
return d.infos
|
||||
}
|
||||
|
||||
func (d *VolcengineCDNDeployer) 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://www.volcengine.com/docs/6454/125711
|
||||
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://www.volcengine.com/docs/6454/125712
|
||||
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
|
||||
}
|
||||
148
internal/deployer/volcengine_live.go
Normal file
148
internal/deployer/volcengine_live.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package deployer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
xerrors "github.com/pkg/errors"
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||
volcenginelive "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-live"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||
"github.com/volcengine/volc-sdk-golang/base"
|
||||
live "github.com/volcengine/volc-sdk-golang/service/live/v20230101"
|
||||
)
|
||||
|
||||
type VolcengineLiveDeployer struct {
|
||||
option *DeployerOption
|
||||
infos []string
|
||||
sdkClient *live.Live
|
||||
sslUploader uploader.Uploader
|
||||
}
|
||||
|
||||
func NewVolcengineLiveDeployer(option *DeployerOption) (Deployer, error) {
|
||||
access := &domain.VolcengineAccess{}
|
||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to get access")
|
||||
}
|
||||
client := live.NewInstance()
|
||||
client.SetCredential(base.Credentials{
|
||||
AccessKeyID: access.AccessKeyID,
|
||||
SecretAccessKey: access.SecretAccessKey,
|
||||
})
|
||||
uploader, err := volcenginelive.New(&volcenginelive.VolcengineLiveUploaderConfig{
|
||||
AccessKeyId: access.AccessKeyID,
|
||||
AccessKeySecret: access.SecretAccessKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||
}
|
||||
return &VolcengineLiveDeployer{
|
||||
option: option,
|
||||
infos: make([]string, 0),
|
||||
sdkClient: client,
|
||||
sslUploader: uploader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *VolcengineLiveDeployer) GetID() string {
|
||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||
}
|
||||
|
||||
func (d *VolcengineLiveDeployer) GetInfos() []string {
|
||||
return d.infos
|
||||
}
|
||||
|
||||
func (d *VolcengineLiveDeployer) 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, "*.") {
|
||||
// 如果是泛域名,获取所有的域名并匹配
|
||||
matchDomains, err := d.getDomainsByWildcardDomain(apiCtx, configDomain)
|
||||
if err != nil {
|
||||
d.infos = append(d.infos, toStr("获取域名列表失败", upres))
|
||||
return xerrors.Wrap(err, "failed to execute sdk request 'live.ListDomainDetail'")
|
||||
}
|
||||
if len(matchDomains) == 0 {
|
||||
return xerrors.Errorf("未查询到匹配的域名: %s", configDomain)
|
||||
}
|
||||
domains = matchDomains
|
||||
} else {
|
||||
domains = append(domains, configDomain)
|
||||
}
|
||||
|
||||
// 部署证书
|
||||
// REF: https://www.volcengine.com/docs/6469/1186278#%E7%BB%91%E5%AE%9A%E8%AF%81%E4%B9%A6d
|
||||
for i := range domains {
|
||||
bindCertReq := &live.BindCertBody{
|
||||
ChainID: upres.CertId,
|
||||
Domain: domains[i],
|
||||
HTTPS: cast.BoolPtr(true),
|
||||
}
|
||||
bindCertResp, err := d.sdkClient.BindCert(apiCtx, bindCertReq)
|
||||
if err != nil {
|
||||
return xerrors.Wrap(err, "failed to execute sdk request 'live.BindCert'")
|
||||
} else {
|
||||
d.infos = append(d.infos, toStr(fmt.Sprintf("%s域名的证书已修改", domains[i]), bindCertResp))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *VolcengineLiveDeployer) getDomainsByWildcardDomain(ctx context.Context, wildcardDomain string) ([]string, error) {
|
||||
pageNum := int32(1)
|
||||
searchTotal := 0
|
||||
domains := make([]string, 0)
|
||||
for {
|
||||
listDomainDetailReq := &live.ListDomainDetailBody{
|
||||
PageNum: pageNum,
|
||||
PageSize: 1000,
|
||||
}
|
||||
// 查询域名列表
|
||||
// REF: https://www.volcengine.com/docs/6469/1186277#%E6%9F%A5%E8%AF%A2%E5%9F%9F%E5%90%8D%E5%88%97%E8%A1%A8
|
||||
listDomainDetailResp, err := d.sdkClient.ListDomainDetail(ctx, listDomainDetailReq)
|
||||
if err != nil {
|
||||
return domains, err
|
||||
}
|
||||
if listDomainDetailResp.Result.DomainList != nil {
|
||||
for _, item := range listDomainDetailResp.Result.DomainList {
|
||||
if matchWildcardDomain(item.Domain, wildcardDomain) {
|
||||
domains = append(domains, item.Domain)
|
||||
}
|
||||
}
|
||||
}
|
||||
searchTotal += len(listDomainDetailResp.Result.DomainList)
|
||||
if int(listDomainDetailResp.Result.Total) > searchTotal {
|
||||
pageNum++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
func matchWildcardDomain(domain, wildcardDomain string) bool {
|
||||
if strings.HasPrefix(wildcardDomain, "*.") {
|
||||
if "*."+domain == wildcardDomain {
|
||||
return true
|
||||
}
|
||||
regexPattern := "^([a-zA-Z0-9_-]+)\\." + regexp.QuoteMeta(wildcardDomain[2:]) + "$"
|
||||
regex := regexp.MustCompile(regexPattern)
|
||||
return regex.MatchString(domain)
|
||||
}
|
||||
return domain == wildcardDomain
|
||||
}
|
||||
@@ -11,7 +11,12 @@ type TencentAccess struct {
|
||||
}
|
||||
|
||||
type HuaweiCloudAccess struct {
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
SecretAccessKey string `json:"secretAccessKey"`
|
||||
Region string `json:"region"`
|
||||
}
|
||||
|
||||
type BaiduCloudAccess struct {
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
SecretAccessKey string `json:"secretAccessKey"`
|
||||
}
|
||||
@@ -32,6 +37,11 @@ type QiniuAccess struct {
|
||||
SecretKey string `json:"secretKey"`
|
||||
}
|
||||
|
||||
type DogeCloudAccess struct {
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
}
|
||||
|
||||
type NameSiloAccess struct {
|
||||
ApiKey string `json:"apiKey"`
|
||||
}
|
||||
@@ -46,6 +56,11 @@ type PdnsAccess struct {
|
||||
ApiKey string `json:"apiKey"`
|
||||
}
|
||||
|
||||
type VolcengineAccess struct {
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
}
|
||||
|
||||
type HttpreqAccess struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
Mode string `json:"mode"`
|
||||
|
||||
@@ -3,6 +3,8 @@ package domain
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/maps"
|
||||
)
|
||||
|
||||
type ApplyConfig struct {
|
||||
@@ -29,7 +31,7 @@ type DeployConfig struct {
|
||||
// 出参:
|
||||
// - 配置项的值。如果配置项不存在或者类型不是字符串,则返回空字符串。
|
||||
func (dc *DeployConfig) GetConfigAsString(key string) string {
|
||||
return dc.GetConfigOrDefaultAsString(key, "")
|
||||
return maps.GetValueAsString(dc.Config, key)
|
||||
}
|
||||
|
||||
// 以字符串形式获取配置项。
|
||||
@@ -41,17 +43,7 @@ func (dc *DeployConfig) GetConfigAsString(key string) string {
|
||||
// 出参:
|
||||
// - 配置项的值。如果配置项不存在或者类型不是字符串,则返回默认值。
|
||||
func (dc *DeployConfig) GetConfigOrDefaultAsString(key string, defaultValue string) string {
|
||||
if dc.Config == nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
if value, ok := dc.Config[key]; ok {
|
||||
if result, ok := value.(string); ok {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue
|
||||
return maps.GetValueOrDefaultAsString(dc.Config, key, defaultValue)
|
||||
}
|
||||
|
||||
// 以 32 位整数形式获取配置项。
|
||||
@@ -62,7 +54,7 @@ func (dc *DeployConfig) GetConfigOrDefaultAsString(key string, defaultValue stri
|
||||
// 出参:
|
||||
// - 配置项的值。如果配置项不存在或者类型不是 32 位整数,则返回 0。
|
||||
func (dc *DeployConfig) GetConfigAsInt32(key string) int32 {
|
||||
return dc.GetConfigOrDefaultAsInt32(key, 0)
|
||||
return maps.GetValueAsInt32(dc.Config, key)
|
||||
}
|
||||
|
||||
// 以 32 位整数形式获取配置项。
|
||||
@@ -74,17 +66,7 @@ func (dc *DeployConfig) GetConfigAsInt32(key string) int32 {
|
||||
// 出参:
|
||||
// - 配置项的值。如果配置项不存在或者类型不是 32 位整数,则返回默认值。
|
||||
func (dc *DeployConfig) GetConfigOrDefaultAsInt32(key string, defaultValue int32) int32 {
|
||||
if dc.Config == nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
if value, ok := dc.Config[key]; ok {
|
||||
if result, ok := value.(int32); ok {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue
|
||||
return maps.GetValueOrDefaultAsInt32(dc.Config, key, defaultValue)
|
||||
}
|
||||
|
||||
// 以布尔形式获取配置项。
|
||||
@@ -95,7 +77,7 @@ func (dc *DeployConfig) GetConfigOrDefaultAsInt32(key string, defaultValue int32
|
||||
// 出参:
|
||||
// - 配置项的值。如果配置项不存在或者类型不是布尔,则返回 false。
|
||||
func (dc *DeployConfig) GetConfigAsBool(key string) bool {
|
||||
return dc.GetConfigOrDefaultAsBool(key, false)
|
||||
return maps.GetValueAsBool(dc.Config, key)
|
||||
}
|
||||
|
||||
// 以布尔形式获取配置项。
|
||||
@@ -107,17 +89,7 @@ func (dc *DeployConfig) GetConfigAsBool(key string) bool {
|
||||
// 出参:
|
||||
// - 配置项的值。如果配置项不存在或者类型不是布尔,则返回默认值。
|
||||
func (dc *DeployConfig) GetConfigOrDefaultAsBool(key string, defaultValue bool) bool {
|
||||
if dc.Config == nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
if value, ok := dc.Config[key]; ok {
|
||||
if result, ok := value.(bool); ok {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue
|
||||
return maps.GetValueOrDefaultAsBool(dc.Config, key, defaultValue)
|
||||
}
|
||||
|
||||
// 以变量字典形式获取配置项。
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package domain
|
||||
|
||||
const (
|
||||
NotifyChannelDingtalk = "dingtalk"
|
||||
NotifyChannelEmail = "email"
|
||||
NotifyChannelWebhook = "webhook"
|
||||
NotifyChannelTelegram = "telegram"
|
||||
NotifyChannelDingtalk = "dingtalk"
|
||||
NotifyChannelLark = "lark"
|
||||
NotifyChannelTelegram = "telegram"
|
||||
NotifyChannelServerChan = "serverchan"
|
||||
NotifyChannelMail = "mail"
|
||||
NotifyChannelBark = "bark"
|
||||
)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ func (s *Setting) GetChannelContent(channel string) (map[string]any, error) {
|
||||
|
||||
v, ok := (*conf)[channel]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("channel %s not found", channel)
|
||||
return nil, fmt.Errorf("channel \"%s\" not found", channel)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
|
||||
@@ -12,19 +12,13 @@ import (
|
||||
"github.com/usual2970/certimate/internal/utils/xtime"
|
||||
)
|
||||
|
||||
type msg struct {
|
||||
subject string
|
||||
message string
|
||||
}
|
||||
|
||||
const (
|
||||
defaultExpireSubject = "您有{COUNT}张证书即将过期"
|
||||
defaultExpireMsg = "有{COUNT}张证书即将过期,域名分别为{DOMAINS},请保持关注!"
|
||||
defaultExpireSubject = "您有 {COUNT} 张证书即将过期"
|
||||
defaultExpireMessage = "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!"
|
||||
)
|
||||
|
||||
func PushExpireMsg() {
|
||||
// 查询即将过期的证书
|
||||
|
||||
records, err := app.GetApp().Dao().FindRecordsByFilter("domains", "expiredAt<{:time}&&certUrl!=''", "-created", 500, 0,
|
||||
dbx.Params{"time": xtime.GetTimeAfter(24 * time.Hour * 15)})
|
||||
if err != nil {
|
||||
@@ -34,12 +28,12 @@ func PushExpireMsg() {
|
||||
|
||||
// 组装消息
|
||||
msg := buildMsg(records)
|
||||
|
||||
if msg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := Send(msg.subject, msg.message); err != nil {
|
||||
// 发送通知
|
||||
if err := SendToAllChannels(msg.Subject, msg.Message); err != nil {
|
||||
app.GetApp().Logger().Error("send expire msg", "error", err)
|
||||
}
|
||||
}
|
||||
@@ -53,22 +47,27 @@ type notifyTemplate struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
func buildMsg(records []*models.Record) *msg {
|
||||
type notifyMessage struct {
|
||||
Subject string
|
||||
Message string
|
||||
}
|
||||
|
||||
func buildMsg(records []*models.Record) *notifyMessage {
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 查询模板信息
|
||||
templateRecord, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='templates'")
|
||||
title := defaultExpireSubject
|
||||
content := defaultExpireMsg
|
||||
subject := defaultExpireSubject
|
||||
message := defaultExpireMessage
|
||||
|
||||
if err == nil {
|
||||
var templates *notifyTemplates
|
||||
templateRecord.UnmarshalJSONField("content", templates)
|
||||
if templates != nil && len(templates.NotifyTemplates) > 0 {
|
||||
title = templates.NotifyTemplates[0].Title
|
||||
content = templates.NotifyTemplates[0].Content
|
||||
subject = templates.NotifyTemplates[0].Title
|
||||
message = templates.NotifyTemplates[0].Content
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,17 +80,17 @@ func buildMsg(records []*models.Record) *msg {
|
||||
}
|
||||
|
||||
countStr := strconv.Itoa(count)
|
||||
domainStr := strings.Join(domains, ",")
|
||||
domainStr := strings.Join(domains, ";")
|
||||
|
||||
title = strings.ReplaceAll(title, "{COUNT}", countStr)
|
||||
title = strings.ReplaceAll(title, "{DOMAINS}", domainStr)
|
||||
subject = strings.ReplaceAll(subject, "{COUNT}", countStr)
|
||||
subject = strings.ReplaceAll(subject, "{DOMAINS}", domainStr)
|
||||
|
||||
content = strings.ReplaceAll(content, "{COUNT}", countStr)
|
||||
content = strings.ReplaceAll(content, "{DOMAINS}", domainStr)
|
||||
message = strings.ReplaceAll(message, "{COUNT}", countStr)
|
||||
message = strings.ReplaceAll(message, "{DOMAINS}", domainStr)
|
||||
|
||||
// 返回消息
|
||||
return &msg{
|
||||
subject: title,
|
||||
message: content,
|
||||
return ¬ifyMessage{
|
||||
Subject: subject,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
66
internal/notify/factory.go
Normal file
66
internal/notify/factory.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||
notifierBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark"
|
||||
notifierDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk"
|
||||
notifierEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
|
||||
notifierLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark"
|
||||
notifierServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan"
|
||||
notifierTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram"
|
||||
notifierWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func createNotifier(channel string, channelConfig map[string]any) (notifier.Notifier, error) {
|
||||
switch channel {
|
||||
case domain.NotifyChannelEmail:
|
||||
return notifierEmail.New(¬ifierEmail.EmailNotifierConfig{
|
||||
SmtpHost: maps.GetValueAsString(channelConfig, "smtpHost"),
|
||||
SmtpPort: maps.GetValueAsInt32(channelConfig, "smtpPort"),
|
||||
SmtpTLS: maps.GetValueOrDefaultAsBool(channelConfig, "smtpTLS", true),
|
||||
Username: maps.GetValueOrDefaultAsString(channelConfig, "username", maps.GetValueAsString(channelConfig, "senderAddress")),
|
||||
Password: maps.GetValueAsString(channelConfig, "password"),
|
||||
SenderAddress: maps.GetValueAsString(channelConfig, "senderAddress"),
|
||||
ReceiverAddress: maps.GetValueAsString(channelConfig, "receiverAddress"),
|
||||
})
|
||||
|
||||
case domain.NotifyChannelWebhook:
|
||||
return notifierWebhook.New(¬ifierWebhook.WebhookNotifierConfig{
|
||||
Url: maps.GetValueAsString(channelConfig, "url"),
|
||||
})
|
||||
|
||||
case domain.NotifyChannelDingtalk:
|
||||
return notifierDingTalk.New(¬ifierDingTalk.DingTalkNotifierConfig{
|
||||
AccessToken: maps.GetValueAsString(channelConfig, "accessToken"),
|
||||
Secret: maps.GetValueAsString(channelConfig, "secret"),
|
||||
})
|
||||
|
||||
case domain.NotifyChannelLark:
|
||||
return notifierLark.New(¬ifierLark.LarkNotifierConfig{
|
||||
WebhookUrl: maps.GetValueAsString(channelConfig, "webhookUrl"),
|
||||
})
|
||||
|
||||
case domain.NotifyChannelTelegram:
|
||||
return notifierTelegram.New(¬ifierTelegram.TelegramNotifierConfig{
|
||||
ApiToken: maps.GetValueAsString(channelConfig, "apiToken"),
|
||||
ChatId: maps.GetValueAsInt64(channelConfig, "chatId"),
|
||||
})
|
||||
|
||||
case domain.NotifyChannelServerChan:
|
||||
return notifierServerChan.New(¬ifierServerChan.ServerChanNotifierConfig{
|
||||
Url: maps.GetValueAsString(channelConfig, "url"),
|
||||
})
|
||||
|
||||
case domain.NotifyChannelBark:
|
||||
return notifierBark.New(¬ifierBark.BarkNotifierConfig{
|
||||
DeviceKey: maps.GetValueAsString(channelConfig, "deviceKey"),
|
||||
ServerUrl: maps.GetValueAsString(channelConfig, "serverUrl"),
|
||||
})
|
||||
}
|
||||
|
||||
return nil, errors.New("unsupported notifier channel")
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"strconv"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||
)
|
||||
|
||||
const defaultSmtpHostPort = "25"
|
||||
|
||||
type Mail struct {
|
||||
username string
|
||||
to string
|
||||
client *mailer.SmtpClient
|
||||
}
|
||||
|
||||
func NewMail(senderAddress, receiverAddresses, smtpHostAddr, smtpHostPort, password string) (*Mail, error) {
|
||||
if smtpHostPort == "" {
|
||||
smtpHostPort = defaultSmtpHostPort
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(smtpHostPort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid smtp port: %w", err)
|
||||
}
|
||||
|
||||
client := mailer.SmtpClient{
|
||||
Host: smtpHostAddr,
|
||||
Port: port,
|
||||
Username: senderAddress,
|
||||
Password: password,
|
||||
Tls: true,
|
||||
}
|
||||
|
||||
return &Mail{
|
||||
username: senderAddress,
|
||||
client: &client,
|
||||
to: receiverAddresses,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *Mail) Send(ctx context.Context, subject, content string) error {
|
||||
message := &mailer.Message{
|
||||
From: mail.Address{
|
||||
Address: m.username,
|
||||
},
|
||||
To: []mail.Address{{Address: m.to}},
|
||||
Subject: subject,
|
||||
Text: content,
|
||||
}
|
||||
|
||||
return m.client.Send(message)
|
||||
}
|
||||
@@ -3,24 +3,16 @@ package notify
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
stdhttp "net/http"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/maps"
|
||||
"github.com/usual2970/certimate/internal/utils/app"
|
||||
|
||||
notifyPackage "github.com/nikoksr/notify"
|
||||
"github.com/nikoksr/notify/service/bark"
|
||||
"github.com/nikoksr/notify/service/dingding"
|
||||
"github.com/nikoksr/notify/service/http"
|
||||
"github.com/nikoksr/notify/service/lark"
|
||||
"github.com/nikoksr/notify/service/telegram"
|
||||
)
|
||||
|
||||
func Send(title, content string) error {
|
||||
// 获取所有的推送渠道
|
||||
notifiers, err := getNotifiers()
|
||||
func SendToAllChannels(subject, message string) error {
|
||||
notifiers, err := getEnabledNotifiers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -28,184 +20,56 @@ func Send(title, content string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
n := notifyPackage.New()
|
||||
// 添加推送渠道
|
||||
n.UseServices(notifiers...)
|
||||
var eg errgroup.Group
|
||||
for _, n := range notifiers {
|
||||
if n == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
return n.Send(context.Background(), title, content)
|
||||
eg.Go(func() error {
|
||||
_, err := n.Notify(context.Background(), subject, message)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
err = eg.Wait()
|
||||
return err
|
||||
}
|
||||
|
||||
type sendTestParam struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Channel string `json:"channel"`
|
||||
Conf map[string]any `json:"conf"`
|
||||
}
|
||||
|
||||
func SendTest(param *sendTestParam) error {
|
||||
notifier, err := getNotifier(param.Channel, param.Conf)
|
||||
func SendToChannel(subject, message string, channel string, channelConfig map[string]any) error {
|
||||
notifier, err := createNotifier(channel, channelConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n := notifyPackage.New()
|
||||
|
||||
// 添加推送渠道
|
||||
n.UseServices(notifier)
|
||||
|
||||
// 发送消息
|
||||
return n.Send(context.Background(), param.Title, param.Content)
|
||||
_, err = notifier.Notify(context.Background(), subject, message)
|
||||
return err
|
||||
}
|
||||
|
||||
func getNotifiers() ([]notifyPackage.Notifier, error) {
|
||||
resp, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='notifyChannels'")
|
||||
func getEnabledNotifiers() ([]notifier.Notifier, error) {
|
||||
settings, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='notifyChannels'")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find notifyChannels error: %w", err)
|
||||
}
|
||||
|
||||
notifiers := make([]notifyPackage.Notifier, 0)
|
||||
|
||||
rs := make(map[string]map[string]any)
|
||||
|
||||
if err := resp.UnmarshalJSONField("content", &rs); err != nil {
|
||||
if err := settings.UnmarshalJSONField("content", &rs); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal notifyChannels error: %w", err)
|
||||
}
|
||||
|
||||
notifiers := make([]notifier.Notifier, 0)
|
||||
for k, v := range rs {
|
||||
|
||||
if !getBool(v, "enabled") {
|
||||
if !maps.GetValueAsBool(v, "enabled") {
|
||||
continue
|
||||
}
|
||||
|
||||
notifier, err := getNotifier(k, v)
|
||||
notifier, err := createNotifier(k, v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
notifiers = append(notifiers, notifier)
|
||||
|
||||
}
|
||||
|
||||
return notifiers, nil
|
||||
}
|
||||
|
||||
func getNotifier(channel string, conf map[string]any) (notifyPackage.Notifier, error) {
|
||||
switch channel {
|
||||
case domain.NotifyChannelTelegram:
|
||||
temp := getTelegramNotifier(conf)
|
||||
if temp == nil {
|
||||
return nil, fmt.Errorf("telegram notifier config error")
|
||||
}
|
||||
|
||||
return temp, nil
|
||||
case domain.NotifyChannelDingtalk:
|
||||
return getDingTalkNotifier(conf), nil
|
||||
case domain.NotifyChannelLark:
|
||||
return getLarkNotifier(conf), nil
|
||||
case domain.NotifyChannelWebhook:
|
||||
return getWebhookNotifier(conf), nil
|
||||
case domain.NotifyChannelServerChan:
|
||||
return getServerChanNotifier(conf), nil
|
||||
case domain.NotifyChannelMail:
|
||||
return getMailNotifier(conf)
|
||||
case domain.NotifyChannelBark:
|
||||
return getBarkNotifier(conf), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("notifier not found")
|
||||
}
|
||||
|
||||
func getWebhookNotifier(conf map[string]any) notifyPackage.Notifier {
|
||||
rs := http.New()
|
||||
|
||||
rs.AddReceiversURLs(getString(conf, "url"))
|
||||
|
||||
return rs
|
||||
}
|
||||
|
||||
func getTelegramNotifier(conf map[string]any) notifyPackage.Notifier {
|
||||
rs, err := telegram.New(getString(conf, "apiToken"))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
chatId := getString(conf, "chatId")
|
||||
|
||||
id, err := strconv.ParseInt(chatId, 10, 64)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rs.AddReceivers(id)
|
||||
return rs
|
||||
}
|
||||
|
||||
func getServerChanNotifier(conf map[string]any) notifyPackage.Notifier {
|
||||
rs := http.New()
|
||||
|
||||
rs.AddReceivers(&http.Webhook{
|
||||
URL: getString(conf, "url"),
|
||||
Header: stdhttp.Header{},
|
||||
ContentType: "application/json",
|
||||
Method: stdhttp.MethodPost,
|
||||
BuildPayload: func(subject, message string) (payload any) {
|
||||
return map[string]string{
|
||||
"text": subject,
|
||||
"desp": message,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return rs
|
||||
}
|
||||
|
||||
func getBarkNotifier(conf map[string]any) notifyPackage.Notifier {
|
||||
deviceKey := getString(conf, "deviceKey")
|
||||
serverURL := getString(conf, "serverUrl")
|
||||
if serverURL == "" {
|
||||
return bark.New(deviceKey)
|
||||
}
|
||||
return bark.NewWithServers(deviceKey, serverURL)
|
||||
}
|
||||
|
||||
func getDingTalkNotifier(conf map[string]any) notifyPackage.Notifier {
|
||||
return dingding.New(&dingding.Config{
|
||||
Token: getString(conf, "accessToken"),
|
||||
Secret: getString(conf, "secret"),
|
||||
})
|
||||
}
|
||||
|
||||
func getLarkNotifier(conf map[string]any) notifyPackage.Notifier {
|
||||
return lark.NewWebhookService(getString(conf, "webhookUrl"))
|
||||
}
|
||||
|
||||
func getMailNotifier(conf map[string]any) (notifyPackage.Notifier, error) {
|
||||
rs, err := NewMail(getString(conf, "senderAddress"),
|
||||
getString(conf, "receiverAddresses"),
|
||||
getString(conf, "smtpHostAddr"),
|
||||
getString(conf, "smtpHostPort"),
|
||||
getString(conf, "password"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func getString(conf map[string]any, key string) string {
|
||||
if _, ok := conf[key]; !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return conf[key].(string)
|
||||
}
|
||||
|
||||
func getBool(conf map[string]any, key string) bool {
|
||||
if _, ok := conf[key]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return conf[key].(bool)
|
||||
}
|
||||
|
||||
@@ -29,18 +29,13 @@ func NewNotifyService(settingRepo SettingRepository) *NotifyService {
|
||||
func (n *NotifyService) Test(ctx context.Context, req *domain.NotifyTestPushReq) error {
|
||||
setting, err := n.settingRepo.GetByName(ctx, "notifyChannels")
|
||||
if err != nil {
|
||||
return fmt.Errorf("get notify channels setting failed: %w", err)
|
||||
return fmt.Errorf("failed to get notify channels settings: %w", err)
|
||||
}
|
||||
|
||||
conf, err := setting.GetChannelContent(req.Channel)
|
||||
channelConfig, err := setting.GetChannelContent(req.Channel)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get notify channel %s config failed: %w", req.Channel, err)
|
||||
return fmt.Errorf("failed to get notify channel \"%s\" config: %w", req.Channel, err)
|
||||
}
|
||||
|
||||
return SendTest(&sendTestParam{
|
||||
Title: notifyTestTitle,
|
||||
Content: notifyTestBody,
|
||||
Channel: req.Channel,
|
||||
Conf: conf,
|
||||
})
|
||||
return SendToChannel(notifyTestTitle, notifyTestBody, req.Channel, channelConfig)
|
||||
}
|
||||
|
||||
23
internal/pkg/core/notifier/notifier.go
Normal file
23
internal/pkg/core/notifier/notifier.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package notifier
|
||||
|
||||
import "context"
|
||||
|
||||
// 表示定义消息通知器的抽象类型接口。
|
||||
type Notifier interface {
|
||||
// 发送通知。
|
||||
//
|
||||
// 入参:
|
||||
// - ctx:上下文。
|
||||
// - subject:通知主题。
|
||||
// - message:通知内容。
|
||||
//
|
||||
// 出参:
|
||||
// - res:发送结果。
|
||||
// - err: 错误。
|
||||
Notify(ctx context.Context, subject string, message string) (res *NotifyResult, err error)
|
||||
}
|
||||
|
||||
// 表示通知发送结果的数据结构。
|
||||
type NotifyResult struct {
|
||||
NotificationData map[string]any `json:"notificationData,omitempty"`
|
||||
}
|
||||
48
internal/pkg/core/notifier/providers/bark/bark.go
Normal file
48
internal/pkg/core/notifier/providers/bark/bark.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package bark
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/nikoksr/notify"
|
||||
"github.com/nikoksr/notify/service/bark"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||
)
|
||||
|
||||
type BarkNotifierConfig struct {
|
||||
ServerUrl string `json:"serverUrl"`
|
||||
DeviceKey string `json:"deviceKey"`
|
||||
}
|
||||
|
||||
type BarkNotifier struct {
|
||||
config *BarkNotifierConfig
|
||||
}
|
||||
|
||||
var _ notifier.Notifier = (*BarkNotifier)(nil)
|
||||
|
||||
func New(config *BarkNotifierConfig) (*BarkNotifier, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
return &BarkNotifier{
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n *BarkNotifier) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
|
||||
var srv notify.Notifier
|
||||
if n.config.ServerUrl == "" {
|
||||
srv = bark.New(n.config.DeviceKey)
|
||||
} else {
|
||||
srv = bark.NewWithServers(n.config.DeviceKey, n.config.ServerUrl)
|
||||
}
|
||||
|
||||
err = srv.Send(ctx, subject, message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ¬ifier.NotifyResult{}, nil
|
||||
}
|
||||
45
internal/pkg/core/notifier/providers/dingtalk/dingtalk.go
Normal file
45
internal/pkg/core/notifier/providers/dingtalk/dingtalk.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package dingtalk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/nikoksr/notify/service/dingding"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||
)
|
||||
|
||||
type DingTalkNotifierConfig struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
type DingTalkNotifier struct {
|
||||
config *DingTalkNotifierConfig
|
||||
}
|
||||
|
||||
var _ notifier.Notifier = (*DingTalkNotifier)(nil)
|
||||
|
||||
func New(config *DingTalkNotifierConfig) (*DingTalkNotifier, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
return &DingTalkNotifier{
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n *DingTalkNotifier) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
|
||||
srv := dingding.New(&dingding.Config{
|
||||
Token: n.config.AccessToken,
|
||||
Secret: n.config.Secret,
|
||||
})
|
||||
|
||||
err = srv.Send(ctx, subject, message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ¬ifier.NotifyResult{}, nil
|
||||
}
|
||||
95
internal/pkg/core/notifier/providers/email/email.go
Normal file
95
internal/pkg/core/notifier/providers/email/email.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
|
||||
"github.com/domodwyer/mailyak/v3"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||
)
|
||||
|
||||
type EmailNotifierConfig struct {
|
||||
SmtpHost string `json:"smtpHost"`
|
||||
SmtpPort int32 `json:"smtpPort"`
|
||||
SmtpTLS bool `json:"smtpTLS"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
SenderAddress string `json:"senderAddress"`
|
||||
ReceiverAddress string `json:"receiverAddress"`
|
||||
}
|
||||
|
||||
type EmailNotifier struct {
|
||||
config *EmailNotifierConfig
|
||||
}
|
||||
|
||||
var _ notifier.Notifier = (*EmailNotifier)(nil)
|
||||
|
||||
func New(config *EmailNotifierConfig) (*EmailNotifier, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
return &EmailNotifier{
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n *EmailNotifier) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
|
||||
var smtpAuth smtp.Auth
|
||||
if n.config.Username != "" || n.config.Password != "" {
|
||||
smtpAuth = smtp.PlainAuth("", n.config.Username, n.config.Password, n.config.SmtpHost)
|
||||
}
|
||||
|
||||
var smtpAddr string
|
||||
if n.config.SmtpPort == 0 {
|
||||
if n.config.SmtpTLS {
|
||||
smtpAddr = fmt.Sprintf("%s:465", n.config.SmtpHost)
|
||||
} else {
|
||||
smtpAddr = fmt.Sprintf("%s:25", n.config.SmtpHost)
|
||||
}
|
||||
} else {
|
||||
smtpAddr = fmt.Sprintf("%s:%d", n.config.SmtpHost, n.config.SmtpPort)
|
||||
}
|
||||
|
||||
var yak *mailyak.MailYak
|
||||
if n.config.SmtpTLS {
|
||||
yak, err = mailyak.NewWithTLS(smtpAddr, smtpAuth, newTlsConfig())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
yak = mailyak.New(smtpAddr, smtpAuth)
|
||||
}
|
||||
|
||||
yak.From(n.config.SenderAddress)
|
||||
yak.To(n.config.ReceiverAddress)
|
||||
yak.Subject(subject)
|
||||
yak.Plain().Set(message)
|
||||
|
||||
err = yak.Send()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ¬ifier.NotifyResult{}, nil
|
||||
}
|
||||
|
||||
func newTlsConfig() *tls.Config {
|
||||
var suiteIds []uint16
|
||||
for _, suite := range tls.CipherSuites() {
|
||||
suiteIds = append(suiteIds, suite.ID)
|
||||
}
|
||||
for _, suite := range tls.InsecureCipherSuites() {
|
||||
suiteIds = append(suiteIds, suite.ID)
|
||||
}
|
||||
|
||||
// 为兼容国内部分低版本 TLS 的 SMTP 服务商
|
||||
return &tls.Config{
|
||||
MinVersion: tls.VersionTLS10,
|
||||
CipherSuites: suiteIds,
|
||||
}
|
||||
}
|
||||
51
internal/pkg/core/notifier/providers/email/email_test.go
Normal file
51
internal/pkg/core/notifier/providers/email/email_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package email_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
notifierEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
|
||||
)
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
CERTIMATE_NOTIFIER_EMAIL_SMTPPORT=465 \
|
||||
CERTIMATE_NOTIFIER_EMAIL_SMTPTLS=true \
|
||||
CERTIMATE_NOTIFIER_EMAIL_SMTPHOST="smtp.example.com" \
|
||||
CERTIMATE_NOTIFIER_EMAIL_USERNAME="your-username" \
|
||||
CERTIMATE_NOTIFIER_EMAIL_PASSWORD="your-password" \
|
||||
CERTIMATE_NOTIFIER_EMAIL_SENDERADDRESS="sender@example.com" \
|
||||
CERTIMATE_NOTIFIER_EMAIL_RECEIVERADDRESS="receiver@example.com" \
|
||||
go test -v -run TestNotify email_test.go
|
||||
*/
|
||||
func TestNotify(t *testing.T) {
|
||||
smtpPort, err := strconv.ParseInt(os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SMTPPORT"), 10, 32)
|
||||
if err != nil {
|
||||
t.Errorf("invalid envvar: %+v", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
smtpTLS, err := strconv.ParseBool(os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SMTPTLS"))
|
||||
if err != nil {
|
||||
t.Errorf("invalid envvar: %+v", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
res, err := notifierEmail.New(¬ifierEmail.EmailNotifierConfig{
|
||||
SmtpHost: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SMTPHOST"),
|
||||
SmtpPort: int32(smtpPort),
|
||||
SmtpTLS: smtpTLS,
|
||||
Username: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_USERNAME"),
|
||||
Password: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_PASSWORD"),
|
||||
SenderAddress: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SENDERADDRESS"),
|
||||
ReceiverAddress: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_RECEIVERADDRESS"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("invalid envvar: %+v", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
t.Logf("notify result: %v", res)
|
||||
}
|
||||
41
internal/pkg/core/notifier/providers/lark/lark.go
Normal file
41
internal/pkg/core/notifier/providers/lark/lark.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package lark
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/nikoksr/notify/service/lark"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||
)
|
||||
|
||||
type LarkNotifierConfig struct {
|
||||
WebhookUrl string `json:"webhookUrl"`
|
||||
}
|
||||
|
||||
type LarkNotifier struct {
|
||||
config *LarkNotifierConfig
|
||||
}
|
||||
|
||||
var _ notifier.Notifier = (*LarkNotifier)(nil)
|
||||
|
||||
func New(config *LarkNotifierConfig) (*LarkNotifier, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
return &LarkNotifier{
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n *LarkNotifier) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
|
||||
srv := lark.NewWebhookService(n.config.WebhookUrl)
|
||||
|
||||
err = srv.Send(ctx, subject, message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ¬ifier.NotifyResult{}, nil
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package serverchan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
notifyHttp "github.com/nikoksr/notify/service/http"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||
)
|
||||
|
||||
type ServerChanNotifierConfig struct {
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
type ServerChanNotifier struct {
|
||||
config *ServerChanNotifierConfig
|
||||
}
|
||||
|
||||
var _ notifier.Notifier = (*ServerChanNotifier)(nil)
|
||||
|
||||
func New(config *ServerChanNotifierConfig) (*ServerChanNotifier, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
return &ServerChanNotifier{
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n *ServerChanNotifier) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
|
||||
srv := notifyHttp.New()
|
||||
|
||||
srv.AddReceivers(¬ifyHttp.Webhook{
|
||||
URL: n.config.Url,
|
||||
Header: http.Header{},
|
||||
ContentType: "application/json",
|
||||
Method: http.MethodPost,
|
||||
BuildPayload: func(subject, message string) (payload any) {
|
||||
return map[string]string{
|
||||
"text": subject,
|
||||
"desp": message,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
err = srv.Send(ctx, subject, message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ¬ifier.NotifyResult{}, nil
|
||||
}
|
||||
47
internal/pkg/core/notifier/providers/telegram/telegram.go
Normal file
47
internal/pkg/core/notifier/providers/telegram/telegram.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/nikoksr/notify/service/telegram"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||
)
|
||||
|
||||
type TelegramNotifierConfig struct {
|
||||
ApiToken string `json:"apiToken"`
|
||||
ChatId int64 `json:"chatId"`
|
||||
}
|
||||
|
||||
type TelegramNotifier struct {
|
||||
config *TelegramNotifierConfig
|
||||
}
|
||||
|
||||
var _ notifier.Notifier = (*TelegramNotifier)(nil)
|
||||
|
||||
func New(config *TelegramNotifierConfig) (*TelegramNotifier, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
return &TelegramNotifier{
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n *TelegramNotifier) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
|
||||
srv, err := telegram.New(n.config.ApiToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srv.AddReceivers(n.config.ChatId)
|
||||
|
||||
err = srv.Send(ctx, subject, message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ¬ifier.NotifyResult{}, nil
|
||||
}
|
||||
43
internal/pkg/core/notifier/providers/webhook/webhook.go
Normal file
43
internal/pkg/core/notifier/providers/webhook/webhook.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/nikoksr/notify/service/http"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||
)
|
||||
|
||||
type WebhookNotifierConfig struct {
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
type WebhookNotifier struct {
|
||||
config *WebhookNotifierConfig
|
||||
}
|
||||
|
||||
var _ notifier.Notifier = (*WebhookNotifier)(nil)
|
||||
|
||||
func New(config *WebhookNotifierConfig) (*WebhookNotifier, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
return &WebhookNotifier{
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n *WebhookNotifier) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
|
||||
srv := http.New()
|
||||
|
||||
srv.AddReceiversURLs(n.config.Url)
|
||||
|
||||
err = srv.Send(ctx, subject, message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ¬ifier.NotifyResult{}, nil
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -26,7 +27,13 @@ type AliyunCASUploader struct {
|
||||
sdkClient *aliyunCas.Client
|
||||
}
|
||||
|
||||
var _ uploader.Uploader = (*AliyunCASUploader)(nil)
|
||||
|
||||
func New(config *AliyunCASUploaderConfig) (*AliyunCASUploader, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
client, err := createSdkClient(
|
||||
config.AccessKeyId,
|
||||
config.AccessKeySecret,
|
||||
|
||||
@@ -4,7 +4,9 @@ import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -28,7 +30,13 @@ type AliyunSLBUploader struct {
|
||||
sdkClient *aliyunSlb.Client
|
||||
}
|
||||
|
||||
var _ uploader.Uploader = (*AliyunSLBUploader)(nil)
|
||||
|
||||
func New(config *AliyunSLBUploaderConfig) (*AliyunSLBUploader, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
client, err := createSdkClient(
|
||||
config.AccessKeyId,
|
||||
config.AccessKeySecret,
|
||||
@@ -82,6 +90,12 @@ func (u *AliyunSLBUploader) Upload(ctx context.Context, certPem string, privkeyP
|
||||
var certId, certName string
|
||||
certName = fmt.Sprintf("certimate_%d", time.Now().UnixMilli())
|
||||
|
||||
// 去除证书和私钥内容中的空白行,以符合阿里云 API 要求
|
||||
// REF: https://github.com/usual2970/certimate/issues/326
|
||||
re := regexp.MustCompile(`(?m)^\s*$\n?`)
|
||||
certPem = strings.TrimSpace(re.ReplaceAllString(certPem, ""))
|
||||
privkeyPem = strings.TrimSpace(re.ReplaceAllString(privkeyPem, ""))
|
||||
|
||||
// 上传新证书
|
||||
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-uploadservercertificate
|
||||
uploadServerCertificateReq := &aliyunSlb.UploadServerCertificateRequest{
|
||||
@@ -114,10 +128,11 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Cl
|
||||
|
||||
var endpoint string
|
||||
switch region {
|
||||
case "cn-hangzhou":
|
||||
case "cn-hangzhou-finance":
|
||||
case "cn-shanghai-finance-1":
|
||||
case "cn-shenzhen-finance-1":
|
||||
case
|
||||
"cn-hangzhou",
|
||||
"cn-hangzhou-finance",
|
||||
"cn-shanghai-finance-1",
|
||||
"cn-shenzhen-finance-1":
|
||||
endpoint = "slb.aliyuncs.com"
|
||||
default:
|
||||
endpoint = fmt.Sprintf("slb.%s.aliyuncs.com", region)
|
||||
|
||||
68
internal/pkg/core/uploader/providers/dogecloud/dogecloud.go
Normal file
68
internal/pkg/core/uploader/providers/dogecloud/dogecloud.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package dogecloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
xerrors "github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||
doge "github.com/usual2970/certimate/internal/pkg/vendors/dogecloud-sdk"
|
||||
)
|
||||
|
||||
type DogeCloudUploaderConfig struct {
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
}
|
||||
|
||||
type DogeCloudUploader struct {
|
||||
config *DogeCloudUploaderConfig
|
||||
sdkClient *doge.Client
|
||||
}
|
||||
|
||||
var _ uploader.Uploader = (*DogeCloudUploader)(nil)
|
||||
|
||||
func New(config *DogeCloudUploaderConfig) (*DogeCloudUploader, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
client, err := createSdkClient(
|
||||
config.AccessKey,
|
||||
config.SecretKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||
}
|
||||
|
||||
return &DogeCloudUploader{
|
||||
config: config,
|
||||
sdkClient: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *DogeCloudUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||
// 生成新证书名(需符合多吉云命名规则)
|
||||
var certId, certName string
|
||||
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
||||
|
||||
// 上传新证书
|
||||
// REF: https://docs.dogecloud.com/cdn/api-cert-upload
|
||||
uploadSslCertResp, err := u.sdkClient.UploadCdnCert(certName, certPem, privkeyPem)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadCdnCert'")
|
||||
}
|
||||
|
||||
certId = fmt.Sprintf("%d", uploadSslCertResp.Data.Id)
|
||||
return &uploader.UploadResult{
|
||||
CertId: certId,
|
||||
CertName: certName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createSdkClient(accessKey, secretKey string) (*doge.Client, error) {
|
||||
client := doge.NewClient(accessKey, secretKey)
|
||||
return client, nil
|
||||
}
|
||||
@@ -32,7 +32,13 @@ type HuaweiCloudELBUploader struct {
|
||||
sdkClient *hcElb.ElbClient
|
||||
}
|
||||
|
||||
var _ uploader.Uploader = (*HuaweiCloudELBUploader)(nil)
|
||||
|
||||
func New(config *HuaweiCloudELBUploaderConfig) (*HuaweiCloudELBUploader, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
client, err := createSdkClient(
|
||||
config.AccessKeyId,
|
||||
config.SecretAccessKey,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -27,7 +28,13 @@ type HuaweiCloudSCMUploader struct {
|
||||
sdkClient *hcScm.ScmClient
|
||||
}
|
||||
|
||||
var _ uploader.Uploader = (*HuaweiCloudSCMUploader)(nil)
|
||||
|
||||
func New(config *HuaweiCloudSCMUploaderConfig) (*HuaweiCloudSCMUploader, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
client, err := createSdkClient(
|
||||
config.AccessKeyId,
|
||||
config.SecretAccessKey,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -23,7 +24,13 @@ type QiniuSSLCertUploader struct {
|
||||
sdkClient *qiniuEx.Client
|
||||
}
|
||||
|
||||
var _ uploader.Uploader = (*QiniuSSLCertUploader)(nil)
|
||||
|
||||
func New(config *QiniuSSLCertUploaderConfig) (*QiniuSSLCertUploader, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
client, err := createSdkClient(
|
||||
config.AccessKey,
|
||||
config.SecretKey,
|
||||
@@ -51,7 +58,7 @@ func (u *QiniuSSLCertUploader) Upload(ctx context.Context, certPem string, privk
|
||||
|
||||
// 上传新证书
|
||||
// REF: https://developer.qiniu.com/fusion/8593/interface-related-certificate
|
||||
uploadSslCertResp, err := u.sdkClient.UploadSslCert(certName, certX509.Subject.CommonName, privkeyPem, certPem)
|
||||
uploadSslCertResp, err := u.sdkClient.UploadSslCert(certName, certX509.Subject.CommonName, certPem, privkeyPem)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadSslCert'")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
xerrors "github.com/pkg/errors"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
@@ -21,7 +22,13 @@ type TencentCloudSSLUploader struct {
|
||||
sdkClient *tcSsl.Client
|
||||
}
|
||||
|
||||
var _ uploader.Uploader = (*TencentCloudSSLUploader)(nil)
|
||||
|
||||
func New(config *TencentCloudSSLUploaderConfig) (*TencentCloudSSLUploader, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
client, err := createSdkClient(
|
||||
config.SecretId,
|
||||
config.SecretKey,
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package volcenginecdn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
xerrors "github.com/pkg/errors"
|
||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||
"github.com/volcengine/volc-sdk-golang/service/cdn"
|
||||
)
|
||||
|
||||
type VolcengineCDNUploaderConfig struct {
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
AccessKeySecret string `json:"accessKeySecret"`
|
||||
}
|
||||
|
||||
type VolcengineCDNUploader struct {
|
||||
config *VolcengineCDNUploaderConfig
|
||||
sdkClient *cdn.CDN
|
||||
}
|
||||
|
||||
var _ uploader.Uploader = (*VolcengineCDNUploader)(nil)
|
||||
|
||||
func New(config *VolcengineCDNUploaderConfig) (*VolcengineCDNUploader, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
instance := cdn.NewInstance()
|
||||
client := instance.Client
|
||||
client.SetAccessKey(config.AccessKeyId)
|
||||
client.SetSecretKey(config.AccessKeySecret)
|
||||
|
||||
return &VolcengineCDNUploader{
|
||||
config: config,
|
||||
sdkClient: instance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *VolcengineCDNUploader) 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://www.volcengine.com/docs/6454/125709
|
||||
pageNum := int64(1)
|
||||
pageSize := int64(100)
|
||||
certSource := "volc_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://www.volcengine.com/docs/6454/1245763
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package volcenginelive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
xerrors "github.com/pkg/errors"
|
||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||
live "github.com/volcengine/volc-sdk-golang/service/live/v20230101"
|
||||
)
|
||||
|
||||
type VolcengineLiveUploaderConfig struct {
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
AccessKeySecret string `json:"accessKeySecret"`
|
||||
}
|
||||
|
||||
type VolcengineLiveUploader struct {
|
||||
config *VolcengineLiveUploaderConfig
|
||||
sdkClient *live.Live
|
||||
}
|
||||
|
||||
var _ uploader.Uploader = (*VolcengineLiveUploader)(nil)
|
||||
|
||||
func New(config *VolcengineLiveUploaderConfig) (*VolcengineLiveUploader, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is nil")
|
||||
}
|
||||
|
||||
client := live.NewInstance()
|
||||
client.SetAccessKey(config.AccessKeyId)
|
||||
client.SetSecretKey(config.AccessKeySecret)
|
||||
|
||||
return &VolcengineLiveUploader{
|
||||
config: config,
|
||||
sdkClient: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *VolcengineLiveUploader) 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://www.volcengine.com/docs/6469/1186278#%E6%9F%A5%E8%AF%A2%E8%AF%81%E4%B9%A6%E5%88%97%E8%A1%A8
|
||||
listCertReq := &live.ListCertV2Body{}
|
||||
listCertResp, err := u.sdkClient.ListCertV2(ctx, listCertReq)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'live.ListCertV2'")
|
||||
}
|
||||
|
||||
if listCertResp.Result.CertList != nil {
|
||||
for _, certDetail := range listCertResp.Result.CertList {
|
||||
|
||||
describeCertDetailSecretReq := &live.DescribeCertDetailSecretV2Body{
|
||||
ChainID: cast.StringPtr(certDetail.ChainID),
|
||||
}
|
||||
// 查询证书详细信息
|
||||
// REF: https://www.volcengine.com/docs/6469/1186278#%E6%9F%A5%E7%9C%8B%E8%AF%81%E4%B9%A6%E8%AF%A6%E6%83%85
|
||||
describeCertDetailSecretResp, detailErr := u.sdkClient.DescribeCertDetailSecretV2(ctx, describeCertDetailSecretReq)
|
||||
if detailErr != nil {
|
||||
continue
|
||||
}
|
||||
var isSameCert bool
|
||||
certificate := strings.Join(describeCertDetailSecretResp.Result.SSL.Chain, "\n\n")
|
||||
if certificate == certPem {
|
||||
isSameCert = true
|
||||
} else {
|
||||
cert, err := x509.ParseCertificateFromPEM(certificate)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
isSameCert = x509.EqualCertificate(cert, certX509)
|
||||
}
|
||||
// 如果已存在相同证书,直接返回已有的证书信息
|
||||
if isSameCert {
|
||||
return &uploader.UploadResult{
|
||||
CertId: certDetail.ChainID,
|
||||
CertName: certDetail.CertName,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成新证书名(需符合火山引擎命名规则)
|
||||
var certId, certName string
|
||||
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
||||
// 上传新证书
|
||||
// REF: https://www.volcengine.com/docs/6469/1186278#%E6%B7%BB%E5%8A%A0%E8%AF%81%E4%B9%A6
|
||||
createCertReq := &live.CreateCertBody{
|
||||
CertName: &certName,
|
||||
UseWay: "https",
|
||||
ProjectName: cast.StringPtr("default"),
|
||||
Rsa: live.CreateCertBodyRsa{
|
||||
Prikey: privkeyPem,
|
||||
Pubkey: certPem,
|
||||
},
|
||||
}
|
||||
createCertResp, err := u.sdkClient.CreateCert(ctx, createCertReq)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'live.CreateCert'")
|
||||
}
|
||||
|
||||
certId = *createCertResp.Result.ChainID
|
||||
return &uploader.UploadResult{
|
||||
CertId: certId,
|
||||
CertName: certName,
|
||||
}, nil
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import "context"
|
||||
|
||||
// 表示定义证书上传者的抽象类型接口。
|
||||
// 表示定义证书上传器的抽象类型接口。
|
||||
// 云服务商通常会提供 SSL 证书管理服务,可供用户集中管理证书。
|
||||
// 注意与 `Deployer` 区分,“上传”通常为“部署”的前置操作。
|
||||
type Uploader interface {
|
||||
|
||||
164
internal/pkg/utils/maps/maps.go
Normal file
164
internal/pkg/utils/maps/maps.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package maps
|
||||
|
||||
import "strconv"
|
||||
|
||||
// 以字符串形式从字典中获取指定键的值。
|
||||
//
|
||||
// 入参:
|
||||
// - dict: 字典。
|
||||
// - key: 键。
|
||||
//
|
||||
// 出参:
|
||||
// - 字典中键对应的值。如果指定键不存在或者值的类型不是字符串,则返回空字符串。
|
||||
func GetValueAsString(dict map[string]any, key string) string {
|
||||
return GetValueOrDefaultAsString(dict, key, "")
|
||||
}
|
||||
|
||||
// 以字符串形式从字典中获取指定键的值。
|
||||
//
|
||||
// 入参:
|
||||
// - dict: 字典。
|
||||
// - key: 键。
|
||||
// - defaultValue: 默认值。
|
||||
//
|
||||
// 出参:
|
||||
// - 字典中键对应的值。如果指定键不存在或者值的类型不是字符串,则返回默认值。
|
||||
func GetValueOrDefaultAsString(dict map[string]any, key string, defaultValue string) string {
|
||||
if dict == nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
if value, ok := dict[key]; ok {
|
||||
if result, ok := value.(string); ok {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// 以 32 位整数形式从字典中获取指定键的值。
|
||||
//
|
||||
// 入参:
|
||||
// - dict: 字典。
|
||||
// - key: 键。
|
||||
//
|
||||
// 出参:
|
||||
// - 字典中键对应的值。如果指定键不存在或者值的类型不是 32 位整数,则返回 0。
|
||||
func GetValueAsInt32(dict map[string]any, key string) int32 {
|
||||
return GetValueOrDefaultAsInt32(dict, key, 0)
|
||||
}
|
||||
|
||||
// 以 32 位整数形式从字典中获取指定键的值。
|
||||
//
|
||||
// 入参:
|
||||
// - dict: 字典。
|
||||
// - key: 键。
|
||||
// - defaultValue: 默认值。
|
||||
//
|
||||
// 出参:
|
||||
// - 字典中键对应的值。如果指定键不存在或者值的类型不是 32 位整数,则返回默认值。
|
||||
func GetValueOrDefaultAsInt32(dict map[string]any, key string, defaultValue int32) int32 {
|
||||
if dict == nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
if value, ok := dict[key]; ok {
|
||||
if result, ok := value.(int32); ok {
|
||||
return result
|
||||
}
|
||||
|
||||
// 兼容字符串类型的值
|
||||
if str, ok := value.(string); ok {
|
||||
if result, err := strconv.ParseInt(str, 10, 32); err == nil {
|
||||
return int32(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// 以 64 位整数形式从字典中获取指定键的值。
|
||||
//
|
||||
// 入参:
|
||||
// - dict: 字典。
|
||||
// - key: 键。
|
||||
//
|
||||
// 出参:
|
||||
// - 字典中键对应的值。如果指定键不存在或者值的类型不是 64 位整数,则返回 0。
|
||||
func GetValueAsInt64(dict map[string]any, key string) int64 {
|
||||
return GetValueOrDefaultAsInt64(dict, key, 0)
|
||||
}
|
||||
|
||||
// 以 64 位整数形式从字典中获取指定键的值。
|
||||
//
|
||||
// 入参:
|
||||
// - dict: 字典。
|
||||
// - key: 键。
|
||||
// - defaultValue: 默认值。
|
||||
//
|
||||
// 出参:
|
||||
// - 字典中键对应的值。如果指定键不存在或者值的类型不是 64 位整数,则返回默认值。
|
||||
func GetValueOrDefaultAsInt64(dict map[string]any, key string, defaultValue int64) int64 {
|
||||
if dict == nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
if value, ok := dict[key]; ok {
|
||||
if result, ok := value.(int64); ok {
|
||||
return result
|
||||
}
|
||||
|
||||
// 兼容字符串类型的值
|
||||
if str, ok := value.(string); ok {
|
||||
if result, err := strconv.ParseInt(str, 10, 64); err == nil {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// 以布尔形式从字典中获取指定键的值。
|
||||
//
|
||||
// 入参:
|
||||
// - dict: 字典。
|
||||
// - key: 键。
|
||||
//
|
||||
// 出参:
|
||||
// - 字典中键对应的值。如果指定键不存在或者值的类型不是布尔,则返回 false。
|
||||
func GetValueAsBool(dict map[string]any, key string) bool {
|
||||
return GetValueOrDefaultAsBool(dict, key, false)
|
||||
}
|
||||
|
||||
// 以布尔形式从字典中获取指定键的值。
|
||||
//
|
||||
// 入参:
|
||||
// - dict: 字典。
|
||||
// - key: 键。
|
||||
// - defaultValue: 默认值。
|
||||
//
|
||||
// 出参:
|
||||
// - 字典中键对应的值。如果指定键不存在或者值的类型不是布尔,则返回默认值。
|
||||
func GetValueOrDefaultAsBool(dict map[string]any, key string, defaultValue bool) bool {
|
||||
if dict == nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
if value, ok := dict[key]; ok {
|
||||
if result, ok := value.(bool); ok {
|
||||
return result
|
||||
}
|
||||
|
||||
// 兼容字符串类型的值
|
||||
if str, ok := value.(string); ok {
|
||||
if result, err := strconv.ParseBool(str); err == nil {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue
|
||||
}
|
||||
183
internal/pkg/vendors/dogecloud-sdk/client.go
vendored
Normal file
183
internal/pkg/vendors/dogecloud-sdk/client.go
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
package dogecloudsdk
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const dogeHost = "https://api.dogecloud.com"
|
||||
|
||||
type Client struct {
|
||||
accessKey string
|
||||
secretKey string
|
||||
}
|
||||
|
||||
func NewClient(accessKey, secretKey string) *Client {
|
||||
return &Client{accessKey: accessKey, secretKey: secretKey}
|
||||
}
|
||||
|
||||
func (c *Client) UploadCdnCert(note, cert, private string) (*UploadCdnCertResponse, error) {
|
||||
req := &UploadCdnCertRequest{
|
||||
Note: note,
|
||||
Certificate: cert,
|
||||
PrivateKey: private,
|
||||
}
|
||||
|
||||
reqBts, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(reqBts, &reqMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respBts, err := c.sendReq(http.MethodPost, "cdn/cert/upload.json", reqMap, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &UploadCdnCertResponse{}
|
||||
err = json.Unmarshal(respBts, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
||||
return nil, fmt.Errorf("dogecloud api error, code: %d, msg: %s", *resp.Code, *resp.Message)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) BindCdnCertWithDomain(certId int64, domain string) (*BindCdnCertResponse, error) {
|
||||
req := &BindCdnCertRequest{
|
||||
CertId: certId,
|
||||
Domain: &domain,
|
||||
}
|
||||
|
||||
reqBts, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(reqBts, &reqMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respBts, err := c.sendReq(http.MethodPost, "cdn/cert/bind.json", reqMap, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &BindCdnCertResponse{}
|
||||
err = json.Unmarshal(respBts, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
||||
return nil, fmt.Errorf("dogecloud api error, code: %d, msg: %s", *resp.Code, *resp.Message)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) BindCdnCertWithDomainId(certId int64, domainId int64) (*BindCdnCertResponse, error) {
|
||||
req := &BindCdnCertRequest{
|
||||
CertId: certId,
|
||||
DomainId: &domainId,
|
||||
}
|
||||
|
||||
reqBts, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(reqBts, &reqMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respBts, err := c.sendReq(http.MethodPost, "cdn/cert/bind.json", reqMap, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &BindCdnCertResponse{}
|
||||
err = json.Unmarshal(respBts, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
||||
return nil, fmt.Errorf("dogecloud api error, code: %d, msg: %s", *resp.Code, *resp.Message)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// 调用多吉云的 API。
|
||||
// https://docs.dogecloud.com/cdn/api-access-token?id=go
|
||||
//
|
||||
// 入参:
|
||||
// - method:GET 或 POST
|
||||
// - path:是调用的 API 接口地址,包含 URL 请求参数 QueryString,例如:/console/vfetch/add.json?url=xxx&a=1&b=2
|
||||
// - data:POST 的数据,对象,例如 {a: 1, b: 2},传递此参数表示不是 GET 请求而是 POST 请求
|
||||
// - jsonMode:数据 data 是否以 JSON 格式请求,默认为 false 则使用表单形式(a=1&b=2)
|
||||
func (c *Client) sendReq(method string, path string, data map[string]interface{}, jsonMode bool) ([]byte, error) {
|
||||
body := ""
|
||||
mime := ""
|
||||
if jsonMode {
|
||||
_body, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body = string(_body)
|
||||
mime = "application/json"
|
||||
} else {
|
||||
values := url.Values{}
|
||||
for k, v := range data {
|
||||
values.Set(k, v.(string))
|
||||
}
|
||||
body = values.Encode()
|
||||
mime = "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
signStr := "/" + path + "\n" + body
|
||||
hmacObj := hmac.New(sha1.New, []byte(c.secretKey))
|
||||
hmacObj.Write([]byte(signStr))
|
||||
sign := hex.EncodeToString(hmacObj.Sum(nil))
|
||||
auth := fmt.Sprintf("TOKEN %s:%s", c.accessKey, sign)
|
||||
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("%s/%s", dogeHost, path), strings.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("Content-Type", mime)
|
||||
req.Header.Add("Authorization", auth)
|
||||
|
||||
client := http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
r, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
31
internal/pkg/vendors/dogecloud-sdk/models.go
vendored
Normal file
31
internal/pkg/vendors/dogecloud-sdk/models.go
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package dogecloudsdk
|
||||
|
||||
type BaseResponse struct {
|
||||
Code *int `json:"code,omitempty"`
|
||||
Message *string `json:"msg,omitempty"`
|
||||
}
|
||||
|
||||
type UploadCdnCertRequest struct {
|
||||
Note string `json:"note"`
|
||||
Certificate string `json:"cert"`
|
||||
PrivateKey string `json:"private"`
|
||||
}
|
||||
|
||||
type UploadCdnCertResponseData struct {
|
||||
Id int64 `json:"id"`
|
||||
}
|
||||
|
||||
type UploadCdnCertResponse struct {
|
||||
BaseResponse
|
||||
Data *UploadCdnCertResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type BindCdnCertRequest struct {
|
||||
CertId int64 `json:"id"`
|
||||
DomainId *int64 `json:"did,omitempty"`
|
||||
Domain *string `json:"domain,omitempty"`
|
||||
}
|
||||
|
||||
type BindCdnCertResponse struct {
|
||||
BaseResponse
|
||||
}
|
||||
14
internal/pkg/vendors/qiniu-sdk/client.go
vendored
14
internal/pkg/vendors/qiniu-sdk/client.go
vendored
@@ -12,7 +12,7 @@ import (
|
||||
xhttp "github.com/usual2970/certimate/internal/utils/http"
|
||||
)
|
||||
|
||||
const qiniuHost = "http://api.qiniu.com"
|
||||
const qiniuHost = "https://api.qiniu.com"
|
||||
|
||||
type Client struct {
|
||||
mac *auth.Credentials
|
||||
@@ -105,12 +105,12 @@ func (c *Client) EnableDomainHttps(domain, certId string, forceHttps, http2Enabl
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) UploadSslCert(name, commonName, pri, ca string) (*UploadSslCertResponse, error) {
|
||||
func (c *Client) UploadSslCert(name, commonName, certificate, privateKey string) (*UploadSslCertResponse, error) {
|
||||
req := &UploadSslCertRequest{
|
||||
Name: name,
|
||||
CommonName: commonName,
|
||||
Pri: pri,
|
||||
Ca: ca,
|
||||
Name: name,
|
||||
CommonName: commonName,
|
||||
Certificate: certificate,
|
||||
PrivateKey: privateKey,
|
||||
}
|
||||
|
||||
reqBytes, err := json.Marshal(req)
|
||||
@@ -129,7 +129,7 @@ func (c *Client) UploadSslCert(name, commonName, pri, ca string) (*UploadSslCert
|
||||
return nil, err
|
||||
}
|
||||
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
||||
return nil, fmt.Errorf("code: %d, error: %s", *resp.Code, *resp.Error)
|
||||
return nil, fmt.Errorf("qiniu api error, code: %d, error: %s", *resp.Code, *resp.Error)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
||||
27
internal/pkg/vendors/qiniu-sdk/models.go
vendored
27
internal/pkg/vendors/qiniu-sdk/models.go
vendored
@@ -1,16 +1,20 @@
|
||||
package qiniusdk
|
||||
|
||||
type BaseResponse struct {
|
||||
Code *int `json:"code,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type UploadSslCertRequest struct {
|
||||
Name string `json:"name"`
|
||||
CommonName string `json:"common_name"`
|
||||
Pri string `json:"pri"`
|
||||
Ca string `json:"ca"`
|
||||
Name string `json:"name"`
|
||||
CommonName string `json:"common_name"`
|
||||
Certificate string `json:"ca"`
|
||||
PrivateKey string `json:"pri"`
|
||||
}
|
||||
|
||||
type UploadSslCertResponse struct {
|
||||
Code *int `json:"code,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
CertID string `json:"certID"`
|
||||
*BaseResponse
|
||||
CertID string `json:"certID"`
|
||||
}
|
||||
|
||||
type DomainInfoHttpsData struct {
|
||||
@@ -20,8 +24,7 @@ type DomainInfoHttpsData struct {
|
||||
}
|
||||
|
||||
type GetDomainInfoResponse struct {
|
||||
Code *int `json:"code,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
BaseResponse
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
CName string `json:"cname"`
|
||||
@@ -39,8 +42,7 @@ type ModifyDomainHttpsConfRequest struct {
|
||||
}
|
||||
|
||||
type ModifyDomainHttpsConfResponse struct {
|
||||
Code *int `json:"code,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
BaseResponse
|
||||
}
|
||||
|
||||
type EnableDomainHttpsRequest struct {
|
||||
@@ -48,6 +50,5 @@ type EnableDomainHttpsRequest struct {
|
||||
}
|
||||
|
||||
type EnableDomainHttpsResponse struct {
|
||||
Code *int `json:"code,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
BaseResponse
|
||||
}
|
||||
|
||||
807
migrations/1730766480_collections_snapshot.go
Normal file
807
migrations/1730766480_collections_snapshot.go
Normal file
@@ -0,0 +1,807 @@
|
||||
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"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(db dbx.Builder) error {
|
||||
jsonData := `[
|
||||
{
|
||||
"id": "z3p974ainxjqlvs",
|
||||
"created": "2024-07-29 10:02:48.334Z",
|
||||
"updated": "2024-10-23 09:25:43.083Z",
|
||||
"name": "domains",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "iuaerpl2",
|
||||
"name": "domain",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "ukkhuw85",
|
||||
"name": "email",
|
||||
"type": "email",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"exceptDomains": null,
|
||||
"onlyDomains": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "v98eebqq",
|
||||
"name": "crontab",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "alc8e9ow",
|
||||
"name": "access",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "4yzbv8urny5ja1e",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "topsc9bj",
|
||||
"name": "certUrl",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "vixgq072",
|
||||
"name": "certStableUrl",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "g3a3sza5",
|
||||
"name": "privateKey",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "gr6iouny",
|
||||
"name": "certificate",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "tk6vnrmn",
|
||||
"name": "issuerCertificate",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "sjo6ibse",
|
||||
"name": "csr",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "x03n1bkj",
|
||||
"name": "expiredAt",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "srybpixz",
|
||||
"name": "targetType",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"aliyun-oss",
|
||||
"aliyun-cdn",
|
||||
"aliyun-dcdn",
|
||||
"ssh",
|
||||
"webhook",
|
||||
"tencent-cdn",
|
||||
"qiniu-cdn",
|
||||
"local"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "xy7yk0mb",
|
||||
"name": "targetAccess",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "4yzbv8urny5ja1e",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "6jqeyggw",
|
||||
"name": "enabled",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "hdsjcchf",
|
||||
"name": "deployed",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "aiya3rev",
|
||||
"name": "rightnow",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "ixznmhzc",
|
||||
"name": "lastDeployedAt",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "ghtlkn5j",
|
||||
"name": "lastDeployment",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "0a1o4e6sstp694f",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "zfnyj9he",
|
||||
"name": "variables",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "1bspzuku",
|
||||
"name": "group",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "teolp9pl72dxlxq",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "g65gfh7a",
|
||||
"name": "nameservers",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "wwrzc3jo",
|
||||
"name": "applyConfig",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "474iwy8r",
|
||||
"name": "deployConfig",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_4ABO6EQ` + "`" + ` ON ` + "`" + `domains` + "`" + ` (` + "`" + `domain` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "4yzbv8urny5ja1e",
|
||||
"created": "2024-07-29 10:04:39.685Z",
|
||||
"updated": "2024-11-05 00:21:32.129Z",
|
||||
"name": "access",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "geeur58v",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "iql7jpwx",
|
||||
"name": "config",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "lr33hiwg",
|
||||
"name": "deleted",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "hsxcnlvd",
|
||||
"name": "usage",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"apply",
|
||||
"deploy",
|
||||
"all"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "c8egzzwj",
|
||||
"name": "group",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "teolp9pl72dxlxq",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_wkoST0j` + "`" + ` ON ` + "`" + `access` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "0a1o4e6sstp694f",
|
||||
"created": "2024-07-30 06:30:27.801Z",
|
||||
"updated": "2024-10-23 09:25:43.084Z",
|
||||
"name": "deployments",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "farvlzk7",
|
||||
"name": "domain",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "z3p974ainxjqlvs",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "jx5f69i3",
|
||||
"name": "log",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "qbxdtg9q",
|
||||
"name": "phase",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"check",
|
||||
"apply",
|
||||
"deploy"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "rglrp1hz",
|
||||
"name": "phaseSuccess",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "lt1g1blu",
|
||||
"name": "deployedAt",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "wledpzgb",
|
||||
"name": "wholeSuccess",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "_pb_users_auth_",
|
||||
"created": "2024-09-12 13:09:54.234Z",
|
||||
"updated": "2024-10-23 09:25:43.085Z",
|
||||
"name": "users",
|
||||
"type": "auth",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_name",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_avatar",
|
||||
"name": "avatar",
|
||||
"type": "file",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"mimeTypes": [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/svg+xml",
|
||||
"image/gif",
|
||||
"image/webp"
|
||||
],
|
||||
"thumbs": null,
|
||||
"maxSelect": 1,
|
||||
"maxSize": 5242880,
|
||||
"protected": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": "id = @request.auth.id",
|
||||
"viewRule": "id = @request.auth.id",
|
||||
"createRule": "",
|
||||
"updateRule": "id = @request.auth.id",
|
||||
"deleteRule": "id = @request.auth.id",
|
||||
"options": {
|
||||
"allowEmailAuth": true,
|
||||
"allowOAuth2Auth": true,
|
||||
"allowUsernameAuth": true,
|
||||
"exceptEmailDomains": null,
|
||||
"manageRule": null,
|
||||
"minPasswordLength": 8,
|
||||
"onlyEmailDomains": null,
|
||||
"onlyVerified": false,
|
||||
"requireEmail": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dy6ccjb60spfy6p",
|
||||
"created": "2024-09-12 23:12:21.677Z",
|
||||
"updated": "2024-10-23 09:25:43.085Z",
|
||||
"name": "settings",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "1tcmdsdf",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "f9wyhypi",
|
||||
"name": "content",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_RO7X9Vw` + "`" + ` ON ` + "`" + `settings` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "teolp9pl72dxlxq",
|
||||
"created": "2024-09-13 12:51:05.611Z",
|
||||
"updated": "2024-10-23 09:25:43.086Z",
|
||||
"name": "access_groups",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "7sajiv6i",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "xp8admif",
|
||||
"name": "access",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "4yzbv8urny5ja1e",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": null,
|
||||
"displayFields": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_RgRXp0R` + "`" + ` ON ` + "`" + `access_groups` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "012d7abbod1hwvr",
|
||||
"created": "2024-10-23 06:37:13.155Z",
|
||||
"updated": "2024-10-23 09:25:43.086Z",
|
||||
"name": "acme_accounts",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "fmjfn0yw",
|
||||
"name": "ca",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "qqwijqzt",
|
||||
"name": "email",
|
||||
"type": "email",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"exceptDomains": null,
|
||||
"onlyDomains": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "genxqtii",
|
||||
"name": "key",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "1aoia909",
|
||||
"name": "resource",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
}
|
||||
]`
|
||||
|
||||
collections := []*models.Collection{}
|
||||
if err := json.Unmarshal([]byte(jsonData), &collections); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return daos.New(db).ImportCollections(collections, true, nil)
|
||||
}, func(db dbx.Builder) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
105
migrations/1731463526_updated_access.go
Normal file
105
migrations/1731463526_updated_access.go
Normal file
@@ -0,0 +1,105 @@
|
||||
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"
|
||||
]
|
||||
}
|
||||
}`), 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"
|
||||
]
|
||||
}
|
||||
}`), edit_configType); err != nil {
|
||||
return err
|
||||
}
|
||||
collection.Schema.AddField(edit_configType)
|
||||
|
||||
return dao.SaveCollection(collection)
|
||||
})
|
||||
}
|
||||
1
ui/public/imgs/providers/baiducloud.svg
Normal file
1
ui/public/imgs/providers/baiducloud.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1684" width="200" height="200"><path d="M482.687 74.101c10.109-5.627 19.662-12.497 30.785-15.998 8.506-2.192 16.685 2.323 23.883 6.38 117.253 68.08 235.062 135.312 352.118 203.753-40.338 22.115-79.662 46.063-119.805 68.506-27.677 15.148-62.814 13.74-89.771-2.388-48.289-28.135-96.871-55.78-145.127-84.014-8.997-5.692-21.232-5.889-30.654-1.21-49.728 28.724-99.456 57.58-149.282 86.173-27.056 15.834-61.963 15.867-89.314 0.687a26252.906 26252.906 0 0 1-113.785-65.628c-0.229-1.178-0.72-3.533-0.949-4.744 110.776-63.533 221.256-127.722 331.9-191.517z" fill="#72AF2D" p-id="1685"></path><path d="M115.552 719.744c0.49-135.148-0.622-270.329 0.556-405.477 32.617 19.367 65.595 38.08 98.441 57.088 12.76 7.427 26.27 14.199 36.74 24.864 15.769 16.39 26.042 38.67 25.845 61.637 0.033 54.57 0.131 109.172-0.065 163.774-1.047 12.203 3.304 25.65 14.493 31.963 40.567 23.72 81.396 47.045 122.095 70.6 14.362 8.638 29.771 15.9 42.4 27.057 18.156 17.11 28.756 41.777 29.116 66.707-0.033 44.559-0.196 89.15 0.066 133.709-10.175-3.468-18.877-9.848-28.201-14.984-108.55-62.716-217.167-125.366-325.652-188.148-10.207-5.66-17.143-16.947-15.834-28.79z" fill="#118CCF" p-id="1686"></path><path d="M815.143 367.397c30.753-17.47 61.015-35.824 92.095-52.705 0.196 135.017-0.066 270.035 0.13 405.052 0.819 11.582-5.3 23.163-15.637 28.627-110.416 63.86-220.896 127.558-331.312 191.386-7.328 4.45-14.722 8.8-22.508 12.432-0.098-44.82 0.065-89.64-0.098-134.428-0.426-31.538 17.47-62.16 44.558-78.06 49.074-28.43 98.245-56.664 147.286-85.159 8.245-4.515 15.311-13.053 14.919-22.9 0.13-55.683 0.065-111.398 0-167.08-0.033-23.784 8.048-47.732 24.21-65.398 12.497-14.362 30.36-22.083 46.357-31.767z" fill="#DA4525" p-id="1687"></path></svg>
|
||||
1
ui/public/imgs/providers/dogecloud.svg
Normal file
1
ui/public/imgs/providers/dogecloud.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
@@ -1,28 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="-0.5 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
<title>Google-color</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
|
||||
</defs>
|
||||
<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>
|
||||
<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: 2.1 KiB After Width: | Height: | Size: 1.7 KiB |
8
ui/public/imgs/providers/volcengine.svg
Normal file
8
ui/public/imgs/providers/volcengine.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="192" height="192">
|
||||
<path d="M0 0 C1.32 0 2.64 0 4 0 C9.5316738 22.52529447 14.8267431 45.0974913 19.96630859 67.715271 C20.87688366 71.72007263 21.79014366 75.72426201 22.703125 79.72851562 C24.47126161 87.48510668 26.23661634 95.24232702 28 103 C28.16081055 102.28344238 28.32162109 101.56688477 28.48730469 100.82861328 C29.22431391 97.58987302 29.98678399 94.35766013 30.75 91.125 C31.12898438 89.43310547 31.12898438 89.43310547 31.515625 87.70703125 C31.90234375 86.09248047 31.90234375 86.09248047 32.296875 84.4453125 C32.52729492 83.45031738 32.75771484 82.45532227 32.99511719 81.43017578 C34 79 34 79 36.10644531 77.65966797 C36.73131836 77.44197754 37.35619141 77.22428711 38 77 C40.25565304 80.38347955 40.99634884 83.52624948 41.87939453 87.42138672 C42.14317902 88.5511644 42.14317902 88.5511644 42.41229248 89.70376587 C42.99201316 92.19434495 43.56265709 94.68690271 44.1328125 97.1796875 C44.533134 98.91228981 44.93384541 100.64480206 45.33493042 102.37722778 C46.17438801 106.00952966 47.00941838 109.64281323 47.84130859 113.27685547 C48.90714146 117.93143946 49.98338211 122.58354121 51.06289864 127.23496819 C51.89220185 130.81312389 52.71620818 134.39248313 53.53860092 137.97223282 C53.93334028 139.6877775 54.32966359 141.40295852 54.72760391 143.11776352 C55.28354421 145.5172615 55.83239226 147.91827521 56.37939453 150.31982422 C56.54456573 151.02649231 56.70973694 151.7331604 56.87991333 152.46124268 C57.68382252 156.02635542 58.2877538 159.34133935 58 163 C57 164 57 164 54.72175503 164.12304783 C53.70501737 164.12212604 52.68827971 164.12120424 51.64073181 164.12025452 C50.47820389 164.12162918 49.31567596 164.12300385 48.11791992 164.12442017 C46.83111328 164.12082489 45.54430664 164.11722961 44.21850586 164.11352539 C42.85876976 164.11324352 41.4990335 164.11340125 40.13929749 164.1139679 C36.43698222 164.11425587 32.73469908 164.10835912 29.03239131 164.10139394 C25.16731219 164.09516043 21.30223242 164.0945566 17.43714905 164.09336853 C10.11315318 164.09025521 2.78917216 164.0820418 -4.53481716 164.07201904 C-12.87083448 164.06085794 -21.20685249 164.05534746 -29.54287541 164.05032361 C-46.69525888 164.03985919 -63.84762787 164.02225973 -81 164 C-82.14187085 160.57438746 -81.82373034 159.34227454 -81.01196289 155.88598633 C-80.77163834 154.84513382 -80.53131378 153.80428131 -80.28370667 152.73188782 C-80.01606003 151.60993515 -79.74841339 150.48798248 -79.47265625 149.33203125 C-79.20488876 148.18208176 -78.93712128 147.03213226 -78.66123962 145.84733582 C-77.80250112 142.1672533 -76.93263711 138.48990227 -76.0625 134.8125 C-75.47908424 132.32113519 -74.89639264 129.82960068 -74.31445312 127.33789062 C-72.88470561 121.22326496 -71.44581004 115.11084638 -70 109 C-68.02 108.505 -68.02 108.505 -66 108 C-65.51891725 109.41601714 -65.04021859 110.83284437 -64.5625 112.25 C-64.29566406 113.03890625 -64.02882813 113.8278125 -63.75390625 114.640625 C-63.08016922 116.74910774 -62.52533639 118.8510768 -62 121 C-57.92546649 106.54247934 -54.67580491 91.88514666 -51.3125 77.25 C-50.42369645 73.38688228 -49.53364952 69.52405334 -48.64213562 65.66156006 C-48.09081632 63.27246643 -47.54096762 60.88303285 -46.99275208 58.4932251 C-46.7454686 57.42070496 -46.49818512 56.34818481 -46.2434082 55.24316406 C-46.02675003 54.30023499 -45.81009186 53.35730591 -45.58686829 52.38580322 C-45 50 -45 50 -44 47 C-43.01 46.67 -42.02 46.34 -41 46 C-38.23625561 48.76374439 -37.91156555 51.86361199 -37.08984375 55.5625 C-36.91747696 56.30959229 -36.74511017 57.05668457 -36.56752014 57.82641602 C-36.01731597 60.21609875 -35.47695373 62.60786961 -34.9375 65 C-34.39285742 67.39172265 -33.84616188 69.78294823 -33.29676819 72.17358398 C-32.95528025 73.66196219 -32.61684088 75.15104374 -32.28172302 76.64086914 C-31.44117319 80.42190439 -31.44117319 80.42190439 -30 84 C-28.35 77.07 -26.7 70.14 -25 63 C-23.02 62.505 -23.02 62.505 -21 62 C-19.68 64.97 -18.36 67.94 -17 71 C-16.80177139 70.12525528 -16.60354279 69.25051056 -16.39930725 68.34925842 C-14.53431568 60.12241124 -12.66583508 51.89636614 -10.79338932 43.6712122 C-9.83077597 39.44213487 -8.86953458 35.21275527 -7.91137695 30.98266602 C-6.98737224 26.90358604 -6.05983134 22.82532526 -5.12978935 18.74761772 C-4.77485423 17.18852063 -4.42121379 15.62912824 -4.06886482 14.06944466 C-3.57671669 11.89194568 -3.08005547 9.71552394 -2.58227539 7.53930664 C-2.30025772 6.29798477 -2.01824005 5.0566629 -1.72767639 3.77772522 C-1 1 -1 1 0 0 Z " fill="#1678FE" transform="translate(108,0)"/>
|
||||
<path d="M0 0 C4.0123413 2.6748942 4.29118676 5.67680766 5.3671875 10.12890625 C5.56982208 10.95140366 5.77245667 11.77390106 5.98123169 12.62132263 C6.40631662 14.37247717 6.82449093 16.12532127 7.23632812 17.87963867 C7.86227451 20.50904418 8.53517407 23.12326358 9.2109375 25.74023438 C12.07068499 37.41556769 12.00448657 46.42095444 9 58 C8.72833008 59.1955249 8.45666016 60.3910498 8.17675781 61.62280273 C7.39051448 65.07318639 6.54720713 68.50474629 5.6875 71.9375 C5.07306448 74.4346121 4.45981035 76.93201514 3.84765625 79.4296875 C3.54102051 80.6784668 3.23438477 81.92724609 2.91845703 83.21386719 C1.17036632 90.41997851 -0.47868082 97.64885161 -2.1340332 104.87670898 C-2.48147583 106.39373413 -2.48147583 106.39373413 -2.8359375 107.94140625 C-3.03751465 108.82513916 -3.2390918 109.70887207 -3.44677734 110.61938477 C-3.94226379 112.75155085 -4.46909406 114.87637623 -5 117 C10.84 117 26.68 117 43 117 C43 117.33 43 117.66 43 118 C15.94 118 -11.12 118 -39 118 C-40.14187085 114.57438746 -39.82373034 113.34227454 -39.01196289 109.88598633 C-38.77163834 108.84513382 -38.53131378 107.80428131 -38.28370667 106.73188782 C-38.01606003 105.60993515 -37.74841339 104.48798248 -37.47265625 103.33203125 C-37.20488876 102.18208176 -36.93712128 101.03213226 -36.66123962 99.84733582 C-35.80250112 96.1672533 -34.93263711 92.48990227 -34.0625 88.8125 C-33.47908424 86.32113519 -32.89639264 83.82960068 -32.31445312 81.33789062 C-30.88470561 75.22326496 -29.44581004 69.11084638 -28 63 C-26.02 62.505 -26.02 62.505 -24 62 C-23.51891725 63.41601714 -23.04021859 64.83284437 -22.5625 66.25 C-22.29566406 67.03890625 -22.02882813 67.8278125 -21.75390625 68.640625 C-21.08016922 70.74910774 -20.52533639 72.8510768 -20 75 C-15.92546649 60.54247934 -12.67580491 45.88514666 -9.3125 31.25 C-8.42369645 27.38688228 -7.53364952 23.52405334 -6.64213562 19.66156006 C-6.09081632 17.27246643 -5.54096762 14.88303285 -4.99275208 12.4932251 C-4.7454686 11.42070496 -4.49818512 10.34818481 -4.2434082 9.24316406 C-4.02675003 8.30023499 -3.81009186 7.35730591 -3.58686829 6.38580322 C-3 4 -3 4 -2 1 C-1.34 0.67 -0.68 0.34 0 0 Z " fill="#167AFE" transform="translate(66,46)"/>
|
||||
<path d="M0 0 C1.32 0.33 2.64 0.66 4 1 C4.46678965 2.58086094 4.9227585 4.16491593 5.375 5.75 C5.63023438 6.63171875 5.88546875 7.5134375 6.1484375 8.421875 C6.87542654 11.47656291 7.4139917 14.4642372 7.90234375 17.5625 C8.98028023 24.22106762 10.55043952 30.7099525 12.1875 37.25 C15.20528073 49.49873355 17.97533372 61.78769858 20.60229492 74.12597656 C22.01333501 80.7015831 23.52136706 87.18589313 25.45800781 93.62841797 C26 96 26 96 26 101 C9.83 101 -6.34 101 -23 101 C-22.38084899 96.04679193 -21.8086416 91.74373782 -20.6171875 87 C-20.34060303 85.88230225 -20.06401855 84.76460449 -19.77905273 83.61303711 C-19.48071533 82.42073486 -19.18237793 81.22843262 -18.875 80 C-16.25563065 69.34110325 -13.7678342 58.66334124 -11.4375 47.9375 C-11.29371918 47.27721802 -11.14993835 46.61693604 -11.00180054 45.93664551 C-9.55006847 39.26293849 -8.11885949 32.58512368 -6.71751404 25.90065002 C-6.30464964 23.93161018 -5.88977083 21.96299305 -5.47485352 19.99438477 C-4.96236238 17.55715172 -4.45477927 15.11887978 -3.95288086 12.67944336 C-3.72479736 11.5941333 -3.49671387 10.50882324 -3.26171875 9.390625 C-3.06537842 8.44219727 -2.86903809 7.49376953 -2.66674805 6.51660156 C-2.03222201 4.1216201 -1.19689606 2.16094777 0 0 Z " fill="#36E1E2" transform="translate(84,62)"/>
|
||||
<path d="M0 0 C0.99 0.33 1.98 0.66 3 1 C4.03887939 3.83370972 4.03887939 3.83370972 4.95800781 7.59326172 C5.2097621 8.60251363 5.2097621 8.60251363 5.46660233 9.63215446 C5.99125051 11.75173458 6.49700713 13.87518769 7 16 C7.30627319 17.26249573 7.61254639 18.52499146 7.92810059 19.82574463 C8.86110039 23.71559829 9.77067925 27.61050619 10.671875 31.5078125 C11.0024033 32.9317254 11.33299132 34.35562444 11.66363525 35.7795105 C12.34771578 38.72856805 13.02972361 41.67809603 13.71044922 44.62792969 C14.58141761 48.40048291 15.45982697 52.17126278 16.33997726 55.94168282 C17.02142891 58.86620824 17.69792035 61.79186451 18.37302399 64.71786118 C18.69498969 66.1096897 19.01873077 67.50110884 19.3443222 68.89209366 C19.79692579 70.8280578 20.24189044 72.76580301 20.68652344 74.70361328 C20.94093201 75.80198517 21.19534058 76.90035706 21.4574585 78.03201294 C21.9607087 80.78505594 22.18590561 83.21129819 22 86 C21 87 21 87 19.23413086 87.11352539 C18.48526611 87.10828857 17.73640137 87.10305176 16.96484375 87.09765625 C16.15595703 87.09443359 15.34707031 87.09121094 14.51367188 87.08789062 C13.66353516 87.07951172 12.81339844 87.07113281 11.9375 87.0625 C11.08349609 87.05798828 10.22949219 87.05347656 9.34960938 87.04882812 C7.23304337 87.03700373 5.11651315 87.01906769 3 87 C3.02868164 85.97616211 3.05736328 84.95232422 3.08691406 83.89746094 C3.00469391 80.21048752 2.5113821 77.20197915 1.609375 73.640625 C1.31353516 72.45114258 1.01769531 71.26166016 0.71289062 70.03613281 C0.39513672 68.78670898 0.07738281 67.53728516 -0.25 66.25 C-0.89501094 63.64690845 -1.53300803 61.04210523 -2.171875 58.4375 C-2.48447266 57.1690625 -2.79707031 55.900625 -3.11914062 54.59375 C-4.28889622 49.82135467 -5.43093728 45.04596159 -6.5 40.25 C-6.67176758 39.53231445 -6.84353516 38.81462891 -7.02050781 38.07519531 C-8.95876389 29.16935201 -6.66000706 20.76440249 -4.5625 12.0625 C-4.34529297 11.11826172 -4.12808594 10.17402344 -3.90429688 9.20117188 C-3.68966797 8.30076172 -3.47503906 7.40035156 -3.25390625 6.47265625 C-3.06127197 5.66062744 -2.8686377 4.84859863 -2.67016602 4.01196289 C-2 2 -2 2 0 0 Z " fill="#37E1E2" transform="translate(144,77)"/>
|
||||
<path d="M0 0 C0.99 0.33 1.98 0.66 3 1 C3.94921875 3.37109375 3.94921875 3.37109375 4.6875 6.4375 C4.93886719 7.42621094 5.19023438 8.41492188 5.44921875 9.43359375 C6.90509112 18.86062549 5.3631431 27.00072147 3.0625 36.1875 C0.61192077 46.01462243 0.61192077 46.01462243 -1 56 C-4.96 56 -8.92 56 -13 56 C-14.13719943 52.5884017 -13.83299816 51.34792773 -13.04370117 47.90185547 C-12.809832 46.86423065 -12.57596283 45.82660583 -12.33500671 44.75753784 C-12.07363419 43.63872223 -11.81226166 42.51990662 -11.54296875 41.3671875 C-11.27994461 40.21960968 -11.01692047 39.07203186 -10.7459259 37.88967896 C-10.18736857 35.46280808 -9.62473117 33.03687321 -9.05834961 30.61181641 C-8.19002923 26.88585293 -7.3393032 23.15617289 -6.49023438 19.42578125 C-5.94636766 17.07267254 -5.40148044 14.71979942 -4.85546875 12.3671875 C-4.60016891 11.24432343 -4.34486908 10.12145935 -4.08183289 8.96456909 C-3.83850723 7.9332486 -3.59518158 6.9019281 -3.34448242 5.83935547 C-3.13272873 4.92685028 -2.92097504 4.01434509 -2.70280457 3.07418823 C-2 1 -2 1 0 0 Z " fill="#38E1E2" transform="translate(40,108)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
194
ui/src/components/certimate/AccessBaiduCloudForm.tsx
Normal file
194
ui/src/components/certimate/AccessBaiduCloudForm.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 BaiduCloudConfig } from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfigContext } from "@/providers/config";
|
||||
|
||||
type AccessBaiduCloudFormProps = {
|
||||
op: "add" | "edit" | "copy";
|
||||
data?: Access;
|
||||
onAfterReq: () => void;
|
||||
};
|
||||
|
||||
const AccessBaiduCloudForm = ({ data, op, onAfterReq }: AccessBaiduCloudFormProps) => {
|
||||
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,
|
||||
accessKeyId: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.access_key_id.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
secretAccessKey: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.secret_access_key.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
});
|
||||
|
||||
let config: BaiduCloudConfig = {
|
||||
accessKeyId: "",
|
||||
secretAccessKey: "",
|
||||
};
|
||||
if (data) config = data.config as BaiduCloudConfig;
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data?.id,
|
||||
name: data?.name || "",
|
||||
configType: "baiducloud",
|
||||
accessKeyId: config.accessKeyId,
|
||||
secretAccessKey: config.secretAccessKey,
|
||||
},
|
||||
});
|
||||
|
||||
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: {
|
||||
accessKeyId: data.accessKeyId,
|
||||
secretAccessKey: data.secretAccessKey,
|
||||
},
|
||||
};
|
||||
|
||||
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="accessKeyId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("access.authorization.form.access_key_id.placeholder")} {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="secretAccessKey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("access.authorization.form.secret_access_key.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("access.authorization.form.secret_access_key.placeholder")} {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t("common.save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessBaiduCloudForm;
|
||||
188
ui/src/components/certimate/AccessDogeCloudForm.tsx
Normal file
188
ui/src/components/certimate/AccessDogeCloudForm.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
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 { Button } from "@/components/ui/button";
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { PbErrorData } from "@/domain/base";
|
||||
import { accessProvidersMap, accessTypeFormSchema, type Access, type DogeCloudConfig } from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfigContext } from "@/providers/config";
|
||||
|
||||
type AccessDogeCloudFormProps = {
|
||||
op: "add" | "edit" | "copy";
|
||||
data?: Access;
|
||||
onAfterReq: () => void;
|
||||
};
|
||||
|
||||
const AccessDogeCloudForm = ({ data, op, onAfterReq }: AccessDogeCloudFormProps) => {
|
||||
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),
|
||||
secretKey: z.string().min(1, "access.authorization.form.secret_key.placeholder").max(64),
|
||||
});
|
||||
|
||||
let config: DogeCloudConfig = {
|
||||
accessKey: "",
|
||||
secretKey: "",
|
||||
};
|
||||
if (data) config = data.config as DogeCloudConfig;
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data?.id,
|
||||
name: data?.name || "",
|
||||
configType: "dogecloud",
|
||||
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 AccessDogeCloudForm;
|
||||
@@ -8,7 +8,9 @@ import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import AccessAliyunForm from "./AccessAliyunForm";
|
||||
import AccessTencentForm from "./AccessTencentForm";
|
||||
import AccessHuaweiCloudForm from "./AccessHuaweicloudForm";
|
||||
import AccessBaiduCloudForm from "./AccessBaiduCloudForm";
|
||||
import AccessQiniuForm from "./AccessQiniuForm";
|
||||
import AccessDogeCloudForm from "./AccessDogeCloudForm";
|
||||
import AccessAwsForm from "./AccessAwsForm";
|
||||
import AccessCloudflareForm from "./AccessCloudflareForm";
|
||||
import AccessNamesiloForm from "./AccessNamesiloForm";
|
||||
@@ -19,6 +21,7 @@ import AccessLocalForm from "./AccessLocalForm";
|
||||
import AccessSSHForm from "./AccessSSHForm";
|
||||
import AccessWebhookForm from "./AccessWebhookForm";
|
||||
import AccessKubernetesForm from "./AccessKubernetesForm";
|
||||
import AccessVolcengineForm from "./AccessVolcengineForm";
|
||||
import { Access } from "@/domain/access";
|
||||
import { AccessTypeSelect } from "./AccessTypeSelect";
|
||||
|
||||
@@ -71,6 +74,17 @@ const AccessEditDialog = ({ trigger, op, data, className }: AccessEditProps) =>
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case "baiducloud":
|
||||
childComponent = (
|
||||
<AccessBaiduCloudForm
|
||||
data={data}
|
||||
op={op}
|
||||
onAfterReq={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case "qiniu":
|
||||
childComponent = (
|
||||
<AccessQiniuForm
|
||||
@@ -82,6 +96,17 @@ const AccessEditDialog = ({ trigger, op, data, className }: AccessEditProps) =>
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case "dogecloud":
|
||||
childComponent = (
|
||||
<AccessDogeCloudForm
|
||||
data={data}
|
||||
op={op}
|
||||
onAfterReq={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case "aws":
|
||||
childComponent = (
|
||||
<AccessAwsForm
|
||||
@@ -192,6 +217,17 @@ const AccessEditDialog = ({ trigger, op, data, className }: AccessEditProps) =>
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case "volcengine":
|
||||
childComponent = (
|
||||
<AccessVolcengineForm
|
||||
data={data}
|
||||
op={op}
|
||||
onAfterReq={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
194
ui/src/components/certimate/AccessVolcengineForm.tsx
Normal file
194
ui/src/components/certimate/AccessVolcengineForm.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 VolcengineConfig } from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfigContext } from "@/providers/config";
|
||||
|
||||
type AccessVolcengineFormProps = {
|
||||
op: "add" | "edit" | "copy";
|
||||
data?: Access;
|
||||
onAfterReq: () => void;
|
||||
};
|
||||
|
||||
const AccessVolcengineForm = ({ data, op, onAfterReq }: AccessVolcengineFormProps) => {
|
||||
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,
|
||||
accessKeyId: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.access_key_id.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
secretAccessKey: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.secret_access_key.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
});
|
||||
|
||||
let config: VolcengineConfig = {
|
||||
accessKeyId: "",
|
||||
secretAccessKey: "",
|
||||
};
|
||||
if (data) config = data.config as VolcengineConfig;
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data?.id,
|
||||
name: data?.name || "",
|
||||
configType: "volcengine",
|
||||
accessKeyId: config.accessKeyId,
|
||||
secretAccessKey: config.secretAccessKey,
|
||||
},
|
||||
});
|
||||
|
||||
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: {
|
||||
accessKeyId: data.accessKeyId,
|
||||
secretAccessKey: data.secretAccessKey,
|
||||
},
|
||||
};
|
||||
|
||||
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="accessKeyId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("access.authorization.form.access_key_id.placeholder")} {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="secretAccessKey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("access.authorization.form.secret_access_key.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("access.authorization.form.secret_access_key.placeholder")} {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t("common.save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessVolcengineForm;
|
||||
@@ -20,11 +20,15 @@ import DeployToTencentCOS from "./DeployToTencentCOS";
|
||||
import DeployToTencentTEO from "./DeployToTencentTEO";
|
||||
import DeployToHuaweiCloudCDN from "./DeployToHuaweiCloudCDN";
|
||||
import DeployToHuaweiCloudELB from "./DeployToHuaweiCloudELB";
|
||||
import DeployToBaiduCloudCDN from "./DeployToBaiduCloudCDN";
|
||||
import DeployToQiniuCDN from "./DeployToQiniuCDN";
|
||||
import DeployToDogeCloudCDN from "./DeployToDogeCloudCDN";
|
||||
import DeployToLocal from "./DeployToLocal";
|
||||
import DeployToSSH from "./DeployToSSH";
|
||||
import DeployToWebhook from "./DeployToWebhook";
|
||||
import DeployToKubernetesSecret from "./DeployToKubernetesSecret";
|
||||
import DeployToVolcengineLive from "./DeployToVolcengineLive"
|
||||
import DeployToVolcengineCDN from "./DeployToVolcengineCDN"
|
||||
import { deployTargetsMap, type DeployConfig } from "@/domain/domain";
|
||||
import { accessProvidersMap } from "@/domain/access";
|
||||
import { useConfigContext } from "@/providers/config";
|
||||
@@ -151,9 +155,15 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
|
||||
case "huaweicloud-elb":
|
||||
childComponent = <DeployToHuaweiCloudELB />;
|
||||
break;
|
||||
case "baiducloud-cdn":
|
||||
childComponent = <DeployToBaiduCloudCDN />;
|
||||
break;
|
||||
case "qiniu-cdn":
|
||||
childComponent = <DeployToQiniuCDN />;
|
||||
break;
|
||||
case "dogecloud-cdn":
|
||||
childComponent = <DeployToDogeCloudCDN />;
|
||||
break;
|
||||
case "local":
|
||||
childComponent = <DeployToLocal />;
|
||||
break;
|
||||
@@ -166,6 +176,12 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
|
||||
case "k8s-secret":
|
||||
childComponent = <DeployToKubernetesSecret />;
|
||||
break;
|
||||
case "volcengine-live":
|
||||
childComponent = <DeployToVolcengineLive />;
|
||||
break;
|
||||
case "volcengine-cdn":
|
||||
childComponent = <DeployToVolcengineCDN />;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
68
ui/src/components/certimate/DeployToBaiduCloudCDN.tsx
Normal file
68
ui/src/components/certimate/DeployToBaiduCloudCDN.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 DeployToBaiduCloudCDNConfigParams = {
|
||||
domain?: string;
|
||||
};
|
||||
|
||||
const DeployToBaiduCloudCDN = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToBaiduCloudCDNConfigParams>();
|
||||
|
||||
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")}</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 DeployToBaiduCloudCDN;
|
||||
68
ui/src/components/certimate/DeployToDogeCloudCDN.tsx
Normal file
68
ui/src/components/certimate/DeployToDogeCloudCDN.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 DeployToDogeCloudCDNConfigParams = {
|
||||
domain?: string;
|
||||
};
|
||||
|
||||
const DeployToDogeCloudCDN = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToDogeCloudCDNConfigParams>();
|
||||
|
||||
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")}</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 DeployToDogeCloudCDN;
|
||||
@@ -44,9 +44,7 @@ const DeployToTencentCLB = () => {
|
||||
}),
|
||||
loadbalancerId: z.string().min(1, t("domain.deployment.form.tencent_clb_loadbalancer_id.placeholder")),
|
||||
listenerId: z.string().optional(),
|
||||
domain: z.string().regex(/^$|^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||
message: t("common.errmsg.domain_invalid"),
|
||||
}),
|
||||
domain: z.string().optional(),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
@@ -63,10 +61,20 @@ const DeployToTencentCLB = () => {
|
||||
path: ["listenerId"],
|
||||
}
|
||||
)
|
||||
.refine((data) => (data.resourceType === "ruledomain" ? !!data.domain?.trim() : true), {
|
||||
message: t("domain.deployment.form.tencent_clb_ruledomain.placeholder"),
|
||||
path: ["domain"],
|
||||
});
|
||||
.refine(
|
||||
(data) => {
|
||||
switch (data.resourceType) {
|
||||
case "ssl-deploy":
|
||||
case "ruledomain":
|
||||
return !!data.domain?.trim() && /^$|^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/.test(data.domain);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: t("domain.deployment.form.tencent_clb_ruledomain.placeholder"),
|
||||
path: ["domain"],
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const res = formSchema.safeParse(config.config);
|
||||
|
||||
68
ui/src/components/certimate/DeployToVolcengineCDN.tsx
Normal file
68
ui/src/components/certimate/DeployToVolcengineCDN.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 DeployToVolcengineCDNConfigParams = {
|
||||
domain?: string;
|
||||
};
|
||||
|
||||
const DeployToVolcengineCDN = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToVolcengineCDNConfigParams>();
|
||||
|
||||
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 DeployToVolcengineCDN;
|
||||
68
ui/src/components/certimate/DeployToVolcengineLive.tsx
Normal file
68
ui/src/components/certimate/DeployToVolcengineLive.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 DeployToVolcengineLiveConfigParams = {
|
||||
domain?: string;
|
||||
};
|
||||
|
||||
const DeployToVolcengineLive = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToVolcengineLiveConfigParams>();
|
||||
|
||||
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 DeployToVolcengineLive;
|
||||
@@ -123,22 +123,29 @@ const Bark = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const [testing, setTesting] = useState<boolean>(false);
|
||||
const handlePushTestClick = async () => {
|
||||
if (testing) return;
|
||||
|
||||
try {
|
||||
setTesting(true);
|
||||
|
||||
await notifyTest("bark");
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.config.push.test.message.success.message"),
|
||||
description: t("settings.notification.config.push.test.message.success.message"),
|
||||
title: t("settings.notification.push_test_message.succeeded.message"),
|
||||
description: t("settings.notification.push_test_message.succeeded.message"),
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.config.push.test.message.failed.message"),
|
||||
description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`,
|
||||
title: t("settings.notification.push_test_message.failed.message"),
|
||||
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -177,67 +184,76 @@ const Bark = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
placeholder={t("settings.notification.bark.serverUrl.placeholder")}
|
||||
value={bark.data.serverUrl}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...bark,
|
||||
data: {
|
||||
...bark.data,
|
||||
serverUrl: e.target.value,
|
||||
},
|
||||
};
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div>
|
||||
<Label>{t("settings.notification.bark.server_url.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("settings.notification.bark.server_url.placeholder")}
|
||||
value={bark.data.serverUrl}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...bark,
|
||||
data: {
|
||||
...bark.data,
|
||||
serverUrl: e.target.value,
|
||||
},
|
||||
};
|
||||
|
||||
checkChanged(newData.data);
|
||||
setBark(newData);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Input
|
||||
placeholder={t("settings.notification.bark.deviceKey.placeholder")}
|
||||
value={bark.data.deviceKey}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...bark,
|
||||
data: {
|
||||
...bark.data,
|
||||
deviceKey: e.target.value,
|
||||
},
|
||||
};
|
||||
|
||||
checkChanged(newData.data);
|
||||
setBark(newData);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex items-center space-x-1 mt-2">
|
||||
<Switch id="airplane-mode" checked={bark.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||
checkChanged(newData.data);
|
||||
setBark(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-2">
|
||||
<Show when={changed}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</Show>
|
||||
<div>
|
||||
<Label>{t("settings.notification.bark.device_key.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("settings.notification.bark.device_key.placeholder")}
|
||||
value={bark.data.deviceKey}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...bark,
|
||||
data: {
|
||||
...bark.data,
|
||||
deviceKey: e.target.value,
|
||||
},
|
||||
};
|
||||
|
||||
<Show when={!changed && bark.id != ""}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
handlePushTestClick();
|
||||
}}
|
||||
>
|
||||
{t("settings.notification.config.push.test.message")}
|
||||
</Button>
|
||||
</Show>
|
||||
checkChanged(newData.data);
|
||||
setBark(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between gap-4">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Switch id="airplane-mode" checked={bark.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-1">
|
||||
<Show when={changed}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
<Show when={!changed && bark.id != ""}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
loading={testing}
|
||||
onClick={() => {
|
||||
handlePushTestClick();
|
||||
}}
|
||||
>
|
||||
{t("settings.notification.push_test_message")}
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -120,23 +120,30 @@ const DingTalk = () => {
|
||||
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const [testing, setTesting] = useState<boolean>(false);
|
||||
const handlePushTestClick = async () => {
|
||||
if (testing) return;
|
||||
|
||||
try {
|
||||
setTesting(true);
|
||||
|
||||
await notifyTest("dingtalk");
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.config.push.test.message.success.message"),
|
||||
description: t("settings.notification.config.push.test.message.success.message"),
|
||||
title: t("settings.notification.push_test_message.succeeded.message"),
|
||||
description: t("settings.notification.push_test_message.succeeded.message"),
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.config.push.test.message.failed.message"),
|
||||
description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`,
|
||||
title: t("settings.notification.push_test_message.failed.message"),
|
||||
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
@@ -177,64 +184,74 @@ const DingTalk = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
placeholder="AccessToken"
|
||||
value={dingtalk.data.accessToken}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...dingtalk,
|
||||
data: {
|
||||
...dingtalk.data,
|
||||
accessToken: e.target.value,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setDingtalk(newData);
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
placeholder={t("settings.notification.dingtalk.secret.placeholder")}
|
||||
className="mt-2"
|
||||
value={dingtalk.data.secret}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...dingtalk,
|
||||
data: {
|
||||
...dingtalk.data,
|
||||
secret: e.target.value,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setDingtalk(newData);
|
||||
}}
|
||||
/>
|
||||
<div className="flex items-center space-x-1 mt-2">
|
||||
<Switch id="airplane-mode" checked={dingtalk.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div>
|
||||
<Label>{t("settings.notification.dingtalk.access_token.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("settings.notification.dingtalk.access_token.placeholder")}
|
||||
value={dingtalk.data.accessToken}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...dingtalk,
|
||||
data: {
|
||||
...dingtalk.data,
|
||||
accessToken: e.target.value,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setDingtalk(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-2">
|
||||
<Show when={changed}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</Show>
|
||||
<div>
|
||||
<Label>{t("settings.notification.dingtalk.secret.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("settings.notification.dingtalk.secret.placeholder")}
|
||||
value={dingtalk.data.secret}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...dingtalk,
|
||||
data: {
|
||||
...dingtalk.data,
|
||||
secret: e.target.value,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setDingtalk(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Show when={!changed && dingtalk.id != ""}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
handlePushTestClick();
|
||||
}}
|
||||
>
|
||||
{t("settings.notification.config.push.test.message")}
|
||||
</Button>
|
||||
</Show>
|
||||
<div className="flex justify-between gap-4">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Switch id="airplane-mode" checked={dingtalk.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-1">
|
||||
<Show when={changed}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
<Show when={!changed && dingtalk.id != ""}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
loading={testing}
|
||||
onClick={() => {
|
||||
handlePushTestClick();
|
||||
}}
|
||||
>
|
||||
{t("settings.notification.push_test_message")}
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
384
ui/src/components/notify/Email.tsx
Normal file
384
ui/src/components/notify/Email.tsx
Normal file
@@ -0,0 +1,384 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { getErrMessage } from "@/lib/error";
|
||||
import { NotifyChannelEmail, NotifyChannels } from "@/domain/settings";
|
||||
import { useNotifyContext } from "@/providers/notify";
|
||||
import { update } from "@/repository/settings";
|
||||
import Show from "@/components/Show";
|
||||
import { notifyTest } from "@/api/notify";
|
||||
|
||||
type EmailSetting = {
|
||||
id: string;
|
||||
name: string;
|
||||
data: NotifyChannelEmail;
|
||||
};
|
||||
|
||||
const Mail = () => {
|
||||
const { config, setChannels } = useNotifyContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [changed, setChanged] = useState<boolean>(false);
|
||||
|
||||
const [mail, setMail] = useState<EmailSetting>({
|
||||
id: config.id ?? "",
|
||||
name: "notifyChannels",
|
||||
data: {
|
||||
smtpHost: "",
|
||||
smtpPort: 465,
|
||||
smtpTLS: true,
|
||||
username: "",
|
||||
password: "",
|
||||
senderAddress: "",
|
||||
receiverAddress: "",
|
||||
enabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
const [originMail, setOriginMail] = useState<EmailSetting>({
|
||||
id: config.id ?? "",
|
||||
name: "notifyChannels",
|
||||
data: {
|
||||
smtpHost: "",
|
||||
smtpPort: 465,
|
||||
smtpTLS: true,
|
||||
username: "",
|
||||
password: "",
|
||||
senderAddress: "",
|
||||
receiverAddress: "",
|
||||
enabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setChanged(false);
|
||||
}, [config]);
|
||||
|
||||
useEffect(() => {
|
||||
const data = getDetailMail();
|
||||
setOriginMail({
|
||||
id: config.id ?? "",
|
||||
name: "email",
|
||||
data,
|
||||
});
|
||||
}, [config]);
|
||||
|
||||
useEffect(() => {
|
||||
const data = getDetailMail();
|
||||
setMail({
|
||||
id: config.id ?? "",
|
||||
name: "email",
|
||||
data,
|
||||
});
|
||||
}, [config]);
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
const getDetailMail = () => {
|
||||
const df: NotifyChannelEmail = {
|
||||
smtpHost: "smtp.example.com",
|
||||
smtpPort: 465,
|
||||
smtpTLS: true,
|
||||
username: "",
|
||||
password: "",
|
||||
senderAddress: "",
|
||||
receiverAddress: "",
|
||||
enabled: false,
|
||||
};
|
||||
if (!config.content) {
|
||||
return df;
|
||||
}
|
||||
const chanels = config.content as NotifyChannels;
|
||||
if (!chanels.email) {
|
||||
return df;
|
||||
}
|
||||
|
||||
return chanels.email as NotifyChannelEmail;
|
||||
};
|
||||
|
||||
const checkChanged = (data: NotifyChannelEmail) => {
|
||||
if (
|
||||
data.smtpHost !== originMail.data.smtpHost ||
|
||||
data.smtpPort !== originMail.data.smtpPort ||
|
||||
data.smtpTLS !== originMail.data.smtpTLS ||
|
||||
data.username !== originMail.data.username ||
|
||||
data.password !== originMail.data.password ||
|
||||
data.senderAddress !== originMail.data.senderAddress ||
|
||||
data.receiverAddress !== originMail.data.receiverAddress
|
||||
) {
|
||||
setChanged(true);
|
||||
} else {
|
||||
setChanged(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveClick = async () => {
|
||||
try {
|
||||
const resp = await update({
|
||||
...config,
|
||||
name: "notifyChannels",
|
||||
content: {
|
||||
...config.content,
|
||||
email: {
|
||||
...mail.data,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setChannels(resp);
|
||||
toast({
|
||||
title: t("common.save.succeeded.message"),
|
||||
description: t("settings.notification.config.saved.message"),
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: t("common.save.failed.message"),
|
||||
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const [testing, setTesting] = useState<boolean>(false);
|
||||
const handlePushTestClick = async () => {
|
||||
if (testing) return;
|
||||
|
||||
try {
|
||||
setTesting(true);
|
||||
|
||||
await notifyTest("email");
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.push_test_message.succeeded.message"),
|
||||
description: t("settings.notification.push_test_message.succeeded.message"),
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.push_test_message.failed.message"),
|
||||
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSwitchChange = async () => {
|
||||
const newData = {
|
||||
...mail,
|
||||
data: {
|
||||
...mail.data,
|
||||
enabled: !mail.data.enabled,
|
||||
},
|
||||
};
|
||||
setMail(newData);
|
||||
|
||||
try {
|
||||
const resp = await update({
|
||||
...config,
|
||||
name: "notifyChannels",
|
||||
content: {
|
||||
...config.content,
|
||||
email: {
|
||||
...newData.data,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setChannels(resp);
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: t("common.save.failed.message"),
|
||||
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="flex space-x-4">
|
||||
<div className="w-2/5">
|
||||
<Label>{t("settings.notification.email.smtp_host.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("settings.notification.email.smtp_host.placeholder")}
|
||||
value={mail.data.smtpHost}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...mail,
|
||||
data: {
|
||||
...mail.data,
|
||||
smtpHost: e.target.value,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setMail(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-2/5">
|
||||
<Label>{t("settings.notification.email.smtp_port.label")}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder={t("settings.notification.email.smtp_port.placeholder")}
|
||||
value={mail.data.smtpPort}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...mail,
|
||||
data: {
|
||||
...mail.data,
|
||||
smtpPort: +e.target.value || 0,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setMail(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-1/5">
|
||||
<Label>{t("settings.notification.email.smtp_tls.label")}</Label>
|
||||
<Switch
|
||||
className="block mt-2"
|
||||
checked={mail.data.smtpTLS}
|
||||
onCheckedChange={(e) => {
|
||||
const newData = {
|
||||
...mail,
|
||||
data: {
|
||||
...mail.data,
|
||||
smtpPort: e && mail.data.smtpPort === 25 ? 465 : !e && mail.data.smtpPort === 465 ? 25 : mail.data.smtpPort,
|
||||
smtpTLS: e,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setMail(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-4">
|
||||
<div className="w-1/2">
|
||||
<Label>{t("settings.notification.email.username.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("settings.notification.email.username.placeholder")}
|
||||
value={mail.data.username}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...mail,
|
||||
data: {
|
||||
...mail.data,
|
||||
username: e.target.value,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setMail(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-1/2">
|
||||
<Label>{t("settings.notification.email.password.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("settings.notification.email.password.placeholder")}
|
||||
value={mail.data.password}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...mail,
|
||||
data: {
|
||||
...mail.data,
|
||||
password: e.target.value,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setMail(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>{t("settings.notification.email.sender_address.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("settings.notification.email.sender_address.placeholder")}
|
||||
value={mail.data.senderAddress}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...mail,
|
||||
data: {
|
||||
...mail.data,
|
||||
senderAddress: e.target.value,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setMail(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>{t("settings.notification.email.receiver_address.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("settings.notification.email.receiver_address.placeholder")}
|
||||
value={mail.data.receiverAddress}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...mail,
|
||||
data: {
|
||||
...mail.data,
|
||||
receiverAddress: e.target.value,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setMail(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between gap-4">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Switch id="airplane-mode" checked={mail.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-1">
|
||||
<Show when={changed}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
<Show when={!changed && mail.id != ""}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
loading={testing}
|
||||
onClick={() => {
|
||||
handlePushTestClick();
|
||||
}}
|
||||
>
|
||||
{t("settings.notification.push_test_message")}
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Mail;
|
||||
@@ -116,23 +116,30 @@ const Lark = () => {
|
||||
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const [testing, setTesting] = useState<boolean>(false);
|
||||
const handlePushTestClick = async () => {
|
||||
if (testing) return;
|
||||
|
||||
try {
|
||||
setTesting(true);
|
||||
|
||||
await notifyTest("lark");
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.config.push.test.message.success.message"),
|
||||
description: t("settings.notification.config.push.test.message.success.message"),
|
||||
title: t("settings.notification.push_test_message.succeeded.message"),
|
||||
description: t("settings.notification.push_test_message.succeeded.message"),
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.config.push.test.message.failed.message"),
|
||||
description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`,
|
||||
title: t("settings.notification.push_test_message.failed.message"),
|
||||
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
@@ -173,49 +180,56 @@ const Lark = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
placeholder="Webhook Url"
|
||||
value={lark.data.webhookUrl}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...lark,
|
||||
data: {
|
||||
...lark.data,
|
||||
webhookUrl: e.target.value,
|
||||
},
|
||||
};
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div>
|
||||
<Label>{t("settings.notification.lark.webhook_url.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("settings.notification.lark.webhook_url.placeholder")}
|
||||
value={lark.data.webhookUrl}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...lark,
|
||||
data: {
|
||||
...lark.data,
|
||||
webhookUrl: e.target.value,
|
||||
},
|
||||
};
|
||||
|
||||
checkChanged(newData.data);
|
||||
setLark(newData);
|
||||
}}
|
||||
/>
|
||||
<div className="flex items-center space-x-1 mt-2">
|
||||
<Switch id="airplane-mode" checked={lark.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||
checkChanged(newData.data);
|
||||
setLark(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-2">
|
||||
<Show when={changed}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</Show>
|
||||
<div className="flex justify-between gap-4">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Switch id="airplane-mode" checked={lark.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||
</div>
|
||||
|
||||
<Show when={!changed && lark.id != ""}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
handlePushTestClick();
|
||||
}}
|
||||
>
|
||||
{t("settings.notification.config.push.test.message")}
|
||||
</Button>
|
||||
</Show>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Show when={changed}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
<Show when={!changed && lark.id != ""}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
loading={testing}
|
||||
onClick={() => {
|
||||
handlePushTestClick();
|
||||
}}
|
||||
>
|
||||
{t("settings.notification.push_test_message")}
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,319 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { getErrMessage } from "@/lib/error";
|
||||
import { NotifyChannelMail, NotifyChannels } from "@/domain/settings";
|
||||
import { useNotifyContext } from "@/providers/notify";
|
||||
import { update } from "@/repository/settings";
|
||||
import Show from "@/components/Show";
|
||||
import { notifyTest } from "@/api/notify";
|
||||
|
||||
type MailSetting = {
|
||||
id: string;
|
||||
name: string;
|
||||
data: NotifyChannelMail;
|
||||
};
|
||||
|
||||
const Mail = () => {
|
||||
const { config, setChannels } = useNotifyContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [changed, setChanged] = useState<boolean>(false);
|
||||
|
||||
const [mail, setmail] = useState<MailSetting>({
|
||||
id: config.id ?? "",
|
||||
name: "notifyChannels",
|
||||
data: {
|
||||
senderAddress: "",
|
||||
receiverAddresses: "",
|
||||
smtpHostAddr: "",
|
||||
smtpHostPort: "25",
|
||||
username: "",
|
||||
password: "",
|
||||
enabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
const [originMail, setoriginMail] = useState<MailSetting>({
|
||||
id: config.id ?? "",
|
||||
name: "notifyChannels",
|
||||
data: {
|
||||
senderAddress: "",
|
||||
receiverAddresses: "",
|
||||
smtpHostAddr: "",
|
||||
smtpHostPort: "25",
|
||||
username: "",
|
||||
password: "",
|
||||
enabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setChanged(false);
|
||||
}, [config]);
|
||||
|
||||
useEffect(() => {
|
||||
const data = getDetailMail();
|
||||
setoriginMail({
|
||||
id: config.id ?? "",
|
||||
name: "mail",
|
||||
data,
|
||||
});
|
||||
}, [config]);
|
||||
|
||||
useEffect(() => {
|
||||
const data = getDetailMail();
|
||||
setmail({
|
||||
id: config.id ?? "",
|
||||
name: "mail",
|
||||
data,
|
||||
});
|
||||
}, [config]);
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
const getDetailMail = () => {
|
||||
const df: NotifyChannelMail = {
|
||||
senderAddress: "",
|
||||
receiverAddresses: "",
|
||||
smtpHostAddr: "",
|
||||
smtpHostPort: "25",
|
||||
username: "",
|
||||
password: "",
|
||||
enabled: false,
|
||||
};
|
||||
if (!config.content) {
|
||||
return df;
|
||||
}
|
||||
const chanels = config.content as NotifyChannels;
|
||||
if (!chanels.mail) {
|
||||
return df;
|
||||
}
|
||||
|
||||
return chanels.mail as NotifyChannelMail;
|
||||
};
|
||||
|
||||
const checkChanged = (data: NotifyChannelMail) => {
|
||||
if (data.senderAddress !== originMail.data.senderAddress || data.receiverAddresses !== originMail.data.receiverAddresses || data.smtpHostAddr !== originMail.data.smtpHostAddr || data.smtpHostPort !== originMail.data.smtpHostPort || data.username !== originMail.data.username || data.password !== originMail.data.password) {
|
||||
setChanged(true);
|
||||
} else {
|
||||
setChanged(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveClick = async () => {
|
||||
try {
|
||||
const resp = await update({
|
||||
...config,
|
||||
name: "notifyChannels",
|
||||
content: {
|
||||
...config.content,
|
||||
mail: {
|
||||
...mail.data,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setChannels(resp);
|
||||
toast({
|
||||
title: t("common.save.succeeded.message"),
|
||||
description: t("settings.notification.config.saved.message"),
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: t("common.save.failed.message"),
|
||||
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handlePushTestClick = async () => {
|
||||
try {
|
||||
await notifyTest("mail");
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.config.push.test.message.success.message"),
|
||||
description: t("settings.notification.config.push.test.message.success.message"),
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.config.push.test.message.failed.message"),
|
||||
description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSwitchChange = async () => {
|
||||
const newData = {
|
||||
...mail,
|
||||
data: {
|
||||
...mail.data,
|
||||
enabled: !mail.data.enabled,
|
||||
},
|
||||
};
|
||||
setmail(newData);
|
||||
|
||||
try {
|
||||
const resp = await update({
|
||||
...config,
|
||||
name: "notifyChannels",
|
||||
content: {
|
||||
...config.content,
|
||||
mail: {
|
||||
...newData.data,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setChannels(resp);
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: t("common.save.failed.message"),
|
||||
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
placeholder={t("settings.notification.mail.sender_address.placeholder")}
|
||||
value={mail.data.senderAddress}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...mail,
|
||||
data: {
|
||||
...mail.data,
|
||||
senderAddress: e.target.value,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setmail(newData);
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
placeholder={t("settings.notification.mail.receiver_address.placeholder")}
|
||||
className="mt-2"
|
||||
value={mail.data.receiverAddresses}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...mail,
|
||||
data: {
|
||||
...mail.data,
|
||||
receiverAddresses: e.target.value,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setmail(newData);
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
placeholder={t("settings.notification.mail.smtp_host.placeholder")}
|
||||
className="mt-2"
|
||||
value={mail.data.smtpHostAddr}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...mail,
|
||||
data: {
|
||||
...mail.data,
|
||||
smtpHostAddr: e.target.value,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setmail(newData);
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
placeholder={t("settings.notification.mail.smtp_port.placeholder")}
|
||||
className="mt-2"
|
||||
value={mail.data.smtpHostPort}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...mail,
|
||||
data: {
|
||||
...mail.data,
|
||||
smtpHostPort: e.target.value,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setmail(newData);
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
placeholder={t("settings.notification.mail.username.placeholder")}
|
||||
className="mt-2"
|
||||
value={mail.data.username}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...mail,
|
||||
data: {
|
||||
...mail.data,
|
||||
username: e.target.value,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setmail(newData);
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
placeholder={t("settings.notification.mail.password.placeholder")}
|
||||
className="mt-2"
|
||||
value={mail.data.password}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...mail,
|
||||
data: {
|
||||
...mail.data,
|
||||
password: e.target.value,
|
||||
},
|
||||
};
|
||||
checkChanged(newData.data);
|
||||
setmail(newData);
|
||||
}}
|
||||
/>
|
||||
<div className="flex items-center space-x-1 mt-2">
|
||||
<Switch id="airplane-mode" checked={mail.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-2">
|
||||
<Show when={changed}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
<Show when={!changed && mail.id != ""}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
handlePushTestClick();
|
||||
}}
|
||||
>
|
||||
{t("settings.notification.config.push.test.message")}
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Mail;
|
||||
@@ -97,7 +97,7 @@ const ServerChan = () => {
|
||||
if (!isValidURL(serverchan.data.url)) {
|
||||
toast({
|
||||
title: t("common.save.failed.message"),
|
||||
description: t("settings.notification.url.errmsg.invalid"),
|
||||
description: t("common.errmsg.url_invalid"),
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
@@ -130,22 +130,29 @@ const ServerChan = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const [testing, setTesting] = useState<boolean>(false);
|
||||
const handlePushTestClick = async () => {
|
||||
if (testing) return;
|
||||
|
||||
try {
|
||||
setTesting(true);
|
||||
|
||||
await notifyTest("serverchan");
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.config.push.test.message.success.message"),
|
||||
description: t("settings.notification.config.push.test.message.success.message"),
|
||||
title: t("settings.notification.push_test_message.succeeded.message"),
|
||||
description: t("settings.notification.push_test_message.succeeded.message"),
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.config.push.test.message.failed.message"),
|
||||
description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`,
|
||||
title: t("settings.notification.push_test_message.failed.message"),
|
||||
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -184,50 +191,56 @@ const ServerChan = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
placeholder={t("settings.notification.serverchan.url.placeholder")}
|
||||
value={serverchan.data.url}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...serverchan,
|
||||
data: {
|
||||
...serverchan.data,
|
||||
url: e.target.value,
|
||||
},
|
||||
};
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div>
|
||||
<Label>{t("settings.notification.serverchan.url.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("settings.notification.serverchan.url.placeholder")}
|
||||
value={serverchan.data.url}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...serverchan,
|
||||
data: {
|
||||
...serverchan.data,
|
||||
url: e.target.value,
|
||||
},
|
||||
};
|
||||
|
||||
checkChanged(newData.data);
|
||||
setServerChan(newData);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex items-center space-x-1 mt-2">
|
||||
<Switch id="airplane-mode" checked={serverchan.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||
checkChanged(newData.data);
|
||||
setServerChan(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-2">
|
||||
<Show when={changed}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</Show>
|
||||
<div className="flex justify-between gap-4">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Switch id="airplane-mode" checked={serverchan.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||
</div>
|
||||
|
||||
<Show when={!changed && serverchan.id != ""}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
handlePushTestClick();
|
||||
}}
|
||||
>
|
||||
{t("settings.notification.config.push.test.message")}
|
||||
</Button>
|
||||
</Show>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Show when={changed}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
<Show when={!changed && serverchan.id != ""}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
loading={testing}
|
||||
onClick={() => {
|
||||
handlePushTestClick();
|
||||
}}
|
||||
>
|
||||
{t("settings.notification.push_test_message")}
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -123,22 +123,29 @@ const Telegram = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const [testing, setTesting] = useState<boolean>(false);
|
||||
const handlePushTestClick = async () => {
|
||||
if (testing) return;
|
||||
|
||||
try {
|
||||
setTesting(true);
|
||||
|
||||
await notifyTest("telegram");
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.config.push.test.message.success.message"),
|
||||
description: t("settings.notification.config.push.test.message.success.message"),
|
||||
title: t("settings.notification.push_test_message.succeeded.message"),
|
||||
description: t("settings.notification.push_test_message.succeeded.message"),
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.config.push.test.message.failed.message"),
|
||||
description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`,
|
||||
title: t("settings.notification.push_test_message.failed.message"),
|
||||
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -177,67 +184,76 @@ const Telegram = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
placeholder="ApiToken"
|
||||
value={telegram.data.apiToken}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...telegram,
|
||||
data: {
|
||||
...telegram.data,
|
||||
apiToken: e.target.value,
|
||||
},
|
||||
};
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div>
|
||||
<Label>{t("settings.notification.telegram.api_token.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("settings.notification.telegram.api_token.placeholder")}
|
||||
value={telegram.data.apiToken}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...telegram,
|
||||
data: {
|
||||
...telegram.data,
|
||||
apiToken: e.target.value,
|
||||
},
|
||||
};
|
||||
|
||||
checkChanged(newData.data);
|
||||
setTelegram(newData);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Input
|
||||
placeholder="ChatId"
|
||||
value={telegram.data.chatId}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...telegram,
|
||||
data: {
|
||||
...telegram.data,
|
||||
chatId: e.target.value,
|
||||
},
|
||||
};
|
||||
|
||||
checkChanged(newData.data);
|
||||
setTelegram(newData);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex items-center space-x-1 mt-2">
|
||||
<Switch id="airplane-mode" checked={telegram.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||
checkChanged(newData.data);
|
||||
setTelegram(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-2">
|
||||
<Show when={changed}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</Show>
|
||||
<div>
|
||||
<Label>{t("settings.notification.telegram.chat_id.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("settings.notification.telegram.chat_id.placeholder")}
|
||||
value={telegram.data.chatId}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...telegram,
|
||||
data: {
|
||||
...telegram.data,
|
||||
chatId: e.target.value,
|
||||
},
|
||||
};
|
||||
|
||||
<Show when={!changed && telegram.id != ""}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
handlePushTestClick();
|
||||
}}
|
||||
>
|
||||
{t("settings.notification.config.push.test.message")}
|
||||
</Button>
|
||||
</Show>
|
||||
checkChanged(newData.data);
|
||||
setTelegram(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between gap-4">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Switch id="airplane-mode" checked={telegram.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-1">
|
||||
<Show when={changed}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
<Show when={!changed && telegram.id != ""}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
loading={testing}
|
||||
onClick={() => {
|
||||
handlePushTestClick();
|
||||
}}
|
||||
>
|
||||
{t("settings.notification.push_test_message")}
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -97,7 +97,7 @@ const Webhook = () => {
|
||||
if (!isValidURL(webhook.data.url)) {
|
||||
toast({
|
||||
title: t("common.save.failed.message"),
|
||||
description: t("settings.notification.url.errmsg.invalid"),
|
||||
description: t("common.errmsg.url_invalid"),
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
@@ -130,22 +130,29 @@ const Webhook = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const [testing, setTesting] = useState<boolean>(false);
|
||||
const handlePushTestClick = async () => {
|
||||
if (testing) return;
|
||||
|
||||
try {
|
||||
setTesting(true);
|
||||
|
||||
await notifyTest("webhook");
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.config.push.test.message.success.message"),
|
||||
description: t("settings.notification.config.push.test.message.success.message"),
|
||||
title: t("settings.notification.push_test_message.succeeded.message"),
|
||||
description: t("settings.notification.push_test_message.succeeded.message"),
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: t("settings.notification.config.push.test.message.failed.message"),
|
||||
description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`,
|
||||
title: t("settings.notification.push_test_message.failed.message"),
|
||||
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -184,50 +191,56 @@ const Webhook = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
placeholder="Url"
|
||||
value={webhook.data.url}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...webhook,
|
||||
data: {
|
||||
...webhook.data,
|
||||
url: e.target.value,
|
||||
},
|
||||
};
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div>
|
||||
<Label>{t("settings.notification.webhook.url.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("settings.notification.webhook.url.placeholder")}
|
||||
value={webhook.data.url}
|
||||
onChange={(e) => {
|
||||
const newData = {
|
||||
...webhook,
|
||||
data: {
|
||||
...webhook.data,
|
||||
url: e.target.value,
|
||||
},
|
||||
};
|
||||
|
||||
checkChanged(newData.data);
|
||||
setWebhook(newData);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex items-center space-x-1 mt-2">
|
||||
<Switch id="airplane-mode" checked={webhook.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||
checkChanged(newData.data);
|
||||
setWebhook(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-2">
|
||||
<Show when={changed}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</Show>
|
||||
<div className="flex justify-between gap-4">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Switch id="airplane-mode" checked={webhook.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||
</div>
|
||||
|
||||
<Show when={!changed && webhook.id != ""}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
handlePushTestClick();
|
||||
}}
|
||||
>
|
||||
{t("settings.notification.config.push.test.message")}
|
||||
</Button>
|
||||
</Show>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Show when={changed}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
<Show when={!changed && webhook.id != ""}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
loading={testing}
|
||||
onClick={() => {
|
||||
handlePushTestClick();
|
||||
}}
|
||||
>
|
||||
{t("settings.notification.push_test_message")}
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -32,11 +33,38 @@ const buttonVariants = cva(
|
||||
|
||||
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, loading, asChild = false, children, ...props }, ref) => {
|
||||
if (asChild) {
|
||||
return (
|
||||
<Slot ref={ref} {...props}>
|
||||
<>
|
||||
{React.Children.map(children as React.ReactElement, (child: React.ReactElement) => {
|
||||
return React.cloneElement(child, {
|
||||
className: cn(buttonVariants({ variant, size }), className),
|
||||
children: (
|
||||
<>
|
||||
{loading && <Loader2 className={cn("h-4 w-4 animate-spin", children && "mr-2")} />}
|
||||
{child.props.children}
|
||||
</>
|
||||
),
|
||||
});
|
||||
})}
|
||||
</>
|
||||
</Slot>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={cn(buttonVariants({ variant, size, className }))} disabled={loading} ref={ref} {...props}>
|
||||
<>
|
||||
{loading && <Loader2 className={cn("h-4 w-4 animate-spin", children && "mr-2")} />}
|
||||
{children}
|
||||
</>
|
||||
</button>
|
||||
);
|
||||
});
|
||||
Button.displayName = "Button";
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@ export const accessProvidersMap: Map<AccessProvider["type"], AccessProvider> = n
|
||||
["aliyun", "common.provider.aliyun", "/imgs/providers/aliyun.svg", "all", "阿里云:alibaba cloud"],
|
||||
["tencent", "common.provider.tencent", "/imgs/providers/tencent.svg", "all", "腾讯云:tencent cloud"],
|
||||
["huaweicloud", "common.provider.huaweicloud", "/imgs/providers/huaweicloud.svg", "all", "华为云:huawei cloud"],
|
||||
["qiniu", "common.provider.qiniu", "/imgs/providers/qiniu.svg", "deploy", "七牛:qiniu"],
|
||||
["baiducloud", "common.provider.baiducloud", "/imgs/providers/baiducloud.svg", "all", "百度智能云:百度云:baidu cloud"],
|
||||
["qiniu", "common.provider.qiniu", "/imgs/providers/qiniu.svg", "deploy", "七牛云:qiniu"],
|
||||
["dogecloud", "common.provider.dogecloud", "/imgs/providers/dogecloud.svg", "deploy", "多吉云:doge cloud"],
|
||||
["volcengine", "common.provider.volcengine", "/imgs/providers/volcengine.svg", "all", "火山引擎"],
|
||||
["aws", "common.provider.aws", "/imgs/providers/aws.svg", "apply", "亚马逊:amazon:aws"],
|
||||
["cloudflare", "common.provider.cloudflare", "/imgs/providers/cloudflare.svg", "apply", "cloudflare:cf:cloud flare"],
|
||||
["namesilo", "common.provider.namesilo", "/imgs/providers/namesilo.svg", "apply", "namesilo"],
|
||||
@@ -34,7 +37,9 @@ export const accessTypeFormSchema = z.union(
|
||||
z.literal("aliyun"),
|
||||
z.literal("tencent"),
|
||||
z.literal("huaweicloud"),
|
||||
z.literal("baiducloud"),
|
||||
z.literal("qiniu"),
|
||||
z.literal("dogecloud"),
|
||||
z.literal("aws"),
|
||||
z.literal("cloudflare"),
|
||||
z.literal("namesilo"),
|
||||
@@ -45,6 +50,7 @@ export const accessTypeFormSchema = z.union(
|
||||
z.literal("ssh"),
|
||||
z.literal("webhook"),
|
||||
z.literal("k8s"),
|
||||
z.literal("volcengine"),
|
||||
],
|
||||
{ message: "access.authorization.form.type.placeholder" }
|
||||
);
|
||||
@@ -60,6 +66,7 @@ export type Access = {
|
||||
| TencentConfig
|
||||
| HuaweiCloudConfig
|
||||
| QiniuConfig
|
||||
| DogeCloudConfig
|
||||
| AwsConfig
|
||||
| CloudflareConfig
|
||||
| NamesiloConfig
|
||||
@@ -69,7 +76,8 @@ export type Access = {
|
||||
| LocalConfig
|
||||
| SSHConfig
|
||||
| WebhookConfig
|
||||
| KubernetesConfig;
|
||||
| KubernetesConfig
|
||||
| VolcengineConfig;
|
||||
deleted?: string;
|
||||
created?: string;
|
||||
updated?: string;
|
||||
@@ -91,11 +99,21 @@ export type HuaweiCloudConfig = {
|
||||
secretAccessKey: string;
|
||||
};
|
||||
|
||||
export type BaiduCloudConfig = {
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
};
|
||||
|
||||
export type QiniuConfig = {
|
||||
accessKey: string;
|
||||
secretKey: string;
|
||||
};
|
||||
|
||||
export type DogeCloudConfig = {
|
||||
accessKey: string;
|
||||
secretKey: string;
|
||||
};
|
||||
|
||||
export type AwsConfig = {
|
||||
region: string;
|
||||
accessKeyId: string;
|
||||
@@ -147,3 +165,8 @@ export type WebhookConfig = {
|
||||
export type KubernetesConfig = {
|
||||
kubeConfig: string;
|
||||
};
|
||||
|
||||
export type VolcengineConfig = {
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
};
|
||||
|
||||
@@ -85,10 +85,14 @@ export const deployTargetsMap: Map<DeployTarget["type"], DeployTarget> = new Map
|
||||
["tencent-teo", "common.provider.tencent.teo", "/imgs/providers/tencent.svg"],
|
||||
["huaweicloud-cdn", "common.provider.huaweicloud.cdn", "/imgs/providers/huaweicloud.svg"],
|
||||
["huaweicloud-elb", "common.provider.huaweicloud.elb", "/imgs/providers/huaweicloud.svg"],
|
||||
["baiducloud-cdn", "common.provider.baiducloud.cdn", "/imgs/providers/baiducloud.svg"],
|
||||
["qiniu-cdn", "common.provider.qiniu.cdn", "/imgs/providers/qiniu.svg"],
|
||||
["dogecloud-cdn", "common.provider.dogecloud.cdn", "/imgs/providers/dogecloud.svg"],
|
||||
["local", "common.provider.local", "/imgs/providers/local.svg"],
|
||||
["ssh", "common.provider.ssh", "/imgs/providers/ssh.svg"],
|
||||
["webhook", "common.provider.webhook", "/imgs/providers/webhook.svg"],
|
||||
["k8s-secret", "common.provider.kubernetes.secret", "/imgs/providers/k8s.svg"],
|
||||
["volcengine-live", "common.provider.volcengine.live", "/imgs/providers/volcengine.svg"],
|
||||
["volcengine-cdn", "common.provider.volcengine.cdn", "/imgs/providers/volcengine.svg"],
|
||||
].map(([type, name, icon]) => [type, { type, provider: type.split("-")[0], name, icon }])
|
||||
);
|
||||
|
||||
@@ -18,24 +18,40 @@ export type NotifyTemplate = {
|
||||
};
|
||||
|
||||
export type NotifyChannels = {
|
||||
email?: NotifyChannelEmail;
|
||||
webhook?: NotifyChannel;
|
||||
dingtalk?: NotifyChannel;
|
||||
lark?: NotifyChannel;
|
||||
telegram?: NotifyChannel;
|
||||
webhook?: NotifyChannel;
|
||||
serverchan?: NotifyChannel;
|
||||
mail?: NotifyChannelMail;
|
||||
bark?: NotifyChannelBark;
|
||||
};
|
||||
|
||||
export type NotifyChannel =
|
||||
| NotifyChannelEmail
|
||||
| NotifyChannelWebhook
|
||||
| NotifyChannelDingTalk
|
||||
| NotifyChannelLark
|
||||
| NotifyChannelTelegram
|
||||
| NotifyChannelWebhook
|
||||
| NotifyChannelServerChan
|
||||
| NotifyChannelMail
|
||||
| NotifyChannelBark;
|
||||
|
||||
export type NotifyChannelEmail = {
|
||||
smtpHost: string;
|
||||
smtpPort: number;
|
||||
smtpTLS: boolean;
|
||||
username: string;
|
||||
password: string;
|
||||
senderAddress: string;
|
||||
receiverAddress: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export type NotifyChannelWebhook = {
|
||||
url: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export type NotifyChannelDingTalk = {
|
||||
accessToken: string;
|
||||
secret: string;
|
||||
@@ -53,26 +69,11 @@ export type NotifyChannelTelegram = {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export type NotifyChannelWebhook = {
|
||||
url: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export type NotifyChannelServerChan = {
|
||||
url: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export type NotifyChannelMail = {
|
||||
senderAddress: string;
|
||||
receiverAddresses: string;
|
||||
smtpHostAddr: string;
|
||||
smtpHostPort: string;
|
||||
username: string;
|
||||
password: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export type NotifyChannelBark = {
|
||||
deviceKey: string;
|
||||
serverUrl: string;
|
||||
|
||||
@@ -1 +1 @@
|
||||
export const version = "Certimate v0.2.13";
|
||||
export const version = "Certimate v0.2.20";
|
||||
|
||||
@@ -68,8 +68,12 @@
|
||||
"common.provider.huaweicloud": "Huawei Cloud",
|
||||
"common.provider.huaweicloud.cdn": "Huawei Cloud - CDN",
|
||||
"common.provider.huaweicloud.elb": "Huawei Cloud - ELB",
|
||||
"common.provider.baiducloud": "Baidu Cloud",
|
||||
"common.provider.baiducloud.cdn": "Baidu Cloud - CDN",
|
||||
"common.provider.qiniu": "Qiniu Cloud",
|
||||
"common.provider.qiniu.cdn": "Qiniu Cloud - CDN",
|
||||
"common.provider.dogecloud": "Doge Cloud",
|
||||
"common.provider.dogecloud.cdn": "Doge Cloud - CDN",
|
||||
"common.provider.aws": "AWS",
|
||||
"common.provider.cloudflare": "Cloudflare",
|
||||
"common.provider.namesilo": "Namesilo",
|
||||
@@ -79,12 +83,15 @@
|
||||
"common.provider.local": "Local Deployment",
|
||||
"common.provider.ssh": "SSH Deployment",
|
||||
"common.provider.webhook": "Webhook",
|
||||
"common.provider.serverchan": "ServerChan",
|
||||
"common.provider.kubernetes": "Kubernetes",
|
||||
"common.provider.kubernetes.secret": "Kubernetes - Secret",
|
||||
"common.provider.email": "Email",
|
||||
"common.provider.dingtalk": "DingTalk",
|
||||
"common.provider.telegram": "Telegram",
|
||||
"common.provider.lark": "Lark",
|
||||
"common.provider.mail": "Mail",
|
||||
"common.provider.bark": "Bark"
|
||||
"common.provider.telegram": "Telegram",
|
||||
"common.provider.serverchan": "ServerChan",
|
||||
"common.provider.bark": "Bark",
|
||||
"common.provider.volcengine": "Volcengine",
|
||||
"common.provider.volcengine.live": "Volcengine - Live",
|
||||
"common.provider.volcengine.cdn": "Volcengine - CDN"
|
||||
}
|
||||
|
||||
@@ -30,20 +30,40 @@
|
||||
"settings.notification.config.enable": "Enable",
|
||||
"settings.notification.config.saved.message": "Configuration saved successfully",
|
||||
"settings.notification.config.failed.message": "Configuration save failed",
|
||||
"settings.notification.config.push.test.message": "Send test notification",
|
||||
"settings.notification.config.push.test.message.failed.message": "Send test notification failed",
|
||||
"settings.notification.config.push.test.message.success.message": "Send test notification successfully",
|
||||
"settings.notification.dingtalk.secret.placeholder": "Signature for signed addition",
|
||||
"settings.notification.url.errmsg.invalid": "Invalid Url format",
|
||||
"settings.notification.serverchan.url.placeholder": "Url, e.g. https://sctapi.ftqq.com/****************.send",
|
||||
"settings.notification.mail.sender_address.placeholder": "Sender email address",
|
||||
"settings.notification.mail.receiver_address.placeholder": "Receiver email address",
|
||||
"settings.notification.mail.smtp_host.placeholder": "SMTP server address",
|
||||
"settings.notification.mail.smtp_port.placeholder": "SMTP server port, if not set, default is 25",
|
||||
"settings.notification.mail.username.placeholder": "username",
|
||||
"settings.notification.mail.password.placeholder": "password",
|
||||
"settings.notification.bark.serverUrl.placeholder": "Server URL, e.g. https://your-bark-server.com, leave it blank to use the bark default server",
|
||||
"settings.notification.bark.deviceKey.placeholder": "Device Key,e.g. XXXXXXXXXXXXXXXXXXXX",
|
||||
"settings.notification.push_test_message": "Send test notification",
|
||||
"settings.notification.push_test_message.succeeded.message": "Send test notification successfully",
|
||||
"settings.notification.push_test_message.failed.message": "Send test notification failed",
|
||||
"settings.notification.email.smtp_host.label": "SMTP Host",
|
||||
"settings.notification.email.smtp_host.placeholder": "Please enter SMTP host",
|
||||
"settings.notification.email.smtp_port.label": "SMTP Port",
|
||||
"settings.notification.email.smtp_port.placeholder": "Please enter SMTP port",
|
||||
"settings.notification.email.smtp_tls.label": "Use TLS/SSL",
|
||||
"settings.notification.email.username.label": "Username",
|
||||
"settings.notification.email.username.placeholder": "please enter username",
|
||||
"settings.notification.email.password.label": "Password",
|
||||
"settings.notification.email.password.placeholder": "please enter password",
|
||||
"settings.notification.email.sender_address.label": "Sender Email Address",
|
||||
"settings.notification.email.sender_address.placeholder": "Please enter sender email address",
|
||||
"settings.notification.email.receiver_address.label": "Receiver Email Address",
|
||||
"settings.notification.email.receiver_address.placeholder": "Please enter receiver email address",
|
||||
"settings.notification.webhook.url.label": "Webhook URL",
|
||||
"settings.notification.webhook.url.placeholder": "Please enter Webhook URL",
|
||||
"settings.notification.dingtalk.access_token.label": "AccessToken",
|
||||
"settings.notification.dingtalk.access_token.placeholder": "Please enter access token",
|
||||
"settings.notification.dingtalk.secret.label": "Secret",
|
||||
"settings.notification.dingtalk.secret.placeholder": "Please enter secret",
|
||||
"settings.notification.lark.webhook_url.label": "Webhook URL",
|
||||
"settings.notification.lark.webhook_url.placeholder": "Please enter Webhook URL",
|
||||
"settings.notification.telegram.api_token.label": "API Token",
|
||||
"settings.notification.telegram.api_token.placeholder": "Please enter API token",
|
||||
"settings.notification.telegram.chat_id.label": "Chat ID",
|
||||
"settings.notification.telegram.chat_id.placeholder": "Please enter Telegram chat ID",
|
||||
"settings.notification.serverchan.url.label": "Server URL",
|
||||
"settings.notification.serverchan.url.placeholder": "Please enter server URL (e.g. https://sctapi.ftqq.com/*****.send)",
|
||||
"settings.notification.bark.server_url.label": "Server URL",
|
||||
"settings.notification.bark.server_url.placeholder": "Please enter server URL (e.g. https://your-bark-server.com. Leave it blank to use the bark default server)",
|
||||
"settings.notification.bark.device_key.label": "Device Key",
|
||||
"settings.notification.bark.device_key.placeholder": "Please enter device key",
|
||||
|
||||
"settings.ca.tab": "Certificate Authority",
|
||||
"settings.ca.provider.errmsg.empty": "Please select a Certificate Authority",
|
||||
|
||||
@@ -68,8 +68,12 @@
|
||||
"common.provider.huaweicloud": "华为云",
|
||||
"common.provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN",
|
||||
"common.provider.huaweicloud.elb": "华为云 - 弹性负载均衡 ELB",
|
||||
"common.provider.baiducloud": "百度智能云",
|
||||
"common.provider.baiducloud.cdn": "百度智能云 - 内容分发网络 CDN",
|
||||
"common.provider.qiniu": "七牛云",
|
||||
"common.provider.qiniu.cdn": "七牛云 - 内容分发网络 CDN",
|
||||
"common.provider.dogecloud": "多吉云",
|
||||
"common.provider.dogecloud.cdn": "多吉云 - 内容分发网络 CDN",
|
||||
"common.provider.aws": "AWS",
|
||||
"common.provider.cloudflare": "Cloudflare",
|
||||
"common.provider.namesilo": "Namesilo",
|
||||
@@ -79,12 +83,15 @@
|
||||
"common.provider.local": "本地部署",
|
||||
"common.provider.ssh": "SSH 部署",
|
||||
"common.provider.webhook": "Webhook",
|
||||
"common.provider.serverchan": "Server酱",
|
||||
"common.provider.kubernetes": "Kubernetes",
|
||||
"common.provider.kubernetes.secret": "Kubernetes - Secret",
|
||||
"common.provider.email": "电子邮件",
|
||||
"common.provider.dingtalk": "钉钉",
|
||||
"common.provider.telegram": "Telegram",
|
||||
"common.provider.lark": "飞书",
|
||||
"common.provider.mail": "电子邮件",
|
||||
"common.provider.bark": "Bark"
|
||||
"common.provider.telegram": "Telegram",
|
||||
"common.provider.serverchan": "Server酱",
|
||||
"common.provider.bark": "Bark",
|
||||
"common.provider.volcengine": "火山引擎",
|
||||
"common.provider.volcengine.live": "火山引擎 - 视频直播",
|
||||
"common.provider.volcengine.cdn": "火山引擎 - CDN"
|
||||
}
|
||||
|
||||
@@ -30,20 +30,40 @@
|
||||
"settings.notification.config.enable": "是否启用",
|
||||
"settings.notification.config.saved.message": "配置保存成功",
|
||||
"settings.notification.config.failed.message": "配置保存失败",
|
||||
"settings.notification.config.push.test.message": "推送测试消息",
|
||||
"settings.notification.config.push.test.message.failed.message": "推送测试消息失败",
|
||||
"settings.notification.config.push.test.message.success.message": "推送测试消息成功",
|
||||
"settings.notification.dingtalk.secret.placeholder": "加签的签名",
|
||||
"settings.notification.url.errmsg.invalid": "URL 格式不正确",
|
||||
"settings.notification.serverchan.url.placeholder": "Url, 形如: https://sctapi.ftqq.com/****************.send",
|
||||
"settings.notification.mail.sender_address.placeholder": "发送邮箱地址",
|
||||
"settings.notification.mail.receiver_address.placeholder": "接收邮箱地址",
|
||||
"settings.notification.mail.smtp_host.placeholder": "SMTP服务器地址",
|
||||
"settings.notification.mail.smtp_port.placeholder": "SMTP服务器端口, 如果未设置, 默认为25",
|
||||
"settings.notification.mail.username.placeholder": "用于登录到邮件服务器的用户名",
|
||||
"settings.notification.mail.password.placeholder": "用于登录到邮件服务器的密码",
|
||||
"settings.notification.bark.serverUrl.placeholder": "服务器URL,形如: https://your-bark-server.com, 留空则使用 Bark 默认服务器",
|
||||
"settings.notification.bark.deviceKey.placeholder": "设备密钥,形如: XXXXXXXXXXXXXXXXXXXX",
|
||||
"settings.notification.push_test_message": "推送测试消息",
|
||||
"settings.notification.push_test_message.failed.message": "推送测试消息失败",
|
||||
"settings.notification.push_test_message.succeeded.message": "推送测试消息成功",
|
||||
"settings.notification.email.smtp_host.label": "SMTP 服务器地址",
|
||||
"settings.notification.email.smtp_host.placeholder": "请输入 SMTP 服务器地址",
|
||||
"settings.notification.email.smtp_port.label": "SMTP 服务器端口",
|
||||
"settings.notification.email.smtp_port.placeholder": "请输入 SMTP 服务器端口",
|
||||
"settings.notification.email.smtp_tls.label": "TLS/SSL 连接",
|
||||
"settings.notification.email.username.label": "用户名",
|
||||
"settings.notification.email.username.placeholder": "请输入用户名",
|
||||
"settings.notification.email.password.label": "密码",
|
||||
"settings.notification.email.password.placeholder": "请输入密码",
|
||||
"settings.notification.email.sender_address.label": "发送邮箱地址",
|
||||
"settings.notification.email.sender_address.placeholder": "请输入发送邮箱地址",
|
||||
"settings.notification.email.receiver_address.label": "接收邮箱地址",
|
||||
"settings.notification.email.receiver_address.placeholder": "请输入接收邮箱地址",
|
||||
"settings.notification.webhook.url.label": "Webhook 回调地址",
|
||||
"settings.notification.webhook.url.placeholder": "请输入 Webhook 回调地址",
|
||||
"settings.notification.dingtalk.access_token.label": "AccessToken",
|
||||
"settings.notification.dingtalk.access_token.placeholder": "请输入 AccessToken",
|
||||
"settings.notification.dingtalk.secret.label": "签名密钥",
|
||||
"settings.notification.dingtalk.secret.placeholder": "请输入签名密钥",
|
||||
"settings.notification.lark.webhook_url.label": "Webhook URL",
|
||||
"settings.notification.lark.webhook_url.placeholder": "请输入 Webhook URL",
|
||||
"settings.notification.telegram.api_token.label": "API Token",
|
||||
"settings.notification.telegram.api_token.placeholder": "请输入 API token",
|
||||
"settings.notification.telegram.chat_id.label": "会话 ID",
|
||||
"settings.notification.telegram.chat_id.placeholder": "请输入 Telegram 会话 ID",
|
||||
"settings.notification.serverchan.url.label": "服务器 URL",
|
||||
"settings.notification.serverchan.url.placeholder": "请输入服务器 URL(形如: https://sctapi.ftqq.com/*****.send)",
|
||||
"settings.notification.bark.server_url.label": "服务器 URL",
|
||||
"settings.notification.bark.server_url.placeholder": "请输入服务器 URL(形如: https://your-bark-server.com;留空则使用 Bark 默认服务器)",
|
||||
"settings.notification.bark.device_key.label": "设备密钥",
|
||||
"settings.notification.bark.device_key.placeholder": "请输入设备密钥",
|
||||
|
||||
"settings.ca.tab": "证书颁发机构(CA)",
|
||||
"settings.ca.provider.errmsg.empty": "请选择证书分发机构",
|
||||
|
||||
@@ -7,7 +7,7 @@ import NotifyTemplate from "@/components/notify/NotifyTemplate";
|
||||
import Telegram from "@/components/notify/Telegram";
|
||||
import Webhook from "@/components/notify/Webhook";
|
||||
import ServerChan from "@/components/notify/ServerChan";
|
||||
import Mail from "@/components/notify/Mail";
|
||||
import Email from "@/components/notify/Email";
|
||||
import Bark from "@/components/notify/Bark";
|
||||
import { NotifyProvider } from "@/providers/notify";
|
||||
|
||||
@@ -27,51 +27,52 @@ const Notify = () => {
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-md p-5 mt-7 shadow-lg">
|
||||
<Accordion type={"single"} className="dark:text-stone-200">
|
||||
<AccordionItem value="item-2" className="dark:border-stone-200">
|
||||
<AccordionTrigger>{t("common.provider.dingtalk")}</AccordionTrigger>
|
||||
<Accordion type={"single"} collapsible={true} className="dark:text-stone-200">
|
||||
<AccordionItem value="item-email" className="dark:border-stone-200">
|
||||
<AccordionTrigger>{t("common.provider.email")}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<DingTalk />
|
||||
<Email />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="item-3" className="dark:border-stone-200">
|
||||
<AccordionTrigger>{t("common.provider.lark")}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<Lark />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="item-4" className="dark:border-stone-200">
|
||||
<AccordionTrigger>{t("common.provider.telegram")}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<Telegram />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="item-5" className="dark:border-stone-200">
|
||||
<AccordionItem value="item-webhook" className="dark:border-stone-200">
|
||||
<AccordionTrigger>{t("common.provider.webhook")}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<Webhook />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="item-6" className="dark:border-stone-200">
|
||||
<AccordionItem value="item-dingtalk" className="dark:border-stone-200">
|
||||
<AccordionTrigger>{t("common.provider.dingtalk")}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<DingTalk />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="item-lark" className="dark:border-stone-200">
|
||||
<AccordionTrigger>{t("common.provider.lark")}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<Lark />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="item-telegram" className="dark:border-stone-200">
|
||||
<AccordionTrigger>{t("common.provider.telegram")}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<Telegram />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="item-serverchan" className="dark:border-stone-200">
|
||||
<AccordionTrigger>{t("common.provider.serverchan")}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<ServerChan />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="item-7" className="dark:border-stone-200">
|
||||
<AccordionTrigger>{t("common.provider.mail")}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<Mail />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="item-8" className="dark:border-stone-200">
|
||||
<AccordionItem value="item-bark" className="dark:border-stone-200">
|
||||
<AccordionTrigger>{t("common.provider.bark")}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<Bark />
|
||||
|
||||
Reference in New Issue
Block a user