Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ff923dd1b | ||
|
|
f4f13f91f2 | ||
|
|
034aa980e6 | ||
|
|
6ac7a51ce0 | ||
|
|
cf0c0e3e2c | ||
|
|
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 |
19
README.md
19
README.md
@@ -75,8 +75,10 @@ make local.run
|
|||||||
| :--------: | :----------: | :----------: | ----------------------------------------------------------------- |
|
| :--------: | :----------: | :----------: | ----------------------------------------------------------------- |
|
||||||
| 阿里云 | √ | √ | 可签发在阿里云注册的域名;可部署到阿里云 OSS、CDN、SLB |
|
| 阿里云 | √ | √ | 可签发在阿里云注册的域名;可部署到阿里云 OSS、CDN、SLB |
|
||||||
| 腾讯云 | √ | √ | 可签发在腾讯云注册的域名;可部署到腾讯云 COS、CDN、ECDN、CLB、TEO |
|
| 腾讯云 | √ | √ | 可签发在腾讯云注册的域名;可部署到腾讯云 COS、CDN、ECDN、CLB、TEO |
|
||||||
|
| 百度智能云 | | √ | 可部署到百度智能云 CDN |
|
||||||
| 华为云 | √ | √ | 可签发在华为云注册的域名;可部署到华为云 CDN、ELB |
|
| 华为云 | √ | √ | 可签发在华为云注册的域名;可部署到华为云 CDN、ELB |
|
||||||
| 七牛云 | | √ | 可部署到七牛云 CDN |
|
| 七牛云 | | √ | 可部署到七牛云 CDN |
|
||||||
|
| 多吉云 | | √ | 可部署到多吉云 CDN |
|
||||||
| AWS | √ | | 可签发在 AWS Route53 托管的域名 |
|
| AWS | √ | | 可签发在 AWS Route53 托管的域名 |
|
||||||
| CloudFlare | √ | | 可签发在 CloudFlare 注册的域名;CloudFlare 服务自带 SSL 证书 |
|
| CloudFlare | √ | | 可签发在 CloudFlare 注册的域名;CloudFlare 服务自带 SSL 证书 |
|
||||||
| GoDaddy | √ | | 可签发在 GoDaddy 注册的域名 |
|
| GoDaddy | √ | | 可签发在 GoDaddy 注册的域名 |
|
||||||
@@ -90,15 +92,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 +188,4 @@ Certimate 是一个免费且开源的项目,采用 [MIT 开源协议](LICENSE.
|
|||||||
## 十、Star 趋势图
|
## 十、Star 趋势图
|
||||||
|
|
||||||
[](https://starchart.cc/usual2970/certimate)
|
[](https://starchart.cc/usual2970/certimate)
|
||||||
|
|
||||||
|
|||||||
50
README_EN.md
50
README_EN.md
@@ -70,34 +70,34 @@ password:1234567890
|
|||||||
|
|
||||||
## List of Supported Providers
|
## List of Supported Providers
|
||||||
|
|
||||||
| Provider | Registration | Deployment | Remarks |
|
| Provider | Registration | Deployment | Remarks |
|
||||||
| :-----------: | :----------: | :--------: | --------------------------------------------------------------------------------------------------------------------- |
|
| :-----------: | :----------: | :--------: | ----------------------------------------------------------------------------------------------------------- |
|
||||||
| Alibaba Cloud | √ | √ | Supports domains registered on Alibaba Cloud; supports deployment to Alibaba Cloud OSS, CDN,SLB |
|
| 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 |
|
| 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 |
|
| Baidu Cloud | | √ | Supports deployment to Baidu Cloud CDN |
|
||||||
| Qiniu Cloud | | √ | Supports deployment to Qiniu Cloud CDN |
|
| Huawei Cloud | √ | √ | Supports domains registered on Huawei Cloud; supports deployment to Huawei Cloud CDN, ELB |
|
||||||
| AWS | √ | | Supports domains managed on AWS Route53 |
|
| Qiniu Cloud | | √ | Supports deployment to Qiniu Cloud CDN |
|
||||||
| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
|
| Doge Cloud | | √ | Supports deployment to Doge Cloud CDN |
|
||||||
| GoDaddy | √ | | Supports domains registered on GoDaddy |
|
| AWS | √ | | Supports domains managed on AWS Route53 |
|
||||||
| Namesilo | √ | | Supports domains registered on Namesilo |
|
| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
|
||||||
| PowerDNS | √ | | Supports domains managed on PowerDNS |
|
| GoDaddy | √ | | Supports domains registered on GoDaddy |
|
||||||
| HTTP Request | √ | | Supports domains which allow managing DNS by HTTP request |
|
| Namesilo | √ | | Supports domains registered on Namesilo |
|
||||||
| Local Deploy | | √ | Supports deployment to local servers |
|
| PowerDNS | √ | | Supports domains managed on PowerDNS |
|
||||||
| SSH | | √ | Supports deployment to SSH servers |
|
| HTTP Request | √ | | Supports domains which allow managing DNS by HTTP request |
|
||||||
| Webhook | | √ | Supports callback to Webhook |
|
| Local Deploy | | √ | Supports deployment to local servers |
|
||||||
| Kubernetes | | √ | Supports deployment to Kubernetes Secret |
|
| SSH | | √ | Supports deployment to SSH servers |
|
||||||
|
| Webhook | | √ | Supports callback to Webhook |
|
||||||
|
| Kubernetes | | √ | Supports deployment to Kubernetes Secret |
|
||||||
|
|
||||||
## Screenshots
|
## 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
|
## Concepts
|
||||||
|
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -13,6 +13,7 @@ require (
|
|||||||
github.com/alibabacloud-go/slb-20140515/v4 v4.0.9
|
github.com/alibabacloud-go/slb-20140515/v4 v4.0.9
|
||||||
github.com/alibabacloud-go/tea v1.2.2
|
github.com/alibabacloud-go/tea v1.2.2
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||||
|
github.com/baidubce/bce-sdk-go v0.9.197
|
||||||
github.com/go-acme/lego/v4 v4.19.2
|
github.com/go-acme/lego/v4 v4.19.2
|
||||||
github.com/gojek/heimdall/v7 v7.0.3
|
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.114
|
||||||
@@ -112,7 +113,7 @@ require (
|
|||||||
github.com/cloudflare/cloudflare-go v0.104.0 // indirect
|
github.com/cloudflare/cloudflare-go v0.104.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/disintegration/imaging v1.6.2 // 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/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/fatih/color v1.17.0 // indirect
|
github.com/fatih/color v1.17.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -159,6 +159,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 h1:NKTa1eqZYw8tiHSRGpP0VtTdub/8
|
|||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o=
|
||||||
github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
|
github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
|
||||||
github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||||
|
github.com/baidubce/bce-sdk-go v0.9.197 h1:TQqa4J+FTagrywhaTQ707ffE1eG3ix1s06eSZ/K+Wk0=
|
||||||
|
github.com/baidubce/bce-sdk-go v0.9.197/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
||||||
github.com/blinkbean/dingtalk v1.1.3 h1:MbidFZYom7DTFHD/YIs+eaI7kRy52kmWE/sy0xjo6E4=
|
github.com/blinkbean/dingtalk v1.1.3 h1:MbidFZYom7DTFHD/YIs+eaI7kRy52kmWE/sy0xjo6E4=
|
||||||
github.com/blinkbean/dingtalk v1.1.3/go.mod h1:9BaLuGSBqY3vT5hstValh48DbsKO7vaHaJnG9pXwbto=
|
github.com/blinkbean/dingtalk v1.1.3/go.mod h1:9BaLuGSBqY3vT5hstValh48DbsKO7vaHaJnG9pXwbto=
|
||||||
github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI=
|
github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI=
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
aliyunAlb "github.com/alibabacloud-go/alb-20200616/v2/client"
|
aliyunAlb "github.com/alibabacloud-go/alb-20200616/v2/client"
|
||||||
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/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")
|
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{
|
uploader, err := uploaderAliyunCas.New(&uploaderAliyunCas.AliyunCASUploaderConfig{
|
||||||
AccessKeyId: access.AccessKeyId,
|
AccessKeyId: access.AccessKeyId,
|
||||||
AccessKeySecret: access.AccessKeySecret,
|
AccessKeySecret: access.AccessKeySecret,
|
||||||
Region: option.DeployConfig.GetConfigAsString("region"),
|
Region: aliCasRegion,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
|||||||
@@ -246,10 +246,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
|
// 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 {
|
for _, domainExtension := range describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension {
|
||||||
if *domainExtension.ServerCertificateId == *describeLoadBalancerHTTPSListenerAttributeResp.Body.ServerCertificateId {
|
if *domainExtension.ServerCertificateId != *describeLoadBalancerHTTPSListenerAttributeResp.Body.ServerCertificateId {
|
||||||
break
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
setDomainExtensionAttributeReq := &aliyunSlb.SetDomainExtensionAttributeRequest{
|
setDomainExtensionAttributeReq := &aliyunSlb.SetDomainExtensionAttributeRequest{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
aliyunNlb "github.com/alibabacloud-go/nlb-20220430/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")
|
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{
|
uploader, err := uploaderAliyunCas.New(&uploaderAliyunCas.AliyunCASUploaderConfig{
|
||||||
AccessKeyId: access.AccessKeyId,
|
AccessKeyId: access.AccessKeyId,
|
||||||
AccessKeySecret: access.AccessKeySecret,
|
AccessKeySecret: access.AccessKeySecret,
|
||||||
Region: option.DeployConfig.GetConfigAsString("region"),
|
Region: aliCasRegion,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
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,7 +33,9 @@ const (
|
|||||||
targetTencentTEO = "tencent-teo"
|
targetTencentTEO = "tencent-teo"
|
||||||
targetHuaweiCloudCDN = "huaweicloud-cdn"
|
targetHuaweiCloudCDN = "huaweicloud-cdn"
|
||||||
targetHuaweiCloudELB = "huaweicloud-elb"
|
targetHuaweiCloudELB = "huaweicloud-elb"
|
||||||
|
targetBaiduCloudCDN = "baiducloud-cdn"
|
||||||
targetQiniuCdn = "qiniu-cdn"
|
targetQiniuCdn = "qiniu-cdn"
|
||||||
|
targetDogeCloudCdn = "dogecloud-cdn"
|
||||||
targetLocal = "local"
|
targetLocal = "local"
|
||||||
targetSSH = "ssh"
|
targetSSH = "ssh"
|
||||||
targetWebhook = "webhook"
|
targetWebhook = "webhook"
|
||||||
@@ -134,8 +136,12 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
|
|||||||
return NewHuaweiCloudCDNDeployer(option)
|
return NewHuaweiCloudCDNDeployer(option)
|
||||||
case targetHuaweiCloudELB:
|
case targetHuaweiCloudELB:
|
||||||
return NewHuaweiCloudELBDeployer(option)
|
return NewHuaweiCloudELBDeployer(option)
|
||||||
|
case targetBaiduCloudCDN:
|
||||||
|
return NewBaiduCloudCDNDeployer(option)
|
||||||
case targetQiniuCdn:
|
case targetQiniuCdn:
|
||||||
return NewQiniuCDNDeployer(option)
|
return NewQiniuCDNDeployer(option)
|
||||||
|
case targetDogeCloudCdn:
|
||||||
|
return NewDogeCloudCDNDeployer(option)
|
||||||
case targetLocal:
|
case targetLocal:
|
||||||
return NewLocalDeployer(option)
|
return NewLocalDeployer(option)
|
||||||
case targetSSH:
|
case targetSSH:
|
||||||
|
|||||||
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))
|
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
||||||
|
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported format")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行命令
|
// 执行命令
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
xerrors "github.com/pkg/errors"
|
xerrors "github.com/pkg/errors"
|
||||||
"github.com/qiniu/go-sdk/v7/auth"
|
"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))
|
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
|
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
||||||
domain := d.option.DeployConfig.GetConfigAsString("domain")
|
|
||||||
getDomainInfoResp, err := d.sdkClient.GetDomainInfo(domain)
|
getDomainInfoResp, err := d.sdkClient.GetDomainInfo(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDomainInfo'")
|
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDomainInfo'")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -13,7 +14,6 @@ import (
|
|||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/fs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SSHDeployer struct {
|
type SSHDeployer struct {
|
||||||
@@ -105,11 +105,14 @@ func (d *SSHDeployer) Deploy(ctx context.Context) error {
|
|||||||
return err
|
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
|
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) {
|
func (d *SSHDeployer) sshExecCommand(sshCli *ssh.Client, command string) (string, string, error) {
|
||||||
session, err := client.NewSession()
|
session, err := sshCli.NewSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", xerrors.Wrap(err, "failed to create ssh session")
|
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
|
return stdoutBuf.String(), stderrBuf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SSHDeployer) writeSftpFileString(client *ssh.Client, path string, content string) error {
|
func (d *SSHDeployer) writeSftpFileString(sshCli *ssh.Client, path string, content string) error {
|
||||||
return d.writeSftpFile(client, path, []byte(content))
|
return d.writeSftpFile(sshCli, path, []byte(content))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SSHDeployer) writeSftpFile(client *ssh.Client, path string, data []byte) error {
|
func (d *SSHDeployer) writeSftpFile(sshCli *ssh.Client, path string, data []byte) error {
|
||||||
sftpCli, err := sftp.NewClient(client)
|
sftpCli, err := sftp.NewClient(sshCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Wrap(err, "failed to create sftp client")
|
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 {
|
func (d *TencentCLBDeployer) Deploy(ctx context.Context) error {
|
||||||
// TODO: 直接部署方式
|
|
||||||
|
|
||||||
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
||||||
case "ssl-deploy":
|
case "ssl-deploy":
|
||||||
// 通过 SSL 服务部署到云资源实例
|
// 通过 SSL 服务部署到云资源实例
|
||||||
@@ -104,7 +102,7 @@ func (d *TencentCLBDeployer) Deploy(ctx context.Context) error {
|
|||||||
func (d *TencentCLBDeployer) createSdkClients(secretId, secretKey, region string) (*tencentCLBDeployerSdkClients, error) {
|
func (d *TencentCLBDeployer) createSdkClients(secretId, secretKey, region string) (*tencentCLBDeployerSdkClients, error) {
|
||||||
credential := common.NewCredential(secretId, secretKey)
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
|
|
||||||
sslClient, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
sslClient, err := tcSsl.NewClient(credential, region, profile.NewClientProfile())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ func NewTencentCOSDeployer(option *DeployerOption) (Deployer, error) {
|
|||||||
client, err := (&TencentCOSDeployer{}).createSdkClient(
|
client, err := (&TencentCOSDeployer{}).createSdkClient(
|
||||||
access.SecretId,
|
access.SecretId,
|
||||||
access.SecretKey,
|
access.SecretKey,
|
||||||
|
option.DeployConfig.GetConfigAsString("region"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
@@ -95,9 +96,9 @@ func (d *TencentCOSDeployer) Deploy(ctx context.Context) error {
|
|||||||
return nil
|
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)
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
client, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
client, err := tcSsl.NewClient(credential, region, profile.NewClientProfile())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,12 @@ type TencentAccess struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HuaweiCloudAccess struct {
|
type HuaweiCloudAccess struct {
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
Region string `json:"region"`
|
Region string `json:"region"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaiduCloudAccess struct {
|
||||||
AccessKeyId string `json:"accessKeyId"`
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
SecretAccessKey string `json:"secretAccessKey"`
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
}
|
}
|
||||||
@@ -32,6 +37,11 @@ type QiniuAccess struct {
|
|||||||
SecretKey string `json:"secretKey"`
|
SecretKey string `json:"secretKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DogeCloudAccess struct {
|
||||||
|
AccessKey string `json:"accessKey"`
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
}
|
||||||
|
|
||||||
type NameSiloAccess struct {
|
type NameSiloAccess struct {
|
||||||
ApiKey string `json:"apiKey"`
|
ApiKey string `json:"apiKey"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package domain
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ApplyConfig struct {
|
type ApplyConfig struct {
|
||||||
@@ -29,7 +31,7 @@ type DeployConfig struct {
|
|||||||
// 出参:
|
// 出参:
|
||||||
// - 配置项的值。如果配置项不存在或者类型不是字符串,则返回空字符串。
|
// - 配置项的值。如果配置项不存在或者类型不是字符串,则返回空字符串。
|
||||||
func (dc *DeployConfig) GetConfigAsString(key string) string {
|
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 {
|
func (dc *DeployConfig) GetConfigOrDefaultAsString(key string, defaultValue string) string {
|
||||||
if dc.Config == nil {
|
return maps.GetValueOrDefaultAsString(dc.Config, key, defaultValue)
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
if value, ok := dc.Config[key]; ok {
|
|
||||||
if result, ok := value.(string); ok {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 以 32 位整数形式获取配置项。
|
// 以 32 位整数形式获取配置项。
|
||||||
@@ -62,7 +54,7 @@ func (dc *DeployConfig) GetConfigOrDefaultAsString(key string, defaultValue stri
|
|||||||
// 出参:
|
// 出参:
|
||||||
// - 配置项的值。如果配置项不存在或者类型不是 32 位整数,则返回 0。
|
// - 配置项的值。如果配置项不存在或者类型不是 32 位整数,则返回 0。
|
||||||
func (dc *DeployConfig) GetConfigAsInt32(key string) int32 {
|
func (dc *DeployConfig) GetConfigAsInt32(key string) int32 {
|
||||||
return dc.GetConfigOrDefaultAsInt32(key, 0)
|
return maps.GetValueAsInt32(dc.Config, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 以 32 位整数形式获取配置项。
|
// 以 32 位整数形式获取配置项。
|
||||||
@@ -74,17 +66,7 @@ func (dc *DeployConfig) GetConfigAsInt32(key string) int32 {
|
|||||||
// 出参:
|
// 出参:
|
||||||
// - 配置项的值。如果配置项不存在或者类型不是 32 位整数,则返回默认值。
|
// - 配置项的值。如果配置项不存在或者类型不是 32 位整数,则返回默认值。
|
||||||
func (dc *DeployConfig) GetConfigOrDefaultAsInt32(key string, defaultValue int32) int32 {
|
func (dc *DeployConfig) GetConfigOrDefaultAsInt32(key string, defaultValue int32) int32 {
|
||||||
if dc.Config == nil {
|
return maps.GetValueOrDefaultAsInt32(dc.Config, key, defaultValue)
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
if value, ok := dc.Config[key]; ok {
|
|
||||||
if result, ok := value.(int32); ok {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 以布尔形式获取配置项。
|
// 以布尔形式获取配置项。
|
||||||
@@ -95,7 +77,7 @@ func (dc *DeployConfig) GetConfigOrDefaultAsInt32(key string, defaultValue int32
|
|||||||
// 出参:
|
// 出参:
|
||||||
// - 配置项的值。如果配置项不存在或者类型不是布尔,则返回 false。
|
// - 配置项的值。如果配置项不存在或者类型不是布尔,则返回 false。
|
||||||
func (dc *DeployConfig) GetConfigAsBool(key string) bool {
|
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 {
|
func (dc *DeployConfig) GetConfigOrDefaultAsBool(key string, defaultValue bool) bool {
|
||||||
if dc.Config == nil {
|
return maps.GetValueOrDefaultAsBool(dc.Config, key, defaultValue)
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
if value, ok := dc.Config[key]; ok {
|
|
||||||
if result, ok := value.(bool); ok {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 以变量字典形式获取配置项。
|
// 以变量字典形式获取配置项。
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NotifyChannelDingtalk = "dingtalk"
|
NotifyChannelEmail = "email"
|
||||||
NotifyChannelWebhook = "webhook"
|
NotifyChannelWebhook = "webhook"
|
||||||
NotifyChannelTelegram = "telegram"
|
NotifyChannelDingtalk = "dingtalk"
|
||||||
NotifyChannelLark = "lark"
|
NotifyChannelLark = "lark"
|
||||||
|
NotifyChannelTelegram = "telegram"
|
||||||
NotifyChannelServerChan = "serverchan"
|
NotifyChannelServerChan = "serverchan"
|
||||||
NotifyChannelMail = "mail"
|
|
||||||
NotifyChannelBark = "bark"
|
NotifyChannelBark = "bark"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func (s *Setting) GetChannelContent(channel string) (map[string]any, error) {
|
|||||||
|
|
||||||
v, ok := (*conf)[channel]
|
v, ok := (*conf)[channel]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("channel %s not found", channel)
|
return nil, fmt.Errorf("channel \"%s\" not found", channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
|
|||||||
@@ -12,19 +12,13 @@ import (
|
|||||||
"github.com/usual2970/certimate/internal/utils/xtime"
|
"github.com/usual2970/certimate/internal/utils/xtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
type msg struct {
|
|
||||||
subject string
|
|
||||||
message string
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultExpireSubject = "您有{COUNT}张证书即将过期"
|
defaultExpireSubject = "您有 {COUNT} 张证书即将过期"
|
||||||
defaultExpireMsg = "有{COUNT}张证书即将过期,域名分别为{DOMAINS},请保持关注!"
|
defaultExpireMessage = "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PushExpireMsg() {
|
func PushExpireMsg() {
|
||||||
// 查询即将过期的证书
|
// 查询即将过期的证书
|
||||||
|
|
||||||
records, err := app.GetApp().Dao().FindRecordsByFilter("domains", "expiredAt<{:time}&&certUrl!=''", "-created", 500, 0,
|
records, err := app.GetApp().Dao().FindRecordsByFilter("domains", "expiredAt<{:time}&&certUrl!=''", "-created", 500, 0,
|
||||||
dbx.Params{"time": xtime.GetTimeAfter(24 * time.Hour * 15)})
|
dbx.Params{"time": xtime.GetTimeAfter(24 * time.Hour * 15)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -34,12 +28,12 @@ func PushExpireMsg() {
|
|||||||
|
|
||||||
// 组装消息
|
// 组装消息
|
||||||
msg := buildMsg(records)
|
msg := buildMsg(records)
|
||||||
|
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
return
|
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)
|
app.GetApp().Logger().Error("send expire msg", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,22 +47,27 @@ type notifyTemplate struct {
|
|||||||
Content string `json:"content"`
|
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 {
|
if len(records) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询模板信息
|
// 查询模板信息
|
||||||
templateRecord, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='templates'")
|
templateRecord, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='templates'")
|
||||||
title := defaultExpireSubject
|
subject := defaultExpireSubject
|
||||||
content := defaultExpireMsg
|
message := defaultExpireMessage
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var templates *notifyTemplates
|
var templates *notifyTemplates
|
||||||
templateRecord.UnmarshalJSONField("content", templates)
|
templateRecord.UnmarshalJSONField("content", templates)
|
||||||
if templates != nil && len(templates.NotifyTemplates) > 0 {
|
if templates != nil && len(templates.NotifyTemplates) > 0 {
|
||||||
title = templates.NotifyTemplates[0].Title
|
subject = templates.NotifyTemplates[0].Title
|
||||||
content = templates.NotifyTemplates[0].Content
|
message = templates.NotifyTemplates[0].Content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,17 +80,17 @@ func buildMsg(records []*models.Record) *msg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
countStr := strconv.Itoa(count)
|
countStr := strconv.Itoa(count)
|
||||||
domainStr := strings.Join(domains, ",")
|
domainStr := strings.Join(domains, ";")
|
||||||
|
|
||||||
title = strings.ReplaceAll(title, "{COUNT}", countStr)
|
subject = strings.ReplaceAll(subject, "{COUNT}", countStr)
|
||||||
title = strings.ReplaceAll(title, "{DOMAINS}", domainStr)
|
subject = strings.ReplaceAll(subject, "{DOMAINS}", domainStr)
|
||||||
|
|
||||||
content = strings.ReplaceAll(content, "{COUNT}", countStr)
|
message = strings.ReplaceAll(message, "{COUNT}", countStr)
|
||||||
content = strings.ReplaceAll(content, "{DOMAINS}", domainStr)
|
message = strings.ReplaceAll(message, "{DOMAINS}", domainStr)
|
||||||
|
|
||||||
// 返回消息
|
// 返回消息
|
||||||
return &msg{
|
return ¬ifyMessage{
|
||||||
subject: title,
|
Subject: subject,
|
||||||
message: content,
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"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"
|
"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 {
|
func SendToAllChannels(subject, message string) error {
|
||||||
// 获取所有的推送渠道
|
notifiers, err := getEnabledNotifiers()
|
||||||
notifiers, err := getNotifiers()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -28,184 +20,56 @@ func Send(title, content string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
n := notifyPackage.New()
|
var eg errgroup.Group
|
||||||
// 添加推送渠道
|
for _, n := range notifiers {
|
||||||
n.UseServices(notifiers...)
|
if n == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// 发送消息
|
eg.Go(func() error {
|
||||||
return n.Send(context.Background(), title, content)
|
_, err := n.Notify(context.Background(), subject, message)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
err = eg.Wait()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type sendTestParam struct {
|
func SendToChannel(subject, message string, channel string, channelConfig map[string]any) error {
|
||||||
Title string `json:"title"`
|
notifier, err := createNotifier(channel, channelConfig)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
n := notifyPackage.New()
|
_, err = notifier.Notify(context.Background(), subject, message)
|
||||||
|
return err
|
||||||
// 添加推送渠道
|
|
||||||
n.UseServices(notifier)
|
|
||||||
|
|
||||||
// 发送消息
|
|
||||||
return n.Send(context.Background(), param.Title, param.Content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNotifiers() ([]notifyPackage.Notifier, error) {
|
func getEnabledNotifiers() ([]notifier.Notifier, error) {
|
||||||
resp, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='notifyChannels'")
|
settings, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='notifyChannels'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("find notifyChannels error: %w", err)
|
return nil, fmt.Errorf("find notifyChannels error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
notifiers := make([]notifyPackage.Notifier, 0)
|
|
||||||
|
|
||||||
rs := make(map[string]map[string]any)
|
rs := make(map[string]map[string]any)
|
||||||
|
if err := settings.UnmarshalJSONField("content", &rs); err != nil {
|
||||||
if err := resp.UnmarshalJSONField("content", &rs); err != nil {
|
|
||||||
return nil, fmt.Errorf("unmarshal notifyChannels error: %w", err)
|
return nil, fmt.Errorf("unmarshal notifyChannels error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notifiers := make([]notifier.Notifier, 0)
|
||||||
for k, v := range rs {
|
for k, v := range rs {
|
||||||
|
if !maps.GetValueAsBool(v, "enabled") {
|
||||||
if !getBool(v, "enabled") {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
notifier, err := getNotifier(k, v)
|
notifier, err := createNotifier(k, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
notifiers = append(notifiers, notifier)
|
notifiers = append(notifiers, notifier)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return notifiers, nil
|
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 {
|
func (n *NotifyService) Test(ctx context.Context, req *domain.NotifyTestPushReq) error {
|
||||||
setting, err := n.settingRepo.GetByName(ctx, "notifyChannels")
|
setting, err := n.settingRepo.GetByName(ctx, "notifyChannels")
|
||||||
if err != nil {
|
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 {
|
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{
|
return SendToChannel(notifyTestTitle, notifyTestBody, req.Channel, channelConfig)
|
||||||
Title: notifyTestTitle,
|
|
||||||
Content: notifyTestBody,
|
|
||||||
Channel: req.Channel,
|
|
||||||
Conf: conf,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -26,7 +27,13 @@ type AliyunCASUploader struct {
|
|||||||
sdkClient *aliyunCas.Client
|
sdkClient *aliyunCas.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ uploader.Uploader = (*AliyunCASUploader)(nil)
|
||||||
|
|
||||||
func New(config *AliyunCASUploaderConfig) (*AliyunCASUploader, error) {
|
func New(config *AliyunCASUploaderConfig) (*AliyunCASUploader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
client, err := createSdkClient(
|
client, err := createSdkClient(
|
||||||
config.AccessKeyId,
|
config.AccessKeyId,
|
||||||
config.AccessKeySecret,
|
config.AccessKeySecret,
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -28,7 +30,13 @@ type AliyunSLBUploader struct {
|
|||||||
sdkClient *aliyunSlb.Client
|
sdkClient *aliyunSlb.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ uploader.Uploader = (*AliyunSLBUploader)(nil)
|
||||||
|
|
||||||
func New(config *AliyunSLBUploaderConfig) (*AliyunSLBUploader, error) {
|
func New(config *AliyunSLBUploaderConfig) (*AliyunSLBUploader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
client, err := createSdkClient(
|
client, err := createSdkClient(
|
||||||
config.AccessKeyId,
|
config.AccessKeyId,
|
||||||
config.AccessKeySecret,
|
config.AccessKeySecret,
|
||||||
@@ -82,6 +90,12 @@ func (u *AliyunSLBUploader) Upload(ctx context.Context, certPem string, privkeyP
|
|||||||
var certId, certName string
|
var certId, certName string
|
||||||
certName = fmt.Sprintf("certimate_%d", time.Now().UnixMilli())
|
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
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-uploadservercertificate
|
||||||
uploadServerCertificateReq := &aliyunSlb.UploadServerCertificateRequest{
|
uploadServerCertificateReq := &aliyunSlb.UploadServerCertificateRequest{
|
||||||
|
|||||||
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
|
sdkClient *hcElb.ElbClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ uploader.Uploader = (*HuaweiCloudELBUploader)(nil)
|
||||||
|
|
||||||
func New(config *HuaweiCloudELBUploaderConfig) (*HuaweiCloudELBUploader, error) {
|
func New(config *HuaweiCloudELBUploaderConfig) (*HuaweiCloudELBUploader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
client, err := createSdkClient(
|
client, err := createSdkClient(
|
||||||
config.AccessKeyId,
|
config.AccessKeyId,
|
||||||
config.SecretAccessKey,
|
config.SecretAccessKey,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -27,7 +28,13 @@ type HuaweiCloudSCMUploader struct {
|
|||||||
sdkClient *hcScm.ScmClient
|
sdkClient *hcScm.ScmClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ uploader.Uploader = (*HuaweiCloudSCMUploader)(nil)
|
||||||
|
|
||||||
func New(config *HuaweiCloudSCMUploaderConfig) (*HuaweiCloudSCMUploader, error) {
|
func New(config *HuaweiCloudSCMUploaderConfig) (*HuaweiCloudSCMUploader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
client, err := createSdkClient(
|
client, err := createSdkClient(
|
||||||
config.AccessKeyId,
|
config.AccessKeyId,
|
||||||
config.SecretAccessKey,
|
config.SecretAccessKey,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -23,7 +24,13 @@ type QiniuSSLCertUploader struct {
|
|||||||
sdkClient *qiniuEx.Client
|
sdkClient *qiniuEx.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ uploader.Uploader = (*QiniuSSLCertUploader)(nil)
|
||||||
|
|
||||||
func New(config *QiniuSSLCertUploaderConfig) (*QiniuSSLCertUploader, error) {
|
func New(config *QiniuSSLCertUploaderConfig) (*QiniuSSLCertUploader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
client, err := createSdkClient(
|
client, err := createSdkClient(
|
||||||
config.AccessKey,
|
config.AccessKey,
|
||||||
config.SecretKey,
|
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
|
// 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 {
|
if err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadSslCert'")
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadSslCert'")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
xerrors "github.com/pkg/errors"
|
xerrors "github.com/pkg/errors"
|
||||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||||
@@ -21,7 +22,13 @@ type TencentCloudSSLUploader struct {
|
|||||||
sdkClient *tcSsl.Client
|
sdkClient *tcSsl.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ uploader.Uploader = (*TencentCloudSSLUploader)(nil)
|
||||||
|
|
||||||
func New(config *TencentCloudSSLUploaderConfig) (*TencentCloudSSLUploader, error) {
|
func New(config *TencentCloudSSLUploaderConfig) (*TencentCloudSSLUploader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
client, err := createSdkClient(
|
client, err := createSdkClient(
|
||||||
config.SecretId,
|
config.SecretId,
|
||||||
config.SecretKey,
|
config.SecretKey,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
// 表示定义证书上传者的抽象类型接口。
|
// 表示定义证书上传器的抽象类型接口。
|
||||||
// 云服务商通常会提供 SSL 证书管理服务,可供用户集中管理证书。
|
// 云服务商通常会提供 SSL 证书管理服务,可供用户集中管理证书。
|
||||||
// 注意与 `Deployer` 区分,“上传”通常为“部署”的前置操作。
|
// 注意与 `Deployer` 区分,“上传”通常为“部署”的前置操作。
|
||||||
type Uploader interface {
|
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"
|
xhttp "github.com/usual2970/certimate/internal/utils/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
const qiniuHost = "http://api.qiniu.com"
|
const qiniuHost = "https://api.qiniu.com"
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
mac *auth.Credentials
|
mac *auth.Credentials
|
||||||
@@ -105,12 +105,12 @@ func (c *Client) EnableDomainHttps(domain, certId string, forceHttps, http2Enabl
|
|||||||
return resp, nil
|
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{
|
req := &UploadSslCertRequest{
|
||||||
Name: name,
|
Name: name,
|
||||||
CommonName: commonName,
|
CommonName: commonName,
|
||||||
Pri: pri,
|
Certificate: certificate,
|
||||||
Ca: ca,
|
PrivateKey: privateKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
reqBytes, err := json.Marshal(req)
|
reqBytes, err := json.Marshal(req)
|
||||||
@@ -129,7 +129,7 @@ func (c *Client) UploadSslCert(name, commonName, pri, ca string) (*UploadSslCert
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
|
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
|
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
|
package qiniusdk
|
||||||
|
|
||||||
|
type BaseResponse struct {
|
||||||
|
Code *int `json:"code,omitempty"`
|
||||||
|
Error *string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type UploadSslCertRequest struct {
|
type UploadSslCertRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
CommonName string `json:"common_name"`
|
CommonName string `json:"common_name"`
|
||||||
Pri string `json:"pri"`
|
Certificate string `json:"ca"`
|
||||||
Ca string `json:"ca"`
|
PrivateKey string `json:"pri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UploadSslCertResponse struct {
|
type UploadSslCertResponse struct {
|
||||||
Code *int `json:"code,omitempty"`
|
*BaseResponse
|
||||||
Error *string `json:"error,omitempty"`
|
CertID string `json:"certID"`
|
||||||
CertID string `json:"certID"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DomainInfoHttpsData struct {
|
type DomainInfoHttpsData struct {
|
||||||
@@ -20,8 +24,7 @@ type DomainInfoHttpsData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetDomainInfoResponse struct {
|
type GetDomainInfoResponse struct {
|
||||||
Code *int `json:"code,omitempty"`
|
BaseResponse
|
||||||
Error *string `json:"error,omitempty"`
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
CName string `json:"cname"`
|
CName string `json:"cname"`
|
||||||
@@ -39,8 +42,7 @@ type ModifyDomainHttpsConfRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ModifyDomainHttpsConfResponse struct {
|
type ModifyDomainHttpsConfResponse struct {
|
||||||
Code *int `json:"code,omitempty"`
|
BaseResponse
|
||||||
Error *string `json:"error,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type EnableDomainHttpsRequest struct {
|
type EnableDomainHttpsRequest struct {
|
||||||
@@ -48,6 +50,5 @@ type EnableDomainHttpsRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type EnableDomainHttpsResponse struct {
|
type EnableDomainHttpsResponse struct {
|
||||||
Code *int `json:"code,omitempty"`
|
BaseResponse
|
||||||
Error *string `json:"error,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
||||||
|
})
|
||||||
|
}
|
||||||
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"?>
|
<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>
|
||||||
<!-- 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>
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.7 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 AccessAliyunForm from "./AccessAliyunForm";
|
||||||
import AccessTencentForm from "./AccessTencentForm";
|
import AccessTencentForm from "./AccessTencentForm";
|
||||||
import AccessHuaweiCloudForm from "./AccessHuaweicloudForm";
|
import AccessHuaweiCloudForm from "./AccessHuaweicloudForm";
|
||||||
|
import AccessBaiduCloudForm from "./AccessBaiduCloudForm";
|
||||||
import AccessQiniuForm from "./AccessQiniuForm";
|
import AccessQiniuForm from "./AccessQiniuForm";
|
||||||
|
import AccessDogeCloudForm from "./AccessDogeCloudForm";
|
||||||
import AccessAwsForm from "./AccessAwsForm";
|
import AccessAwsForm from "./AccessAwsForm";
|
||||||
import AccessCloudflareForm from "./AccessCloudflareForm";
|
import AccessCloudflareForm from "./AccessCloudflareForm";
|
||||||
import AccessNamesiloForm from "./AccessNamesiloForm";
|
import AccessNamesiloForm from "./AccessNamesiloForm";
|
||||||
@@ -71,6 +73,17 @@ const AccessEditDialog = ({ trigger, op, data, className }: AccessEditProps) =>
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case "baiducloud":
|
||||||
|
childComponent = (
|
||||||
|
<AccessBaiduCloudForm
|
||||||
|
data={data}
|
||||||
|
op={op}
|
||||||
|
onAfterReq={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
case "qiniu":
|
case "qiniu":
|
||||||
childComponent = (
|
childComponent = (
|
||||||
<AccessQiniuForm
|
<AccessQiniuForm
|
||||||
@@ -82,6 +95,17 @@ const AccessEditDialog = ({ trigger, op, data, className }: AccessEditProps) =>
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case "dogecloud":
|
||||||
|
childComponent = (
|
||||||
|
<AccessDogeCloudForm
|
||||||
|
data={data}
|
||||||
|
op={op}
|
||||||
|
onAfterReq={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
case "aws":
|
case "aws":
|
||||||
childComponent = (
|
childComponent = (
|
||||||
<AccessAwsForm
|
<AccessAwsForm
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ import DeployToTencentCOS from "./DeployToTencentCOS";
|
|||||||
import DeployToTencentTEO from "./DeployToTencentTEO";
|
import DeployToTencentTEO from "./DeployToTencentTEO";
|
||||||
import DeployToHuaweiCloudCDN from "./DeployToHuaweiCloudCDN";
|
import DeployToHuaweiCloudCDN from "./DeployToHuaweiCloudCDN";
|
||||||
import DeployToHuaweiCloudELB from "./DeployToHuaweiCloudELB";
|
import DeployToHuaweiCloudELB from "./DeployToHuaweiCloudELB";
|
||||||
|
import DeployToBaiduCloudCDN from "./DeployToBaiduCloudCDN";
|
||||||
import DeployToQiniuCDN from "./DeployToQiniuCDN";
|
import DeployToQiniuCDN from "./DeployToQiniuCDN";
|
||||||
|
import DeployToDogeCloudCDN from "./DeployToDogeCloudCDN";
|
||||||
import DeployToLocal from "./DeployToLocal";
|
import DeployToLocal from "./DeployToLocal";
|
||||||
import DeployToSSH from "./DeployToSSH";
|
import DeployToSSH from "./DeployToSSH";
|
||||||
import DeployToWebhook from "./DeployToWebhook";
|
import DeployToWebhook from "./DeployToWebhook";
|
||||||
@@ -151,9 +153,15 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
|
|||||||
case "huaweicloud-elb":
|
case "huaweicloud-elb":
|
||||||
childComponent = <DeployToHuaweiCloudELB />;
|
childComponent = <DeployToHuaweiCloudELB />;
|
||||||
break;
|
break;
|
||||||
|
case "baiducloud-cdn":
|
||||||
|
childComponent = <DeployToBaiduCloudCDN />;
|
||||||
|
break;
|
||||||
case "qiniu-cdn":
|
case "qiniu-cdn":
|
||||||
childComponent = <DeployToQiniuCDN />;
|
childComponent = <DeployToQiniuCDN />;
|
||||||
break;
|
break;
|
||||||
|
case "dogecloud-cdn":
|
||||||
|
childComponent = <DeployToDogeCloudCDN />;
|
||||||
|
break;
|
||||||
case "local":
|
case "local":
|
||||||
childComponent = <DeployToLocal />;
|
childComponent = <DeployToLocal />;
|
||||||
break;
|
break;
|
||||||
|
|||||||
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")),
|
loadbalancerId: z.string().min(1, t("domain.deployment.form.tencent_clb_loadbalancer_id.placeholder")),
|
||||||
listenerId: z.string().optional(),
|
listenerId: z.string().optional(),
|
||||||
domain: z.string().regex(/^$|^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
domain: z.string().optional(),
|
||||||
message: t("common.errmsg.domain_invalid"),
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
@@ -63,10 +61,20 @@ const DeployToTencentCLB = () => {
|
|||||||
path: ["listenerId"],
|
path: ["listenerId"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.refine((data) => (data.resourceType === "ruledomain" ? !!data.domain?.trim() : true), {
|
.refine(
|
||||||
message: t("domain.deployment.form.tencent_clb_ruledomain.placeholder"),
|
(data) => {
|
||||||
path: ["domain"],
|
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(() => {
|
useEffect(() => {
|
||||||
const res = formSchema.safeParse(config.config);
|
const res = formSchema.safeParse(config.config);
|
||||||
|
|||||||
@@ -123,22 +123,29 @@ const Bark = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [testing, setTesting] = useState<boolean>(false);
|
||||||
const handlePushTestClick = async () => {
|
const handlePushTestClick = async () => {
|
||||||
|
if (testing) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
setTesting(true);
|
||||||
|
|
||||||
await notifyTest("bark");
|
await notifyTest("bark");
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t("settings.notification.config.push.test.message.success.message"),
|
title: t("settings.notification.push_test_message.succeeded.message"),
|
||||||
description: t("settings.notification.config.push.test.message.success.message"),
|
description: t("settings.notification.push_test_message.succeeded.message"),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = getErrMessage(e);
|
const msg = getErrMessage(e);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t("settings.notification.config.push.test.message.failed.message"),
|
title: t("settings.notification.push_test_message.failed.message"),
|
||||||
description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`,
|
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
setTesting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -177,67 +184,76 @@ const Bark = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="flex flex-col space-y-4">
|
||||||
<Input
|
<div>
|
||||||
placeholder={t("settings.notification.bark.serverUrl.placeholder")}
|
<Label>{t("settings.notification.bark.server_url.label")}</Label>
|
||||||
value={bark.data.serverUrl}
|
<Input
|
||||||
onChange={(e) => {
|
placeholder={t("settings.notification.bark.server_url.placeholder")}
|
||||||
const newData = {
|
value={bark.data.serverUrl}
|
||||||
...bark,
|
onChange={(e) => {
|
||||||
data: {
|
const newData = {
|
||||||
...bark.data,
|
...bark,
|
||||||
serverUrl: e.target.value,
|
data: {
|
||||||
},
|
...bark.data,
|
||||||
};
|
serverUrl: e.target.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
checkChanged(newData.data);
|
checkChanged(newData.data);
|
||||||
setBark(newData);
|
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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div>
|
||||||
<Show when={changed}>
|
<Label>{t("settings.notification.bark.device_key.label")}</Label>
|
||||||
<Button
|
<Input
|
||||||
onClick={() => {
|
placeholder={t("settings.notification.bark.device_key.placeholder")}
|
||||||
handleSaveClick();
|
value={bark.data.deviceKey}
|
||||||
}}
|
onChange={(e) => {
|
||||||
>
|
const newData = {
|
||||||
{t("common.save")}
|
...bark,
|
||||||
</Button>
|
data: {
|
||||||
</Show>
|
...bark.data,
|
||||||
|
deviceKey: e.target.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
<Show when={!changed && bark.id != ""}>
|
checkChanged(newData.data);
|
||||||
<Button
|
setBark(newData);
|
||||||
variant="secondary"
|
}}
|
||||||
onClick={() => {
|
/>
|
||||||
handlePushTestClick();
|
</div>
|
||||||
}}
|
|
||||||
>
|
<div className="flex justify-between gap-4">
|
||||||
{t("settings.notification.config.push.test.message")}
|
<div className="flex items-center space-x-1">
|
||||||
</Button>
|
<Switch id="airplane-mode" checked={bark.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||||
</Show>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -120,23 +120,30 @@ const DingTalk = () => {
|
|||||||
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
|
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
setTesting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [testing, setTesting] = useState<boolean>(false);
|
||||||
const handlePushTestClick = async () => {
|
const handlePushTestClick = async () => {
|
||||||
|
if (testing) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
setTesting(true);
|
||||||
|
|
||||||
await notifyTest("dingtalk");
|
await notifyTest("dingtalk");
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t("settings.notification.config.push.test.message.success.message"),
|
title: t("settings.notification.push_test_message.succeeded.message"),
|
||||||
description: t("settings.notification.config.push.test.message.success.message"),
|
description: t("settings.notification.push_test_message.succeeded.message"),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = getErrMessage(e);
|
const msg = getErrMessage(e);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t("settings.notification.config.push.test.message.failed.message"),
|
title: t("settings.notification.push_test_message.failed.message"),
|
||||||
description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`,
|
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -177,64 +184,74 @@ const DingTalk = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="flex flex-col space-y-4">
|
||||||
<Input
|
<div>
|
||||||
placeholder="AccessToken"
|
<Label>{t("settings.notification.dingtalk.access_token.label")}</Label>
|
||||||
value={dingtalk.data.accessToken}
|
<Input
|
||||||
onChange={(e) => {
|
placeholder={t("settings.notification.dingtalk.access_token.placeholder")}
|
||||||
const newData = {
|
value={dingtalk.data.accessToken}
|
||||||
...dingtalk,
|
onChange={(e) => {
|
||||||
data: {
|
const newData = {
|
||||||
...dingtalk.data,
|
...dingtalk,
|
||||||
accessToken: e.target.value,
|
data: {
|
||||||
},
|
...dingtalk.data,
|
||||||
};
|
accessToken: e.target.value,
|
||||||
checkChanged(newData.data);
|
},
|
||||||
setDingtalk(newData);
|
};
|
||||||
}}
|
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>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div>
|
||||||
<Show when={changed}>
|
<Label>{t("settings.notification.dingtalk.secret.label")}</Label>
|
||||||
<Button
|
<Input
|
||||||
onClick={() => {
|
placeholder={t("settings.notification.dingtalk.secret.placeholder")}
|
||||||
handleSaveClick();
|
value={dingtalk.data.secret}
|
||||||
}}
|
onChange={(e) => {
|
||||||
>
|
const newData = {
|
||||||
{t("common.save")}
|
...dingtalk,
|
||||||
</Button>
|
data: {
|
||||||
</Show>
|
...dingtalk.data,
|
||||||
|
secret: e.target.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
checkChanged(newData.data);
|
||||||
|
setDingtalk(newData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Show when={!changed && dingtalk.id != ""}>
|
<div className="flex justify-between gap-4">
|
||||||
<Button
|
<div className="flex items-center space-x-1">
|
||||||
variant="secondary"
|
<Switch id="airplane-mode" checked={dingtalk.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||||
onClick={() => {
|
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||||
handlePushTestClick();
|
</div>
|
||||||
}}
|
|
||||||
>
|
<div className="flex items-center space-x-1">
|
||||||
{t("settings.notification.config.push.test.message")}
|
<Show when={changed}>
|
||||||
</Button>
|
<Button
|
||||||
</Show>
|
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>
|
||||||
</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}`,
|
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
setTesting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [testing, setTesting] = useState<boolean>(false);
|
||||||
const handlePushTestClick = async () => {
|
const handlePushTestClick = async () => {
|
||||||
|
if (testing) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
setTesting(true);
|
||||||
|
|
||||||
await notifyTest("lark");
|
await notifyTest("lark");
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t("settings.notification.config.push.test.message.success.message"),
|
title: t("settings.notification.push_test_message.succeeded.message"),
|
||||||
description: t("settings.notification.config.push.test.message.success.message"),
|
description: t("settings.notification.push_test_message.succeeded.message"),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = getErrMessage(e);
|
const msg = getErrMessage(e);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t("settings.notification.config.push.test.message.failed.message"),
|
title: t("settings.notification.push_test_message.failed.message"),
|
||||||
description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`,
|
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -173,49 +180,56 @@ const Lark = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="flex flex-col space-y-4">
|
||||||
<Input
|
<div>
|
||||||
placeholder="Webhook Url"
|
<Label>{t("settings.notification.lark.webhook_url.label")}</Label>
|
||||||
value={lark.data.webhookUrl}
|
<Input
|
||||||
onChange={(e) => {
|
placeholder={t("settings.notification.lark.webhook_url.placeholder")}
|
||||||
const newData = {
|
value={lark.data.webhookUrl}
|
||||||
...lark,
|
onChange={(e) => {
|
||||||
data: {
|
const newData = {
|
||||||
...lark.data,
|
...lark,
|
||||||
webhookUrl: e.target.value,
|
data: {
|
||||||
},
|
...lark.data,
|
||||||
};
|
webhookUrl: e.target.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
checkChanged(newData.data);
|
checkChanged(newData.data);
|
||||||
setLark(newData);
|
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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-between gap-4">
|
||||||
<Show when={changed}>
|
<div className="flex items-center space-x-1">
|
||||||
<Button
|
<Switch id="airplane-mode" checked={lark.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||||
onClick={() => {
|
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||||
handleSaveClick();
|
</div>
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("common.save")}
|
|
||||||
</Button>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Show when={!changed && lark.id != ""}>
|
<div className="flex items-center space-x-1">
|
||||||
<Button
|
<Show when={changed}>
|
||||||
variant="secondary"
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handlePushTestClick();
|
handleSaveClick();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("settings.notification.config.push.test.message")}
|
{t("common.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
<Show when={!changed && lark.id != ""}>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
loading={testing}
|
||||||
|
onClick={() => {
|
||||||
|
handlePushTestClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("settings.notification.push_test_message")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
</div>
|
</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)) {
|
if (!isValidURL(serverchan.data.url)) {
|
||||||
toast({
|
toast({
|
||||||
title: t("common.save.failed.message"),
|
title: t("common.save.failed.message"),
|
||||||
description: t("settings.notification.url.errmsg.invalid"),
|
description: t("common.errmsg.url_invalid"),
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -130,22 +130,29 @@ const ServerChan = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [testing, setTesting] = useState<boolean>(false);
|
||||||
const handlePushTestClick = async () => {
|
const handlePushTestClick = async () => {
|
||||||
|
if (testing) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
setTesting(true);
|
||||||
|
|
||||||
await notifyTest("serverchan");
|
await notifyTest("serverchan");
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t("settings.notification.config.push.test.message.success.message"),
|
title: t("settings.notification.push_test_message.succeeded.message"),
|
||||||
description: t("settings.notification.config.push.test.message.success.message"),
|
description: t("settings.notification.push_test_message.succeeded.message"),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = getErrMessage(e);
|
const msg = getErrMessage(e);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t("settings.notification.config.push.test.message.failed.message"),
|
title: t("settings.notification.push_test_message.failed.message"),
|
||||||
description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`,
|
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
setTesting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -184,50 +191,56 @@ const ServerChan = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="flex flex-col space-y-4">
|
||||||
<Input
|
<div>
|
||||||
placeholder={t("settings.notification.serverchan.url.placeholder")}
|
<Label>{t("settings.notification.serverchan.url.label")}</Label>
|
||||||
value={serverchan.data.url}
|
<Input
|
||||||
onChange={(e) => {
|
placeholder={t("settings.notification.serverchan.url.placeholder")}
|
||||||
const newData = {
|
value={serverchan.data.url}
|
||||||
...serverchan,
|
onChange={(e) => {
|
||||||
data: {
|
const newData = {
|
||||||
...serverchan.data,
|
...serverchan,
|
||||||
url: e.target.value,
|
data: {
|
||||||
},
|
...serverchan.data,
|
||||||
};
|
url: e.target.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
checkChanged(newData.data);
|
checkChanged(newData.data);
|
||||||
setServerChan(newData);
|
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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-between gap-4">
|
||||||
<Show when={changed}>
|
<div className="flex items-center space-x-1">
|
||||||
<Button
|
<Switch id="airplane-mode" checked={serverchan.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||||
onClick={() => {
|
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||||
handleSaveClick();
|
</div>
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("common.save")}
|
|
||||||
</Button>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Show when={!changed && serverchan.id != ""}>
|
<div className="flex items-center space-x-1">
|
||||||
<Button
|
<Show when={changed}>
|
||||||
variant="secondary"
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handlePushTestClick();
|
handleSaveClick();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("settings.notification.config.push.test.message")}
|
{t("common.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
<Show when={!changed && serverchan.id != ""}>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
loading={testing}
|
||||||
|
onClick={() => {
|
||||||
|
handlePushTestClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("settings.notification.push_test_message")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -123,22 +123,29 @@ const Telegram = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [testing, setTesting] = useState<boolean>(false);
|
||||||
const handlePushTestClick = async () => {
|
const handlePushTestClick = async () => {
|
||||||
|
if (testing) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
setTesting(true);
|
||||||
|
|
||||||
await notifyTest("telegram");
|
await notifyTest("telegram");
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t("settings.notification.config.push.test.message.success.message"),
|
title: t("settings.notification.push_test_message.succeeded.message"),
|
||||||
description: t("settings.notification.config.push.test.message.success.message"),
|
description: t("settings.notification.push_test_message.succeeded.message"),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = getErrMessage(e);
|
const msg = getErrMessage(e);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t("settings.notification.config.push.test.message.failed.message"),
|
title: t("settings.notification.push_test_message.failed.message"),
|
||||||
description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`,
|
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
setTesting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -177,67 +184,76 @@ const Telegram = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="flex flex-col space-y-4">
|
||||||
<Input
|
<div>
|
||||||
placeholder="ApiToken"
|
<Label>{t("settings.notification.telegram.api_token.label")}</Label>
|
||||||
value={telegram.data.apiToken}
|
<Input
|
||||||
onChange={(e) => {
|
placeholder={t("settings.notification.telegram.api_token.placeholder")}
|
||||||
const newData = {
|
value={telegram.data.apiToken}
|
||||||
...telegram,
|
onChange={(e) => {
|
||||||
data: {
|
const newData = {
|
||||||
...telegram.data,
|
...telegram,
|
||||||
apiToken: e.target.value,
|
data: {
|
||||||
},
|
...telegram.data,
|
||||||
};
|
apiToken: e.target.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
checkChanged(newData.data);
|
checkChanged(newData.data);
|
||||||
setTelegram(newData);
|
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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div>
|
||||||
<Show when={changed}>
|
<Label>{t("settings.notification.telegram.chat_id.label")}</Label>
|
||||||
<Button
|
<Input
|
||||||
onClick={() => {
|
placeholder={t("settings.notification.telegram.chat_id.placeholder")}
|
||||||
handleSaveClick();
|
value={telegram.data.chatId}
|
||||||
}}
|
onChange={(e) => {
|
||||||
>
|
const newData = {
|
||||||
{t("common.save")}
|
...telegram,
|
||||||
</Button>
|
data: {
|
||||||
</Show>
|
...telegram.data,
|
||||||
|
chatId: e.target.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
<Show when={!changed && telegram.id != ""}>
|
checkChanged(newData.data);
|
||||||
<Button
|
setTelegram(newData);
|
||||||
variant="secondary"
|
}}
|
||||||
onClick={() => {
|
/>
|
||||||
handlePushTestClick();
|
</div>
|
||||||
}}
|
|
||||||
>
|
<div className="flex justify-between gap-4">
|
||||||
{t("settings.notification.config.push.test.message")}
|
<div className="flex items-center space-x-1">
|
||||||
</Button>
|
<Switch id="airplane-mode" checked={telegram.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||||
</Show>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ const Webhook = () => {
|
|||||||
if (!isValidURL(webhook.data.url)) {
|
if (!isValidURL(webhook.data.url)) {
|
||||||
toast({
|
toast({
|
||||||
title: t("common.save.failed.message"),
|
title: t("common.save.failed.message"),
|
||||||
description: t("settings.notification.url.errmsg.invalid"),
|
description: t("common.errmsg.url_invalid"),
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -130,22 +130,29 @@ const Webhook = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [testing, setTesting] = useState<boolean>(false);
|
||||||
const handlePushTestClick = async () => {
|
const handlePushTestClick = async () => {
|
||||||
|
if (testing) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
setTesting(true);
|
||||||
|
|
||||||
await notifyTest("webhook");
|
await notifyTest("webhook");
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t("settings.notification.config.push.test.message.success.message"),
|
title: t("settings.notification.push_test_message.succeeded.message"),
|
||||||
description: t("settings.notification.config.push.test.message.success.message"),
|
description: t("settings.notification.push_test_message.succeeded.message"),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = getErrMessage(e);
|
const msg = getErrMessage(e);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t("settings.notification.config.push.test.message.failed.message"),
|
title: t("settings.notification.push_test_message.failed.message"),
|
||||||
description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`,
|
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
setTesting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -184,50 +191,56 @@ const Webhook = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="flex flex-col space-y-4">
|
||||||
<Input
|
<div>
|
||||||
placeholder="Url"
|
<Label>{t("settings.notification.webhook.url.label")}</Label>
|
||||||
value={webhook.data.url}
|
<Input
|
||||||
onChange={(e) => {
|
placeholder={t("settings.notification.webhook.url.placeholder")}
|
||||||
const newData = {
|
value={webhook.data.url}
|
||||||
...webhook,
|
onChange={(e) => {
|
||||||
data: {
|
const newData = {
|
||||||
...webhook.data,
|
...webhook,
|
||||||
url: e.target.value,
|
data: {
|
||||||
},
|
...webhook.data,
|
||||||
};
|
url: e.target.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
checkChanged(newData.data);
|
checkChanged(newData.data);
|
||||||
setWebhook(newData);
|
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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-between gap-4">
|
||||||
<Show when={changed}>
|
<div className="flex items-center space-x-1">
|
||||||
<Button
|
<Switch id="airplane-mode" checked={webhook.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||||
onClick={() => {
|
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||||
handleSaveClick();
|
</div>
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("common.save")}
|
|
||||||
</Button>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Show when={!changed && webhook.id != ""}>
|
<div className="flex items-center space-x-1">
|
||||||
<Button
|
<Show when={changed}>
|
||||||
variant="secondary"
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handlePushTestClick();
|
handleSaveClick();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("settings.notification.config.push.test.message")}
|
{t("common.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
<Show when={!changed && webhook.id != ""}>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
loading={testing}
|
||||||
|
onClick={() => {
|
||||||
|
handlePushTestClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("settings.notification.push_test_message")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Slot } from "@radix-ui/react-slot";
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -32,11 +33,38 @@ const buttonVariants = cva(
|
|||||||
|
|
||||||
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
||||||
asChild?: boolean;
|
asChild?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, asChild = false, ...props }, ref) => {
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, loading, asChild = false, children, ...props }, ref) => {
|
||||||
const Comp = asChild ? Slot : "button";
|
if (asChild) {
|
||||||
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
|
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";
|
Button.displayName = "Button";
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ export const accessProvidersMap: Map<AccessProvider["type"], AccessProvider> = n
|
|||||||
["aliyun", "common.provider.aliyun", "/imgs/providers/aliyun.svg", "all", "阿里云:alibaba cloud"],
|
["aliyun", "common.provider.aliyun", "/imgs/providers/aliyun.svg", "all", "阿里云:alibaba cloud"],
|
||||||
["tencent", "common.provider.tencent", "/imgs/providers/tencent.svg", "all", "腾讯云:tencent cloud"],
|
["tencent", "common.provider.tencent", "/imgs/providers/tencent.svg", "all", "腾讯云:tencent cloud"],
|
||||||
["huaweicloud", "common.provider.huaweicloud", "/imgs/providers/huaweicloud.svg", "all", "华为云:huawei 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"],
|
||||||
["aws", "common.provider.aws", "/imgs/providers/aws.svg", "apply", "亚马逊:amazon:aws"],
|
["aws", "common.provider.aws", "/imgs/providers/aws.svg", "apply", "亚马逊:amazon:aws"],
|
||||||
["cloudflare", "common.provider.cloudflare", "/imgs/providers/cloudflare.svg", "apply", "cloudflare:cf:cloud flare"],
|
["cloudflare", "common.provider.cloudflare", "/imgs/providers/cloudflare.svg", "apply", "cloudflare:cf:cloud flare"],
|
||||||
["namesilo", "common.provider.namesilo", "/imgs/providers/namesilo.svg", "apply", "namesilo"],
|
["namesilo", "common.provider.namesilo", "/imgs/providers/namesilo.svg", "apply", "namesilo"],
|
||||||
@@ -34,7 +36,9 @@ export const accessTypeFormSchema = z.union(
|
|||||||
z.literal("aliyun"),
|
z.literal("aliyun"),
|
||||||
z.literal("tencent"),
|
z.literal("tencent"),
|
||||||
z.literal("huaweicloud"),
|
z.literal("huaweicloud"),
|
||||||
|
z.literal("baiducloud"),
|
||||||
z.literal("qiniu"),
|
z.literal("qiniu"),
|
||||||
|
z.literal("dogecloud"),
|
||||||
z.literal("aws"),
|
z.literal("aws"),
|
||||||
z.literal("cloudflare"),
|
z.literal("cloudflare"),
|
||||||
z.literal("namesilo"),
|
z.literal("namesilo"),
|
||||||
@@ -60,6 +64,7 @@ export type Access = {
|
|||||||
| TencentConfig
|
| TencentConfig
|
||||||
| HuaweiCloudConfig
|
| HuaweiCloudConfig
|
||||||
| QiniuConfig
|
| QiniuConfig
|
||||||
|
| DogeCloudConfig
|
||||||
| AwsConfig
|
| AwsConfig
|
||||||
| CloudflareConfig
|
| CloudflareConfig
|
||||||
| NamesiloConfig
|
| NamesiloConfig
|
||||||
@@ -91,11 +96,21 @@ export type HuaweiCloudConfig = {
|
|||||||
secretAccessKey: string;
|
secretAccessKey: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BaiduCloudConfig = {
|
||||||
|
accessKeyId: string;
|
||||||
|
secretAccessKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type QiniuConfig = {
|
export type QiniuConfig = {
|
||||||
accessKey: string;
|
accessKey: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DogeCloudConfig = {
|
||||||
|
accessKey: string;
|
||||||
|
secretKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type AwsConfig = {
|
export type AwsConfig = {
|
||||||
region: string;
|
region: string;
|
||||||
accessKeyId: string;
|
accessKeyId: string;
|
||||||
|
|||||||
@@ -85,7 +85,9 @@ export const deployTargetsMap: Map<DeployTarget["type"], DeployTarget> = new Map
|
|||||||
["tencent-teo", "common.provider.tencent.teo", "/imgs/providers/tencent.svg"],
|
["tencent-teo", "common.provider.tencent.teo", "/imgs/providers/tencent.svg"],
|
||||||
["huaweicloud-cdn", "common.provider.huaweicloud.cdn", "/imgs/providers/huaweicloud.svg"],
|
["huaweicloud-cdn", "common.provider.huaweicloud.cdn", "/imgs/providers/huaweicloud.svg"],
|
||||||
["huaweicloud-elb", "common.provider.huaweicloud.elb", "/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"],
|
["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"],
|
["local", "common.provider.local", "/imgs/providers/local.svg"],
|
||||||
["ssh", "common.provider.ssh", "/imgs/providers/ssh.svg"],
|
["ssh", "common.provider.ssh", "/imgs/providers/ssh.svg"],
|
||||||
["webhook", "common.provider.webhook", "/imgs/providers/webhook.svg"],
|
["webhook", "common.provider.webhook", "/imgs/providers/webhook.svg"],
|
||||||
|
|||||||
@@ -18,24 +18,40 @@ export type NotifyTemplate = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type NotifyChannels = {
|
export type NotifyChannels = {
|
||||||
|
email?: NotifyChannelEmail;
|
||||||
|
webhook?: NotifyChannel;
|
||||||
dingtalk?: NotifyChannel;
|
dingtalk?: NotifyChannel;
|
||||||
lark?: NotifyChannel;
|
lark?: NotifyChannel;
|
||||||
telegram?: NotifyChannel;
|
telegram?: NotifyChannel;
|
||||||
webhook?: NotifyChannel;
|
|
||||||
serverchan?: NotifyChannel;
|
serverchan?: NotifyChannel;
|
||||||
mail?: NotifyChannelMail;
|
|
||||||
bark?: NotifyChannelBark;
|
bark?: NotifyChannelBark;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NotifyChannel =
|
export type NotifyChannel =
|
||||||
|
| NotifyChannelEmail
|
||||||
|
| NotifyChannelWebhook
|
||||||
| NotifyChannelDingTalk
|
| NotifyChannelDingTalk
|
||||||
| NotifyChannelLark
|
| NotifyChannelLark
|
||||||
| NotifyChannelTelegram
|
| NotifyChannelTelegram
|
||||||
| NotifyChannelWebhook
|
|
||||||
| NotifyChannelServerChan
|
| NotifyChannelServerChan
|
||||||
| NotifyChannelMail
|
|
||||||
| NotifyChannelBark;
|
| 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 = {
|
export type NotifyChannelDingTalk = {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
@@ -53,26 +69,11 @@ export type NotifyChannelTelegram = {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NotifyChannelWebhook = {
|
|
||||||
url: string;
|
|
||||||
enabled: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NotifyChannelServerChan = {
|
export type NotifyChannelServerChan = {
|
||||||
url: string;
|
url: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NotifyChannelMail = {
|
|
||||||
senderAddress: string;
|
|
||||||
receiverAddresses: string;
|
|
||||||
smtpHostAddr: string;
|
|
||||||
smtpHostPort: string;
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
enabled: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NotifyChannelBark = {
|
export type NotifyChannelBark = {
|
||||||
deviceKey: string;
|
deviceKey: string;
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export const version = "Certimate v0.2.13";
|
export const version = "Certimate v0.2.19";
|
||||||
|
|||||||
@@ -68,8 +68,12 @@
|
|||||||
"common.provider.huaweicloud": "Huawei Cloud",
|
"common.provider.huaweicloud": "Huawei Cloud",
|
||||||
"common.provider.huaweicloud.cdn": "Huawei Cloud - CDN",
|
"common.provider.huaweicloud.cdn": "Huawei Cloud - CDN",
|
||||||
"common.provider.huaweicloud.elb": "Huawei Cloud - ELB",
|
"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": "Qiniu Cloud",
|
||||||
"common.provider.qiniu.cdn": "Qiniu Cloud - CDN",
|
"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.aws": "AWS",
|
||||||
"common.provider.cloudflare": "Cloudflare",
|
"common.provider.cloudflare": "Cloudflare",
|
||||||
"common.provider.namesilo": "Namesilo",
|
"common.provider.namesilo": "Namesilo",
|
||||||
@@ -79,12 +83,12 @@
|
|||||||
"common.provider.local": "Local Deployment",
|
"common.provider.local": "Local Deployment",
|
||||||
"common.provider.ssh": "SSH Deployment",
|
"common.provider.ssh": "SSH Deployment",
|
||||||
"common.provider.webhook": "Webhook",
|
"common.provider.webhook": "Webhook",
|
||||||
"common.provider.serverchan": "ServerChan",
|
|
||||||
"common.provider.kubernetes": "Kubernetes",
|
"common.provider.kubernetes": "Kubernetes",
|
||||||
"common.provider.kubernetes.secret": "Kubernetes - Secret",
|
"common.provider.kubernetes.secret": "Kubernetes - Secret",
|
||||||
|
"common.provider.email": "Email",
|
||||||
"common.provider.dingtalk": "DingTalk",
|
"common.provider.dingtalk": "DingTalk",
|
||||||
"common.provider.telegram": "Telegram",
|
|
||||||
"common.provider.lark": "Lark",
|
"common.provider.lark": "Lark",
|
||||||
"common.provider.mail": "Mail",
|
"common.provider.telegram": "Telegram",
|
||||||
|
"common.provider.serverchan": "ServerChan",
|
||||||
"common.provider.bark": "Bark"
|
"common.provider.bark": "Bark"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,20 +30,40 @@
|
|||||||
"settings.notification.config.enable": "Enable",
|
"settings.notification.config.enable": "Enable",
|
||||||
"settings.notification.config.saved.message": "Configuration saved successfully",
|
"settings.notification.config.saved.message": "Configuration saved successfully",
|
||||||
"settings.notification.config.failed.message": "Configuration save failed",
|
"settings.notification.config.failed.message": "Configuration save failed",
|
||||||
"settings.notification.config.push.test.message": "Send test notification",
|
"settings.notification.push_test_message": "Send test notification",
|
||||||
"settings.notification.config.push.test.message.failed.message": "Send test notification failed",
|
"settings.notification.push_test_message.succeeded.message": "Send test notification successfully",
|
||||||
"settings.notification.config.push.test.message.success.message": "Send test notification successfully",
|
"settings.notification.push_test_message.failed.message": "Send test notification failed",
|
||||||
"settings.notification.dingtalk.secret.placeholder": "Signature for signed addition",
|
"settings.notification.email.smtp_host.label": "SMTP Host",
|
||||||
"settings.notification.url.errmsg.invalid": "Invalid Url format",
|
"settings.notification.email.smtp_host.placeholder": "Please enter SMTP host",
|
||||||
"settings.notification.serverchan.url.placeholder": "Url, e.g. https://sctapi.ftqq.com/****************.send",
|
"settings.notification.email.smtp_port.label": "SMTP Port",
|
||||||
"settings.notification.mail.sender_address.placeholder": "Sender email address",
|
"settings.notification.email.smtp_port.placeholder": "Please enter SMTP port",
|
||||||
"settings.notification.mail.receiver_address.placeholder": "Receiver email address",
|
"settings.notification.email.smtp_tls.label": "Use TLS/SSL",
|
||||||
"settings.notification.mail.smtp_host.placeholder": "SMTP server address",
|
"settings.notification.email.username.label": "Username",
|
||||||
"settings.notification.mail.smtp_port.placeholder": "SMTP server port, if not set, default is 25",
|
"settings.notification.email.username.placeholder": "please enter username",
|
||||||
"settings.notification.mail.username.placeholder": "username",
|
"settings.notification.email.password.label": "Password",
|
||||||
"settings.notification.mail.password.placeholder": "password",
|
"settings.notification.email.password.placeholder": "please enter 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.email.sender_address.label": "Sender Email Address",
|
||||||
"settings.notification.bark.deviceKey.placeholder": "Device Key,e.g. XXXXXXXXXXXXXXXXXXXX",
|
"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.tab": "Certificate Authority",
|
||||||
"settings.ca.provider.errmsg.empty": "Please select a Certificate Authority",
|
"settings.ca.provider.errmsg.empty": "Please select a Certificate Authority",
|
||||||
|
|||||||
@@ -68,8 +68,12 @@
|
|||||||
"common.provider.huaweicloud": "华为云",
|
"common.provider.huaweicloud": "华为云",
|
||||||
"common.provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN",
|
"common.provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN",
|
||||||
"common.provider.huaweicloud.elb": "华为云 - 弹性负载均衡 ELB",
|
"common.provider.huaweicloud.elb": "华为云 - 弹性负载均衡 ELB",
|
||||||
|
"common.provider.baiducloud": "百度智能云",
|
||||||
|
"common.provider.baiducloud.cdn": "百度智能云 - 内容分发网络 CDN",
|
||||||
"common.provider.qiniu": "七牛云",
|
"common.provider.qiniu": "七牛云",
|
||||||
"common.provider.qiniu.cdn": "七牛云 - 内容分发网络 CDN",
|
"common.provider.qiniu.cdn": "七牛云 - 内容分发网络 CDN",
|
||||||
|
"common.provider.dogecloud": "多吉云",
|
||||||
|
"common.provider.dogecloud.cdn": "多吉云 - 内容分发网络 CDN",
|
||||||
"common.provider.aws": "AWS",
|
"common.provider.aws": "AWS",
|
||||||
"common.provider.cloudflare": "Cloudflare",
|
"common.provider.cloudflare": "Cloudflare",
|
||||||
"common.provider.namesilo": "Namesilo",
|
"common.provider.namesilo": "Namesilo",
|
||||||
@@ -79,12 +83,12 @@
|
|||||||
"common.provider.local": "本地部署",
|
"common.provider.local": "本地部署",
|
||||||
"common.provider.ssh": "SSH 部署",
|
"common.provider.ssh": "SSH 部署",
|
||||||
"common.provider.webhook": "Webhook",
|
"common.provider.webhook": "Webhook",
|
||||||
"common.provider.serverchan": "Server酱",
|
|
||||||
"common.provider.kubernetes": "Kubernetes",
|
"common.provider.kubernetes": "Kubernetes",
|
||||||
"common.provider.kubernetes.secret": "Kubernetes - Secret",
|
"common.provider.kubernetes.secret": "Kubernetes - Secret",
|
||||||
|
"common.provider.email": "电子邮件",
|
||||||
"common.provider.dingtalk": "钉钉",
|
"common.provider.dingtalk": "钉钉",
|
||||||
"common.provider.telegram": "Telegram",
|
|
||||||
"common.provider.lark": "飞书",
|
"common.provider.lark": "飞书",
|
||||||
"common.provider.mail": "电子邮件",
|
"common.provider.telegram": "Telegram",
|
||||||
|
"common.provider.serverchan": "Server酱",
|
||||||
"common.provider.bark": "Bark"
|
"common.provider.bark": "Bark"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,20 +30,40 @@
|
|||||||
"settings.notification.config.enable": "是否启用",
|
"settings.notification.config.enable": "是否启用",
|
||||||
"settings.notification.config.saved.message": "配置保存成功",
|
"settings.notification.config.saved.message": "配置保存成功",
|
||||||
"settings.notification.config.failed.message": "配置保存失败",
|
"settings.notification.config.failed.message": "配置保存失败",
|
||||||
"settings.notification.config.push.test.message": "推送测试消息",
|
"settings.notification.push_test_message": "推送测试消息",
|
||||||
"settings.notification.config.push.test.message.failed.message": "推送测试消息失败",
|
"settings.notification.push_test_message.failed.message": "推送测试消息失败",
|
||||||
"settings.notification.config.push.test.message.success.message": "推送测试消息成功",
|
"settings.notification.push_test_message.succeeded.message": "推送测试消息成功",
|
||||||
"settings.notification.dingtalk.secret.placeholder": "加签的签名",
|
"settings.notification.email.smtp_host.label": "SMTP 服务器地址",
|
||||||
"settings.notification.url.errmsg.invalid": "URL 格式不正确",
|
"settings.notification.email.smtp_host.placeholder": "请输入 SMTP 服务器地址",
|
||||||
"settings.notification.serverchan.url.placeholder": "Url, 形如: https://sctapi.ftqq.com/****************.send",
|
"settings.notification.email.smtp_port.label": "SMTP 服务器端口",
|
||||||
"settings.notification.mail.sender_address.placeholder": "发送邮箱地址",
|
"settings.notification.email.smtp_port.placeholder": "请输入 SMTP 服务器端口",
|
||||||
"settings.notification.mail.receiver_address.placeholder": "接收邮箱地址",
|
"settings.notification.email.smtp_tls.label": "TLS/SSL 连接",
|
||||||
"settings.notification.mail.smtp_host.placeholder": "SMTP服务器地址",
|
"settings.notification.email.username.label": "用户名",
|
||||||
"settings.notification.mail.smtp_port.placeholder": "SMTP服务器端口, 如果未设置, 默认为25",
|
"settings.notification.email.username.placeholder": "请输入用户名",
|
||||||
"settings.notification.mail.username.placeholder": "用于登录到邮件服务器的用户名",
|
"settings.notification.email.password.label": "密码",
|
||||||
"settings.notification.mail.password.placeholder": "用于登录到邮件服务器的密码",
|
"settings.notification.email.password.placeholder": "请输入密码",
|
||||||
"settings.notification.bark.serverUrl.placeholder": "服务器URL,形如: https://your-bark-server.com, 留空则使用 Bark 默认服务器",
|
"settings.notification.email.sender_address.label": "发送邮箱地址",
|
||||||
"settings.notification.bark.deviceKey.placeholder": "设备密钥,形如: XXXXXXXXXXXXXXXXXXXX",
|
"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.tab": "证书颁发机构(CA)",
|
||||||
"settings.ca.provider.errmsg.empty": "请选择证书分发机构",
|
"settings.ca.provider.errmsg.empty": "请选择证书分发机构",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import NotifyTemplate from "@/components/notify/NotifyTemplate";
|
|||||||
import Telegram from "@/components/notify/Telegram";
|
import Telegram from "@/components/notify/Telegram";
|
||||||
import Webhook from "@/components/notify/Webhook";
|
import Webhook from "@/components/notify/Webhook";
|
||||||
import ServerChan from "@/components/notify/ServerChan";
|
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 Bark from "@/components/notify/Bark";
|
||||||
import { NotifyProvider } from "@/providers/notify";
|
import { NotifyProvider } from "@/providers/notify";
|
||||||
|
|
||||||
@@ -27,51 +27,52 @@ const Notify = () => {
|
|||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border rounded-md p-5 mt-7 shadow-lg">
|
<div className="border rounded-md p-5 mt-7 shadow-lg">
|
||||||
<Accordion type={"single"} className="dark:text-stone-200">
|
<Accordion type={"single"} className="dark:text-stone-200">
|
||||||
<AccordionItem value="item-2" className="dark:border-stone-200">
|
<AccordionItem value="item-email" className="dark:border-stone-200">
|
||||||
<AccordionTrigger>{t("common.provider.dingtalk")}</AccordionTrigger>
|
<AccordionTrigger>{t("common.provider.email")}</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<DingTalk />
|
<Email />
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|
||||||
<AccordionItem value="item-3" className="dark:border-stone-200">
|
<AccordionItem value="item-webhook" 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">
|
|
||||||
<AccordionTrigger>{t("common.provider.webhook")}</AccordionTrigger>
|
<AccordionTrigger>{t("common.provider.webhook")}</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<Webhook />
|
<Webhook />
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</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>
|
<AccordionTrigger>{t("common.provider.serverchan")}</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<ServerChan />
|
<ServerChan />
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|
||||||
<AccordionItem value="item-7" className="dark:border-stone-200">
|
<AccordionItem value="item-bark" className="dark:border-stone-200">
|
||||||
<AccordionTrigger>{t("common.provider.mail")}</AccordionTrigger>
|
|
||||||
<AccordionContent>
|
|
||||||
<Mail />
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
|
|
||||||
<AccordionItem value="item-8" className="dark:border-stone-200">
|
|
||||||
<AccordionTrigger>{t("common.provider.bark")}</AccordionTrigger>
|
<AccordionTrigger>{t("common.provider.bark")}</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<Bark />
|
<Bark />
|
||||||
|
|||||||
Reference in New Issue
Block a user