mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-18 17:31:55 +08:00
merge source
This commit is contained in:
parent
57ae6d5b40
commit
1562e92e74
@ -76,7 +76,7 @@ go run main.go serve
|
|||||||
| :--------: | :----------: | :----------: | ------------------------------------------------------------ |
|
| :--------: | :----------: | :----------: | ------------------------------------------------------------ |
|
||||||
| 阿里云 | √ | √ | 可签发在阿里云注册的域名;可部署到阿里云 OSS、CDN |
|
| 阿里云 | √ | √ | 可签发在阿里云注册的域名;可部署到阿里云 OSS、CDN |
|
||||||
| 腾讯云 | √ | √ | 可签发在腾讯云注册的域名;可部署到腾讯云 CDN |
|
| 腾讯云 | √ | √ | 可签发在腾讯云注册的域名;可部署到腾讯云 CDN |
|
||||||
| 华为云 | √ | | 可签发在华为云注册的域名 |
|
| 华为云 | √ | √ | 可签发在华为云注册的域名;可部署到华为云 CDN |
|
||||||
| 七牛云 | | √ | 可部署到七牛云 CDN |
|
| 七牛云 | | √ | 可部署到七牛云 CDN |
|
||||||
| AWS | √ | | 可签发在 AWS Route53 托管的域名 |
|
| AWS | √ | | 可签发在 AWS Route53 托管的域名 |
|
||||||
| CloudFlare | √ | | 可签发在 CloudFlare 注册的域名;CloudFlare 服务自带 SSL 证书 |
|
| CloudFlare | √ | | 可签发在 CloudFlare 注册的域名;CloudFlare 服务自带 SSL 证书 |
|
||||||
|
@ -75,7 +75,7 @@ password:1234567890
|
|||||||
| :-----------: | :----------: | :--------: | ------------------------------------------------------------------------------------------- |
|
| :-----------: | :----------: | :--------: | ------------------------------------------------------------------------------------------- |
|
||||||
| Alibaba Cloud | √ | √ | Supports domains registered on Alibaba Cloud; supports deployment to Alibaba Cloud OSS, CDN |
|
| Alibaba Cloud | √ | √ | Supports domains registered on Alibaba Cloud; supports deployment to Alibaba Cloud OSS, CDN |
|
||||||
| Tencent Cloud | √ | √ | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud CDN |
|
| Tencent Cloud | √ | √ | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud CDN |
|
||||||
| Huawei Cloud | √ | | Supports domains registered on Huawei Cloud |
|
| Huawei Cloud | √ | √ | Supports domains registered on Huawei; supports deployment to Huawei Cloud CDN |
|
||||||
| Qiniu Cloud | | √ | Supports deployment to Qiniu Cloud CDN |
|
| Qiniu Cloud | | √ | Supports deployment to Qiniu Cloud CDN |
|
||||||
| AWS | √ | | Supports domains managed on AWS Route53 |
|
| AWS | √ | | Supports domains managed on AWS Route53 |
|
||||||
| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
|
| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
|
||||||
|
2
go.mod
2
go.mod
@ -12,6 +12,7 @@ require (
|
|||||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||||
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/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
|
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
|
||||||
github.com/nikoksr/notify v1.0.0
|
github.com/nikoksr/notify v1.0.0
|
||||||
github.com/pkg/sftp v1.13.6
|
github.com/pkg/sftp v1.13.6
|
||||||
@ -46,7 +47,6 @@ require (
|
|||||||
github.com/google/gnostic-models v0.6.8 // indirect
|
github.com/google/gnostic-models v0.6.8 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114 // indirect
|
|
||||||
github.com/imdario/mergo v0.3.6 // indirect
|
github.com/imdario/mergo v0.3.6 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
@ -19,12 +19,15 @@ import (
|
|||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
|
|
||||||
|
"certimate/internal/domain"
|
||||||
|
"certimate/internal/utils/app"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
configTypeAliyun = "aliyun"
|
configTypeAliyun = "aliyun"
|
||||||
configTypeTencent = "tencent"
|
configTypeTencent = "tencent"
|
||||||
configTypeHuaweicloud = "huaweicloud"
|
configTypeHuaweiCloud = "huaweicloud"
|
||||||
configTypeAws = "aws"
|
configTypeAws = "aws"
|
||||||
configTypeCloudflare = "cloudflare"
|
configTypeCloudflare = "cloudflare"
|
||||||
configTypeNamesilo = "namesilo"
|
configTypeNamesilo = "namesilo"
|
||||||
@ -128,7 +131,7 @@ func Get(record *models.Record) (Applicant, error) {
|
|||||||
return NewAliyun(option), nil
|
return NewAliyun(option), nil
|
||||||
case configTypeTencent:
|
case configTypeTencent:
|
||||||
return NewTencent(option), nil
|
return NewTencent(option), nil
|
||||||
case configTypeHuaweicloud:
|
case configTypeHuaweiCloud:
|
||||||
return NewHuaweiCloud(option), nil
|
return NewHuaweiCloud(option), nil
|
||||||
case configTypeAws:
|
case configTypeAws:
|
||||||
return NewAws(option), nil
|
return NewAws(option), nil
|
||||||
|
@ -20,7 +20,7 @@ type AliyunCDNDeployer struct {
|
|||||||
infos []string
|
infos []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAliyunCdnDeployer(option *DeployerOption) (*AliyunCDNDeployer, error) {
|
func NewAliyunCDNDeployer(option *DeployerOption) (*AliyunCDNDeployer, error) {
|
||||||
access := &domain.AliyunAccess{}
|
access := &domain.AliyunAccess{}
|
||||||
json.Unmarshal([]byte(option.Access), access)
|
json.Unmarshal([]byte(option.Access), access)
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ func NewAliyunCdnDeployer(option *DeployerOption) (*AliyunCDNDeployer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunCDNDeployer) GetID() string {
|
func (d *AliyunCDNDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AceessRecord.GetString("name"), d.option.AceessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunCDNDeployer) GetInfo() []string {
|
func (d *AliyunCDNDeployer) GetInfo() []string {
|
||||||
|
@ -25,7 +25,7 @@ type AliyunESADeployer struct {
|
|||||||
infos []string
|
infos []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAliyunEsaDeployer(option *DeployerOption) (*AliyunESADeployer, error) {
|
func NewAliyunESADeployer(option *DeployerOption) (*AliyunESADeployer, error) {
|
||||||
access := &domain.AliyunAccess{}
|
access := &domain.AliyunAccess{}
|
||||||
json.Unmarshal([]byte(option.Access), access)
|
json.Unmarshal([]byte(option.Access), access)
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ func NewAliyunEsaDeployer(option *DeployerOption) (*AliyunESADeployer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunESADeployer) GetID() string {
|
func (d *AliyunESADeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AceessRecord.GetString("name"), d.option.AceessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunESADeployer) GetInfo() []string {
|
func (d *AliyunESADeployer) GetInfo() []string {
|
||||||
|
@ -16,7 +16,7 @@ type AliyunOSSDeployer struct {
|
|||||||
infos []string
|
infos []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAliyunOssDeployer(option *DeployerOption) (Deployer, error) {
|
func NewAliyunOSSDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.AliyunAccess{}
|
access := &domain.AliyunAccess{}
|
||||||
json.Unmarshal([]byte(option.Access), access)
|
json.Unmarshal([]byte(option.Access), access)
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ func NewAliyunOssDeployer(option *DeployerOption) (Deployer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunOSSDeployer) GetID() string {
|
func (d *AliyunOSSDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AceessRecord.GetString("name"), d.option.AceessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyunOSSDeployer) GetInfo() []string {
|
func (d *AliyunOSSDeployer) GetInfo() []string {
|
||||||
|
@ -15,15 +15,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
targetAliyunOSS = "aliyun-oss"
|
targetAliyunOSS = "aliyun-oss"
|
||||||
targetAliyunCDN = "aliyun-cdn"
|
targetAliyunCDN = "aliyun-cdn"
|
||||||
targetAliyunESA = "aliyun-dcdn"
|
targetAliyunESA = "aliyun-dcdn"
|
||||||
targetTencentCDN = "tencent-cdn"
|
targetTencentCDN = "tencent-cdn"
|
||||||
targetQiniuCdn = "qiniu-cdn"
|
targetHuaweiCloudCDN = "huaweicloud-cdn"
|
||||||
targetLocal = "local"
|
targetQiniuCdn = "qiniu-cdn"
|
||||||
targetSSH = "ssh"
|
targetLocal = "local"
|
||||||
targetWebhook = "webhook"
|
targetSSH = "ssh"
|
||||||
targetK8sSecret = "k8s-secret"
|
targetWebhook = "webhook"
|
||||||
|
targetK8sSecret = "k8s-secret"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeployerOption struct {
|
type DeployerOption struct {
|
||||||
@ -31,7 +32,7 @@ type DeployerOption struct {
|
|||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
Product string `json:"product"`
|
Product string `json:"product"`
|
||||||
Access string `json:"access"`
|
Access string `json:"access"`
|
||||||
AceessRecord *models.Record `json:"-"`
|
AccessRecord *models.Record `json:"-"`
|
||||||
DeployConfig domain.DeployConfig `json:"deployConfig"`
|
DeployConfig domain.DeployConfig `json:"deployConfig"`
|
||||||
Certificate applicant.Certificate `json:"certificate"`
|
Certificate applicant.Certificate `json:"certificate"`
|
||||||
Variables map[string]string `json:"variables"`
|
Variables map[string]string `json:"variables"`
|
||||||
@ -83,7 +84,7 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
|
|||||||
Domain: record.GetString("domain"),
|
Domain: record.GetString("domain"),
|
||||||
Product: getProduct(deployConfig.Type),
|
Product: getProduct(deployConfig.Type),
|
||||||
Access: access.GetString("config"),
|
Access: access.GetString("config"),
|
||||||
AceessRecord: access,
|
AccessRecord: access,
|
||||||
DeployConfig: deployConfig,
|
DeployConfig: deployConfig,
|
||||||
}
|
}
|
||||||
if cert != nil {
|
if cert != nil {
|
||||||
@ -97,13 +98,15 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
|
|||||||
|
|
||||||
switch deployConfig.Type {
|
switch deployConfig.Type {
|
||||||
case targetAliyunOSS:
|
case targetAliyunOSS:
|
||||||
return NewAliyunOssDeployer(option)
|
return NewAliyunOSSDeployer(option)
|
||||||
case targetAliyunCDN:
|
case targetAliyunCDN:
|
||||||
return NewAliyunCdnDeployer(option)
|
return NewAliyunCDNDeployer(option)
|
||||||
case targetAliyunESA:
|
case targetAliyunESA:
|
||||||
return NewAliyunEsaDeployer(option)
|
return NewAliyunESADeployer(option)
|
||||||
case targetTencentCDN:
|
case targetTencentCDN:
|
||||||
return NewTencentCDNDeployer(option)
|
return NewTencentCDNDeployer(option)
|
||||||
|
case targetHuaweiCloudCDN:
|
||||||
|
return NewHuaweiCloudCDNDeployer(option)
|
||||||
case targetQiniuCdn:
|
case targetQiniuCdn:
|
||||||
return NewQiniuCDNDeployer(option)
|
return NewQiniuCDNDeployer(option)
|
||||||
case targetLocal:
|
case targetLocal:
|
||||||
|
150
internal/deployer/huaweicloud_cdn.go
Normal file
150
internal/deployer/huaweicloud_cdn.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
|
||||||
|
cdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2"
|
||||||
|
cdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model"
|
||||||
|
cdnRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/region"
|
||||||
|
|
||||||
|
"certimate/internal/domain"
|
||||||
|
"certimate/internal/utils/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HuaweiCloudCDNDeployer struct {
|
||||||
|
option *DeployerOption
|
||||||
|
infos []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHuaweiCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
|
return &HuaweiCloudCDNDeployer{
|
||||||
|
option: option,
|
||||||
|
infos: make([]string, 0),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudCDNDeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudCDNDeployer) GetInfo() []string {
|
||||||
|
return d.infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
|
||||||
|
access := &domain.HuaweiCloudAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := d.createClient(access)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("HuaweiCloudCdnClient 创建成功", nil))
|
||||||
|
|
||||||
|
// 查询加速域名配置
|
||||||
|
showDomainFullConfigReq := &cdnModel.ShowDomainFullConfigRequest{
|
||||||
|
DomainName: getDeployString(d.option.DeployConfig, "domain"),
|
||||||
|
}
|
||||||
|
showDomainFullConfigResp, err := client.ShowDomainFullConfig(showDomainFullConfigReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已查询到加速域名配置", showDomainFullConfigResp))
|
||||||
|
|
||||||
|
// 更新加速域名配置
|
||||||
|
certName := fmt.Sprintf("%s-%s", d.option.DomainId, rand.RandStr(12))
|
||||||
|
updateDomainMultiCertificatesReq := &cdnModel.UpdateDomainMultiCertificatesRequest{
|
||||||
|
Body: &cdnModel.UpdateDomainMultiCertificatesRequestBody{
|
||||||
|
Https: mergeHuaweiCloudCDNConfig(showDomainFullConfigResp.Configs, &cdnModel.UpdateDomainMultiCertificatesRequestBodyContent{
|
||||||
|
DomainName: getDeployString(d.option.DeployConfig, "domain"),
|
||||||
|
HttpsSwitch: 1,
|
||||||
|
CertName: &certName,
|
||||||
|
Certificate: &d.option.Certificate.Certificate,
|
||||||
|
PrivateKey: &d.option.Certificate.PrivateKey,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
updateDomainMultiCertificatesResp, err := client.UpdateDomainMultiCertificates(updateDomainMultiCertificatesReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.infos = append(d.infos, toStr("已更新加速域名配置", updateDomainMultiCertificatesResp))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudCDNDeployer) createClient(access *domain.HuaweiCloudAccess) (*cdn.CdnClient, error) {
|
||||||
|
auth, err := global.NewCredentialsBuilder().
|
||||||
|
WithAk(access.AccessKeyId).
|
||||||
|
WithSk(access.SecretAccessKey).
|
||||||
|
SafeBuild()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
region, err := cdnRegion.SafeValueOf(access.Region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hcClient, err := cdn.CdnClientBuilder().
|
||||||
|
WithRegion(region).
|
||||||
|
WithCredential(auth).
|
||||||
|
SafeBuild()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := cdn.NewCdnClient(hcClient)
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeHuaweiCloudCDNConfig(src *cdnModel.ConfigsGetBody, dest *cdnModel.UpdateDomainMultiCertificatesRequestBodyContent) *cdnModel.UpdateDomainMultiCertificatesRequestBodyContent {
|
||||||
|
if src == nil {
|
||||||
|
return dest
|
||||||
|
}
|
||||||
|
|
||||||
|
// 华为云 API 中不传的字段表示使用默认值、而非保留原值,因此这里需要把原配置中的参数重新赋值回去
|
||||||
|
// 而且蛋疼的是查询接口返回的数据结构和更新接口传入的参数结构不一致,需要做很多转化
|
||||||
|
// REF: https://support.huaweicloud.com/api-cdn/ShowDomainFullConfig.html
|
||||||
|
// REF: https://support.huaweicloud.com/api-cdn/UpdateDomainMultiCertificates.html
|
||||||
|
|
||||||
|
if *src.OriginProtocol == "follow" {
|
||||||
|
accessOriginWay := int32(1)
|
||||||
|
dest.AccessOriginWay = &accessOriginWay
|
||||||
|
} else if *src.OriginProtocol == "http" {
|
||||||
|
accessOriginWay := int32(2)
|
||||||
|
dest.AccessOriginWay = &accessOriginWay
|
||||||
|
} else if *src.OriginProtocol == "https" {
|
||||||
|
accessOriginWay := int32(3)
|
||||||
|
dest.AccessOriginWay = &accessOriginWay
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.ForceRedirect != nil {
|
||||||
|
dest.ForceRedirectConfig = &cdnModel.ForceRedirect{}
|
||||||
|
|
||||||
|
if src.ForceRedirect.Status == "on" {
|
||||||
|
dest.ForceRedirectConfig.Switch = 1
|
||||||
|
dest.ForceRedirectConfig.RedirectType = src.ForceRedirect.Type
|
||||||
|
} else {
|
||||||
|
dest.ForceRedirectConfig.Switch = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.Https != nil {
|
||||||
|
if *src.Https.Http2Status == "on" {
|
||||||
|
http2 := int32(1)
|
||||||
|
dest.Http2 = &http2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dest
|
||||||
|
}
|
@ -8,11 +8,9 @@ import (
|
|||||||
k8sMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
k8sMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
)
|
|
||||||
|
|
||||||
type KubernetesAccess struct {
|
"certimate/internal/domain"
|
||||||
KubeConfig string `json:"kubeConfig"`
|
)
|
||||||
}
|
|
||||||
|
|
||||||
type K8sSecretDeployer struct {
|
type K8sSecretDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
@ -27,7 +25,7 @@ func NewK8sSecretDeployer(option *DeployerOption) (Deployer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *K8sSecretDeployer) GetID() string {
|
func (d *K8sSecretDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AceessRecord.GetString("name"), d.option.AceessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *K8sSecretDeployer) GetInfo() []string {
|
func (d *K8sSecretDeployer) GetInfo() []string {
|
||||||
@ -35,7 +33,7 @@ func (d *K8sSecretDeployer) GetInfo() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *K8sSecretDeployer) Deploy(ctx context.Context) error {
|
func (d *K8sSecretDeployer) Deploy(ctx context.Context) error {
|
||||||
access := &KubernetesAccess{}
|
access := &domain.KubernetesAccess{}
|
||||||
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -86,7 +84,7 @@ func (d *K8sSecretDeployer) Deploy(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *K8sSecretDeployer) createClient(access *KubernetesAccess) (*kubernetes.Clientset, error) {
|
func (d *K8sSecretDeployer) createClient(access *domain.KubernetesAccess) (*kubernetes.Clientset, error) {
|
||||||
kubeConfig, err := clientcmd.Load([]byte(access.KubeConfig))
|
kubeConfig, err := clientcmd.Load([]byte(access.KubeConfig))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -8,9 +8,9 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
|
||||||
|
|
||||||
type LocalAccess struct{}
|
"certimate/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
type LocalDeployer struct {
|
type LocalDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
@ -25,7 +25,7 @@ func NewLocalDeployer(option *DeployerOption) (Deployer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *LocalDeployer) GetID() string {
|
func (d *LocalDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AceessRecord.GetString("name"), d.option.AceessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *LocalDeployer) GetInfo() []string {
|
func (d *LocalDeployer) GetInfo() []string {
|
||||||
@ -33,7 +33,7 @@ func (d *LocalDeployer) GetInfo() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
||||||
access := &LocalAccess{}
|
access := &domain.LocalAccess{}
|
||||||
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ func NewQiniuCDNDeployer(option *DeployerOption) (*QiniuCDNDeployer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) GetID() string {
|
func (d *QiniuCDNDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AceessRecord.GetString("name"), d.option.AceessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) GetInfo() []string {
|
func (d *QiniuCDNDeployer) GetInfo() []string {
|
||||||
|
@ -10,16 +10,9 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/sftp"
|
"github.com/pkg/sftp"
|
||||||
sshPkg "golang.org/x/crypto/ssh"
|
sshPkg "golang.org/x/crypto/ssh"
|
||||||
)
|
|
||||||
|
|
||||||
type SSHAccess struct {
|
"certimate/internal/domain"
|
||||||
Host string `json:"host"`
|
)
|
||||||
Port string `json:"port"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
KeyPassphrase string `json:"keyPassphrase"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SSHDeployer struct {
|
type SSHDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
@ -34,7 +27,7 @@ func NewSSHDeployer(option *DeployerOption) (Deployer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *SSHDeployer) GetID() string {
|
func (d *SSHDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AceessRecord.GetString("name"), d.option.AceessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SSHDeployer) GetInfo() []string {
|
func (d *SSHDeployer) GetInfo() []string {
|
||||||
@ -42,7 +35,7 @@ func (d *SSHDeployer) GetInfo() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *SSHDeployer) Deploy(ctx context.Context) error {
|
func (d *SSHDeployer) Deploy(ctx context.Context) error {
|
||||||
access := &SSHAccess{}
|
access := &domain.SSHAccess{}
|
||||||
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -130,7 +123,7 @@ func (d *SSHDeployer) upload(client *sshPkg.Client, content, path string) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SSHDeployer) createClient(access *SSHAccess) (*sshPkg.Client, error) {
|
func (d *SSHDeployer) createClient(access *domain.SSHAccess) (*sshPkg.Client, error) {
|
||||||
var authMethod sshPkg.AuthMethod
|
var authMethod sshPkg.AuthMethod
|
||||||
|
|
||||||
if access.Key != "" {
|
if access.Key != "" {
|
||||||
|
@ -41,7 +41,7 @@ func NewTencentCDNDeployer(option *DeployerOption) (Deployer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) GetID() string {
|
func (d *TencentCDNDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AceessRecord.GetString("name"), d.option.AceessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) GetInfo() []string {
|
func (d *TencentCDNDeployer) GetInfo() []string {
|
||||||
|
@ -7,13 +7,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"certimate/internal/domain"
|
||||||
xhttp "certimate/internal/utils/http"
|
xhttp "certimate/internal/utils/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebhookAccess struct {
|
|
||||||
Url string `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebhookDeployer struct {
|
type WebhookDeployer struct {
|
||||||
option *DeployerOption
|
option *DeployerOption
|
||||||
infos []string
|
infos []string
|
||||||
@ -27,7 +24,7 @@ func NewWebhookDeployer(option *DeployerOption) (Deployer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *WebhookDeployer) GetID() string {
|
func (d *WebhookDeployer) GetID() string {
|
||||||
return fmt.Sprintf("%s-%s", d.option.AceessRecord.GetString("name"), d.option.AceessRecord.Id)
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *WebhookDeployer) GetInfo() []string {
|
func (d *WebhookDeployer) GetInfo() []string {
|
||||||
@ -42,7 +39,7 @@ type webhookData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *WebhookDeployer) Deploy(ctx context.Context) error {
|
func (d *WebhookDeployer) Deploy(ctx context.Context) error {
|
||||||
access := &WebhookAccess{}
|
access := &domain.WebhookAccess{}
|
||||||
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
||||||
return fmt.Errorf("failed to parse hook access config: %w", err)
|
return fmt.Errorf("failed to parse hook access config: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -51,4 +51,21 @@ type HttpreqAccess struct {
|
|||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
type LocalAccess struct{}
|
||||||
|
|
||||||
|
type SSHAccess struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Port string `json:"port"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
KeyPassphrase string `json:"keyPassphrase"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebhookAccess struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KubernetesAccess struct {
|
||||||
|
KubeConfig string `json:"kubeConfig"`
|
||||||
}
|
}
|
||||||
|
23
internal/domain/err.go
Normal file
23
internal/domain/err.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
var ErrAuthFailed = NewXError(4999, "auth failed")
|
||||||
|
|
||||||
|
type XError struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXError(code int, msg string) *XError {
|
||||||
|
return &XError{code, msg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *XError) Error() string {
|
||||||
|
return e.Msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *XError) GetCode() int {
|
||||||
|
if e.Code == 0 {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return e.Code
|
||||||
|
}
|
12
internal/domain/notify.go
Normal file
12
internal/domain/notify.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
const (
|
||||||
|
NotifyChannelDingtalk = "dingtalk"
|
||||||
|
NotifyChannelWebhook = "webhook"
|
||||||
|
NotifyChannelTelegram = "telegram"
|
||||||
|
NotifyChannelLark = "lark"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NotifyTestPushReq struct {
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
}
|
31
internal/domain/setting.go
Normal file
31
internal/domain/setting.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Setting struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
Updated time.Time `json:"updated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChannelsConfig map[string]map[string]any
|
||||||
|
|
||||||
|
func (s *Setting) GetChannelContent(channel string) (map[string]any, error) {
|
||||||
|
conf := &ChannelsConfig{}
|
||||||
|
if err := json.Unmarshal([]byte(s.Content), conf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := (*conf)[channel]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("channel %s not found", channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"certimate/internal/domain"
|
||||||
"certimate/internal/utils/app"
|
"certimate/internal/utils/app"
|
||||||
|
|
||||||
notifyPackage "github.com/nikoksr/notify"
|
notifyPackage "github.com/nikoksr/notify"
|
||||||
@ -14,13 +15,6 @@ import (
|
|||||||
"github.com/nikoksr/notify/service/telegram"
|
"github.com/nikoksr/notify/service/telegram"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
notifyChannelDingtalk = "dingtalk"
|
|
||||||
notifyChannelWebhook = "webhook"
|
|
||||||
notifyChannelTelegram = "telegram"
|
|
||||||
notifyChannelLark = "lark"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Send(title, content string) error {
|
func Send(title, content string) error {
|
||||||
// 获取所有的推送渠道
|
// 获取所有的推送渠道
|
||||||
notifiers, err := getNotifiers()
|
notifiers, err := getNotifiers()
|
||||||
@ -39,6 +33,28 @@ func Send(title, content string) error {
|
|||||||
return n.Send(context.Background(), title, content)
|
return n.Send(context.Background(), title, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type sendTestParam struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
Conf map[string]any `json:"conf"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendTest(param *sendTestParam) error {
|
||||||
|
notifier, err := getNotifier(param.Channel, param.Conf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := notifyPackage.New()
|
||||||
|
|
||||||
|
// 添加推送渠道
|
||||||
|
n.UseServices(notifier)
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
return n.Send(context.Background(), param.Title, param.Content)
|
||||||
|
}
|
||||||
|
|
||||||
func getNotifiers() ([]notifyPackage.Notifier, error) {
|
func getNotifiers() ([]notifyPackage.Notifier, error) {
|
||||||
resp, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='notifyChannels'")
|
resp, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='notifyChannels'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -59,27 +75,38 @@ func getNotifiers() ([]notifyPackage.Notifier, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch k {
|
notifier, err := getNotifier(k, v)
|
||||||
case notifyChannelTelegram:
|
if err != nil {
|
||||||
temp := getTelegramNotifier(v)
|
continue
|
||||||
if temp == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
notifiers = append(notifiers, temp)
|
|
||||||
case notifyChannelDingtalk:
|
|
||||||
notifiers = append(notifiers, getDingTalkNotifier(v))
|
|
||||||
case notifyChannelLark:
|
|
||||||
notifiers = append(notifiers, getLarkNotifier(v))
|
|
||||||
case notifyChannelWebhook:
|
|
||||||
notifiers = append(notifiers, getWebhookNotifier(v))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("notifier not found")
|
||||||
|
}
|
||||||
|
|
||||||
func getWebhookNotifier(conf map[string]any) notifyPackage.Notifier {
|
func getWebhookNotifier(conf map[string]any) notifyPackage.Notifier {
|
||||||
rs := http.New()
|
rs := http.New()
|
||||||
|
|
||||||
|
46
internal/notify/service.go
Normal file
46
internal/notify/service.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package notify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"certimate/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
notifyTestTitle = "测试通知"
|
||||||
|
notifyTestBody = "欢迎使用 Certimate ,这是一条测试通知。"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SettingRepository interface {
|
||||||
|
GetByName(ctx context.Context, name string) (*domain.Setting, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotifyService struct {
|
||||||
|
settingRepo SettingRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNotifyService(settingRepo SettingRepository) *NotifyService {
|
||||||
|
return &NotifyService{
|
||||||
|
settingRepo: settingRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NotifyService) Test(ctx context.Context, req *domain.NotifyTestPushReq) error {
|
||||||
|
setting, err := n.settingRepo.GetByName(ctx, "notifyChannels")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get notify channels setting failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conf, err := setting.GetChannelContent(req.Channel)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get notify channel %s config failed: %w", req.Channel, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return SendTest(&sendTestParam{
|
||||||
|
Title: notifyTestTitle,
|
||||||
|
Content: notifyTestBody,
|
||||||
|
Channel: req.Channel,
|
||||||
|
Conf: conf,
|
||||||
|
})
|
||||||
|
}
|
31
internal/repository/setting.go
Normal file
31
internal/repository/setting.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"certimate/internal/domain"
|
||||||
|
"certimate/internal/utils/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SettingRepository struct{}
|
||||||
|
|
||||||
|
func NewSettingRepository() *SettingRepository {
|
||||||
|
return &SettingRepository{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingRepository) GetByName(ctx context.Context, name string) (*domain.Setting, error) {
|
||||||
|
resp, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='"+name+"'")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := &domain.Setting{
|
||||||
|
ID: resp.GetString("id"),
|
||||||
|
Name: resp.GetString("name"),
|
||||||
|
Content: resp.GetString("content"),
|
||||||
|
Created: resp.GetTime("created"),
|
||||||
|
Updated: resp.GetTime("updated"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs, nil
|
||||||
|
}
|
41
internal/rest/notify.go
Normal file
41
internal/rest/notify.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"certimate/internal/domain"
|
||||||
|
"certimate/internal/utils/resp"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NotifyService interface {
|
||||||
|
Test(ctx context.Context, req *domain.NotifyTestPushReq) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type notifyHandler struct {
|
||||||
|
service NotifyService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNotifyHandler(route *echo.Group, service NotifyService) {
|
||||||
|
handler := ¬ifyHandler{
|
||||||
|
service: service,
|
||||||
|
}
|
||||||
|
|
||||||
|
group := route.Group("/notify")
|
||||||
|
|
||||||
|
group.POST("/test", handler.test)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *notifyHandler) test(c echo.Context) error {
|
||||||
|
req := &domain.NotifyTestPushReq{}
|
||||||
|
if err := c.Bind(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := handler.service.Test(c.Request().Context(), req); err != nil {
|
||||||
|
return resp.Err(c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Succ(c, nil)
|
||||||
|
}
|
19
internal/routes/routes.go
Normal file
19
internal/routes/routes.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"certimate/internal/notify"
|
||||||
|
"certimate/internal/repository"
|
||||||
|
"certimate/internal/rest"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v5"
|
||||||
|
"github.com/pocketbase/pocketbase/apis"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Register(e *echo.Echo) {
|
||||||
|
notifyRepo := repository.NewSettingRepository()
|
||||||
|
notifySvc := notify.NewNotifyService(notifyRepo)
|
||||||
|
|
||||||
|
group := e.Group("/api", apis.RequireAdminAuth())
|
||||||
|
|
||||||
|
rest.NewNotifyHandler(group, notifySvc)
|
||||||
|
}
|
39
internal/utils/resp/resp.go
Normal file
39
internal/utils/resp/resp.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package resp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"certimate/internal/domain"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Succ(e echo.Context, data interface{}) error {
|
||||||
|
rs := &Response{
|
||||||
|
Code: 0,
|
||||||
|
Msg: "success",
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
return e.JSON(http.StatusOK, rs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Err(e echo.Context, err error) error {
|
||||||
|
xerr, ok := err.(*domain.XError)
|
||||||
|
code := 100
|
||||||
|
if ok {
|
||||||
|
code = xerr.GetCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := &Response{
|
||||||
|
Code: code,
|
||||||
|
Msg: err.Error(),
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
return e.JSON(http.StatusOK, rs)
|
||||||
|
}
|
3
main.go
3
main.go
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase/plugins/migratecmd"
|
"github.com/pocketbase/pocketbase/plugins/migratecmd"
|
||||||
|
|
||||||
"certimate/internal/domains"
|
"certimate/internal/domains"
|
||||||
|
"certimate/internal/routes"
|
||||||
"certimate/internal/utils/app"
|
"certimate/internal/utils/app"
|
||||||
_ "certimate/migrations"
|
_ "certimate/migrations"
|
||||||
"certimate/ui"
|
"certimate/ui"
|
||||||
@ -34,6 +35,8 @@ func main() {
|
|||||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||||
domains.InitSchedule()
|
domains.InitSchedule()
|
||||||
|
|
||||||
|
routes.Register(e.Router)
|
||||||
|
|
||||||
e.Router.GET(
|
e.Router.GET(
|
||||||
"/*",
|
"/*",
|
||||||
echo.StaticDirectoryHandler(ui.DistDirFS, false),
|
echo.StaticDirectoryHandler(ui.DistDirFS, false),
|
||||||
|
329
ui/dist/assets/index-BYO3zdEX.js
vendored
Normal file
329
ui/dist/assets/index-BYO3zdEX.js
vendored
Normal file
File diff suppressed because one or more lines are too long
329
ui/dist/assets/index-tBXwi-8W.js
vendored
Normal file
329
ui/dist/assets/index-tBXwi-8W.js
vendored
Normal file
File diff suppressed because one or more lines are too long
15
ui/dist/index.html
vendored
Normal file
15
ui/dist/index.html
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Certimate - Your Trusted SSL Automation Partner</title>
|
||||||
|
<script type="module" crossorigin src="/assets/index-BYO3zdEX.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="/assets/index-YqBWA4KK.css">
|
||||||
|
</head>
|
||||||
|
<body class="bg-background">
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
22
ui/src/api/notify.ts
Normal file
22
ui/src/api/notify.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { getPb } from "@/repository/api";
|
||||||
|
|
||||||
|
export const notifyTest = async (channel: string) => {
|
||||||
|
const pb = getPb();
|
||||||
|
|
||||||
|
const resp = await pb.send("/api/notify/test", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
channel,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resp.code != 0) {
|
||||||
|
throw new Error(resp.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
};
|
||||||
|
|
@ -8,7 +8,7 @@ import { ScrollArea } from "@/components/ui/scroll-area";
|
|||||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
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 AccessQiniuForm from "./AccessQiniuForm";
|
import AccessQiniuForm from "./AccessQiniuForm";
|
||||||
import AccessAwsForm from "./AccessAwsForm";
|
import AccessAwsForm from "./AccessAwsForm";
|
||||||
import AccessCloudflareForm from "./AccessCloudflareForm";
|
import AccessCloudflareForm from "./AccessCloudflareForm";
|
||||||
@ -63,7 +63,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
|||||||
break;
|
break;
|
||||||
case "huaweicloud":
|
case "huaweicloud":
|
||||||
form = (
|
form = (
|
||||||
<AccessHuaweicloudForm
|
<AccessHuaweiCloudForm
|
||||||
data={data}
|
data={data}
|
||||||
op={op}
|
op={op}
|
||||||
onAfterReq={() => {
|
onAfterReq={() => {
|
||||||
|
@ -8,17 +8,17 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { PbErrorData } from "@/domain/base";
|
import { PbErrorData } from "@/domain/base";
|
||||||
import { Access, accessFormType, HuaweicloudConfig, getUsageByConfigType } from "@/domain/access";
|
import { Access, accessFormType, HuaweiCloudConfig, getUsageByConfigType } from "@/domain/access";
|
||||||
import { save } from "@/repository/access";
|
import { save } from "@/repository/access";
|
||||||
import { useConfig } from "@/providers/config";
|
import { useConfig } from "@/providers/config";
|
||||||
|
|
||||||
type AccessHuaweicloudFormProps = {
|
type AccessHuaweiCloudFormProps = {
|
||||||
op: "add" | "edit" | "copy";
|
op: "add" | "edit" | "copy";
|
||||||
data?: Access;
|
data?: Access;
|
||||||
onAfterReq: () => void;
|
onAfterReq: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AccessHuaweicloudForm = ({ data, op, onAfterReq }: AccessHuaweicloudFormProps) => {
|
const AccessHuaweiCloudForm = ({ data, op, onAfterReq }: AccessHuaweiCloudFormProps) => {
|
||||||
const { addAccess, updateAccess } = useConfig();
|
const { addAccess, updateAccess } = useConfig();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
@ -42,12 +42,12 @@ const AccessHuaweicloudForm = ({ data, op, onAfterReq }: AccessHuaweicloudFormPr
|
|||||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||||
});
|
});
|
||||||
|
|
||||||
let config: HuaweicloudConfig = {
|
let config: HuaweiCloudConfig = {
|
||||||
region: "cn-north-1",
|
region: "cn-north-1",
|
||||||
accessKeyId: "",
|
accessKeyId: "",
|
||||||
secretAccessKey: "",
|
secretAccessKey: "",
|
||||||
};
|
};
|
||||||
if (data) config = data.config as HuaweicloudConfig;
|
if (data) config = data.config as HuaweiCloudConfig;
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
@ -215,4 +215,4 @@ const AccessHuaweicloudForm = ({ data, op, onAfterReq }: AccessHuaweicloudFormPr
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AccessHuaweicloudForm;
|
export default AccessHuaweiCloudForm;
|
||||||
|
@ -10,6 +10,8 @@ import { getErrMessage } from "@/lib/error";
|
|||||||
import { NotifyChannelDingTalk, NotifyChannels } from "@/domain/settings";
|
import { NotifyChannelDingTalk, NotifyChannels } from "@/domain/settings";
|
||||||
import { useNotify } from "@/providers/notify";
|
import { useNotify } from "@/providers/notify";
|
||||||
import { update } from "@/repository/settings";
|
import { update } from "@/repository/settings";
|
||||||
|
import Show from "@/components/Show";
|
||||||
|
import { notifyTest } from "@/api/notify";
|
||||||
|
|
||||||
type DingTalkSetting = {
|
type DingTalkSetting = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -21,6 +23,8 @@ const DingTalk = () => {
|
|||||||
const { config, setChannels } = useNotify();
|
const { config, setChannels } = useNotify();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [changed, setChanged] = useState<boolean>(false);
|
||||||
|
|
||||||
const [dingtalk, setDingtalk] = useState<DingTalkSetting>({
|
const [dingtalk, setDingtalk] = useState<DingTalkSetting>({
|
||||||
id: config.id ?? "",
|
id: config.id ?? "",
|
||||||
name: "notifyChannels",
|
name: "notifyChannels",
|
||||||
@ -31,23 +35,30 @@ const DingTalk = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
const [originDingtalk, setOriginDingtalk] = useState<DingTalkSetting>({
|
||||||
const getDetailDingTalk = () => {
|
id: config.id ?? "",
|
||||||
const df: NotifyChannelDingTalk = {
|
name: "notifyChannels",
|
||||||
accessToken: "",
|
data: {
|
||||||
secret: "",
|
accessToken: "",
|
||||||
enabled: false,
|
secret: "",
|
||||||
};
|
enabled: false,
|
||||||
if (!config.content) {
|
},
|
||||||
return df;
|
});
|
||||||
}
|
|
||||||
const chanels = config.content as NotifyChannels;
|
|
||||||
if (!chanels.dingtalk) {
|
|
||||||
return df;
|
|
||||||
}
|
|
||||||
|
|
||||||
return chanels.dingtalk as NotifyChannelDingTalk;
|
useEffect(() => {
|
||||||
};
|
setChanged(false);
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = getDetailDingTalk();
|
||||||
|
setOriginDingtalk({
|
||||||
|
id: config.id ?? "",
|
||||||
|
name: "dingtalk",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
const data = getDetailDingTalk();
|
const data = getDetailDingTalk();
|
||||||
setDingtalk({
|
setDingtalk({
|
||||||
id: config.id ?? "",
|
id: config.id ?? "",
|
||||||
@ -58,6 +69,31 @@ const DingTalk = () => {
|
|||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const getDetailDingTalk = () => {
|
||||||
|
const df: NotifyChannelDingTalk = {
|
||||||
|
accessToken: "",
|
||||||
|
secret: "",
|
||||||
|
enabled: false,
|
||||||
|
};
|
||||||
|
if (!config.content) {
|
||||||
|
return df;
|
||||||
|
}
|
||||||
|
const chanels = config.content as NotifyChannels;
|
||||||
|
if (!chanels.dingtalk) {
|
||||||
|
return df;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chanels.dingtalk as NotifyChannelDingTalk;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkChanged = (data: NotifyChannelDingTalk) => {
|
||||||
|
if (data.accessToken !== originDingtalk.data.accessToken || data.secret !== originDingtalk.data.secret) {
|
||||||
|
setChanged(true);
|
||||||
|
} else {
|
||||||
|
setChanged(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSaveClick = async () => {
|
const handleSaveClick = async () => {
|
||||||
try {
|
try {
|
||||||
const resp = await update({
|
const resp = await update({
|
||||||
@ -87,19 +123,74 @@ const DingTalk = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePushTestClick = async () => {
|
||||||
|
try {
|
||||||
|
await notifyTest("dingtalk");
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
...dingtalk,
|
||||||
|
data: {
|
||||||
|
...dingtalk.data,
|
||||||
|
enabled: !dingtalk.data.enabled,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
setDingtalk(newData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await update({
|
||||||
|
...config,
|
||||||
|
name: "notifyChannels",
|
||||||
|
content: {
|
||||||
|
...config.content,
|
||||||
|
dingtalk: {
|
||||||
|
...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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
placeholder="AccessToken"
|
placeholder="AccessToken"
|
||||||
value={dingtalk.data.accessToken}
|
value={dingtalk.data.accessToken}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setDingtalk({
|
const newData = {
|
||||||
...dingtalk,
|
...dingtalk,
|
||||||
data: {
|
data: {
|
||||||
...dingtalk.data,
|
...dingtalk.data,
|
||||||
accessToken: e.target.value,
|
accessToken: e.target.value,
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
checkChanged(newData.data);
|
||||||
|
setDingtalk(newData);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
@ -107,43 +198,47 @@ const DingTalk = () => {
|
|||||||
className="mt-2"
|
className="mt-2"
|
||||||
value={dingtalk.data.secret}
|
value={dingtalk.data.secret}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setDingtalk({
|
const newData = {
|
||||||
...dingtalk,
|
...dingtalk,
|
||||||
data: {
|
data: {
|
||||||
...dingtalk.data,
|
...dingtalk.data,
|
||||||
secret: e.target.value,
|
secret: e.target.value,
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
checkChanged(newData.data);
|
||||||
|
setDingtalk(newData);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center space-x-1 mt-2">
|
<div className="flex items-center space-x-1 mt-2">
|
||||||
<Switch
|
<Switch id="airplane-mode" checked={dingtalk.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||||
id="airplane-mode"
|
|
||||||
checked={dingtalk.data.enabled}
|
|
||||||
onCheckedChange={() => {
|
|
||||||
setDingtalk({
|
|
||||||
...dingtalk,
|
|
||||||
data: {
|
|
||||||
...dingtalk.data,
|
|
||||||
enabled: !dingtalk.data.enabled,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-end mt-2">
|
||||||
<Button
|
<Show when={changed}>
|
||||||
onClick={() => {
|
<Button
|
||||||
handleSaveClick();
|
onClick={() => {
|
||||||
}}
|
handleSaveClick();
|
||||||
>
|
}}
|
||||||
{t("common.save")}
|
>
|
||||||
</Button>
|
{t("common.save")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={!changed && dingtalk.id != ""}>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
handlePushTestClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("settings.notification.config.push.test.message")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DingTalk;
|
export default DingTalk;
|
||||||
|
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import { Input } from "../ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Switch } from "../ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Label } from "../ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { useNotify } from "@/providers/notify";
|
import { useNotify } from "@/providers/notify";
|
||||||
import { NotifyChannelLark, NotifyChannels } from "@/domain/settings";
|
import { NotifyChannelLark, NotifyChannels } from "@/domain/settings";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { update } from "@/repository/settings";
|
import { update } from "@/repository/settings";
|
||||||
import { getErrMessage } from "@/lib/error";
|
import { getErrMessage } from "@/lib/error";
|
||||||
import { useToast } from "../ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { notifyTest } from "@/api/notify";
|
||||||
|
import Show from "@/components/Show";
|
||||||
|
|
||||||
type LarkSetting = {
|
type LarkSetting = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -20,6 +22,8 @@ const Lark = () => {
|
|||||||
const { config, setChannels } = useNotify();
|
const { config, setChannels } = useNotify();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [changed, setChanged] = useState<boolean>(false);
|
||||||
|
|
||||||
const [lark, setLark] = useState<LarkSetting>({
|
const [lark, setLark] = useState<LarkSetting>({
|
||||||
id: config.id ?? "",
|
id: config.id ?? "",
|
||||||
name: "notifyChannels",
|
name: "notifyChannels",
|
||||||
@ -29,22 +33,29 @@ const Lark = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
const [originLark, setOriginLark] = useState<LarkSetting>({
|
||||||
const getDetailLark = () => {
|
id: config.id ?? "",
|
||||||
const df: NotifyChannelLark = {
|
name: "notifyChannels",
|
||||||
webhookUrl: "",
|
data: {
|
||||||
enabled: false,
|
webhookUrl: "",
|
||||||
};
|
enabled: false,
|
||||||
if (!config.content) {
|
},
|
||||||
return df;
|
});
|
||||||
}
|
|
||||||
const chanels = config.content as NotifyChannels;
|
|
||||||
if (!chanels.lark) {
|
|
||||||
return df;
|
|
||||||
}
|
|
||||||
|
|
||||||
return chanels.lark as NotifyChannelLark;
|
useEffect(() => {
|
||||||
};
|
setChanged(false);
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = getDetailLark();
|
||||||
|
setOriginLark({
|
||||||
|
id: config.id ?? "",
|
||||||
|
name: "lark",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
const data = getDetailLark();
|
const data = getDetailLark();
|
||||||
setLark({
|
setLark({
|
||||||
id: config.id ?? "",
|
id: config.id ?? "",
|
||||||
@ -55,6 +66,30 @@ const Lark = () => {
|
|||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const checkChanged = (data: NotifyChannelLark) => {
|
||||||
|
if (data.webhookUrl !== originLark.data.webhookUrl) {
|
||||||
|
setChanged(true);
|
||||||
|
} else {
|
||||||
|
setChanged(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDetailLark = () => {
|
||||||
|
const df: NotifyChannelLark = {
|
||||||
|
webhookUrl: "",
|
||||||
|
enabled: false,
|
||||||
|
};
|
||||||
|
if (!config.content) {
|
||||||
|
return df;
|
||||||
|
}
|
||||||
|
const chanels = config.content as NotifyChannels;
|
||||||
|
if (!chanels.lark) {
|
||||||
|
return df;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chanels.lark as NotifyChannelLark;
|
||||||
|
};
|
||||||
|
|
||||||
const handleSaveClick = async () => {
|
const handleSaveClick = async () => {
|
||||||
try {
|
try {
|
||||||
const resp = await update({
|
const resp = await update({
|
||||||
@ -78,9 +113,60 @@ const Lark = () => {
|
|||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t("common.save.failed.message"),
|
title: t("common.save.failed.message"),
|
||||||
description: `${t(
|
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
|
||||||
"settings.notification.config.failed.message"
|
variant: "destructive",
|
||||||
)}: ${msg}`,
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePushTestClick = async () => {
|
||||||
|
try {
|
||||||
|
await notifyTest("lark");
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
...lark,
|
||||||
|
data: {
|
||||||
|
...lark.data,
|
||||||
|
enabled: !lark.data.enabled,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
setLark(newData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await update({
|
||||||
|
...config,
|
||||||
|
name: "notifyChannels",
|
||||||
|
content: {
|
||||||
|
...config.content,
|
||||||
|
lark: {
|
||||||
|
...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",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -92,45 +178,48 @@ const Lark = () => {
|
|||||||
placeholder="Webhook Url"
|
placeholder="Webhook Url"
|
||||||
value={lark.data.webhookUrl}
|
value={lark.data.webhookUrl}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setLark({
|
const newData = {
|
||||||
...lark,
|
...lark,
|
||||||
data: {
|
data: {
|
||||||
...lark.data,
|
...lark.data,
|
||||||
webhookUrl: e.target.value,
|
webhookUrl: e.target.value,
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
|
checkChanged(newData.data);
|
||||||
|
setLark(newData);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center space-x-1 mt-2">
|
<div className="flex items-center space-x-1 mt-2">
|
||||||
<Switch
|
<Switch id="airplane-mode" checked={lark.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||||
id="airplane-mode"
|
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||||
checked={lark.data.enabled}
|
|
||||||
onCheckedChange={() => {
|
|
||||||
setLark({
|
|
||||||
...lark,
|
|
||||||
data: {
|
|
||||||
...lark.data,
|
|
||||||
enabled: !lark.data.enabled,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="airplane-mode">
|
|
||||||
{t("settings.notification.config.enable")}
|
|
||||||
</Label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-end mt-2">
|
||||||
<Button
|
<Show when={changed}>
|
||||||
onClick={() => {
|
<Button
|
||||||
handleSaveClick();
|
onClick={() => {
|
||||||
}}
|
handleSaveClick();
|
||||||
>
|
}}
|
||||||
{t("common.save")}
|
>
|
||||||
</Button>
|
{t("common.save")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={!changed && lark.id != ""}>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
handlePushTestClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("settings.notification.config.push.test.message")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Lark;
|
export default Lark;
|
||||||
|
|
||||||
|
@ -10,6 +10,8 @@ import { getErrMessage } from "@/lib/error";
|
|||||||
import { NotifyChannels, NotifyChannelTelegram } from "@/domain/settings";
|
import { NotifyChannels, NotifyChannelTelegram } from "@/domain/settings";
|
||||||
import { update } from "@/repository/settings";
|
import { update } from "@/repository/settings";
|
||||||
import { useNotify } from "@/providers/notify";
|
import { useNotify } from "@/providers/notify";
|
||||||
|
import { notifyTest } from "@/api/notify";
|
||||||
|
import Show from "@/components/Show";
|
||||||
|
|
||||||
type TelegramSetting = {
|
type TelegramSetting = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -21,6 +23,8 @@ const Telegram = () => {
|
|||||||
const { config, setChannels } = useNotify();
|
const { config, setChannels } = useNotify();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [changed, setChanged] = useState<boolean>(false);
|
||||||
|
|
||||||
const [telegram, setTelegram] = useState<TelegramSetting>({
|
const [telegram, setTelegram] = useState<TelegramSetting>({
|
||||||
id: config.id ?? "",
|
id: config.id ?? "",
|
||||||
name: "notifyChannels",
|
name: "notifyChannels",
|
||||||
@ -31,23 +35,30 @@ const Telegram = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
const [originTelegram, setOriginTelegram] = useState<TelegramSetting>({
|
||||||
const getDetailTelegram = () => {
|
id: config.id ?? "",
|
||||||
const df: NotifyChannelTelegram = {
|
name: "notifyChannels",
|
||||||
apiToken: "",
|
data: {
|
||||||
chatId: "",
|
apiToken: "",
|
||||||
enabled: false,
|
chatId: "",
|
||||||
};
|
enabled: false,
|
||||||
if (!config.content) {
|
},
|
||||||
return df;
|
});
|
||||||
}
|
|
||||||
const chanels = config.content as NotifyChannels;
|
|
||||||
if (!chanels.telegram) {
|
|
||||||
return df;
|
|
||||||
}
|
|
||||||
|
|
||||||
return chanels.telegram as NotifyChannelTelegram;
|
useEffect(() => {
|
||||||
};
|
setChanged(false);
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = getDetailTelegram();
|
||||||
|
setOriginTelegram({
|
||||||
|
id: config.id ?? "",
|
||||||
|
name: "common.provider.telegram",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
const data = getDetailTelegram();
|
const data = getDetailTelegram();
|
||||||
setTelegram({
|
setTelegram({
|
||||||
id: config.id ?? "",
|
id: config.id ?? "",
|
||||||
@ -58,6 +69,31 @@ const Telegram = () => {
|
|||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const checkChanged = (data: NotifyChannelTelegram) => {
|
||||||
|
if (data.apiToken !== originTelegram.data.apiToken || data.chatId !== originTelegram.data.chatId) {
|
||||||
|
setChanged(true);
|
||||||
|
} else {
|
||||||
|
setChanged(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDetailTelegram = () => {
|
||||||
|
const df: NotifyChannelTelegram = {
|
||||||
|
apiToken: "",
|
||||||
|
chatId: "",
|
||||||
|
enabled: false,
|
||||||
|
};
|
||||||
|
if (!config.content) {
|
||||||
|
return df;
|
||||||
|
}
|
||||||
|
const chanels = config.content as NotifyChannels;
|
||||||
|
if (!chanels.telegram) {
|
||||||
|
return df;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chanels.telegram as NotifyChannelTelegram;
|
||||||
|
};
|
||||||
|
|
||||||
const handleSaveClick = async () => {
|
const handleSaveClick = async () => {
|
||||||
try {
|
try {
|
||||||
const resp = await update({
|
const resp = await update({
|
||||||
@ -87,19 +123,75 @@ const Telegram = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePushTestClick = async () => {
|
||||||
|
try {
|
||||||
|
await notifyTest("telegram");
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
...telegram,
|
||||||
|
data: {
|
||||||
|
...telegram.data,
|
||||||
|
enabled: !telegram.data.enabled,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
setTelegram(newData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await update({
|
||||||
|
...config,
|
||||||
|
name: "notifyChannels",
|
||||||
|
content: {
|
||||||
|
...config.content,
|
||||||
|
telegram: {
|
||||||
|
...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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
placeholder="ApiToken"
|
placeholder="ApiToken"
|
||||||
value={telegram.data.apiToken}
|
value={telegram.data.apiToken}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setTelegram({
|
const newData = {
|
||||||
...telegram,
|
...telegram,
|
||||||
data: {
|
data: {
|
||||||
...telegram.data,
|
...telegram.data,
|
||||||
apiToken: e.target.value,
|
apiToken: e.target.value,
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
|
checkChanged(newData.data);
|
||||||
|
setTelegram(newData);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -107,44 +199,49 @@ const Telegram = () => {
|
|||||||
placeholder="ChatId"
|
placeholder="ChatId"
|
||||||
value={telegram.data.chatId}
|
value={telegram.data.chatId}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setTelegram({
|
const newData = {
|
||||||
...telegram,
|
...telegram,
|
||||||
data: {
|
data: {
|
||||||
...telegram.data,
|
...telegram.data,
|
||||||
chatId: e.target.value,
|
chatId: e.target.value,
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
|
checkChanged(newData.data);
|
||||||
|
setTelegram(newData);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex items-center space-x-1 mt-2">
|
<div className="flex items-center space-x-1 mt-2">
|
||||||
<Switch
|
<Switch id="airplane-mode" checked={telegram.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||||
id="airplane-mode"
|
|
||||||
checked={telegram.data.enabled}
|
|
||||||
onCheckedChange={() => {
|
|
||||||
setTelegram({
|
|
||||||
...telegram,
|
|
||||||
data: {
|
|
||||||
...telegram.data,
|
|
||||||
enabled: !telegram.data.enabled,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-end mt-2">
|
||||||
<Button
|
<Show when={changed}>
|
||||||
onClick={() => {
|
<Button
|
||||||
handleSaveClick();
|
onClick={() => {
|
||||||
}}
|
handleSaveClick();
|
||||||
>
|
}}
|
||||||
{t("common.save")}
|
>
|
||||||
</Button>
|
{t("common.save")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={!changed && telegram.id != ""}>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
handlePushTestClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("settings.notification.config.push.test.message")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Telegram;
|
export default Telegram;
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ import { isValidURL } from "@/lib/url";
|
|||||||
import { NotifyChannels, NotifyChannelWebhook } from "@/domain/settings";
|
import { NotifyChannels, NotifyChannelWebhook } from "@/domain/settings";
|
||||||
import { update } from "@/repository/settings";
|
import { update } from "@/repository/settings";
|
||||||
import { useNotify } from "@/providers/notify";
|
import { useNotify } from "@/providers/notify";
|
||||||
|
import { notifyTest } from "@/api/notify";
|
||||||
|
import Show from "@/components/Show";
|
||||||
|
|
||||||
type WebhookSetting = {
|
type WebhookSetting = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -21,6 +23,7 @@ type WebhookSetting = {
|
|||||||
const Webhook = () => {
|
const Webhook = () => {
|
||||||
const { config, setChannels } = useNotify();
|
const { config, setChannels } = useNotify();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [changed, setChanged] = useState<boolean>(false);
|
||||||
|
|
||||||
const [webhook, setWebhook] = useState<WebhookSetting>({
|
const [webhook, setWebhook] = useState<WebhookSetting>({
|
||||||
id: config.id ?? "",
|
id: config.id ?? "",
|
||||||
@ -31,22 +34,29 @@ const Webhook = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
const [originWebhook, setOriginWebhook] = useState<WebhookSetting>({
|
||||||
const getDetailWebhook = () => {
|
id: config.id ?? "",
|
||||||
const df: NotifyChannelWebhook = {
|
name: "notifyChannels",
|
||||||
url: "",
|
data: {
|
||||||
enabled: false,
|
url: "",
|
||||||
};
|
enabled: false,
|
||||||
if (!config.content) {
|
},
|
||||||
return df;
|
});
|
||||||
}
|
|
||||||
const chanels = config.content as NotifyChannels;
|
|
||||||
if (!chanels.webhook) {
|
|
||||||
return df;
|
|
||||||
}
|
|
||||||
|
|
||||||
return chanels.webhook as NotifyChannelWebhook;
|
useEffect(() => {
|
||||||
};
|
setChanged(false);
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = getDetailWebhook();
|
||||||
|
setOriginWebhook({
|
||||||
|
id: config.id ?? "",
|
||||||
|
name: "webhook",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
const data = getDetailWebhook();
|
const data = getDetailWebhook();
|
||||||
setWebhook({
|
setWebhook({
|
||||||
id: config.id ?? "",
|
id: config.id ?? "",
|
||||||
@ -57,6 +67,30 @@ const Webhook = () => {
|
|||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const checkChanged = (data: NotifyChannelWebhook) => {
|
||||||
|
if (data.url !== originWebhook.data.url) {
|
||||||
|
setChanged(true);
|
||||||
|
} else {
|
||||||
|
setChanged(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDetailWebhook = () => {
|
||||||
|
const df: NotifyChannelWebhook = {
|
||||||
|
url: "",
|
||||||
|
enabled: false,
|
||||||
|
};
|
||||||
|
if (!config.content) {
|
||||||
|
return df;
|
||||||
|
}
|
||||||
|
const chanels = config.content as NotifyChannels;
|
||||||
|
if (!chanels.webhook) {
|
||||||
|
return df;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chanels.webhook as NotifyChannelWebhook;
|
||||||
|
};
|
||||||
|
|
||||||
const handleSaveClick = async () => {
|
const handleSaveClick = async () => {
|
||||||
try {
|
try {
|
||||||
webhook.data.url = webhook.data.url.trim();
|
webhook.data.url = webhook.data.url.trim();
|
||||||
@ -96,50 +130,108 @@ const Webhook = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePushTestClick = async () => {
|
||||||
|
try {
|
||||||
|
await notifyTest("webhook");
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
...webhook,
|
||||||
|
data: {
|
||||||
|
...webhook.data,
|
||||||
|
enabled: !webhook.data.enabled,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
setWebhook(newData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await update({
|
||||||
|
...config,
|
||||||
|
name: "notifyChannels",
|
||||||
|
content: {
|
||||||
|
...config.content,
|
||||||
|
webhook: {
|
||||||
|
...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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Url"
|
placeholder="Url"
|
||||||
value={webhook.data.url}
|
value={webhook.data.url}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setWebhook({
|
const newData = {
|
||||||
...webhook,
|
...webhook,
|
||||||
data: {
|
data: {
|
||||||
...webhook.data,
|
...webhook.data,
|
||||||
url: e.target.value,
|
url: e.target.value,
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
|
checkChanged(newData.data);
|
||||||
|
setWebhook(newData);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex items-center space-x-1 mt-2">
|
<div className="flex items-center space-x-1 mt-2">
|
||||||
<Switch
|
<Switch id="airplane-mode" checked={webhook.data.enabled} onCheckedChange={handleSwitchChange} />
|
||||||
id="airplane-mode"
|
|
||||||
checked={webhook.data.enabled}
|
|
||||||
onCheckedChange={() => {
|
|
||||||
setWebhook({
|
|
||||||
...webhook,
|
|
||||||
data: {
|
|
||||||
...webhook.data,
|
|
||||||
enabled: !webhook.data.enabled,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-end mt-2">
|
||||||
<Button
|
<Show when={changed}>
|
||||||
onClick={() => {
|
<Button
|
||||||
handleSaveClick();
|
onClick={() => {
|
||||||
}}
|
handleSaveClick();
|
||||||
>
|
}}
|
||||||
{t("common.save")}
|
>
|
||||||
</Button>
|
{t("common.save")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={!changed && webhook.id != ""}>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
handlePushTestClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("settings.notification.config.push.test.message")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Webhook;
|
export default Webhook;
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export type Access = {
|
|||||||
config:
|
config:
|
||||||
| AliyunConfig
|
| AliyunConfig
|
||||||
| TencentConfig
|
| TencentConfig
|
||||||
| HuaweicloudConfig
|
| HuaweiCloudConfig
|
||||||
| QiniuConfig
|
| QiniuConfig
|
||||||
| AwsConfig
|
| AwsConfig
|
||||||
| CloudflareConfig
|
| CloudflareConfig
|
||||||
@ -79,7 +79,7 @@ export type TencentConfig = {
|
|||||||
secretKey: string;
|
secretKey: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HuaweicloudConfig = {
|
export type HuaweiCloudConfig = {
|
||||||
region: string;
|
region: string;
|
||||||
accessKeyId: string;
|
accessKeyId: string;
|
||||||
secretAccessKey: string;
|
secretAccessKey: string;
|
||||||
|
@ -71,6 +71,7 @@ export const targetTypeMap: Map<string, [string, string]> = new Map([
|
|||||||
["aliyun-cdn", ["common.provider.aliyun.cdn", "/imgs/providers/aliyun.svg"]],
|
["aliyun-cdn", ["common.provider.aliyun.cdn", "/imgs/providers/aliyun.svg"]],
|
||||||
["aliyun-dcdn", ["common.provider.aliyun.dcdn", "/imgs/providers/aliyun.svg"]],
|
["aliyun-dcdn", ["common.provider.aliyun.dcdn", "/imgs/providers/aliyun.svg"]],
|
||||||
["tencent-cdn", ["common.provider.tencent.cdn", "/imgs/providers/tencent.svg"]],
|
["tencent-cdn", ["common.provider.tencent.cdn", "/imgs/providers/tencent.svg"]],
|
||||||
|
["huaweicloud-cdn", ["common.provider.huaweicloud.cdn", "/imgs/providers/huaweicloud.svg"]],
|
||||||
["qiniu-cdn", ["common.provider.qiniu.cdn", "/imgs/providers/qiniu.svg"]],
|
["qiniu-cdn", ["common.provider.qiniu.cdn", "/imgs/providers/qiniu.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"]],
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
"common.provider.tencent": "Tencent",
|
"common.provider.tencent": "Tencent",
|
||||||
"common.provider.tencent.cdn": "Tencent - CDN",
|
"common.provider.tencent.cdn": "Tencent - CDN",
|
||||||
"common.provider.huaweicloud": "Huawei Cloud",
|
"common.provider.huaweicloud": "Huawei Cloud",
|
||||||
|
"common.provider.huaweicloud.cdn": "Huawei Cloud - CDN",
|
||||||
"common.provider.qiniu": "Qiniu",
|
"common.provider.qiniu": "Qiniu",
|
||||||
"common.provider.qiniu.cdn": "Qiniu - CDN",
|
"common.provider.qiniu.cdn": "Qiniu - CDN",
|
||||||
"common.provider.aws": "AWS",
|
"common.provider.aws": "AWS",
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"domain.deploy.started.tips": "Deployment initiated, please check the deployment log later.",
|
"domain.deploy.started.tips": "Deployment initiated, please check the deployment log later.",
|
||||||
"domain.deploy.failed.message": "Execution Failed",
|
"domain.deploy.failed.message": "Execution Failed",
|
||||||
"domain.deploy.failed.tips": "Execution failed, please check the details in <1>Deployment History</1>.",
|
"domain.deploy.failed.tips": "Execution failed, please check the details in <1>Deployment History</1>.",
|
||||||
"domain.deploy_forced": "Force Deployment",
|
"domain.deploy_forced": "Force Deploy",
|
||||||
|
|
||||||
"domain.props.expiry": "Validity Period",
|
"domain.props.expiry": "Validity Period",
|
||||||
"domain.props.expiry.date1": "Valid for {{date}} days",
|
"domain.props.expiry.date1": "Valid for {{date}} days",
|
||||||
|
@ -30,6 +30,9 @@
|
|||||||
"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.config.push.test.message.failed.message": "Send test notification failed",
|
||||||
|
"settings.notification.config.push.test.message.success.message": "Send test notification successfully",
|
||||||
"settings.notification.dingtalk.secret.placeholder": "Signature for signed addition",
|
"settings.notification.dingtalk.secret.placeholder": "Signature for signed addition",
|
||||||
"settings.notification.url.errmsg.invalid": "Invalid Url format",
|
"settings.notification.url.errmsg.invalid": "Invalid Url format",
|
||||||
|
|
||||||
@ -39,3 +42,4 @@
|
|||||||
"settings.ca.eab_hmac_key.errmsg.empty": "Please enter EAB_HMAC_KEY.",
|
"settings.ca.eab_hmac_key.errmsg.empty": "Please enter EAB_HMAC_KEY.",
|
||||||
"settings.ca.eab_kid_hmac_key.errmsg.empty": "Please enter EAB_KID and EAB_HMAC_KEY"
|
"settings.ca.eab_kid_hmac_key.errmsg.empty": "Please enter EAB_KID and EAB_HMAC_KEY"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
"common.provider.aliyun.cdn": "阿里云 - CDN",
|
"common.provider.aliyun.cdn": "阿里云 - CDN",
|
||||||
"common.provider.aliyun.dcdn": "阿里云 - DCDN",
|
"common.provider.aliyun.dcdn": "阿里云 - DCDN",
|
||||||
"common.provider.huaweicloud": "华为云",
|
"common.provider.huaweicloud": "华为云",
|
||||||
|
"common.provider.huaweicloud.cdn": "华为云 - CDN",
|
||||||
"common.provider.qiniu": "七牛云",
|
"common.provider.qiniu": "七牛云",
|
||||||
"common.provider.qiniu.cdn": "七牛云 - CDN",
|
"common.provider.qiniu.cdn": "七牛云 - CDN",
|
||||||
"common.provider.aws": "AWS",
|
"common.provider.aws": "AWS",
|
||||||
|
@ -30,6 +30,9 @@
|
|||||||
"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.config.push.test.message.failed.message": "推送测试消息失败",
|
||||||
|
"settings.notification.config.push.test.message.success.message": "推送测试消息成功",
|
||||||
"settings.notification.dingtalk.secret.placeholder": "加签的签名",
|
"settings.notification.dingtalk.secret.placeholder": "加签的签名",
|
||||||
"settings.notification.url.errmsg.invalid": "URL 格式不正确",
|
"settings.notification.url.errmsg.invalid": "URL 格式不正确",
|
||||||
|
|
||||||
@ -39,3 +42,4 @@
|
|||||||
"settings.ca.eab_hmac_key.errmsg.empty": "请输入EAB_HMAC_KEY",
|
"settings.ca.eab_hmac_key.errmsg.empty": "请输入EAB_HMAC_KEY",
|
||||||
"settings.ca.eab_kid_hmac_key.errmsg.empty": "请输入EAB_KID和EAB_HMAC_KEY"
|
"settings.ca.eab_kid_hmac_key.errmsg.empty": "请输入EAB_KID和EAB_HMAC_KEY"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user