From 7a0972495f6d4c245c3268d2e98d86b8701dc11b Mon Sep 17 00:00:00 2001 From: Jacky Date: Wed, 9 Apr 2025 13:13:41 +0000 Subject: [PATCH] feat: add Lark and Lark Custom notification support --- .../components/Notification/notifications.ts | 12 +++--- .../ExternalNotify/ExternalNotifyEditor.vue | 1 - .../components/ExternalNotify/bark.ts | 4 +- .../components/ExternalNotify/dingtalk.ts | 4 +- .../components/ExternalNotify/index.ts | 4 ++ .../components/ExternalNotify/lark.ts | 14 ++++++ .../components/ExternalNotify/lark_custom.ts | 38 ++++++++++++++++ .../components/ExternalNotify/telegram.ts | 4 +- .../components/ExternalNotify/types.d.ts | 2 +- .../components/ExternalNotify/index.ts | 6 +++ cmd/external_notifier/generate.go | 26 +++++++---- internal/notification/bark.go | 5 +-- internal/notification/dingding.go | 6 +-- internal/notification/lark.go | 30 +++++++++++++ internal/notification/lark_custom.go | 43 +++++++++++++++++++ internal/notification/telegram.go | 7 +-- query/external_notifies.gen.go | 6 ++- 17 files changed, 174 insertions(+), 38 deletions(-) create mode 100644 app/src/views/preference/components/ExternalNotify/lark.ts create mode 100644 app/src/views/preference/components/ExternalNotify/lark_custom.ts create mode 100644 cmd/external_notifier/app/src/views/preference/components/ExternalNotify/index.ts create mode 100644 internal/notification/lark.go create mode 100644 internal/notification/lark_custom.go diff --git a/app/src/components/Notification/notifications.ts b/app/src/components/Notification/notifications.ts index 04e7bce0..d6f1f3e3 100644 --- a/app/src/components/Notification/notifications.ts +++ b/app/src/components/Notification/notifications.ts @@ -4,6 +4,12 @@ const notifications: Record string, content: (args: any) => string }> = { + // user module notifications + 'All Recovery Codes Have Been Used': { + title: () => $gettext('All Recovery Codes Have Been Used'), + content: (args: any) => $gettext('Please generate new recovery codes in the preferences immediately to prevent lockout.', args), + }, + // cluster module notifications 'Reload Remote Nginx Error': { title: () => $gettext('Reload Remote Nginx Error'), @@ -149,12 +155,6 @@ const notifications: Record string, content: (args: any) title: () => $gettext('Save Remote Stream Success'), content: (args: any) => $gettext('Save stream %{name} to %{node} successfully', args), }, - - // user module notifications - 'All Recovery Codes Have Been Used': { - title: () => $gettext('All Recovery Codes Have Been Used'), - content: (args: any) => $gettext('Please generate new recovery codes in the preferences immediately to prevent lockout.', args), - }, } export default notifications diff --git a/app/src/views/preference/components/ExternalNotify/ExternalNotifyEditor.vue b/app/src/views/preference/components/ExternalNotify/ExternalNotifyEditor.vue index 9f69d58b..df24192d 100644 --- a/app/src/views/preference/components/ExternalNotify/ExternalNotifyEditor.vue +++ b/app/src/views/preference/components/ExternalNotify/ExternalNotifyEditor.vue @@ -26,7 +26,6 @@ const columns = computed(() => { type: input, config: { label: item.label, - required: true, }, }, })) diff --git a/app/src/views/preference/components/ExternalNotify/bark.ts b/app/src/views/preference/components/ExternalNotify/bark.ts index 03ce3b36..2e22be4b 100644 --- a/app/src/views/preference/components/ExternalNotify/bark.ts +++ b/app/src/views/preference/components/ExternalNotify/bark.ts @@ -6,11 +6,11 @@ const BarkConfig: ExternalNotifyConfig = { config: [ { key: 'device_key', - label: () => $gettext('Device Key'), + label: 'Device Key', }, { key: 'server_url', - label: () => $gettext('Server URL'), + label: 'Server URL', }, ], } diff --git a/app/src/views/preference/components/ExternalNotify/dingtalk.ts b/app/src/views/preference/components/ExternalNotify/dingtalk.ts index 3bc5cf70..86cc6cbd 100644 --- a/app/src/views/preference/components/ExternalNotify/dingtalk.ts +++ b/app/src/views/preference/components/ExternalNotify/dingtalk.ts @@ -6,11 +6,11 @@ const DingTalkConfig: ExternalNotifyConfig = { config: [ { key: 'access_token', - label: () => $gettext('Access Token'), + label: 'Access Token', }, { key: 'secret', - label: () => $gettext('Secret (Optional)'), + label: 'Secret (Optional)', }, ], } diff --git a/app/src/views/preference/components/ExternalNotify/index.ts b/app/src/views/preference/components/ExternalNotify/index.ts index 3bd78d54..6f99a4a2 100644 --- a/app/src/views/preference/components/ExternalNotify/index.ts +++ b/app/src/views/preference/components/ExternalNotify/index.ts @@ -1,11 +1,15 @@ // This file is auto-generated by notification generator. DO NOT EDIT. import BarkConfig from './bark' import DingTalkConfig from './dingtalk' +import LarkConfig from './lark' +import LarkCustomConfig from './lark_custom' import TelegramConfig from './telegram' const configMap = { bark: BarkConfig, dingtalk: DingTalkConfig, + lark: LarkConfig, + lark_custom: LarkCustomConfig, telegram: TelegramConfig, } diff --git a/app/src/views/preference/components/ExternalNotify/lark.ts b/app/src/views/preference/components/ExternalNotify/lark.ts new file mode 100644 index 00000000..85b80337 --- /dev/null +++ b/app/src/views/preference/components/ExternalNotify/lark.ts @@ -0,0 +1,14 @@ +// This file is auto-generated by notification generator. DO NOT EDIT. +import type { ExternalNotifyConfig } from './types' + +const LarkConfig: ExternalNotifyConfig = { + name: () => $gettext('Lark'), + config: [ + { + key: 'webhook_url', + label: 'Webhook URL', + }, + ], +} + +export default LarkConfig diff --git a/app/src/views/preference/components/ExternalNotify/lark_custom.ts b/app/src/views/preference/components/ExternalNotify/lark_custom.ts new file mode 100644 index 00000000..c5df6fed --- /dev/null +++ b/app/src/views/preference/components/ExternalNotify/lark_custom.ts @@ -0,0 +1,38 @@ +// This file is auto-generated by notification generator. DO NOT EDIT. +import type { ExternalNotifyConfig } from './types' + +const LarkCustomConfig: ExternalNotifyConfig = { + name: () => $gettext('Lark Custom'), + config: [ + { + key: 'app_id', + label: 'App ID', + }, + { + key: 'app_secret', + label: 'App Secret', + }, + { + key: 'open_id', + label: 'Open ID', + }, + { + key: 'user_id', + label: 'User ID', + }, + { + key: 'union_id', + label: 'Union ID', + }, + { + key: 'email', + label: 'Email', + }, + { + key: 'chat_id', + label: 'Chat ID', + }, + ], +} + +export default LarkCustomConfig diff --git a/app/src/views/preference/components/ExternalNotify/telegram.ts b/app/src/views/preference/components/ExternalNotify/telegram.ts index f1771746..799d116a 100644 --- a/app/src/views/preference/components/ExternalNotify/telegram.ts +++ b/app/src/views/preference/components/ExternalNotify/telegram.ts @@ -6,11 +6,11 @@ const TelegramConfig: ExternalNotifyConfig = { config: [ { key: 'bot_token', - label: () => $gettext('Bot Token'), + label: 'Bot Token', }, { key: 'chat_id', - label: () => $gettext('Chat ID'), + label: 'Chat ID', }, ], } diff --git a/app/src/views/preference/components/ExternalNotify/types.d.ts b/app/src/views/preference/components/ExternalNotify/types.d.ts index a3f7f748..17bc8371 100644 --- a/app/src/views/preference/components/ExternalNotify/types.d.ts +++ b/app/src/views/preference/components/ExternalNotify/types.d.ts @@ -1,6 +1,6 @@ export interface ExternalNotifyConfigItem { key: string - label: () => string + label: string } export interface ExternalNotifyConfig { diff --git a/cmd/external_notifier/app/src/views/preference/components/ExternalNotify/index.ts b/cmd/external_notifier/app/src/views/preference/components/ExternalNotify/index.ts new file mode 100644 index 00000000..944ab082 --- /dev/null +++ b/cmd/external_notifier/app/src/views/preference/components/ExternalNotify/index.ts @@ -0,0 +1,6 @@ +// This file is auto-generated by notification generator. DO NOT EDIT. + +const configMap = { +} + +export default configMap diff --git a/cmd/external_notifier/generate.go b/cmd/external_notifier/generate.go index 130196e7..4c42f429 100644 --- a/cmd/external_notifier/generate.go +++ b/cmd/external_notifier/generate.go @@ -32,23 +32,23 @@ type FieldInfo struct { const tsConfigTemplate = `// This file is auto-generated by notification generator. DO NOT EDIT. import type { ExternalNotifyConfig } from './types' -const {{.Name}}Config: ExternalNotifyConfig = { +const {{.Name | replaceSpaces}}Config: ExternalNotifyConfig = { name: () => $gettext('{{.Name}}'), config: [ {{- range .Fields}} { key: '{{.Key}}', - label: () => $gettext('{{.Title}}'), + label: '{{.Title}}', }, {{- end}} ], } -export default {{.Name}}Config +export default {{.Name | replaceSpaces}}Config ` // Regular expression to extract @external_notifier annotation -var externalNotifierRegex = regexp.MustCompile(`@external_notifier\((\w+)\)`) +var externalNotifierRegex = regexp.MustCompile(`@external_notifier\(([a-zA-Z0-9 _]+)\)`) func main() { if err := GenerateExternalNotifiers(); err != nil { @@ -142,7 +142,7 @@ func extractNotifierInfo(filePath string) (NotifierInfo, bool) { if len(matches) > 1 { notifierInfo.Name = matches[1] notifierInfo.ConfigKey = strings.ToLower(typeSpec.Name.Name) - notifierInfo.FileName = strings.ToLower(matches[1]) + notifierInfo.FileName = strings.ToLower(strings.ReplaceAll(matches[1], " ", "_")) found = true // Extract fields @@ -205,8 +205,15 @@ func extractNotifierInfo(filePath string) (NotifierInfo, bool) { // Generate TypeScript config file for a notifier func generateTSConfig(notifier NotifierInfo, outputDir string) error { - // Create template - tmpl, err := template.New("tsConfig").Parse(tsConfigTemplate) + // Create function map for template + funcMap := template.FuncMap{ + "replaceSpaces": func(s string) string { + return strings.ReplaceAll(s, " ", "") + }, + } + + // Create template with function map + tmpl, err := template.New("tsConfig").Funcs(funcMap).Parse(tsConfigTemplate) if err != nil { return fmt.Errorf("error creating template: %w", err) } @@ -242,7 +249,7 @@ func updateIndexFile(notifiers []NotifierInfo, outputDir string) error { for _, notifier := range notifiers { fileName := notifier.FileName - configName := notifier.Name + "Config" + configName := strings.ReplaceAll(notifier.Name, " ", "") + "Config" imports.WriteString(fmt.Sprintf("import %s from './%s'\n", configName, fileName)) } @@ -250,7 +257,8 @@ func updateIndexFile(notifiers []NotifierInfo, outputDir string) error { // Generate the map configMap.WriteString("const configMap = {\n") for _, notifier := range notifiers { - configMap.WriteString(fmt.Sprintf(" %s: %sConfig", strings.ToLower(notifier.Name), notifier.Name)) + configKey := strings.ToLower(strings.ReplaceAll(notifier.Name, " ", "_")) + configMap.WriteString(fmt.Sprintf(" %s: %sConfig", configKey, strings.ReplaceAll(notifier.Name, " ", ""))) configMap.WriteString(",\n") } configMap.WriteString("}\n") diff --git a/internal/notification/bark.go b/internal/notification/bark.go index 0b35f541..046c3947 100644 --- a/internal/notification/bark.go +++ b/internal/notification/bark.go @@ -4,7 +4,6 @@ import ( "context" "github.com/0xJacky/Nginx-UI/model" - "github.com/nikoksr/notify" "github.com/nikoksr/notify/service/bark" "github.com/uozi-tech/cosy/map2struct" ) @@ -26,8 +25,6 @@ func init() { return ErrInvalidNotifierConfig } barkService := bark.NewWithServers(barkConfig.DeviceKey, barkConfig.ServerURL) - externalNotify := notify.New() - externalNotify.UseServices(barkService) - return externalNotify.Send(ctx, msg.GetTitle(n.Language), msg.GetContent(n.Language)) + return barkService.Send(ctx, msg.GetTitle(n.Language), msg.GetContent(n.Language)) }) } diff --git a/internal/notification/dingding.go b/internal/notification/dingding.go index 5be4f5ca..ba2361fc 100644 --- a/internal/notification/dingding.go +++ b/internal/notification/dingding.go @@ -4,7 +4,6 @@ import ( "context" "github.com/0xJacky/Nginx-UI/model" - "github.com/nikoksr/notify" "github.com/nikoksr/notify/service/dingding" "github.com/uozi-tech/cosy/map2struct" ) @@ -31,9 +30,6 @@ func init() { Token: dingTalkConfig.AccessToken, Secret: dingTalkConfig.Secret, }) - // Use the service - externalNotify := notify.New() - externalNotify.UseServices(dingTalkService) - return externalNotify.Send(ctx, msg.GetTitle(n.Language), msg.GetContent(n.Language)) + return dingTalkService.Send(ctx, msg.GetTitle(n.Language), msg.GetContent(n.Language)) }) } diff --git a/internal/notification/lark.go b/internal/notification/lark.go new file mode 100644 index 00000000..dd0c738e --- /dev/null +++ b/internal/notification/lark.go @@ -0,0 +1,30 @@ +package notification + +import ( + "context" + + "github.com/0xJacky/Nginx-UI/model" + "github.com/nikoksr/notify/service/lark" + "github.com/uozi-tech/cosy/map2struct" +) + +// @external_notifier(Lark) +type Lark struct { + WebhookURL string `json:"webhook_url" title:"Webhook URL"` +} + +func init() { + RegisterExternalNotifier("lark", func(ctx context.Context, n *model.ExternalNotify, msg *ExternalMessage) error { + larkConfig := &Lark{} + err := map2struct.WeakDecode(n.Config, larkConfig) + if err != nil { + return err + } + if larkConfig.WebhookURL == "" { + return ErrInvalidNotifierConfig + } + + larkService := lark.NewWebhookService(larkConfig.WebhookURL) + return larkService.Send(ctx, msg.GetTitle(n.Language), msg.GetContent(n.Language)) + }) +} diff --git a/internal/notification/lark_custom.go b/internal/notification/lark_custom.go new file mode 100644 index 00000000..c54a4225 --- /dev/null +++ b/internal/notification/lark_custom.go @@ -0,0 +1,43 @@ +package notification + +import ( + "context" + + "github.com/0xJacky/Nginx-UI/model" + "github.com/nikoksr/notify/service/lark" + "github.com/uozi-tech/cosy/map2struct" +) + +// @external_notifier(Lark Custom) +type LarkCustom struct { + AppID string `json:"app_id" title:"App ID"` + AppSecret string `json:"app_secret" title:"App Secret"` + OpenID string `json:"open_id" title:"Open ID"` + UserID string `json:"user_id" title:"User ID"` + UnionID string `json:"union_id" title:"Union ID"` + Email string `json:"email" title:"Email"` + ChatID string `json:"chat_id" title:"Chat ID"` +} + +func init() { + RegisterExternalNotifier("lark_custom", func(ctx context.Context, n *model.ExternalNotify, msg *ExternalMessage) error { + larkCustomConfig := &LarkCustom{} + err := map2struct.WeakDecode(n.Config, larkCustomConfig) + if err != nil { + return err + } + if larkCustomConfig.AppID == "" || larkCustomConfig.AppSecret == "" { + return ErrInvalidNotifierConfig + } + + larkCustomAppService := lark.NewCustomAppService(larkCustomConfig.AppID, larkCustomConfig.AppSecret) + larkCustomAppService.AddReceivers( + lark.OpenID(larkCustomConfig.OpenID), + lark.UserID(larkCustomConfig.UserID), + lark.UnionID(larkCustomConfig.UnionID), + lark.Email(larkCustomConfig.Email), + lark.ChatID(larkCustomConfig.ChatID), + ) + return larkCustomAppService.Send(ctx, msg.GetTitle(n.Language), msg.GetContent(n.Language)) + }) +} diff --git a/internal/notification/telegram.go b/internal/notification/telegram.go index 5f690964..657ea645 100644 --- a/internal/notification/telegram.go +++ b/internal/notification/telegram.go @@ -7,7 +7,6 @@ import ( "strconv" "github.com/0xJacky/Nginx-UI/model" - "github.com/nikoksr/notify" "github.com/nikoksr/notify/service/telegram" "github.com/uozi-tech/cosy/map2struct" ) @@ -46,9 +45,7 @@ func init() { } telegramService.AddReceivers(chatIDInt) - - externalNotify := notify.New() - externalNotify.UseServices(telegramService) - return externalNotify.Send(ctx, msg.GetTitle(n.Language), msg.GetContent(n.Language)) + + return telegramService.Send(ctx, msg.GetTitle(n.Language), msg.GetContent(n.Language)) }) } diff --git a/query/external_notifies.gen.go b/query/external_notifies.gen.go index 8136464c..5b153d54 100644 --- a/query/external_notifies.gen.go +++ b/query/external_notifies.gen.go @@ -33,6 +33,7 @@ func newExternalNotify(db *gorm.DB, opts ...gen.DOOption) externalNotify { _externalNotify.UpdatedAt = field.NewTime(tableName, "updated_at") _externalNotify.DeletedAt = field.NewField(tableName, "deleted_at") _externalNotify.Type = field.NewString(tableName, "type") + _externalNotify.Language = field.NewString(tableName, "language") _externalNotify.Config = field.NewField(tableName, "config") _externalNotify.fillFieldMap() @@ -49,6 +50,7 @@ type externalNotify struct { UpdatedAt field.Time DeletedAt field.Field Type field.String + Language field.String Config field.Field fieldMap map[string]field.Expr @@ -71,6 +73,7 @@ func (e *externalNotify) updateTableName(table string) *externalNotify { e.UpdatedAt = field.NewTime(table, "updated_at") e.DeletedAt = field.NewField(table, "deleted_at") e.Type = field.NewString(table, "type") + e.Language = field.NewString(table, "language") e.Config = field.NewField(table, "config") e.fillFieldMap() @@ -88,12 +91,13 @@ func (e *externalNotify) GetFieldByName(fieldName string) (field.OrderExpr, bool } func (e *externalNotify) fillFieldMap() { - e.fieldMap = make(map[string]field.Expr, 6) + e.fieldMap = make(map[string]field.Expr, 7) e.fieldMap["id"] = e.ID e.fieldMap["created_at"] = e.CreatedAt e.fieldMap["updated_at"] = e.UpdatedAt e.fieldMap["deleted_at"] = e.DeletedAt e.fieldMap["type"] = e.Type + e.fieldMap["language"] = e.Language e.fieldMap["config"] = e.Config }