diff --git a/app/src/language/ar/app.po b/app/src/language/ar/app.po index 8f1659a67..46fac7908 100644 --- a/app/src/language/ar/app.po +++ b/app/src/language/ar/app.po @@ -2792,6 +2792,10 @@ msgstr "الانتقال إلى عارض السجلات الخام" msgid "Gotify" msgstr "غوتيفاي" +#: src/views/preference/components/ExternalNotify/ntfy.ts:5 +msgid "Ntfy" +msgstr "نتفي" + #: src/views/dashboard/components/SiteHealthCheckModal.vue:502 msgid "" "gRPC health check requires server to implement gRPC Health Check service " diff --git a/app/src/language/de_DE/app.po b/app/src/language/de_DE/app.po index abae11a17..e4449850e 100644 --- a/app/src/language/de_DE/app.po +++ b/app/src/language/de_DE/app.po @@ -2836,6 +2836,10 @@ msgstr "Zum Rohprotokoll-Viewer gehen" msgid "Gotify" msgstr "Gotify" +#: src/views/preference/components/ExternalNotify/ntfy.ts:5 +msgid "Ntfy" +msgstr "Ntfy" + #: src/views/dashboard/components/SiteHealthCheckModal.vue:502 msgid "" "gRPC health check requires server to implement gRPC Health Check service " diff --git a/app/src/language/en/app.po b/app/src/language/en/app.po index e4c65bcbe..cf600bf51 100644 --- a/app/src/language/en/app.po +++ b/app/src/language/en/app.po @@ -2713,6 +2713,10 @@ msgstr "" msgid "Gotify" msgstr "" +#: src/views/preference/components/ExternalNotify/ntfy.ts:5 +msgid "Ntfy" +msgstr "" + #: src/views/dashboard/components/SiteHealthCheckModal.vue:502 msgid "" "gRPC health check requires server to implement gRPC Health Check service " diff --git a/app/src/language/es/app.po b/app/src/language/es/app.po index 16b1f9a4e..779db5dbd 100644 --- a/app/src/language/es/app.po +++ b/app/src/language/es/app.po @@ -2841,6 +2841,10 @@ msgstr "Ir al Visor de Registros en Bruto" msgid "Gotify" msgstr "Gotify" +#: src/views/preference/components/ExternalNotify/ntfy.ts:5 +msgid "Ntfy" +msgstr "Ntfy" + #: src/views/dashboard/components/SiteHealthCheckModal.vue:502 msgid "" "gRPC health check requires server to implement gRPC Health Check service " diff --git a/app/src/language/fr_FR/app.po b/app/src/language/fr_FR/app.po index 439273593..f4b1e0b8f 100644 --- a/app/src/language/fr_FR/app.po +++ b/app/src/language/fr_FR/app.po @@ -2846,6 +2846,10 @@ msgstr "Aller au visualiseur de logs bruts" msgid "Gotify" msgstr "Gotify" +#: src/views/preference/components/ExternalNotify/ntfy.ts:5 +msgid "Ntfy" +msgstr "Ntfy" + #: src/views/dashboard/components/SiteHealthCheckModal.vue:502 msgid "" "gRPC health check requires server to implement gRPC Health Check service " diff --git a/app/src/language/ja_JP/app.po b/app/src/language/ja_JP/app.po index 0fb850cd8..43c250dad 100644 --- a/app/src/language/ja_JP/app.po +++ b/app/src/language/ja_JP/app.po @@ -2798,6 +2798,10 @@ msgstr "生ログビューアへ移動" msgid "Gotify" msgstr "Gotify" +#: src/views/preference/components/ExternalNotify/ntfy.ts:5 +msgid "Ntfy" +msgstr "Ntfy" + #: src/views/dashboard/components/SiteHealthCheckModal.vue:502 msgid "" "gRPC health check requires server to implement gRPC Health Check service " diff --git a/app/src/language/ko_KR/app.po b/app/src/language/ko_KR/app.po index 212a45c6a..1cf3a9715 100644 --- a/app/src/language/ko_KR/app.po +++ b/app/src/language/ko_KR/app.po @@ -2775,6 +2775,10 @@ msgstr "원시 로그 뷰어로 이동" msgid "Gotify" msgstr "Gotify" +#: src/views/preference/components/ExternalNotify/ntfy.ts:5 +msgid "Ntfy" +msgstr "Ntfy" + #: src/views/dashboard/components/SiteHealthCheckModal.vue:502 msgid "" "gRPC health check requires server to implement gRPC Health Check service " diff --git a/app/src/language/pt_PT/app.po b/app/src/language/pt_PT/app.po index c54529ab6..e839e3f3f 100644 --- a/app/src/language/pt_PT/app.po +++ b/app/src/language/pt_PT/app.po @@ -2821,6 +2821,10 @@ msgstr "Ir para o Visualizador de Logs Brutos" msgid "Gotify" msgstr "Gotify" +#: src/views/preference/components/ExternalNotify/ntfy.ts:5 +msgid "Ntfy" +msgstr "Ntfy" + #: src/views/dashboard/components/SiteHealthCheckModal.vue:502 msgid "" "gRPC health check requires server to implement gRPC Health Check service " diff --git a/app/src/language/ru_RU/app.po b/app/src/language/ru_RU/app.po index 37e4df962..3ec545c0c 100644 --- a/app/src/language/ru_RU/app.po +++ b/app/src/language/ru_RU/app.po @@ -2820,6 +2820,10 @@ msgstr "Перейти к просмотру сырых логов" msgid "Gotify" msgstr "Gotify" +#: src/views/preference/components/ExternalNotify/ntfy.ts:5 +msgid "Ntfy" +msgstr "Ntfy" + #: src/views/dashboard/components/SiteHealthCheckModal.vue:502 msgid "" "gRPC health check requires server to implement gRPC Health Check service " diff --git a/app/src/language/tr_TR/app.po b/app/src/language/tr_TR/app.po index 087edbaea..7aff6d3fb 100644 --- a/app/src/language/tr_TR/app.po +++ b/app/src/language/tr_TR/app.po @@ -2819,6 +2819,10 @@ msgstr "Ham Log Görüntüleyiciye Git" msgid "Gotify" msgstr "Gotify" +#: src/views/preference/components/ExternalNotify/ntfy.ts:5 +msgid "Ntfy" +msgstr "Ntfy" + #: src/views/dashboard/components/SiteHealthCheckModal.vue:502 msgid "" "gRPC health check requires server to implement gRPC Health Check service " diff --git a/app/src/language/uk_UA/app.po b/app/src/language/uk_UA/app.po index 216babd71..0ec5cc9be 100644 --- a/app/src/language/uk_UA/app.po +++ b/app/src/language/uk_UA/app.po @@ -2891,6 +2891,10 @@ msgstr "Перейти до перегляду сирих логів" msgid "Gotify" msgstr "Gotify" +#: src/views/preference/components/ExternalNotify/ntfy.ts:5 +msgid "Ntfy" +msgstr "Ntfy" + #: src/views/dashboard/components/SiteHealthCheckModal.vue:502 msgid "" "gRPC health check requires server to implement gRPC Health Check service " diff --git a/app/src/language/vi_VN/app.po b/app/src/language/vi_VN/app.po index 61e611cb7..cc8b2bc39 100644 --- a/app/src/language/vi_VN/app.po +++ b/app/src/language/vi_VN/app.po @@ -2791,6 +2791,10 @@ msgstr "Đi đến Trình xem Nhật ký Thô" msgid "Gotify" msgstr "Gotify" +#: src/views/preference/components/ExternalNotify/ntfy.ts:5 +msgid "Ntfy" +msgstr "Ntfy" + #: src/views/dashboard/components/SiteHealthCheckModal.vue:502 msgid "" "gRPC health check requires server to implement gRPC Health Check service " diff --git a/app/src/language/zh_CN/app.po b/app/src/language/zh_CN/app.po index ddd994bb4..34db81cae 100644 --- a/app/src/language/zh_CN/app.po +++ b/app/src/language/zh_CN/app.po @@ -2755,6 +2755,10 @@ msgstr "转到原始日志查看器" msgid "Gotify" msgstr "Gotify" +#: src/views/preference/components/ExternalNotify/ntfy.ts:5 +msgid "Ntfy" +msgstr "Ntfy" + #: src/views/dashboard/components/SiteHealthCheckModal.vue:502 msgid "" "gRPC health check requires server to implement gRPC Health Check service " diff --git a/app/src/language/zh_TW/app.po b/app/src/language/zh_TW/app.po index 154a41785..c7726c6f2 100644 --- a/app/src/language/zh_TW/app.po +++ b/app/src/language/zh_TW/app.po @@ -2760,6 +2760,10 @@ msgstr "轉到原始日誌查看器" msgid "Gotify" msgstr "Gotify" +#: src/views/preference/components/ExternalNotify/ntfy.ts:5 +msgid "Ntfy" +msgstr "Ntfy" + #: src/views/dashboard/components/SiteHealthCheckModal.vue:502 msgid "" "gRPC health check requires server to implement gRPC Health Check service " diff --git a/app/src/views/preference/components/ExternalNotify/index.ts b/app/src/views/preference/components/ExternalNotify/index.ts index 241191b75..d673a4635 100644 --- a/app/src/views/preference/components/ExternalNotify/index.ts +++ b/app/src/views/preference/components/ExternalNotify/index.ts @@ -4,6 +4,7 @@ import DingTalkConfig from './dingtalk' import GotifyConfig from './gotify' import LarkConfig from './lark' import LarkCustomConfig from './lark_custom' +import NtfyConfig from './ntfy' import TelegramConfig from './telegram' import WeComConfig from './wecom' @@ -13,6 +14,7 @@ const configMap = { gotify: GotifyConfig, lark: LarkConfig, lark_custom: LarkCustomConfig, + ntfy: NtfyConfig, telegram: TelegramConfig, wecom: WeComConfig, } diff --git a/app/src/views/preference/components/ExternalNotify/ntfy.ts b/app/src/views/preference/components/ExternalNotify/ntfy.ts new file mode 100644 index 000000000..4e0a91515 --- /dev/null +++ b/app/src/views/preference/components/ExternalNotify/ntfy.ts @@ -0,0 +1,46 @@ +// This file is auto-generated by notification generator. DO NOT EDIT. +import type { ExternalNotifyConfig } from './types' + +const NtfyConfig: ExternalNotifyConfig = { + name: () => $gettext('Ntfy'), + config: [ + { + key: 'server_url', + label: 'Server URL', + }, + { + key: 'topic', + label: 'Topic', + }, + { + key: 'priority', + label: 'Priority(int, one of: 1, 2, 3, 4, 5)', + }, + { + key: 'tags', + label: 'Tags(string array)', + }, + { + key: 'click', + label: 'Click URL', + }, + { + key: 'actions', + label: 'Actions(JSON array)', + }, + { + key: 'username', + label: 'Username', + }, + { + key: 'password', + label: 'Password', + }, + { + key: 'token', + label: 'Token', + }, + ], +} + +export default NtfyConfig diff --git a/internal/notification/ntfy.go b/internal/notification/ntfy.go new file mode 100644 index 000000000..1a4e3f2e0 --- /dev/null +++ b/internal/notification/ntfy.go @@ -0,0 +1,129 @@ +package notification + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/0xJacky/Nginx-UI/model" + "github.com/uozi-tech/cosy/map2struct" + "net/http" + "strconv" +) + +const ( + DEFAULT_NTFY_PRIORITY = 3 + DEFAULT_NTFY_ICON = "https://nginxui.com/assets/logo.svg" +) + +// @external_notifier(Ntfy) +type Ntfy struct { + ServerURL string `json:"server_url" title:"Server URL"` + Topic string `json:"topic" title:"Topic"` + Priority string `json:"priority" title:"Priority"` + Tags string `json:"tags" title:"Tags"` + Click string `json:"click" title:"Click URL"` + Actions string `json:"actions" title:"Actions"` + Username string `json:"username" title:"Username"` + Password string `json:"password" title:"Password"` + Token string `json:"token" title:"Token"` +} + +type NtfyMessage struct { + Topic string `json:"topic,omitempty"` + Message string `json:"message,omitempty"` + Title string `json:"title,omitempty"` + Priority int `json:"priority,omitempty"` + Tags []string `json:"tags,omitempty"` + Click string `json:"click,omitempty"` + Actions []interface{} `json:"actions,omitempty"` + Icon string `json:"icon,omitempty"` +} + +func init() { + RegisterExternalNotifier("ntfy", func(ctx context.Context, n *model.ExternalNotify, msg *ExternalMessage) error { + ntfyConfig := &Ntfy{} + err := map2struct.WeakDecode(n.Config, ntfyConfig) + if err != nil { + return err + } + if ntfyConfig.ServerURL == "" || ntfyConfig.Topic == "" { + return ErrInvalidNotifierConfig + } + + // Convert priority string to int + priority := DEFAULT_NTFY_PRIORITY + if ntfyConfig.Priority != "" { + p, err := strconv.Atoi(ntfyConfig.Priority) + if err != nil { + return fmt.Errorf("invalid priority: %w", err) + } + if p < 1 || p > 5 { + return fmt.Errorf("invalid priority: must be between 1 and 5") + } + priority = p + } + + // Prepare the message + ntfyMsg := NtfyMessage{ + Topic: ntfyConfig.Topic, + Message: msg.GetContent(n.Language), + Title: msg.GetTitle(n.Language), + Priority: priority, + Icon: DEFAULT_NTFY_ICON, + Click: ntfyConfig.Click, + } + + // Add tags if provided + if ntfyConfig.Tags != "" { + var tags []string + if err := json.Unmarshal([]byte(ntfyConfig.Tags), &tags); err != nil { + return fmt.Errorf("invalid tags: %w", err) + } + ntfyMsg.Tags = tags + } + + // Add actions if provided + if ntfyConfig.Actions != "" { + var actions []interface{} + if err := json.Unmarshal([]byte(ntfyConfig.Actions), &actions); err != nil { + return fmt.Errorf("invalid actions: %w", err) + } + ntfyMsg.Actions = actions + } + + // Create HTTP request + jsonData, err := json.Marshal(ntfyMsg) + if err != nil { + return fmt.Errorf("failed to marshal ntfy message: %w", err) + } + req, err := http.NewRequestWithContext(ctx, "POST", ntfyConfig.ServerURL, bytes.NewBuffer(jsonData)) + if err != nil { + return fmt.Errorf("failed to create HTTP request: %w", err) + } + + // Set headers + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", "Nginx-UI") + if ntfyConfig.Token != "" { + req.Header.Set("Authorization", "Bearer "+ntfyConfig.Token) + } else if ntfyConfig.Username != "" && ntfyConfig.Password != "" { + req.SetBasicAuth(ntfyConfig.Username, ntfyConfig.Password) + } + + // Send request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to send ntfy request: %w", err) + } + defer resp.Body.Close() + + // Check response status + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("ntfy request failed with status: %d", resp.StatusCode) + } + + return nil + }) +}