From 83674e4b351c8ec8e4d66e8b8c54c481c3daecf2 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sat, 9 Nov 2024 09:47:14 +0800 Subject: [PATCH 01/10] refactor: ensure compile-time check for `Uploader` implementations --- internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go | 2 ++ internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go | 2 ++ internal/pkg/core/uploader/providers/dogecloud/dogecloud.go | 2 ++ .../core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go | 2 ++ .../core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go | 2 ++ .../pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go | 2 ++ .../uploader/providers/tencentcloud-ssl/tencentcloud_ssl.go | 2 ++ internal/pkg/core/uploader/uploader.go | 2 +- 8 files changed, 15 insertions(+), 1 deletion(-) diff --git a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go index c524f115..dc0a9f8e 100644 --- a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go +++ b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go @@ -26,6 +26,8 @@ type AliyunCASUploader struct { sdkClient *aliyunCas.Client } +var _ uploader.Uploader = (*AliyunCASUploader)(nil) + func New(config *AliyunCASUploaderConfig) (*AliyunCASUploader, error) { client, err := createSdkClient( config.AccessKeyId, diff --git a/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go b/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go index 9a396c89..aad8e219 100644 --- a/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go +++ b/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go @@ -28,6 +28,8 @@ type AliyunSLBUploader struct { sdkClient *aliyunSlb.Client } +var _ uploader.Uploader = (*AliyunSLBUploader)(nil) + func New(config *AliyunSLBUploaderConfig) (*AliyunSLBUploader, error) { client, err := createSdkClient( config.AccessKeyId, diff --git a/internal/pkg/core/uploader/providers/dogecloud/dogecloud.go b/internal/pkg/core/uploader/providers/dogecloud/dogecloud.go index 2083e314..b73c5cea 100644 --- a/internal/pkg/core/uploader/providers/dogecloud/dogecloud.go +++ b/internal/pkg/core/uploader/providers/dogecloud/dogecloud.go @@ -21,6 +21,8 @@ type DogeCloudUploader struct { sdkClient *doge.Client } +var _ uploader.Uploader = (*DogeCloudUploader)(nil) + func New(config *DogeCloudUploaderConfig) (*DogeCloudUploader, error) { client, err := createSdkClient( config.AccessKey, diff --git a/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go index 198494b3..e313164f 100644 --- a/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go @@ -32,6 +32,8 @@ type HuaweiCloudELBUploader struct { sdkClient *hcElb.ElbClient } +var _ uploader.Uploader = (*HuaweiCloudELBUploader)(nil) + func New(config *HuaweiCloudELBUploaderConfig) (*HuaweiCloudELBUploader, error) { client, err := createSdkClient( config.AccessKeyId, diff --git a/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go b/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go index b8510900..4809faeb 100644 --- a/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go +++ b/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go @@ -27,6 +27,8 @@ type HuaweiCloudSCMUploader struct { sdkClient *hcScm.ScmClient } +var _ uploader.Uploader = (*HuaweiCloudSCMUploader)(nil) + func New(config *HuaweiCloudSCMUploaderConfig) (*HuaweiCloudSCMUploader, error) { client, err := createSdkClient( config.AccessKeyId, diff --git a/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go b/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go index 010c55d1..148c2998 100644 --- a/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go +++ b/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go @@ -23,6 +23,8 @@ type QiniuSSLCertUploader struct { sdkClient *qiniuEx.Client } +var _ uploader.Uploader = (*QiniuSSLCertUploader)(nil) + func New(config *QiniuSSLCertUploaderConfig) (*QiniuSSLCertUploader, error) { client, err := createSdkClient( config.AccessKey, diff --git a/internal/pkg/core/uploader/providers/tencentcloud-ssl/tencentcloud_ssl.go b/internal/pkg/core/uploader/providers/tencentcloud-ssl/tencentcloud_ssl.go index c6e5dcb5..97a029c5 100644 --- a/internal/pkg/core/uploader/providers/tencentcloud-ssl/tencentcloud_ssl.go +++ b/internal/pkg/core/uploader/providers/tencentcloud-ssl/tencentcloud_ssl.go @@ -21,6 +21,8 @@ type TencentCloudSSLUploader struct { sdkClient *tcSsl.Client } +var _ uploader.Uploader = (*TencentCloudSSLUploader)(nil) + func New(config *TencentCloudSSLUploaderConfig) (*TencentCloudSSLUploader, error) { client, err := createSdkClient( config.SecretId, diff --git a/internal/pkg/core/uploader/uploader.go b/internal/pkg/core/uploader/uploader.go index 87a4d633..1fc34d82 100644 --- a/internal/pkg/core/uploader/uploader.go +++ b/internal/pkg/core/uploader/uploader.go @@ -2,7 +2,7 @@ import "context" -// 表示定义证书上传者的抽象类型接口。 +// 表示定义证书上传器的抽象类型接口。 // 云服务商通常会提供 SSL 证书管理服务,可供用户集中管理证书。 // 注意与 `Deployer` 区分,“上传”通常为“部署”的前置操作。 type Uploader interface { From 551b06b4e83f6969ed7a05bae41fa888a3aebf99 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sat, 9 Nov 2024 20:06:22 +0800 Subject: [PATCH 02/10] feat: notifier --- internal/pkg/core/notifier/notifier.go | 23 +++++++ .../pkg/core/notifier/providers/bark/bark.go | 48 +++++++++++++ .../notifier/providers/dingtalk/dingtalk.go | 45 ++++++++++++ .../core/notifier/providers/email/email.go | 69 +++++++++++++++++++ .../pkg/core/notifier/providers/lark/lark.go | 41 +++++++++++ .../notifier/providers/telegram/telegram.go | 47 +++++++++++++ .../notifier/providers/webhook/webhook.go | 43 ++++++++++++ .../providers/aliyun-cas/aliyun_cas.go | 5 ++ .../providers/aliyun-slb/aliyun_slb.go | 5 ++ .../uploader/providers/dogecloud/dogecloud.go | 5 ++ .../huaweicloud-elb/huaweicloud_elb.go | 4 ++ .../huaweicloud-scm/huaweicloud_scm.go | 5 ++ .../providers/qiniu-sslcert/qiniu_sslcert.go | 5 ++ .../tencentcloud-ssl/tencentcloud_ssl.go | 5 ++ 14 files changed, 350 insertions(+) create mode 100644 internal/pkg/core/notifier/notifier.go create mode 100644 internal/pkg/core/notifier/providers/bark/bark.go create mode 100644 internal/pkg/core/notifier/providers/dingtalk/dingtalk.go create mode 100644 internal/pkg/core/notifier/providers/email/email.go create mode 100644 internal/pkg/core/notifier/providers/lark/lark.go create mode 100644 internal/pkg/core/notifier/providers/telegram/telegram.go create mode 100644 internal/pkg/core/notifier/providers/webhook/webhook.go diff --git a/internal/pkg/core/notifier/notifier.go b/internal/pkg/core/notifier/notifier.go new file mode 100644 index 00000000..d8819395 --- /dev/null +++ b/internal/pkg/core/notifier/notifier.go @@ -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"` +} diff --git a/internal/pkg/core/notifier/providers/bark/bark.go b/internal/pkg/core/notifier/providers/bark/bark.go new file mode 100644 index 00000000..aefd6ab6 --- /dev/null +++ b/internal/pkg/core/notifier/providers/bark/bark.go @@ -0,0 +1,48 @@ +package email + +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 +} diff --git a/internal/pkg/core/notifier/providers/dingtalk/dingtalk.go b/internal/pkg/core/notifier/providers/dingtalk/dingtalk.go new file mode 100644 index 00000000..2d1e1220 --- /dev/null +++ b/internal/pkg/core/notifier/providers/dingtalk/dingtalk.go @@ -0,0 +1,45 @@ +package email + +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 +} diff --git a/internal/pkg/core/notifier/providers/email/email.go b/internal/pkg/core/notifier/providers/email/email.go new file mode 100644 index 00000000..5f767659 --- /dev/null +++ b/internal/pkg/core/notifier/providers/email/email.go @@ -0,0 +1,69 @@ +package email + +import ( + "context" + "errors" + "fmt" + "net/smtp" + "os" + + "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 yak *mailyak.MailYak + if n.config.SmtpTLS { + os.Setenv("GODEBUG", "tlsrsakex=1") // Fix for TLS handshake error + yak, err = mailyak.NewWithTLS(fmt.Sprintf("%s:%d", n.config.SmtpHost, n.config.SmtpPort), smtpAuth, nil) + if err != nil { + return nil, err + } + } else { + yak = mailyak.New(fmt.Sprintf("%s:%d", n.config.SmtpHost, n.config.SmtpPort), 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 +} diff --git a/internal/pkg/core/notifier/providers/lark/lark.go b/internal/pkg/core/notifier/providers/lark/lark.go new file mode 100644 index 00000000..1afd9660 --- /dev/null +++ b/internal/pkg/core/notifier/providers/lark/lark.go @@ -0,0 +1,41 @@ +package email + +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 +} diff --git a/internal/pkg/core/notifier/providers/telegram/telegram.go b/internal/pkg/core/notifier/providers/telegram/telegram.go new file mode 100644 index 00000000..e214a4c3 --- /dev/null +++ b/internal/pkg/core/notifier/providers/telegram/telegram.go @@ -0,0 +1,47 @@ +package email + +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 +} diff --git a/internal/pkg/core/notifier/providers/webhook/webhook.go b/internal/pkg/core/notifier/providers/webhook/webhook.go new file mode 100644 index 00000000..1a3cdac8 --- /dev/null +++ b/internal/pkg/core/notifier/providers/webhook/webhook.go @@ -0,0 +1,43 @@ +package email + +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 +} diff --git a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go index dc0a9f8e..463d10bd 100644 --- a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go +++ b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go @@ -2,6 +2,7 @@ import ( "context" + "errors" "fmt" "strings" "time" @@ -29,6 +30,10 @@ type AliyunCASUploader struct { var _ uploader.Uploader = (*AliyunCASUploader)(nil) func New(config *AliyunCASUploaderConfig) (*AliyunCASUploader, error) { + if config == nil { + return nil, errors.New("config is nil") + } + client, err := createSdkClient( config.AccessKeyId, config.AccessKeySecret, diff --git a/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go b/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go index aad8e219..aebf674c 100644 --- a/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go +++ b/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go @@ -4,6 +4,7 @@ import ( "context" "crypto/sha256" "encoding/hex" + "errors" "fmt" "strings" "time" @@ -31,6 +32,10 @@ type AliyunSLBUploader struct { var _ uploader.Uploader = (*AliyunSLBUploader)(nil) func New(config *AliyunSLBUploaderConfig) (*AliyunSLBUploader, error) { + if config == nil { + return nil, errors.New("config is nil") + } + client, err := createSdkClient( config.AccessKeyId, config.AccessKeySecret, diff --git a/internal/pkg/core/uploader/providers/dogecloud/dogecloud.go b/internal/pkg/core/uploader/providers/dogecloud/dogecloud.go index 7b11d462..1daec4bb 100644 --- a/internal/pkg/core/uploader/providers/dogecloud/dogecloud.go +++ b/internal/pkg/core/uploader/providers/dogecloud/dogecloud.go @@ -2,6 +2,7 @@ import ( "context" + "errors" "fmt" "time" @@ -24,6 +25,10 @@ type DogeCloudUploader struct { 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, diff --git a/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go index e313164f..5b6ab376 100644 --- a/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go @@ -35,6 +35,10 @@ type HuaweiCloudELBUploader struct { var _ uploader.Uploader = (*HuaweiCloudELBUploader)(nil) func New(config *HuaweiCloudELBUploaderConfig) (*HuaweiCloudELBUploader, error) { + if config == nil { + return nil, errors.New("config is nil") + } + client, err := createSdkClient( config.AccessKeyId, config.SecretAccessKey, diff --git a/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go b/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go index 4809faeb..45450d9e 100644 --- a/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go +++ b/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go @@ -2,6 +2,7 @@ import ( "context" + "errors" "fmt" "time" @@ -30,6 +31,10 @@ type HuaweiCloudSCMUploader struct { var _ uploader.Uploader = (*HuaweiCloudSCMUploader)(nil) func New(config *HuaweiCloudSCMUploaderConfig) (*HuaweiCloudSCMUploader, error) { + if config == nil { + return nil, errors.New("config is nil") + } + client, err := createSdkClient( config.AccessKeyId, config.SecretAccessKey, diff --git a/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go b/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go index 148c2998..afd36316 100644 --- a/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go +++ b/internal/pkg/core/uploader/providers/qiniu-sslcert/qiniu_sslcert.go @@ -2,6 +2,7 @@ import ( "context" + "errors" "fmt" "time" @@ -26,6 +27,10 @@ type QiniuSSLCertUploader struct { var _ uploader.Uploader = (*QiniuSSLCertUploader)(nil) func New(config *QiniuSSLCertUploaderConfig) (*QiniuSSLCertUploader, error) { + if config == nil { + return nil, errors.New("config is nil") + } + client, err := createSdkClient( config.AccessKey, config.SecretKey, diff --git a/internal/pkg/core/uploader/providers/tencentcloud-ssl/tencentcloud_ssl.go b/internal/pkg/core/uploader/providers/tencentcloud-ssl/tencentcloud_ssl.go index 97a029c5..f0755f3e 100644 --- a/internal/pkg/core/uploader/providers/tencentcloud-ssl/tencentcloud_ssl.go +++ b/internal/pkg/core/uploader/providers/tencentcloud-ssl/tencentcloud_ssl.go @@ -2,6 +2,7 @@ import ( "context" + "errors" xerrors "github.com/pkg/errors" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" @@ -24,6 +25,10 @@ type TencentCloudSSLUploader struct { var _ uploader.Uploader = (*TencentCloudSSLUploader)(nil) func New(config *TencentCloudSSLUploaderConfig) (*TencentCloudSSLUploader, error) { + if config == nil { + return nil, errors.New("config is nil") + } + client, err := createSdkClient( config.SecretId, config.SecretKey, From 94579d65c4f0eb4deb6e2c365c64109dbb1b2aa3 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sat, 9 Nov 2024 20:29:13 +0800 Subject: [PATCH 03/10] refactor: clean code --- internal/notify/notify.go | 16 ------ internal/notify/utils.go | 17 ++++++ .../providers/serverchan/serverchan.go | 55 +++++++++++++++++++ 3 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 internal/notify/utils.go create mode 100644 internal/pkg/core/notifier/providers/serverchan/serverchan.go diff --git a/internal/notify/notify.go b/internal/notify/notify.go index f414b6d4..4189f62b 100644 --- a/internal/notify/notify.go +++ b/internal/notify/notify.go @@ -193,19 +193,3 @@ func getMailNotifier(conf map[string]any) (notifyPackage.Notifier, error) { 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) -} diff --git a/internal/notify/utils.go b/internal/notify/utils.go new file mode 100644 index 00000000..0c04ed15 --- /dev/null +++ b/internal/notify/utils.go @@ -0,0 +1,17 @@ +package notify + +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) +} diff --git a/internal/pkg/core/notifier/providers/serverchan/serverchan.go b/internal/pkg/core/notifier/providers/serverchan/serverchan.go new file mode 100644 index 00000000..4d87826f --- /dev/null +++ b/internal/pkg/core/notifier/providers/serverchan/serverchan.go @@ -0,0 +1,55 @@ +package email + +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 +} From 150b666d4b07e4d3b9f4a6434533368e8a252792 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sat, 9 Nov 2024 20:46:49 +0800 Subject: [PATCH 04/10] refactor: maps utils --- internal/domain/domains.go | 44 ++------- internal/notify/notify.go | 38 +++----- internal/notify/utils.go | 26 ++--- internal/pkg/utils/maps/maps.go | 166 ++++++++++++++++++++++++++++++++ 4 files changed, 204 insertions(+), 70 deletions(-) create mode 100644 internal/pkg/utils/maps/maps.go diff --git a/internal/domain/domains.go b/internal/domain/domains.go index b679625f..2bc07074 100644 --- a/internal/domain/domains.go +++ b/internal/domain/domains.go @@ -3,6 +3,8 @@ package domain import ( "encoding/json" "strings" + + "github.com/usual2970/certimate/internal/pkg/utils/maps" ) type ApplyConfig struct { @@ -29,7 +31,7 @@ type DeployConfig struct { // 出参: // - 配置项的值。如果配置项不存在或者类型不是字符串,则返回空字符串。 func (dc *DeployConfig) GetConfigAsString(key string) string { - return dc.GetConfigOrDefaultAsString(key, "") + return maps.GetValueAsString(dc.Config, key) } // 以字符串形式获取配置项。 @@ -41,17 +43,7 @@ func (dc *DeployConfig) GetConfigAsString(key string) string { // 出参: // - 配置项的值。如果配置项不存在或者类型不是字符串,则返回默认值。 func (dc *DeployConfig) GetConfigOrDefaultAsString(key string, defaultValue string) string { - if dc.Config == nil { - return defaultValue - } - - if value, ok := dc.Config[key]; ok { - if result, ok := value.(string); ok { - return result - } - } - - return defaultValue + return maps.GetValueOrDefaultAsString(dc.Config, key, defaultValue) } // 以 32 位整数形式获取配置项。 @@ -62,7 +54,7 @@ func (dc *DeployConfig) GetConfigOrDefaultAsString(key string, defaultValue stri // 出参: // - 配置项的值。如果配置项不存在或者类型不是 32 位整数,则返回 0。 func (dc *DeployConfig) GetConfigAsInt32(key string) int32 { - return dc.GetConfigOrDefaultAsInt32(key, 0) + return maps.GetValueAsInt32(dc.Config, key) } // 以 32 位整数形式获取配置项。 @@ -74,17 +66,7 @@ func (dc *DeployConfig) GetConfigAsInt32(key string) int32 { // 出参: // - 配置项的值。如果配置项不存在或者类型不是 32 位整数,则返回默认值。 func (dc *DeployConfig) GetConfigOrDefaultAsInt32(key string, defaultValue int32) int32 { - if dc.Config == nil { - return defaultValue - } - - if value, ok := dc.Config[key]; ok { - if result, ok := value.(int32); ok { - return result - } - } - - return defaultValue + return maps.GetValueOrDefaultAsInt32(dc.Config, key, defaultValue) } // 以布尔形式获取配置项。 @@ -95,7 +77,7 @@ func (dc *DeployConfig) GetConfigOrDefaultAsInt32(key string, defaultValue int32 // 出参: // - 配置项的值。如果配置项不存在或者类型不是布尔,则返回 false。 func (dc *DeployConfig) GetConfigAsBool(key string) bool { - return dc.GetConfigOrDefaultAsBool(key, false) + return maps.GetValueAsBool(dc.Config, key) } // 以布尔形式获取配置项。 @@ -107,17 +89,7 @@ func (dc *DeployConfig) GetConfigAsBool(key string) bool { // 出参: // - 配置项的值。如果配置项不存在或者类型不是布尔,则返回默认值。 func (dc *DeployConfig) GetConfigOrDefaultAsBool(key string, defaultValue bool) bool { - if dc.Config == nil { - return defaultValue - } - - if value, ok := dc.Config[key]; ok { - if result, ok := value.(bool); ok { - return result - } - } - - return defaultValue + return maps.GetValueOrDefaultAsBool(dc.Config, key, defaultValue) } // 以变量字典形式获取配置项。 diff --git a/internal/notify/notify.go b/internal/notify/notify.go index 4189f62b..9e09c32b 100644 --- a/internal/notify/notify.go +++ b/internal/notify/notify.go @@ -3,7 +3,6 @@ package notify import ( "context" "fmt" - "strconv" stdhttp "net/http" @@ -74,7 +73,7 @@ func getNotifiers() ([]notifyPackage.Notifier, error) { for k, v := range rs { - if !getBool(v, "enabled") { + if !getConfigAsBool(v, "enabled") { continue } @@ -119,25 +118,18 @@ func getNotifier(channel string, conf map[string]any) (notifyPackage.Notifier, e func getWebhookNotifier(conf map[string]any) notifyPackage.Notifier { rs := http.New() - rs.AddReceiversURLs(getString(conf, "url")) + rs.AddReceiversURLs(getConfigAsString(conf, "url")) return rs } func getTelegramNotifier(conf map[string]any) notifyPackage.Notifier { - rs, err := telegram.New(getString(conf, "apiToken")) + rs, err := telegram.New(getConfigAsString(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) + rs.AddReceivers(getConfigAsInt64(conf, "chatId")) return rs } @@ -145,7 +137,7 @@ func getServerChanNotifier(conf map[string]any) notifyPackage.Notifier { rs := http.New() rs.AddReceivers(&http.Webhook{ - URL: getString(conf, "url"), + URL: getConfigAsString(conf, "url"), Header: stdhttp.Header{}, ContentType: "application/json", Method: stdhttp.MethodPost, @@ -161,8 +153,8 @@ func getServerChanNotifier(conf map[string]any) notifyPackage.Notifier { } func getBarkNotifier(conf map[string]any) notifyPackage.Notifier { - deviceKey := getString(conf, "deviceKey") - serverURL := getString(conf, "serverUrl") + deviceKey := getConfigAsString(conf, "deviceKey") + serverURL := getConfigAsString(conf, "serverUrl") if serverURL == "" { return bark.New(deviceKey) } @@ -171,21 +163,21 @@ func getBarkNotifier(conf map[string]any) notifyPackage.Notifier { func getDingTalkNotifier(conf map[string]any) notifyPackage.Notifier { return dingding.New(&dingding.Config{ - Token: getString(conf, "accessToken"), - Secret: getString(conf, "secret"), + Token: getConfigAsString(conf, "accessToken"), + Secret: getConfigAsString(conf, "secret"), }) } func getLarkNotifier(conf map[string]any) notifyPackage.Notifier { - return lark.NewWebhookService(getString(conf, "webhookUrl")) + return lark.NewWebhookService(getConfigAsString(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"), + rs, err := NewMail(getConfigAsString(conf, "senderAddress"), + getConfigAsString(conf, "receiverAddresses"), + getConfigAsString(conf, "smtpHostAddr"), + getConfigAsString(conf, "smtpHostPort"), + getConfigAsString(conf, "password"), ) if err != nil { return nil, err diff --git a/internal/notify/utils.go b/internal/notify/utils.go index 0c04ed15..5d7e9ac9 100644 --- a/internal/notify/utils.go +++ b/internal/notify/utils.go @@ -1,17 +1,21 @@ package notify -func getString(conf map[string]any, key string) string { - if _, ok := conf[key]; !ok { - return "" - } +import ( + "github.com/usual2970/certimate/internal/pkg/utils/maps" +) - return conf[key].(string) +func getConfigAsString(conf map[string]any, key string) string { + return maps.GetValueAsString(conf, key) } -func getBool(conf map[string]any, key string) bool { - if _, ok := conf[key]; !ok { - return false - } - - return conf[key].(bool) +func getConfigAsInt32(conf map[string]any, key string) int32 { + return maps.GetValueAsInt32(conf, key) +} + +func getConfigAsInt64(conf map[string]any, key string) int64 { + return maps.GetValueAsInt64(conf, key) +} + +func getConfigAsBool(conf map[string]any, key string) bool { + return maps.GetValueAsBool(conf, key) } diff --git a/internal/pkg/utils/maps/maps.go b/internal/pkg/utils/maps/maps.go new file mode 100644 index 00000000..6f9746e8 --- /dev/null +++ b/internal/pkg/utils/maps/maps.go @@ -0,0 +1,166 @@ +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 s, ok := value.(string); ok { + if result, err := strconv.ParseInt(s, 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 s, ok := value.(string); ok { + if result, err := strconv.ParseInt(s, 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 str == "true" || str == "True" || str == "1" { + return true + } else if str == "false" || str == "False" || str == "0" { + return false + } + } + } + + return defaultValue +} From 5d9333442621b74f57e534a8a6e291cd26017756 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sun, 10 Nov 2024 18:03:20 +0800 Subject: [PATCH 05/10] refactor: re-implement logic of notify --- go.mod | 2 +- internal/domain/notify.go | 6 +- internal/notify/expire.go | 45 +++-- internal/notify/factory.go | 66 +++++++ internal/notify/mail.go | 56 ------ internal/notify/notify.go | 168 +++--------------- internal/notify/service.go | 13 +- internal/notify/utils.go | 21 --- .../core/notifier/providers/email/email.go | 34 +++- internal/pkg/utils/maps/maps.go | 24 +-- 10 files changed, 166 insertions(+), 269 deletions(-) create mode 100644 internal/notify/factory.go delete mode 100644 internal/notify/mail.go delete mode 100644 internal/notify/utils.go diff --git a/go.mod b/go.mod index 7b565e12..9f111413 100644 --- a/go.mod +++ b/go.mod @@ -113,7 +113,7 @@ require ( github.com/cloudflare/cloudflare-go v0.104.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/disintegration/imaging v1.6.2 // indirect - github.com/domodwyer/mailyak/v3 v3.6.2 // indirect + github.com/domodwyer/mailyak/v3 v3.6.2 github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/color v1.17.0 // indirect github.com/gabriel-vasile/mimetype v1.4.6 // indirect diff --git a/internal/domain/notify.go b/internal/domain/notify.go index 440bbef7..6c164f39 100644 --- a/internal/domain/notify.go +++ b/internal/domain/notify.go @@ -1,12 +1,12 @@ package domain const ( - NotifyChannelDingtalk = "dingtalk" + NotifyChannelEmail = "email" NotifyChannelWebhook = "webhook" - NotifyChannelTelegram = "telegram" + NotifyChannelDingtalk = "dingtalk" NotifyChannelLark = "lark" + NotifyChannelTelegram = "telegram" NotifyChannelServerChan = "serverchan" - NotifyChannelMail = "mail" NotifyChannelBark = "bark" ) diff --git a/internal/notify/expire.go b/internal/notify/expire.go index d4942272..5dc424ce 100644 --- a/internal/notify/expire.go +++ b/internal/notify/expire.go @@ -12,19 +12,13 @@ import ( "github.com/usual2970/certimate/internal/utils/xtime" ) -type msg struct { - subject string - message string -} - const ( - defaultExpireSubject = "您有{COUNT}张证书即将过期" - defaultExpireMsg = "有{COUNT}张证书即将过期,域名分别为{DOMAINS},请保持关注!" + defaultExpireSubject = "您有 {COUNT} 张证书即将过期" + defaultExpireMessage = "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!" ) func PushExpireMsg() { // 查询即将过期的证书 - records, err := app.GetApp().Dao().FindRecordsByFilter("domains", "expiredAt<{:time}&&certUrl!=''", "-created", 500, 0, dbx.Params{"time": xtime.GetTimeAfter(24 * time.Hour * 15)}) if err != nil { @@ -34,12 +28,12 @@ func PushExpireMsg() { // 组装消息 msg := buildMsg(records) - if msg == nil { return } - if err := Send(msg.subject, msg.message); err != nil { + // 发送通知 + if err := SendToAllChannels(msg.Subject, msg.Message); err != nil { app.GetApp().Logger().Error("send expire msg", "error", err) } } @@ -53,22 +47,27 @@ type notifyTemplate struct { Content string `json:"content"` } -func buildMsg(records []*models.Record) *msg { +type notifyMessage struct { + Subject string + Message string +} + +func buildMsg(records []*models.Record) *notifyMessage { if len(records) == 0 { return nil } // 查询模板信息 templateRecord, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='templates'") - title := defaultExpireSubject - content := defaultExpireMsg + subject := defaultExpireSubject + message := defaultExpireMessage if err == nil { var templates *notifyTemplates templateRecord.UnmarshalJSONField("content", templates) if templates != nil && len(templates.NotifyTemplates) > 0 { - title = templates.NotifyTemplates[0].Title - content = templates.NotifyTemplates[0].Content + subject = templates.NotifyTemplates[0].Title + message = templates.NotifyTemplates[0].Content } } @@ -81,17 +80,17 @@ func buildMsg(records []*models.Record) *msg { } countStr := strconv.Itoa(count) - domainStr := strings.Join(domains, ",") + domainStr := strings.Join(domains, ";") - title = strings.ReplaceAll(title, "{COUNT}", countStr) - title = strings.ReplaceAll(title, "{DOMAINS}", domainStr) + subject = strings.ReplaceAll(subject, "{COUNT}", countStr) + subject = strings.ReplaceAll(subject, "{DOMAINS}", domainStr) - content = strings.ReplaceAll(content, "{COUNT}", countStr) - content = strings.ReplaceAll(content, "{DOMAINS}", domainStr) + message = strings.ReplaceAll(message, "{COUNT}", countStr) + message = strings.ReplaceAll(message, "{DOMAINS}", domainStr) // 返回消息 - return &msg{ - subject: title, - message: content, + return ¬ifyMessage{ + Subject: subject, + Message: message, } } diff --git a/internal/notify/factory.go b/internal/notify/factory.go new file mode 100644 index 00000000..ccdd5389 --- /dev/null +++ b/internal/notify/factory.go @@ -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") +} diff --git a/internal/notify/mail.go b/internal/notify/mail.go deleted file mode 100644 index 060ffcd6..00000000 --- a/internal/notify/mail.go +++ /dev/null @@ -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) -} diff --git a/internal/notify/notify.go b/internal/notify/notify.go index 9e09c32b..6b218a18 100644 --- a/internal/notify/notify.go +++ b/internal/notify/notify.go @@ -4,22 +4,15 @@ import ( "context" "fmt" - stdhttp "net/http" + "golang.org/x/sync/errgroup" - "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/pkg/core/notifier" + "github.com/usual2970/certimate/internal/pkg/utils/maps" "github.com/usual2970/certimate/internal/utils/app" - - notifyPackage "github.com/nikoksr/notify" - "github.com/nikoksr/notify/service/bark" - "github.com/nikoksr/notify/service/dingding" - "github.com/nikoksr/notify/service/http" - "github.com/nikoksr/notify/service/lark" - "github.com/nikoksr/notify/service/telegram" ) -func Send(title, content string) error { - // 获取所有的推送渠道 - notifiers, err := getNotifiers() +func SendToAllChannels(subject, message string) error { + notifiers, err := getEnabledNotifiers() if err != nil { return err } @@ -27,161 +20,56 @@ func Send(title, content string) error { return nil } - n := notifyPackage.New() - // 添加推送渠道 - n.UseServices(notifiers...) + var eg errgroup.Group + for _, n := range notifiers { + if n == nil { + continue + } - // 发送消息 - return n.Send(context.Background(), title, content) + eg.Go(func() error { + _, err := n.Notify(context.Background(), subject, message) + return err + }) + } + + err = eg.Wait() + return err } -type sendTestParam struct { - Title string `json:"title"` - Content string `json:"content"` - Channel string `json:"channel"` - Conf map[string]any `json:"conf"` -} - -func SendTest(param *sendTestParam) error { - notifier, err := getNotifier(param.Channel, param.Conf) +func SendToChannel(subject, message string, channel string, channelConfig map[string]any) error { + notifier, err := createNotifier(channel, channelConfig) if err != nil { return err } - n := notifyPackage.New() - - // 添加推送渠道 - n.UseServices(notifier) - - // 发送消息 - return n.Send(context.Background(), param.Title, param.Content) + _, err = notifier.Notify(context.Background(), subject, message) + return err } -func getNotifiers() ([]notifyPackage.Notifier, error) { - resp, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='notifyChannels'") +func getEnabledNotifiers() ([]notifier.Notifier, error) { + settings, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='notifyChannels'") if err != nil { return nil, fmt.Errorf("find notifyChannels error: %w", err) } - notifiers := make([]notifyPackage.Notifier, 0) - rs := make(map[string]map[string]any) - - if err := resp.UnmarshalJSONField("content", &rs); err != nil { + if err := settings.UnmarshalJSONField("content", &rs); err != nil { return nil, fmt.Errorf("unmarshal notifyChannels error: %w", err) } + notifiers := make([]notifier.Notifier, 0) for k, v := range rs { - - if !getConfigAsBool(v, "enabled") { + if !maps.GetValueAsBool(v, "enabled") { continue } - notifier, err := getNotifier(k, v) + notifier, err := createNotifier(k, v) if err != nil { continue } notifiers = append(notifiers, notifier) - } return notifiers, nil } - -func getNotifier(channel string, conf map[string]any) (notifyPackage.Notifier, error) { - switch channel { - case domain.NotifyChannelTelegram: - temp := getTelegramNotifier(conf) - if temp == nil { - return nil, fmt.Errorf("telegram notifier config error") - } - - return temp, nil - case domain.NotifyChannelDingtalk: - return getDingTalkNotifier(conf), nil - case domain.NotifyChannelLark: - return getLarkNotifier(conf), nil - case domain.NotifyChannelWebhook: - return getWebhookNotifier(conf), nil - case domain.NotifyChannelServerChan: - return getServerChanNotifier(conf), nil - case domain.NotifyChannelMail: - return getMailNotifier(conf) - case domain.NotifyChannelBark: - return getBarkNotifier(conf), nil - } - - return nil, fmt.Errorf("notifier not found") -} - -func getWebhookNotifier(conf map[string]any) notifyPackage.Notifier { - rs := http.New() - - rs.AddReceiversURLs(getConfigAsString(conf, "url")) - - return rs -} - -func getTelegramNotifier(conf map[string]any) notifyPackage.Notifier { - rs, err := telegram.New(getConfigAsString(conf, "apiToken")) - if err != nil { - return nil - } - - rs.AddReceivers(getConfigAsInt64(conf, "chatId")) - return rs -} - -func getServerChanNotifier(conf map[string]any) notifyPackage.Notifier { - rs := http.New() - - rs.AddReceivers(&http.Webhook{ - URL: getConfigAsString(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 := getConfigAsString(conf, "deviceKey") - serverURL := getConfigAsString(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: getConfigAsString(conf, "accessToken"), - Secret: getConfigAsString(conf, "secret"), - }) -} - -func getLarkNotifier(conf map[string]any) notifyPackage.Notifier { - return lark.NewWebhookService(getConfigAsString(conf, "webhookUrl")) -} - -func getMailNotifier(conf map[string]any) (notifyPackage.Notifier, error) { - rs, err := NewMail(getConfigAsString(conf, "senderAddress"), - getConfigAsString(conf, "receiverAddresses"), - getConfigAsString(conf, "smtpHostAddr"), - getConfigAsString(conf, "smtpHostPort"), - getConfigAsString(conf, "password"), - ) - if err != nil { - return nil, err - } - - return rs, nil -} diff --git a/internal/notify/service.go b/internal/notify/service.go index 22b77160..76162ff1 100644 --- a/internal/notify/service.go +++ b/internal/notify/service.go @@ -29,18 +29,13 @@ func NewNotifyService(settingRepo SettingRepository) *NotifyService { func (n *NotifyService) Test(ctx context.Context, req *domain.NotifyTestPushReq) error { setting, err := n.settingRepo.GetByName(ctx, "notifyChannels") if err != nil { - return fmt.Errorf("get notify channels setting failed: %w", err) + return fmt.Errorf("failed to get notify channels settings: %w", err) } - conf, err := setting.GetChannelContent(req.Channel) + channelConfig, err := setting.GetChannelContent(req.Channel) if err != nil { - return fmt.Errorf("get notify channel %s config failed: %w", req.Channel, err) + return fmt.Errorf("failed to get notify channel \"%s\" config: %w", req.Channel, err) } - return SendTest(&sendTestParam{ - Title: notifyTestTitle, - Content: notifyTestBody, - Channel: req.Channel, - Conf: conf, - }) + return SendToChannel(notifyTestTitle, notifyTestBody, req.Channel, channelConfig) } diff --git a/internal/notify/utils.go b/internal/notify/utils.go deleted file mode 100644 index 5d7e9ac9..00000000 --- a/internal/notify/utils.go +++ /dev/null @@ -1,21 +0,0 @@ -package notify - -import ( - "github.com/usual2970/certimate/internal/pkg/utils/maps" -) - -func getConfigAsString(conf map[string]any, key string) string { - return maps.GetValueAsString(conf, key) -} - -func getConfigAsInt32(conf map[string]any, key string) int32 { - return maps.GetValueAsInt32(conf, key) -} - -func getConfigAsInt64(conf map[string]any, key string) int64 { - return maps.GetValueAsInt64(conf, key) -} - -func getConfigAsBool(conf map[string]any, key string) bool { - return maps.GetValueAsBool(conf, key) -} diff --git a/internal/pkg/core/notifier/providers/email/email.go b/internal/pkg/core/notifier/providers/email/email.go index 5f767659..b79618cc 100644 --- a/internal/pkg/core/notifier/providers/email/email.go +++ b/internal/pkg/core/notifier/providers/email/email.go @@ -2,10 +2,10 @@ import ( "context" + "crypto/tls" "errors" "fmt" "net/smtp" - "os" "github.com/domodwyer/mailyak/v3" @@ -44,15 +44,25 @@ func (n *EmailNotifier) Notify(ctx context.Context, subject string, message stri 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 { - os.Setenv("GODEBUG", "tlsrsakex=1") // Fix for TLS handshake error - yak, err = mailyak.NewWithTLS(fmt.Sprintf("%s:%d", n.config.SmtpHost, n.config.SmtpPort), smtpAuth, nil) + yak, err = mailyak.NewWithTLS(smtpAddr, smtpAuth, newTlsConfig()) if err != nil { return nil, err } } else { - yak = mailyak.New(fmt.Sprintf("%s:%d", n.config.SmtpHost, n.config.SmtpPort), smtpAuth) + yak = mailyak.New(smtpAddr, smtpAuth) } yak.From(n.config.SenderAddress) @@ -67,3 +77,19 @@ func (n *EmailNotifier) Notify(ctx context.Context, subject string, message stri 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, + } +} diff --git a/internal/pkg/utils/maps/maps.go b/internal/pkg/utils/maps/maps.go index 6f9746e8..b475f09d 100644 --- a/internal/pkg/utils/maps/maps.go +++ b/internal/pkg/utils/maps/maps.go @@ -9,7 +9,7 @@ import "strconv" // - key: 键。 // // 出参: -// - 字典中键对应的值。如果指定键不存在或者类型不是字符串,则返回空字符串。 +// - 字典中键对应的值。如果指定键不存在或者值的类型不是字符串,则返回空字符串。 func GetValueAsString(dict map[string]any, key string) string { return GetValueOrDefaultAsString(dict, key, "") } @@ -22,7 +22,7 @@ func GetValueAsString(dict map[string]any, key string) string { // - defaultValue: 默认值。 // // 出参: -// - 字典中键对应的值。如果指定键不存在或者类型不是字符串,则返回默认值。 +// - 字典中键对应的值。如果指定键不存在或者值的类型不是字符串,则返回默认值。 func GetValueOrDefaultAsString(dict map[string]any, key string, defaultValue string) string { if dict == nil { return defaultValue @@ -44,7 +44,7 @@ func GetValueOrDefaultAsString(dict map[string]any, key string, defaultValue str // - key: 键。 // // 出参: -// - 字典中键对应的值。如果指定键不存在或者类型不是 32 位整数,则返回 0。 +// - 字典中键对应的值。如果指定键不存在或者值的类型不是 32 位整数,则返回 0。 func GetValueAsInt32(dict map[string]any, key string) int32 { return GetValueOrDefaultAsInt32(dict, key, 0) } @@ -57,7 +57,7 @@ func GetValueAsInt32(dict map[string]any, key string) int32 { // - defaultValue: 默认值。 // // 出参: -// - 字典中键对应的值。如果指定键不存在或者类型不是 32 位整数,则返回默认值。 +// - 字典中键对应的值。如果指定键不存在或者值的类型不是 32 位整数,则返回默认值。 func GetValueOrDefaultAsInt32(dict map[string]any, key string, defaultValue int32) int32 { if dict == nil { return defaultValue @@ -69,8 +69,8 @@ func GetValueOrDefaultAsInt32(dict map[string]any, key string, defaultValue int3 } // 兼容字符串类型的值 - if s, ok := value.(string); ok { - if result, err := strconv.ParseInt(s, 10, 32); err == nil { + if str, ok := value.(string); ok { + if result, err := strconv.ParseInt(str, 10, 32); err == nil { return int32(result) } } @@ -86,7 +86,7 @@ func GetValueOrDefaultAsInt32(dict map[string]any, key string, defaultValue int3 // - key: 键。 // // 出参: -// - 字典中键对应的值。如果指定键不存在或者类型不是 64 位整数,则返回 0。 +// - 字典中键对应的值。如果指定键不存在或者值的类型不是 64 位整数,则返回 0。 func GetValueAsInt64(dict map[string]any, key string) int64 { return GetValueOrDefaultAsInt64(dict, key, 0) } @@ -99,7 +99,7 @@ func GetValueAsInt64(dict map[string]any, key string) int64 { // - defaultValue: 默认值。 // // 出参: -// - 字典中键对应的值。如果指定键不存在或者类型不是 64 位整数,则返回默认值。 +// - 字典中键对应的值。如果指定键不存在或者值的类型不是 64 位整数,则返回默认值。 func GetValueOrDefaultAsInt64(dict map[string]any, key string, defaultValue int64) int64 { if dict == nil { return defaultValue @@ -111,8 +111,8 @@ func GetValueOrDefaultAsInt64(dict map[string]any, key string, defaultValue int6 } // 兼容字符串类型的值 - if s, ok := value.(string); ok { - if result, err := strconv.ParseInt(s, 10, 64); err == nil { + if str, ok := value.(string); ok { + if result, err := strconv.ParseInt(str, 10, 64); err == nil { return result } } @@ -128,7 +128,7 @@ func GetValueOrDefaultAsInt64(dict map[string]any, key string, defaultValue int6 // - key: 键。 // // 出参: -// - 字典中键对应的值。如果指定键不存在或者类型不是布尔,则返回 false。 +// - 字典中键对应的值。如果指定键不存在或者值的类型不是布尔,则返回 false。 func GetValueAsBool(dict map[string]any, key string) bool { return GetValueOrDefaultAsBool(dict, key, false) } @@ -141,7 +141,7 @@ func GetValueAsBool(dict map[string]any, key string) bool { // - defaultValue: 默认值。 // // 出参: -// - 字典中键对应的值。如果指定键不存在或者类型不是布尔,则返回默认值。 +// - 字典中键对应的值。如果指定键不存在或者值的类型不是布尔,则返回默认值。 func GetValueOrDefaultAsBool(dict map[string]any, key string, defaultValue bool) bool { if dict == nil { return defaultValue From 8b04e96a7d43ae015b54b618f445c988c5d26651 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sun, 10 Nov 2024 18:21:43 +0800 Subject: [PATCH 06/10] feat: new UI for email notify settings --- internal/domain/setting.go | 2 +- ui/src/components/notify/Bark.tsx | 10 +- ui/src/components/notify/DingTalk.tsx | 10 +- ui/src/components/notify/Email.tsx | 357 +++++++++++++++++++++++ ui/src/components/notify/Lark.tsx | 10 +- ui/src/components/notify/Mail.tsx | 319 -------------------- ui/src/components/notify/ServerChan.tsx | 12 +- ui/src/components/notify/Telegram.tsx | 10 +- ui/src/components/notify/Webhook.tsx | 12 +- ui/src/domain/settings.ts | 39 +-- ui/src/i18n/locales/en/nls.common.json | 6 +- ui/src/i18n/locales/en/nls.settings.json | 19 +- ui/src/i18n/locales/zh/nls.common.json | 6 +- ui/src/i18n/locales/zh/nls.settings.json | 19 +- ui/src/pages/setting/Notify.tsx | 57 ++-- 15 files changed, 463 insertions(+), 425 deletions(-) create mode 100644 ui/src/components/notify/Email.tsx delete mode 100644 ui/src/components/notify/Mail.tsx diff --git a/internal/domain/setting.go b/internal/domain/setting.go index a704805d..ec36be8a 100644 --- a/internal/domain/setting.go +++ b/internal/domain/setting.go @@ -24,7 +24,7 @@ func (s *Setting) GetChannelContent(channel string) (map[string]any, error) { v, ok := (*conf)[channel] if !ok { - return nil, fmt.Errorf("channel %s not found", channel) + return nil, fmt.Errorf("channel \"%s\" not found", channel) } return v, nil diff --git a/ui/src/components/notify/Bark.tsx b/ui/src/components/notify/Bark.tsx index 8eaf8042..e4cb42ce 100644 --- a/ui/src/components/notify/Bark.tsx +++ b/ui/src/components/notify/Bark.tsx @@ -128,15 +128,15 @@ const Bark = () => { await notifyTest("bark"); toast({ - title: t("settings.notification.config.push.test.message.success.message"), - description: t("settings.notification.config.push.test.message.success.message"), + title: t("settings.notification.push_test_message.succeeded.message"), + description: t("settings.notification.push_test_message.succeeded.message"), }); } catch (e) { const msg = getErrMessage(e); toast({ - title: t("settings.notification.config.push.test.message.failed.message"), - description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`, + title: t("settings.notification.push_test_message.failed.message"), + description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`, variant: "destructive", }); } @@ -235,7 +235,7 @@ const Bark = () => { handlePushTestClick(); }} > - {t("settings.notification.config.push.test.message")} + {t("settings.notification.push_test_message")} diff --git a/ui/src/components/notify/DingTalk.tsx b/ui/src/components/notify/DingTalk.tsx index c3841144..baedf637 100644 --- a/ui/src/components/notify/DingTalk.tsx +++ b/ui/src/components/notify/DingTalk.tsx @@ -128,15 +128,15 @@ const DingTalk = () => { await notifyTest("dingtalk"); toast({ - title: t("settings.notification.config.push.test.message.success.message"), - description: t("settings.notification.config.push.test.message.success.message"), + title: t("settings.notification.push_test_message.succeeded.message"), + description: t("settings.notification.push_test_message.succeeded.message"), }); } catch (e) { const msg = getErrMessage(e); toast({ - title: t("settings.notification.config.push.test.message.failed.message"), - description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`, + title: t("settings.notification.push_test_message.failed.message"), + description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`, variant: "destructive", }); } @@ -232,7 +232,7 @@ const DingTalk = () => { handlePushTestClick(); }} > - {t("settings.notification.config.push.test.message")} + {t("settings.notification.push_test_message")} diff --git a/ui/src/components/notify/Email.tsx b/ui/src/components/notify/Email.tsx new file mode 100644 index 00000000..2e3dab0d --- /dev/null +++ b/ui/src/components/notify/Email.tsx @@ -0,0 +1,357 @@ +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(false); + + const [mail, setMail] = useState({ + id: config.id ?? "", + name: "notifyChannels", + data: { + smtpHost: "", + smtpPort: 465, + smtpTLS: true, + username: "", + password: "", + senderAddress: "", + receiverAddress: "", + enabled: false, + }, + }); + + const [originMail, setOriginMail] = useState({ + 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 handlePushTestClick = async () => { + try { + 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", + }); + } + }; + + 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 ( +
+ { + const newData = { + ...mail, + data: { + ...mail.data, + smtpHost: e.target.value, + }, + }; + checkChanged(newData.data); + setMail(newData); + }} + /> + + { + const newData = { + ...mail, + data: { + ...mail.data, + smtpPort: +e.target.value || 0, + }, + }; + checkChanged(newData.data); + setMail(newData); + }} + /> + + { + const newData = { + ...mail, + data: { + ...mail.data, + smtpTLS: e, + }, + }; + checkChanged(newData.data); + setMail(newData); + }} + /> + + { + const newData = { + ...mail, + data: { + ...mail.data, + username: e.target.value, + }, + }; + checkChanged(newData.data); + setMail(newData); + }} + /> + + { + const newData = { + ...mail, + data: { + ...mail.data, + password: e.target.value, + }, + }; + checkChanged(newData.data); + setMail(newData); + }} + /> + + { + const newData = { + ...mail, + data: { + ...mail.data, + senderAddress: e.target.value, + }, + }; + checkChanged(newData.data); + setMail(newData); + }} + /> + + { + const newData = { + ...mail, + data: { + ...mail.data, + receiverAddress: e.target.value, + }, + }; + checkChanged(newData.data); + setMail(newData); + }} + /> + +
+
+
+ + +
+ +
+ + + + + + + +
+
+
+
+ ); +}; + +export default Mail; diff --git a/ui/src/components/notify/Lark.tsx b/ui/src/components/notify/Lark.tsx index f8bf7534..abd3b886 100644 --- a/ui/src/components/notify/Lark.tsx +++ b/ui/src/components/notify/Lark.tsx @@ -124,15 +124,15 @@ const Lark = () => { await notifyTest("lark"); toast({ - title: t("settings.notification.config.push.test.message.success.message"), - description: t("settings.notification.config.push.test.message.success.message"), + title: t("settings.notification.push_test_message.succeeded.message"), + description: t("settings.notification.push_test_message.succeeded.message"), }); } catch (e) { const msg = getErrMessage(e); toast({ - title: t("settings.notification.config.push.test.message.failed.message"), - description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`, + title: t("settings.notification.push_test_message.failed.message"), + description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`, variant: "destructive", }); } @@ -213,7 +213,7 @@ const Lark = () => { handlePushTestClick(); }} > - {t("settings.notification.config.push.test.message")} + {t("settings.notification.push_test_message")} diff --git a/ui/src/components/notify/Mail.tsx b/ui/src/components/notify/Mail.tsx deleted file mode 100644 index 7b6195fb..00000000 --- a/ui/src/components/notify/Mail.tsx +++ /dev/null @@ -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(false); - - const [mail, setmail] = useState({ - id: config.id ?? "", - name: "notifyChannels", - data: { - senderAddress: "", - receiverAddresses: "", - smtpHostAddr: "", - smtpHostPort: "25", - username: "", - password: "", - enabled: false, - }, - }); - - const [originMail, setoriginMail] = useState({ - 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 ( -
- { - const newData = { - ...mail, - data: { - ...mail.data, - senderAddress: e.target.value, - }, - }; - checkChanged(newData.data); - setmail(newData); - }} - /> - { - const newData = { - ...mail, - data: { - ...mail.data, - receiverAddresses: e.target.value, - }, - }; - checkChanged(newData.data); - setmail(newData); - }} - /> - { - const newData = { - ...mail, - data: { - ...mail.data, - smtpHostAddr: e.target.value, - }, - }; - checkChanged(newData.data); - setmail(newData); - }} - /> - { - const newData = { - ...mail, - data: { - ...mail.data, - smtpHostPort: e.target.value, - }, - }; - checkChanged(newData.data); - setmail(newData); - }} - /> - { - const newData = { - ...mail, - data: { - ...mail.data, - username: e.target.value, - }, - }; - checkChanged(newData.data); - setmail(newData); - }} - /> - { - const newData = { - ...mail, - data: { - ...mail.data, - password: e.target.value, - }, - }; - checkChanged(newData.data); - setmail(newData); - }} - /> -
- - -
- -
- - - - - - - -
-
- ); -}; - -export default Mail; diff --git a/ui/src/components/notify/ServerChan.tsx b/ui/src/components/notify/ServerChan.tsx index f31c7bdc..cc2d0c00 100644 --- a/ui/src/components/notify/ServerChan.tsx +++ b/ui/src/components/notify/ServerChan.tsx @@ -97,7 +97,7 @@ const ServerChan = () => { if (!isValidURL(serverchan.data.url)) { toast({ title: t("common.save.failed.message"), - description: t("settings.notification.url.errmsg.invalid"), + description: t("common.errmsg.url_invalid"), variant: "destructive", }); return; @@ -135,15 +135,15 @@ const ServerChan = () => { await notifyTest("serverchan"); toast({ - title: t("settings.notification.config.push.test.message.success.message"), - description: t("settings.notification.config.push.test.message.success.message"), + title: t("settings.notification.push_test_message.succeeded.message"), + description: t("settings.notification.push_test_message.succeeded.message"), }); } catch (e) { const msg = getErrMessage(e); toast({ - title: t("settings.notification.config.push.test.message.failed.message"), - description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`, + title: t("settings.notification.push_test_message.failed.message"), + description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`, variant: "destructive", }); } @@ -225,7 +225,7 @@ const ServerChan = () => { handlePushTestClick(); }} > - {t("settings.notification.config.push.test.message")} + {t("settings.notification.push_test_message")} diff --git a/ui/src/components/notify/Telegram.tsx b/ui/src/components/notify/Telegram.tsx index 419126fc..bafe9042 100644 --- a/ui/src/components/notify/Telegram.tsx +++ b/ui/src/components/notify/Telegram.tsx @@ -128,15 +128,15 @@ const Telegram = () => { await notifyTest("telegram"); toast({ - title: t("settings.notification.config.push.test.message.success.message"), - description: t("settings.notification.config.push.test.message.success.message"), + title: t("settings.notification.push_test_message.succeeded.message"), + description: t("settings.notification.push_test_message.succeeded.message"), }); } catch (e) { const msg = getErrMessage(e); toast({ - title: t("settings.notification.config.push.test.message.failed.message"), - description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`, + title: t("settings.notification.push_test_message.failed.message"), + description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`, variant: "destructive", }); } @@ -235,7 +235,7 @@ const Telegram = () => { handlePushTestClick(); }} > - {t("settings.notification.config.push.test.message")} + {t("settings.notification.push_test_message")} diff --git a/ui/src/components/notify/Webhook.tsx b/ui/src/components/notify/Webhook.tsx index b614b50c..1d3d9157 100644 --- a/ui/src/components/notify/Webhook.tsx +++ b/ui/src/components/notify/Webhook.tsx @@ -97,7 +97,7 @@ const Webhook = () => { if (!isValidURL(webhook.data.url)) { toast({ title: t("common.save.failed.message"), - description: t("settings.notification.url.errmsg.invalid"), + description: t("common.errmsg.url_invalid"), variant: "destructive", }); return; @@ -135,15 +135,15 @@ const Webhook = () => { await notifyTest("webhook"); toast({ - title: t("settings.notification.config.push.test.message.success.message"), - description: t("settings.notification.config.push.test.message.success.message"), + title: t("settings.notification.push_test_message.succeeded.message"), + description: t("settings.notification.push_test_message.succeeded.message"), }); } catch (e) { const msg = getErrMessage(e); toast({ - title: t("settings.notification.config.push.test.message.failed.message"), - description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`, + title: t("settings.notification.push_test_message.failed.message"), + description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`, variant: "destructive", }); } @@ -225,7 +225,7 @@ const Webhook = () => { handlePushTestClick(); }} > - {t("settings.notification.config.push.test.message")} + {t("settings.notification.push_test_message")} diff --git a/ui/src/domain/settings.ts b/ui/src/domain/settings.ts index 7962ef7a..35061c42 100644 --- a/ui/src/domain/settings.ts +++ b/ui/src/domain/settings.ts @@ -18,24 +18,40 @@ export type NotifyTemplate = { }; export type NotifyChannels = { + email?: NotifyChannelEmail; + webhook?: NotifyChannel; dingtalk?: NotifyChannel; lark?: NotifyChannel; telegram?: NotifyChannel; - webhook?: NotifyChannel; serverchan?: NotifyChannel; - mail?: NotifyChannelMail; bark?: NotifyChannelBark; }; export type NotifyChannel = + | NotifyChannelEmail + | NotifyChannelWebhook | NotifyChannelDingTalk | NotifyChannelLark | NotifyChannelTelegram - | NotifyChannelWebhook | NotifyChannelServerChan - | NotifyChannelMail | NotifyChannelBark; +export type NotifyChannelEmail = { + smtpHost: string; + smtpPort: number; + smtpTLS: boolean; + username: string; + password: string; + senderAddress: string; + receiverAddress: string; + enabled: boolean; +}; + +export type NotifyChannelWebhook = { + url: string; + enabled: boolean; +}; + export type NotifyChannelDingTalk = { accessToken: string; secret: string; @@ -53,26 +69,11 @@ export type NotifyChannelTelegram = { enabled: boolean; }; -export type NotifyChannelWebhook = { - url: string; - enabled: boolean; -}; - export type NotifyChannelServerChan = { url: string; enabled: boolean; }; -export type NotifyChannelMail = { - senderAddress: string; - receiverAddresses: string; - smtpHostAddr: string; - smtpHostPort: string; - username: string; - password: string; - enabled: boolean; -}; - export type NotifyChannelBark = { deviceKey: string; serverUrl: string; diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index 69517f1e..41159d67 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -83,12 +83,12 @@ "common.provider.local": "Local Deployment", "common.provider.ssh": "SSH Deployment", "common.provider.webhook": "Webhook", - "common.provider.serverchan": "ServerChan", "common.provider.kubernetes": "Kubernetes", "common.provider.kubernetes.secret": "Kubernetes - Secret", + "common.provider.email": "Email", "common.provider.dingtalk": "DingTalk", - "common.provider.telegram": "Telegram", "common.provider.lark": "Lark", - "common.provider.mail": "Mail", + "common.provider.telegram": "Telegram", + "common.provider.serverchan": "ServerChan", "common.provider.bark": "Bark" } diff --git a/ui/src/i18n/locales/en/nls.settings.json b/ui/src/i18n/locales/en/nls.settings.json index a7d2f857..f945797e 100644 --- a/ui/src/i18n/locales/en/nls.settings.json +++ b/ui/src/i18n/locales/en/nls.settings.json @@ -30,18 +30,17 @@ "settings.notification.config.enable": "Enable", "settings.notification.config.saved.message": "Configuration saved successfully", "settings.notification.config.failed.message": "Configuration save failed", - "settings.notification.config.push.test.message": "Send test notification", - "settings.notification.config.push.test.message.failed.message": "Send test notification failed", - "settings.notification.config.push.test.message.success.message": "Send test notification successfully", + "settings.notification.push_test_message": "Send test notification", + "settings.notification.push_test_message.succeeded.message": "Send test notification successfully", + "settings.notification.push_test_message.failed.message": "Send test notification failed", + "settings.notification.email.smtp_host.placeholder": "SMTP server address", + "settings.notification.email.smtp_port.placeholder": "SMTP server port", + "settings.notification.email.username.placeholder": "username", + "settings.notification.email.password.placeholder": "password", + "settings.notification.email.sender_address.placeholder": "Sender email address", + "settings.notification.email.receiver_address.placeholder": "Receiver email address", "settings.notification.dingtalk.secret.placeholder": "Signature for signed addition", - "settings.notification.url.errmsg.invalid": "Invalid Url format", "settings.notification.serverchan.url.placeholder": "Url, e.g. https://sctapi.ftqq.com/****************.send", - "settings.notification.mail.sender_address.placeholder": "Sender email address", - "settings.notification.mail.receiver_address.placeholder": "Receiver email address", - "settings.notification.mail.smtp_host.placeholder": "SMTP server address", - "settings.notification.mail.smtp_port.placeholder": "SMTP server port, if not set, default is 25", - "settings.notification.mail.username.placeholder": "username", - "settings.notification.mail.password.placeholder": "password", "settings.notification.bark.serverUrl.placeholder": "Server URL, e.g. https://your-bark-server.com, leave it blank to use the bark default server", "settings.notification.bark.deviceKey.placeholder": "Device Key,e.g. XXXXXXXXXXXXXXXXXXXX", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index 5657836e..f14729a5 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -83,12 +83,12 @@ "common.provider.local": "本地部署", "common.provider.ssh": "SSH 部署", "common.provider.webhook": "Webhook", - "common.provider.serverchan": "Server酱", "common.provider.kubernetes": "Kubernetes", "common.provider.kubernetes.secret": "Kubernetes - Secret", + "common.provider.email": "电子邮件", "common.provider.dingtalk": "钉钉", - "common.provider.telegram": "Telegram", "common.provider.lark": "飞书", - "common.provider.mail": "电子邮件", + "common.provider.telegram": "Telegram", + "common.provider.serverchan": "Server酱", "common.provider.bark": "Bark" } diff --git a/ui/src/i18n/locales/zh/nls.settings.json b/ui/src/i18n/locales/zh/nls.settings.json index 23185807..2509cf91 100644 --- a/ui/src/i18n/locales/zh/nls.settings.json +++ b/ui/src/i18n/locales/zh/nls.settings.json @@ -30,18 +30,17 @@ "settings.notification.config.enable": "是否启用", "settings.notification.config.saved.message": "配置保存成功", "settings.notification.config.failed.message": "配置保存失败", - "settings.notification.config.push.test.message": "推送测试消息", - "settings.notification.config.push.test.message.failed.message": "推送测试消息失败", - "settings.notification.config.push.test.message.success.message": "推送测试消息成功", + "settings.notification.push_test_message": "推送测试消息", + "settings.notification.push_test_message.failed.message": "推送测试消息失败", + "settings.notification.push_test_message.succeeded.message": "推送测试消息成功", + "settings.notification.email.smtp_host.placeholder": "SMTP服务器地址", + "settings.notification.email.smtp_port.placeholder": "SMTP服务器端口, 如果未设置, 默认为25", + "settings.notification.email.username.placeholder": "用于登录到邮件服务器的用户名", + "settings.notification.email.password.placeholder": "用于登录到邮件服务器的密码", + "settings.notification.email.sender_address.placeholder": "发送邮箱地址", + "settings.notification.email.receiver_address.placeholder": "接收邮箱地址", "settings.notification.dingtalk.secret.placeholder": "加签的签名", - "settings.notification.url.errmsg.invalid": "URL 格式不正确", "settings.notification.serverchan.url.placeholder": "Url, 形如: https://sctapi.ftqq.com/****************.send", - "settings.notification.mail.sender_address.placeholder": "发送邮箱地址", - "settings.notification.mail.receiver_address.placeholder": "接收邮箱地址", - "settings.notification.mail.smtp_host.placeholder": "SMTP服务器地址", - "settings.notification.mail.smtp_port.placeholder": "SMTP服务器端口, 如果未设置, 默认为25", - "settings.notification.mail.username.placeholder": "用于登录到邮件服务器的用户名", - "settings.notification.mail.password.placeholder": "用于登录到邮件服务器的密码", "settings.notification.bark.serverUrl.placeholder": "服务器URL,形如: https://your-bark-server.com, 留空则使用 Bark 默认服务器", "settings.notification.bark.deviceKey.placeholder": "设备密钥,形如: XXXXXXXXXXXXXXXXXXXX", diff --git a/ui/src/pages/setting/Notify.tsx b/ui/src/pages/setting/Notify.tsx index c29d0405..c89d2a24 100644 --- a/ui/src/pages/setting/Notify.tsx +++ b/ui/src/pages/setting/Notify.tsx @@ -7,7 +7,7 @@ import NotifyTemplate from "@/components/notify/NotifyTemplate"; import Telegram from "@/components/notify/Telegram"; import Webhook from "@/components/notify/Webhook"; import ServerChan from "@/components/notify/ServerChan"; -import Mail from "@/components/notify/Mail"; +import Email from "@/components/notify/Email"; import Bark from "@/components/notify/Bark"; import { NotifyProvider } from "@/providers/notify"; @@ -27,51 +27,52 @@ const Notify = () => { +
- - {t("common.provider.dingtalk")} + + {t("common.provider.email")} - + - - {t("common.provider.lark")} - - - - - - - {t("common.provider.telegram")} - - - - - - + {t("common.provider.webhook")} - + + {t("common.provider.dingtalk")} + + + + + + + {t("common.provider.lark")} + + + + + + + {t("common.provider.telegram")} + + + + + + {t("common.provider.serverchan")} - - {t("common.provider.mail")} - - - - - - + {t("common.provider.bark")} From 44497a0969d4b6c893e577fd56e1c074db3f9042 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sun, 10 Nov 2024 19:52:50 +0800 Subject: [PATCH 07/10] feat: new UI for notify settings --- ui/src/components/notify/Bark.tsx | 122 +++++----- ui/src/components/notify/DingTalk.tsx | 119 ++++----- ui/src/components/notify/Email.tsx | 297 ++++++++++++----------- ui/src/components/notify/Lark.tsx | 84 ++++--- ui/src/components/notify/ServerChan.tsx | 85 ++++--- ui/src/components/notify/Telegram.tsx | 122 +++++----- ui/src/components/notify/Webhook.tsx | 85 ++++--- ui/src/i18n/locales/en/nls.settings.json | 41 +++- ui/src/i18n/locales/zh/nls.settings.json | 41 +++- 9 files changed, 549 insertions(+), 447 deletions(-) diff --git a/ui/src/components/notify/Bark.tsx b/ui/src/components/notify/Bark.tsx index e4cb42ce..6584b542 100644 --- a/ui/src/components/notify/Bark.tsx +++ b/ui/src/components/notify/Bark.tsx @@ -177,67 +177,75 @@ const Bark = () => { }; return ( -
- { - const newData = { - ...bark, - data: { - ...bark.data, - serverUrl: e.target.value, - }, - }; +
+
+ + { + const newData = { + ...bark, + data: { + ...bark.data, + serverUrl: e.target.value, + }, + }; - checkChanged(newData.data); - setBark(newData); - }} - /> - - { - const newData = { - ...bark, - data: { - ...bark.data, - deviceKey: e.target.value, - }, - }; - - checkChanged(newData.data); - setBark(newData); - }} - /> - -
- - + checkChanged(newData.data); + setBark(newData); + }} + />
-
- - - +
+ + { + const newData = { + ...bark, + data: { + ...bark.data, + deviceKey: e.target.value, + }, + }; - - - + checkChanged(newData.data); + setBark(newData); + }} + /> +
+ +
+
+ + +
+ +
+ + + + + + + +
); diff --git a/ui/src/components/notify/DingTalk.tsx b/ui/src/components/notify/DingTalk.tsx index baedf637..76ab89d9 100644 --- a/ui/src/components/notify/DingTalk.tsx +++ b/ui/src/components/notify/DingTalk.tsx @@ -177,64 +177,73 @@ const DingTalk = () => { }; return ( -
- { - const newData = { - ...dingtalk, - data: { - ...dingtalk.data, - accessToken: e.target.value, - }, - }; - checkChanged(newData.data); - setDingtalk(newData); - }} - /> - { - const newData = { - ...dingtalk, - data: { - ...dingtalk.data, - secret: e.target.value, - }, - }; - checkChanged(newData.data); - setDingtalk(newData); - }} - /> -
- - +
+
+ + { + const newData = { + ...dingtalk, + data: { + ...dingtalk.data, + accessToken: e.target.value, + }, + }; + checkChanged(newData.data); + setDingtalk(newData); + }} + />
-
- - - +
+ + { + const newData = { + ...dingtalk, + data: { + ...dingtalk.data, + secret: e.target.value, + }, + }; + checkChanged(newData.data); + setDingtalk(newData); + }} + /> +
- - - +
+
+ + +
+ +
+ + + + + + + +
); diff --git a/ui/src/components/notify/Email.tsx b/ui/src/components/notify/Email.tsx index 2e3dab0d..ecc83b0a 100644 --- a/ui/src/components/notify/Email.tsx +++ b/ui/src/components/notify/Email.tsx @@ -200,154 +200,173 @@ const Mail = () => { }; return ( -
- { - const newData = { - ...mail, - data: { - ...mail.data, - smtpHost: e.target.value, - }, - }; - checkChanged(newData.data); - setMail(newData); - }} - /> +
+
+
+ + { + const newData = { + ...mail, + data: { + ...mail.data, + smtpHost: e.target.value, + }, + }; + checkChanged(newData.data); + setMail(newData); + }} + /> +
- { - const newData = { - ...mail, - data: { - ...mail.data, - smtpPort: +e.target.value || 0, - }, - }; - checkChanged(newData.data); - setMail(newData); - }} - /> +
+ + { + const newData = { + ...mail, + data: { + ...mail.data, + smtpPort: +e.target.value || 0, + }, + }; + checkChanged(newData.data); + setMail(newData); + }} + /> +
- { - const newData = { - ...mail, - data: { - ...mail.data, - smtpTLS: e, - }, - }; - checkChanged(newData.data); - setMail(newData); - }} - /> +
+ + { + 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); + }} + /> +
+
- { - const newData = { - ...mail, - data: { - ...mail.data, - username: e.target.value, - }, - }; - checkChanged(newData.data); - setMail(newData); - }} - /> +
+
+ + { + const newData = { + ...mail, + data: { + ...mail.data, + username: e.target.value, + }, + }; + checkChanged(newData.data); + setMail(newData); + }} + /> +
- { - const newData = { - ...mail, - data: { - ...mail.data, - password: e.target.value, - }, - }; - checkChanged(newData.data); - setMail(newData); - }} - /> +
+ + { + const newData = { + ...mail, + data: { + ...mail.data, + password: e.target.value, + }, + }; + checkChanged(newData.data); + setMail(newData); + }} + /> +
+
- { - const newData = { - ...mail, - data: { - ...mail.data, - senderAddress: e.target.value, - }, - }; - checkChanged(newData.data); - setMail(newData); - }} - /> +
+ + { + const newData = { + ...mail, + data: { + ...mail.data, + senderAddress: e.target.value, + }, + }; + checkChanged(newData.data); + setMail(newData); + }} + /> +
- { - const newData = { - ...mail, - data: { - ...mail.data, - receiverAddress: e.target.value, - }, - }; - checkChanged(newData.data); - setMail(newData); - }} - /> +
+ + { + const newData = { + ...mail, + data: { + ...mail.data, + receiverAddress: e.target.value, + }, + }; + checkChanged(newData.data); + setMail(newData); + }} + /> +
-
-
-
- - -
+
+
+ + +
-
- - - +
+ + + - - - -
+ + +
diff --git a/ui/src/components/notify/Lark.tsx b/ui/src/components/notify/Lark.tsx index abd3b886..91041357 100644 --- a/ui/src/components/notify/Lark.tsx +++ b/ui/src/components/notify/Lark.tsx @@ -173,49 +173,55 @@ const Lark = () => { }; return ( -
- { - const newData = { - ...lark, - data: { - ...lark.data, - webhookUrl: e.target.value, - }, - }; +
+
+ + { + const newData = { + ...lark, + data: { + ...lark.data, + webhookUrl: e.target.value, + }, + }; - checkChanged(newData.data); - setLark(newData); - }} - /> -
- - + checkChanged(newData.data); + setLark(newData); + }} + />
-
- - - +
+
+ + +
- - - +
+ + + + + + + +
); diff --git a/ui/src/components/notify/ServerChan.tsx b/ui/src/components/notify/ServerChan.tsx index cc2d0c00..97f995f8 100644 --- a/ui/src/components/notify/ServerChan.tsx +++ b/ui/src/components/notify/ServerChan.tsx @@ -184,50 +184,55 @@ const ServerChan = () => { }; return ( -
- { - const newData = { - ...serverchan, - data: { - ...serverchan.data, - url: e.target.value, - }, - }; +
+
+ + { + const newData = { + ...serverchan, + data: { + ...serverchan.data, + url: e.target.value, + }, + }; - checkChanged(newData.data); - setServerChan(newData); - }} - /> - -
- - + checkChanged(newData.data); + setServerChan(newData); + }} + />
-
- - - +
+
+ + +
- - - +
+ + + + + + + +
); diff --git a/ui/src/components/notify/Telegram.tsx b/ui/src/components/notify/Telegram.tsx index bafe9042..fdc47c05 100644 --- a/ui/src/components/notify/Telegram.tsx +++ b/ui/src/components/notify/Telegram.tsx @@ -177,67 +177,75 @@ const Telegram = () => { }; return ( -
- { - const newData = { - ...telegram, - data: { - ...telegram.data, - apiToken: e.target.value, - }, - }; +
+
+ + { + const newData = { + ...telegram, + data: { + ...telegram.data, + apiToken: e.target.value, + }, + }; - checkChanged(newData.data); - setTelegram(newData); - }} - /> - - { - const newData = { - ...telegram, - data: { - ...telegram.data, - chatId: e.target.value, - }, - }; - - checkChanged(newData.data); - setTelegram(newData); - }} - /> - -
- - + checkChanged(newData.data); + setTelegram(newData); + }} + />
-
- - - +
+ + { + const newData = { + ...telegram, + data: { + ...telegram.data, + chatId: e.target.value, + }, + }; - - - + checkChanged(newData.data); + setTelegram(newData); + }} + /> +
+ +
+
+ + +
+ +
+ + + + + + + +
); diff --git a/ui/src/components/notify/Webhook.tsx b/ui/src/components/notify/Webhook.tsx index 1d3d9157..6bacc16e 100644 --- a/ui/src/components/notify/Webhook.tsx +++ b/ui/src/components/notify/Webhook.tsx @@ -184,50 +184,55 @@ const Webhook = () => { }; return ( -
- { - const newData = { - ...webhook, - data: { - ...webhook.data, - url: e.target.value, - }, - }; +
+
+ + { + const newData = { + ...webhook, + data: { + ...webhook.data, + url: e.target.value, + }, + }; - checkChanged(newData.data); - setWebhook(newData); - }} - /> - -
- - + checkChanged(newData.data); + setWebhook(newData); + }} + />
-
- - - +
+
+ + +
- - - +
+ + + + + + + +
); diff --git a/ui/src/i18n/locales/en/nls.settings.json b/ui/src/i18n/locales/en/nls.settings.json index f945797e..b427a02a 100644 --- a/ui/src/i18n/locales/en/nls.settings.json +++ b/ui/src/i18n/locales/en/nls.settings.json @@ -33,16 +33,37 @@ "settings.notification.push_test_message": "Send test notification", "settings.notification.push_test_message.succeeded.message": "Send test notification successfully", "settings.notification.push_test_message.failed.message": "Send test notification failed", - "settings.notification.email.smtp_host.placeholder": "SMTP server address", - "settings.notification.email.smtp_port.placeholder": "SMTP server port", - "settings.notification.email.username.placeholder": "username", - "settings.notification.email.password.placeholder": "password", - "settings.notification.email.sender_address.placeholder": "Sender email address", - "settings.notification.email.receiver_address.placeholder": "Receiver email address", - "settings.notification.dingtalk.secret.placeholder": "Signature for signed addition", - "settings.notification.serverchan.url.placeholder": "Url, e.g. https://sctapi.ftqq.com/****************.send", - "settings.notification.bark.serverUrl.placeholder": "Server URL, e.g. https://your-bark-server.com, leave it blank to use the bark default server", - "settings.notification.bark.deviceKey.placeholder": "Device Key,e.g. XXXXXXXXXXXXXXXXXXXX", + "settings.notification.email.smtp_host.label": "SMTP Host", + "settings.notification.email.smtp_host.placeholder": "Please enter SMTP host", + "settings.notification.email.smtp_port.label": "SMTP Port", + "settings.notification.email.smtp_port.placeholder": "Please enter SMTP port", + "settings.notification.email.smtp_tls.label": "TLS/SSL connection", + "settings.notification.email.username.label": "Username", + "settings.notification.email.username.placeholder": "please enter username", + "settings.notification.email.password.label": "Password", + "settings.notification.email.password.placeholder": "please enter password", + "settings.notification.email.sender_address.label": "Sender Email Address", + "settings.notification.email.sender_address.placeholder": "Please enter sender email address", + "settings.notification.email.receiver_address.label": "Receiver Email Address", + "settings.notification.email.receiver_address.placeholder": "Please enter receiver email address", + "settings.notification.webhook.url.label": "Webhook URL", + "settings.notification.webhook.url.placeholder": "Please enter Webhook URL", + "settings.notification.dingtalk.access_token.label": "AccessToken", + "settings.notification.dingtalk.access_token.placeholder": "Please enter access token", + "settings.notification.dingtalk.secret.label": "Secret", + "settings.notification.dingtalk.secret.placeholder": "Please enter secret", + "settings.notification.lark.webhook_url.label": "Webhook URL", + "settings.notification.lark.webhook_url.placeholder": "Please enter Webhook URL", + "settings.notification.telegram.api_token.label": "API Token", + "settings.notification.telegram.api_token.placeholder": "Please enter API token", + "settings.notification.telegram.chat_id.label": "Chat ID", + "settings.notification.telegram.chat_id.placeholder": "Please enter Telegram chat ID", + "settings.notification.serverchan.url.label": "Server URL", + "settings.notification.serverchan.url.placeholder": "Please enter server URL (e.g. https://sctapi.ftqq.com/*****.send)", + "settings.notification.bark.server_url.label": "Server URL", + "settings.notification.bark.server_url.placeholder": "Please enter server URL (e.g. https://your-bark-server.com. Leave it blank to use the bark default server)", + "settings.notification.bark.device_key.label": "Device Key", + "settings.notification.bark.device_key.placeholder": "Please enter device key", "settings.ca.tab": "Certificate Authority", "settings.ca.provider.errmsg.empty": "Please select a Certificate Authority", diff --git a/ui/src/i18n/locales/zh/nls.settings.json b/ui/src/i18n/locales/zh/nls.settings.json index 2509cf91..9f6f7f28 100644 --- a/ui/src/i18n/locales/zh/nls.settings.json +++ b/ui/src/i18n/locales/zh/nls.settings.json @@ -33,16 +33,37 @@ "settings.notification.push_test_message": "推送测试消息", "settings.notification.push_test_message.failed.message": "推送测试消息失败", "settings.notification.push_test_message.succeeded.message": "推送测试消息成功", - "settings.notification.email.smtp_host.placeholder": "SMTP服务器地址", - "settings.notification.email.smtp_port.placeholder": "SMTP服务器端口, 如果未设置, 默认为25", - "settings.notification.email.username.placeholder": "用于登录到邮件服务器的用户名", - "settings.notification.email.password.placeholder": "用于登录到邮件服务器的密码", - "settings.notification.email.sender_address.placeholder": "发送邮箱地址", - "settings.notification.email.receiver_address.placeholder": "接收邮箱地址", - "settings.notification.dingtalk.secret.placeholder": "加签的签名", - "settings.notification.serverchan.url.placeholder": "Url, 形如: https://sctapi.ftqq.com/****************.send", - "settings.notification.bark.serverUrl.placeholder": "服务器URL,形如: https://your-bark-server.com, 留空则使用 Bark 默认服务器", - "settings.notification.bark.deviceKey.placeholder": "设备密钥,形如: XXXXXXXXXXXXXXXXXXXX", + "settings.notification.email.smtp_host.label": "SMTP 服务器地址", + "settings.notification.email.smtp_host.placeholder": "请输入 SMTP 服务器地址", + "settings.notification.email.smtp_port.label": "SMTP 服务器端口", + "settings.notification.email.smtp_port.placeholder": "请输入 SMTP 服务器端口", + "settings.notification.email.smtp_tls.label": "TLS/SSL 连接", + "settings.notification.email.username.label": "用户名", + "settings.notification.email.username.placeholder": "请输入用户名", + "settings.notification.email.password.label": "密码", + "settings.notification.email.password.placeholder": "请输入密码", + "settings.notification.email.sender_address.label": "发送邮箱地址", + "settings.notification.email.sender_address.placeholder": "请输入发送邮箱地址", + "settings.notification.email.receiver_address.label": "接收邮箱地址", + "settings.notification.email.receiver_address.placeholder": "请输入接收邮箱地址", + "settings.notification.webhook.url.label": "Webhook 回调地址", + "settings.notification.webhook.url.placeholder": "请输入 Webhook 回调地址", + "settings.notification.dingtalk.access_token.label": "AccessToken", + "settings.notification.dingtalk.access_token.placeholder": "请输入 AccessToken", + "settings.notification.dingtalk.secret.label": "签名密钥", + "settings.notification.dingtalk.secret.placeholder": "请输入签名密钥", + "settings.notification.lark.webhook_url.label": "Webhook URL", + "settings.notification.lark.webhook_url.placeholder": "请输入 Webhook URL", + "settings.notification.telegram.api_token.label": "API Token", + "settings.notification.telegram.api_token.placeholder": "请输入 API token", + "settings.notification.telegram.chat_id.label": "会话 ID", + "settings.notification.telegram.chat_id.placeholder": "请输入 Telegram 会话 ID", + "settings.notification.serverchan.url.label": "服务器 URL", + "settings.notification.serverchan.url.placeholder": "请输入服务器 URL(形如: https://sctapi.ftqq.com/*****.send)", + "settings.notification.bark.server_url.label": "服务器 URL", + "settings.notification.bark.server_url.placeholder": "请输入服务器 URL(形如: https://your-bark-server.com;留空则使用 Bark 默认服务器)", + "settings.notification.bark.device_key.label": "设备密钥", + "settings.notification.bark.device_key.placeholder": "请输入设备密钥", "settings.ca.tab": "证书颁发机构(CA)", "settings.ca.provider.errmsg.empty": "请选择证书分发机构", From 8fecebc254e1afdd839510b40c950315da3279cf Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sun, 10 Nov 2024 20:00:19 +0800 Subject: [PATCH 08/10] feat: show loading button when pushing test notifications --- ui/src/components/notify/Bark.tsx | 8 ++++++ ui/src/components/notify/DingTalk.tsx | 8 ++++++ ui/src/components/notify/Email.tsx | 8 ++++++ ui/src/components/notify/Lark.tsx | 8 ++++++ ui/src/components/notify/ServerChan.tsx | 8 ++++++ ui/src/components/notify/Telegram.tsx | 8 ++++++ ui/src/components/notify/Webhook.tsx | 8 ++++++ ui/src/components/ui/button.tsx | 34 ++++++++++++++++++++++--- 8 files changed, 87 insertions(+), 3 deletions(-) diff --git a/ui/src/components/notify/Bark.tsx b/ui/src/components/notify/Bark.tsx index 6584b542..0b651cb6 100644 --- a/ui/src/components/notify/Bark.tsx +++ b/ui/src/components/notify/Bark.tsx @@ -123,8 +123,13 @@ const Bark = () => { } }; + const [testing, setTesting] = useState(false); const handlePushTestClick = async () => { + if (testing) return; + try { + setTesting(true); + await notifyTest("bark"); toast({ @@ -139,6 +144,8 @@ const Bark = () => { description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`, variant: "destructive", }); + } finally { + setTesting(false); } }; @@ -238,6 +245,7 @@ const Bark = () => { + ); }); Button.displayName = "Button"; From 1bedb31a3ca2c10bd159914d70156a985beb106e Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sun, 10 Nov 2024 20:06:18 +0800 Subject: [PATCH 09/10] fix: fix typo --- internal/pkg/core/notifier/providers/bark/bark.go | 2 +- internal/pkg/core/notifier/providers/dingtalk/dingtalk.go | 2 +- internal/pkg/core/notifier/providers/lark/lark.go | 2 +- internal/pkg/core/notifier/providers/serverchan/serverchan.go | 2 +- internal/pkg/core/notifier/providers/telegram/telegram.go | 2 +- internal/pkg/core/notifier/providers/webhook/webhook.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/pkg/core/notifier/providers/bark/bark.go b/internal/pkg/core/notifier/providers/bark/bark.go index aefd6ab6..cd8119e1 100644 --- a/internal/pkg/core/notifier/providers/bark/bark.go +++ b/internal/pkg/core/notifier/providers/bark/bark.go @@ -1,4 +1,4 @@ -package email +package bark import ( "context" diff --git a/internal/pkg/core/notifier/providers/dingtalk/dingtalk.go b/internal/pkg/core/notifier/providers/dingtalk/dingtalk.go index 2d1e1220..9ca1fcac 100644 --- a/internal/pkg/core/notifier/providers/dingtalk/dingtalk.go +++ b/internal/pkg/core/notifier/providers/dingtalk/dingtalk.go @@ -1,4 +1,4 @@ -package email +package dingtalk import ( "context" diff --git a/internal/pkg/core/notifier/providers/lark/lark.go b/internal/pkg/core/notifier/providers/lark/lark.go index 1afd9660..7a1dfc13 100644 --- a/internal/pkg/core/notifier/providers/lark/lark.go +++ b/internal/pkg/core/notifier/providers/lark/lark.go @@ -1,4 +1,4 @@ -package email +package lark import ( "context" diff --git a/internal/pkg/core/notifier/providers/serverchan/serverchan.go b/internal/pkg/core/notifier/providers/serverchan/serverchan.go index 4d87826f..07d2b6e0 100644 --- a/internal/pkg/core/notifier/providers/serverchan/serverchan.go +++ b/internal/pkg/core/notifier/providers/serverchan/serverchan.go @@ -1,4 +1,4 @@ -package email +package serverchan import ( "context" diff --git a/internal/pkg/core/notifier/providers/telegram/telegram.go b/internal/pkg/core/notifier/providers/telegram/telegram.go index e214a4c3..3560b87b 100644 --- a/internal/pkg/core/notifier/providers/telegram/telegram.go +++ b/internal/pkg/core/notifier/providers/telegram/telegram.go @@ -1,4 +1,4 @@ -package email +package telegram import ( "context" diff --git a/internal/pkg/core/notifier/providers/webhook/webhook.go b/internal/pkg/core/notifier/providers/webhook/webhook.go index 1a3cdac8..aa27014a 100644 --- a/internal/pkg/core/notifier/providers/webhook/webhook.go +++ b/internal/pkg/core/notifier/providers/webhook/webhook.go @@ -1,4 +1,4 @@ -package email +package webhook import ( "context" From 2994cb5c6536c8cf3578c6eb14d1c4722cd00530 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sun, 10 Nov 2024 20:28:01 +0800 Subject: [PATCH 10/10] test: add unit test case for email notifier --- .../notifier/providers/email/email_test.go | 51 +++++++++++++++++++ internal/pkg/utils/maps/maps.go | 6 +-- ui/src/i18n/locales/en/nls.settings.json | 2 +- 3 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 internal/pkg/core/notifier/providers/email/email_test.go diff --git a/internal/pkg/core/notifier/providers/email/email_test.go b/internal/pkg/core/notifier/providers/email/email_test.go new file mode 100644 index 00000000..1197a1a6 --- /dev/null +++ b/internal/pkg/core/notifier/providers/email/email_test.go @@ -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) +} diff --git a/internal/pkg/utils/maps/maps.go b/internal/pkg/utils/maps/maps.go index b475f09d..6f3c6fe6 100644 --- a/internal/pkg/utils/maps/maps.go +++ b/internal/pkg/utils/maps/maps.go @@ -154,10 +154,8 @@ func GetValueOrDefaultAsBool(dict map[string]any, key string, defaultValue bool) // 兼容字符串类型的值 if str, ok := value.(string); ok { - if str == "true" || str == "True" || str == "1" { - return true - } else if str == "false" || str == "False" || str == "0" { - return false + if result, err := strconv.ParseBool(str); err == nil { + return result } } } diff --git a/ui/src/i18n/locales/en/nls.settings.json b/ui/src/i18n/locales/en/nls.settings.json index b427a02a..580c4ef1 100644 --- a/ui/src/i18n/locales/en/nls.settings.json +++ b/ui/src/i18n/locales/en/nls.settings.json @@ -37,7 +37,7 @@ "settings.notification.email.smtp_host.placeholder": "Please enter SMTP host", "settings.notification.email.smtp_port.label": "SMTP Port", "settings.notification.email.smtp_port.placeholder": "Please enter SMTP port", - "settings.notification.email.smtp_tls.label": "TLS/SSL connection", + "settings.notification.email.smtp_tls.label": "Use TLS/SSL", "settings.notification.email.username.label": "Username", "settings.notification.email.username.placeholder": "please enter username", "settings.notification.email.password.label": "Password",