From 891d3a87596fbd724ce00abf1743a220c3f24124 Mon Sep 17 00:00:00 2001 From: Sentiago <76674116+Ssentiago@users.noreply.github.com> Date: Sun, 21 Jun 2026 18:45:33 +0300 Subject: [PATCH] feat(memory): add memory threshold alerts (#5366) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(memory): add memory threshold alerts Add memory (RAM) threshold alerts following the same architecture as CPU alerts: CheckMemJob with @every 1m cadence, memoryAlarmWanted gate, tgMemory/smtpMemory per-subscriber settings (default 80%), EventBusCheckboxes with inline threshold input, i18n for en-US/ru-RU with English defaults. # Conflicts: # internal/web/translation/ar-EG.json # internal/web/translation/es-ES.json # internal/web/translation/fa-IR.json # internal/web/translation/id-ID.json # internal/web/translation/ja-JP.json # internal/web/translation/pt-BR.json # internal/web/translation/ru-RU.json # internal/web/translation/tr-TR.json # internal/web/translation/uk-UA.json # internal/web/translation/vi-VN.json # internal/web/translation/zh-CN.json # internal/web/translation/zh-TW.json * fix: address code review findings for memory alerts - Remove dead settingService field from CheckMemJob - Fix cpuThreshold double-emoji in 12 locale files (code prepends 🔴) - Align TgCpu/TgMemory fields in entity.go - Add missing SetTgMemory function * fix: restore settingService in CheckMemJob for consistency with CheckCpuJob --- frontend/public/openapi.json | 28 ++++++++++++++ .../ui/notifications/EmailNotifications.tsx | 8 ++++ .../notifications/TelegramNotifications.tsx | 8 ++++ frontend/src/generated/examples.ts | 4 ++ frontend/src/generated/schemas.ts | 28 ++++++++++++++ frontend/src/generated/types.ts | 4 ++ frontend/src/generated/zod.ts | 4 ++ frontend/src/models/setting.ts | 2 + internal/eventbus/events.go | 3 +- internal/web/entity/entity.go | 4 +- internal/web/job/check_memory_usage.go | 35 ++++++++++++++++++ internal/web/service/email/subscriber.go | 13 +++++++ internal/web/service/setting.go | 18 +++++++++ internal/web/service/tgbot/tgbot_event.go | 12 ++++++ internal/web/translation/ar-EG.json | 6 ++- internal/web/translation/en-US.json | 8 ++-- internal/web/translation/es-ES.json | 8 ++-- internal/web/translation/fa-IR.json | 10 +++-- internal/web/translation/id-ID.json | 6 ++- internal/web/translation/ja-JP.json | 8 ++-- internal/web/translation/pt-BR.json | 8 ++-- internal/web/translation/ru-RU.json | 8 ++-- internal/web/translation/tr-TR.json | 6 ++- internal/web/translation/uk-UA.json | 8 ++-- internal/web/translation/vi-VN.json | 8 ++-- internal/web/translation/zh-CN.json | 6 ++- internal/web/translation/zh-TW.json | 8 ++-- internal/web/web.go | 37 ++++++++++++++++++- 28 files changed, 267 insertions(+), 39 deletions(-) create mode 100644 internal/web/job/check_memory_usage.go diff --git a/frontend/public/openapi.json b/frontend/public/openapi.json index a5e2d9222..d2c6c2714 100644 --- a/frontend/public/openapi.json +++ b/frontend/public/openapi.json @@ -160,6 +160,12 @@ "description": "SMTP server host", "type": "string" }, + "smtpMemory": { + "description": "Memory threshold for email notifications", + "maximum": 100, + "minimum": 0, + "type": "integer" + }, "smtpPassword": { "description": "SMTP password", "type": "string" @@ -335,6 +341,12 @@ "description": "Telegram bot language", "type": "string" }, + "tgMemory": { + "description": "Memory usage threshold for alerts (percent)", + "maximum": 100, + "minimum": 0, + "type": "integer" + }, "tgRunTime": { "description": "Cron schedule for Telegram notifications", "type": "string" @@ -428,6 +440,7 @@ "smtpEnabledEvents", "smtpEncryptionType", "smtpHost", + "smtpMemory", "smtpPassword", "smtpPort", "smtpTo", @@ -470,6 +483,7 @@ "tgCpu", "tgEnabledEvents", "tgLang", + "tgMemory", "tgRunTime", "timeLocation", "trafficDiff", @@ -641,6 +655,12 @@ "description": "SMTP server host", "type": "string" }, + "smtpMemory": { + "description": "Memory threshold for email notifications", + "maximum": 100, + "minimum": 0, + "type": "integer" + }, "smtpPassword": { "description": "SMTP password", "type": "string" @@ -816,6 +836,12 @@ "description": "Telegram bot language", "type": "string" }, + "tgMemory": { + "description": "Memory usage threshold for alerts (percent)", + "maximum": 100, + "minimum": 0, + "type": "integer" + }, "tgRunTime": { "description": "Cron schedule for Telegram notifications", "type": "string" @@ -916,6 +942,7 @@ "smtpEnabledEvents", "smtpEncryptionType", "smtpHost", + "smtpMemory", "smtpPassword", "smtpPort", "smtpTo", @@ -958,6 +985,7 @@ "tgCpu", "tgEnabledEvents", "tgLang", + "tgMemory", "tgRunTime", "timeLocation", "trafficDiff", diff --git a/frontend/src/components/ui/notifications/EmailNotifications.tsx b/frontend/src/components/ui/notifications/EmailNotifications.tsx index 9d760af9a..d458dd7d5 100644 --- a/frontend/src/components/ui/notifications/EmailNotifications.tsx +++ b/frontend/src/components/ui/notifications/EmailNotifications.tsx @@ -41,6 +41,14 @@ const GROUPS: NotificationGroupConfig[] = [ ), }, + { + key: 'memory.high', + label: 'eventMemoryHigh', + settingKey: 'smtpMemory', + extra: ({ value, onChange }) => ( + + ), + }, ], }, { diff --git a/frontend/src/components/ui/notifications/TelegramNotifications.tsx b/frontend/src/components/ui/notifications/TelegramNotifications.tsx index 4fa7dfc6a..2ce63c4b1 100644 --- a/frontend/src/components/ui/notifications/TelegramNotifications.tsx +++ b/frontend/src/components/ui/notifications/TelegramNotifications.tsx @@ -41,6 +41,14 @@ const GROUPS: NotificationGroupConfig[] = [ ), }, + { + key: 'memory.high', + label: 'eventMemoryHigh', + settingKey: 'tgMemory', + extra: ({ value, onChange }) => ( + + ), + }, ], }, { diff --git a/frontend/src/generated/examples.ts b/frontend/src/generated/examples.ts index af7c57f1f..e476514f1 100644 --- a/frontend/src/generated/examples.ts +++ b/frontend/src/generated/examples.ts @@ -35,6 +35,7 @@ export const EXAMPLES: Record = { "smtpEnabledEvents": "", "smtpEncryptionType": "", "smtpHost": "", + "smtpMemory": 0, "smtpPassword": "", "smtpPort": 1, "smtpTo": "", @@ -77,6 +78,7 @@ export const EXAMPLES: Record = { "tgCpu": 0, "tgEnabledEvents": "", "tgLang": "", + "tgMemory": 0, "tgRunTime": "", "timeLocation": "", "trafficDiff": 0, @@ -133,6 +135,7 @@ export const EXAMPLES: Record = { "smtpEnabledEvents": "", "smtpEncryptionType": "", "smtpHost": "", + "smtpMemory": 0, "smtpPassword": "", "smtpPort": 1, "smtpTo": "", @@ -175,6 +178,7 @@ export const EXAMPLES: Record = { "tgCpu": 0, "tgEnabledEvents": "", "tgLang": "", + "tgMemory": 0, "tgRunTime": "", "timeLocation": "", "trafficDiff": 0, diff --git a/frontend/src/generated/schemas.ts b/frontend/src/generated/schemas.ts index 9d33ca30d..7fed98bd3 100644 --- a/frontend/src/generated/schemas.ts +++ b/frontend/src/generated/schemas.ts @@ -134,6 +134,12 @@ export const SCHEMAS: Record = { "description": "SMTP server host", "type": "string" }, + "smtpMemory": { + "description": "Memory threshold for email notifications", + "maximum": 100, + "minimum": 0, + "type": "integer" + }, "smtpPassword": { "description": "SMTP password", "type": "string" @@ -309,6 +315,12 @@ export const SCHEMAS: Record = { "description": "Telegram bot language", "type": "string" }, + "tgMemory": { + "description": "Memory usage threshold for alerts (percent)", + "maximum": 100, + "minimum": 0, + "type": "integer" + }, "tgRunTime": { "description": "Cron schedule for Telegram notifications", "type": "string" @@ -402,6 +414,7 @@ export const SCHEMAS: Record = { "smtpEnabledEvents", "smtpEncryptionType", "smtpHost", + "smtpMemory", "smtpPassword", "smtpPort", "smtpTo", @@ -444,6 +457,7 @@ export const SCHEMAS: Record = { "tgCpu", "tgEnabledEvents", "tgLang", + "tgMemory", "tgRunTime", "timeLocation", "trafficDiff", @@ -615,6 +629,12 @@ export const SCHEMAS: Record = { "description": "SMTP server host", "type": "string" }, + "smtpMemory": { + "description": "Memory threshold for email notifications", + "maximum": 100, + "minimum": 0, + "type": "integer" + }, "smtpPassword": { "description": "SMTP password", "type": "string" @@ -790,6 +810,12 @@ export const SCHEMAS: Record = { "description": "Telegram bot language", "type": "string" }, + "tgMemory": { + "description": "Memory usage threshold for alerts (percent)", + "maximum": 100, + "minimum": 0, + "type": "integer" + }, "tgRunTime": { "description": "Cron schedule for Telegram notifications", "type": "string" @@ -890,6 +916,7 @@ export const SCHEMAS: Record = { "smtpEnabledEvents", "smtpEncryptionType", "smtpHost", + "smtpMemory", "smtpPassword", "smtpPort", "smtpTo", @@ -932,6 +959,7 @@ export const SCHEMAS: Record = { "tgCpu", "tgEnabledEvents", "tgLang", + "tgMemory", "tgRunTime", "timeLocation", "trafficDiff", diff --git a/frontend/src/generated/types.ts b/frontend/src/generated/types.ts index 674570ada..b8c8f45d6 100644 --- a/frontend/src/generated/types.ts +++ b/frontend/src/generated/types.ts @@ -41,6 +41,7 @@ export interface AllSetting { smtpEnabledEvents: string; smtpEncryptionType: string; smtpHost: string; + smtpMemory: number; smtpPassword: string; smtpPort: number; smtpTo: string; @@ -83,6 +84,7 @@ export interface AllSetting { tgCpu: number; tgEnabledEvents: string; tgLang: string; + tgMemory: number; tgRunTime: string; timeLocation: string; trafficDiff: number; @@ -140,6 +142,7 @@ export interface AllSettingView { smtpEnabledEvents: string; smtpEncryptionType: string; smtpHost: string; + smtpMemory: number; smtpPassword: string; smtpPort: number; smtpTo: string; @@ -182,6 +185,7 @@ export interface AllSettingView { tgCpu: number; tgEnabledEvents: string; tgLang: string; + tgMemory: number; tgRunTime: string; timeLocation: string; trafficDiff: number; diff --git a/frontend/src/generated/zod.ts b/frontend/src/generated/zod.ts index 7b3d6e6f3..458e2f99b 100644 --- a/frontend/src/generated/zod.ts +++ b/frontend/src/generated/zod.ts @@ -53,6 +53,7 @@ export const AllSettingSchema = z.object({ smtpEnabledEvents: z.string(), smtpEncryptionType: z.string(), smtpHost: z.string(), + smtpMemory: z.number().int().min(0).max(100), smtpPassword: z.string(), smtpPort: z.number().int().min(1).max(65535), smtpTo: z.string(), @@ -95,6 +96,7 @@ export const AllSettingSchema = z.object({ tgCpu: z.number().int().min(0).max(100), tgEnabledEvents: z.string(), tgLang: z.string(), + tgMemory: z.number().int().min(0).max(100), tgRunTime: z.string(), timeLocation: z.string(), trafficDiff: z.number().int().min(0).max(100), @@ -153,6 +155,7 @@ export const AllSettingViewSchema = z.object({ smtpEnabledEvents: z.string(), smtpEncryptionType: z.string(), smtpHost: z.string(), + smtpMemory: z.number().int().min(0).max(100), smtpPassword: z.string(), smtpPort: z.number().int().min(1).max(65535), smtpTo: z.string(), @@ -195,6 +198,7 @@ export const AllSettingViewSchema = z.object({ tgCpu: z.number().int().min(0).max(100), tgEnabledEvents: z.string(), tgLang: z.string(), + tgMemory: z.number().int().min(0).max(100), tgRunTime: z.string(), timeLocation: z.string(), trafficDiff: z.number().int().min(0).max(100), diff --git a/frontend/src/models/setting.ts b/frontend/src/models/setting.ts index e76e08d67..f2d630d83 100644 --- a/frontend/src/models/setting.ts +++ b/frontend/src/models/setting.ts @@ -22,6 +22,7 @@ export class AllSetting { tgRunTime = '@daily'; tgBotBackup = false; tgCpu = 80; + tgMemory = 80; tgLang = 'en-US'; twoFactorEnable = false; twoFactorToken = ''; @@ -91,6 +92,7 @@ export class AllSetting { smtpEncryptionType = 'starttls'; smtpEnabledEvents = ''; smtpCpu = 80; + smtpMemory = 80; hasTgBotToken = false; hasTwoFactorToken = false; hasLdapPassword = false; diff --git a/internal/eventbus/events.go b/internal/eventbus/events.go index fdd44d601..b181bfc48 100644 --- a/internal/eventbus/events.go +++ b/internal/eventbus/events.go @@ -18,7 +18,8 @@ const ( EventNodeUp EventType = "node.up" // System health - EventCPUHigh EventType = "cpu.high" + EventCPUHigh EventType = "cpu.high" + EventMemoryHigh EventType = "memory.high" // Security EventLoginAttempt EventType = "login.attempt" diff --git a/internal/web/entity/entity.go b/internal/web/entity/entity.go index d651c06c9..33b2b52e3 100644 --- a/internal/web/entity/entity.go +++ b/internal/web/entity/entity.go @@ -47,6 +47,7 @@ type AllSetting struct { TgRunTime string `json:"tgRunTime" form:"tgRunTime"` // Cron schedule for Telegram notifications TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"` // Enable database backup via Telegram TgCpu int `json:"tgCpu" form:"tgCpu" validate:"gte=0,lte=100"` // CPU usage threshold for alerts (percent) + TgMemory int `json:"tgMemory" form:"tgMemory" validate:"gte=0,lte=100"` // Memory usage threshold for alerts (percent) TgLang string `json:"tgLang" form:"tgLang"` // Telegram bot language TgEnabledEvents string `json:"tgEnabledEvents" form:"tgEnabledEvents"` // Comma-separated event types to send via Telegram @@ -59,7 +60,8 @@ type AllSetting struct { SmtpTo string `json:"smtpTo" form:"smtpTo"` // Comma-separated recipient emails SmtpEncryptionType string `json:"smtpEncryptionType" form:"smtpEncryptionType"` // SMTP encryption: none, starttls, tls SmtpEnabledEvents string `json:"smtpEnabledEvents" form:"smtpEnabledEvents"` // Comma-separated event types to send via email - SmtpCpu int `json:"smtpCpu" form:"smtpCpu" validate:"gte=0,lte=100"` // CPU threshold for email notifications + SmtpCpu int `json:"smtpCpu" form:"smtpCpu" validate:"gte=0,lte=100"` // CPU threshold for email notifications + SmtpMemory int `json:"smtpMemory" form:"smtpMemory" validate:"gte=0,lte=100"` // Memory threshold for email notifications // Security settings TimeLocation string `json:"timeLocation" form:"timeLocation"` // Time zone location diff --git a/internal/web/job/check_memory_usage.go b/internal/web/job/check_memory_usage.go new file mode 100644 index 000000000..18dfd3a7a --- /dev/null +++ b/internal/web/job/check_memory_usage.go @@ -0,0 +1,35 @@ +package job + +import ( + "github.com/mhsanaei/3x-ui/v3/internal/eventbus" + "github.com/mhsanaei/3x-ui/v3/internal/web/service" + + "github.com/shirou/gopsutil/v4/mem" +) + +// CheckMemJob monitors memory usage and publishes events when threshold is exceeded. +type CheckMemJob struct { + settingService service.SettingService +} + +// NewCheckMemJob creates a new memory monitoring job instance. +func NewCheckMemJob() *CheckMemJob { + return new(CheckMemJob) +} + +// Run checks memory usage and publishes a memory.high event with raw metric data. +func (j *CheckMemJob) Run() { + memInfo, err := mem.VirtualMemory() + if err != nil || memInfo == nil { + return + } + + if EventBus != nil { + EventBus.Publish(eventbus.Event{ + Type: eventbus.EventMemoryHigh, + Data: &eventbus.SystemMetricData{ + Percent: memInfo.UsedPercent, + }, + }) + } +} diff --git a/internal/web/service/email/subscriber.go b/internal/web/service/email/subscriber.go index f9fdd0989..bb4a319e9 100644 --- a/internal/web/service/email/subscriber.go +++ b/internal/web/service/email/subscriber.go @@ -148,6 +148,19 @@ func (s *Subscriber) formatMessage(e eventbus.Event) (subject, body string) { body = wrap(subject, content) } + case eventbus.EventMemoryHigh: + if data, ok := e.Data.(*eventbus.SystemMetricData); ok { + smtpMemory, err := s.settingService.GetSmtpMemory() + if err != nil || smtpMemory <= 0 || data.Percent <= float64(smtpMemory) { + return + } + subject = host + " " + i18n("tgbot.messages.memoryThreshold", + "Percent=="+strconv.FormatFloat(data.Percent, 'f', 2, 64), + "Threshold=="+fmt.Sprintf("%d", smtpMemory)) + content := kv(i18n("email.labelStatus"), ``+i18n("email.statusHigh")+``) + body = wrap(subject, content) + } + case eventbus.EventLoginAttempt: if data, ok := e.Data.(*eventbus.LoginEventData); ok { if data.Status == "success" { diff --git a/internal/web/service/setting.go b/internal/web/service/setting.go index 7a5cd411b..0021cec21 100644 --- a/internal/web/service/setting.go +++ b/internal/web/service/setting.go @@ -63,6 +63,7 @@ var defaultValueMap = map[string]string{ "tgRunTime": "@daily", "tgBotBackup": "false", "tgCpu": "80", + "tgMemory": "80", "tgLang": "en-US", "twoFactorEnable": "false", "twoFactorToken": "", @@ -131,6 +132,7 @@ var defaultValueMap = map[string]string{ "tgEnabledEvents": "login.attempt,cpu.high", "smtpEnabledEvents": "login.attempt,cpu.high", "smtpCpu": "80", + "smtpMemory": "80", // Email (SMTP) notifications "smtpEnable": "false", @@ -531,6 +533,14 @@ func (s *SettingService) GetTgCpu() (int, error) { return s.getInt("tgCpu") } +func (s *SettingService) GetTgMemory() (int, error) { + return s.getInt("tgMemory") +} + +func (s *SettingService) SetTgMemory(value int) error { + return s.setInt("tgMemory", value) +} + func (s *SettingService) GetTgLang() (string, error) { return s.getString("tgLang") } @@ -1017,6 +1027,14 @@ func (s *SettingService) SetSmtpCpu(value int) error { return s.setInt("smtpCpu", value) } +func (s *SettingService) GetSmtpMemory() (int, error) { + return s.getInt("smtpMemory") +} + +func (s *SettingService) SetSmtpMemory(value int) error { + return s.setInt("smtpMemory", value) +} + func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error { if err := s.preserveRedactedSecrets(allSetting); err != nil { return err diff --git a/internal/web/service/tgbot/tgbot_event.go b/internal/web/service/tgbot/tgbot_event.go index d66784cd6..822dacf57 100644 --- a/internal/web/service/tgbot/tgbot_event.go +++ b/internal/web/service/tgbot/tgbot_event.go @@ -123,6 +123,18 @@ func (t *Tgbot) formatEventMessage(e eventbus.Event) string { } return "" + case eventbus.EventMemoryHigh: + if data, ok := e.Data.(*eventbus.SystemMetricData); ok { + tgMemory, err := t.settingService.GetTgMemory() + if err != nil || tgMemory <= 0 || data.Percent <= float64(tgMemory) { + return "" + } + return header + "🔴 " + t.I18nBot("tgbot.messages.memoryThreshold", + "Percent=="+strconv.FormatFloat(data.Percent, 'f', 2, 64), + "Threshold=="+strconv.Itoa(tgMemory)) + } + return "" + case eventbus.EventLoginAttempt: if data, ok := e.Data.(*eventbus.LoginEventData); ok { if data.Status == "success" { diff --git a/internal/web/translation/ar-EG.json b/internal/web/translation/ar-EG.json index cbfa078d4..e6fe2dddb 100644 --- a/internal/web/translation/ar-EG.json +++ b/internal/web/translation/ar-EG.json @@ -1309,6 +1309,7 @@ "smtpErrorRelay": "الخادم يرفض الإرسال من هذا العنوان", "smtpErrorEof": "تم إغلاق الاتصال من قبل الخادم", "smtpErrorUnknown": "خطأ SMTP: {{ .Error }}", + "eventMemoryHigh": "ارتفاع استخدام الذاكرة (%)", "remarkTemplate": "قالب الملاحظة", "remarkTemplateDesc": "عند تعيينه، يحل هذا محل نموذج الملاحظة لكل رابط اشتراك — اكتب صيغتك الخاصة باستخدام رموز المتغيرات (استخدم الزر لإدراجها). اتركه فارغاً لاستخدام النموذج أعلاه." }, @@ -1865,7 +1866,7 @@ "idDesc": "عرض معرف Telegram الخاص بك" }, "messages": { - "cpuThreshold": "🔴 حمل المعالج {{ .Percent }}% عدى الحد المسموح ({{ .Threshold }}%)", + "cpuThreshold": "حمل المعالج {{ .Percent }}% عدى الحد المسموح ({{ .Threshold }}%)", "selectUserFailed": "❌ حصل خطأ في اختيار المستخدم!", "userSaved": "✅ حفظت بيانات مستخدم Telegram.", "loginSuccess": "✅ تسجيل الدخول للبانل تم بنجاح.\r\n", @@ -1940,7 +1941,8 @@ "eventNodeUp": "العقدة {{ .Name }} متصلة", "eventCPUHigh": "ارتفاع استخدام المعالج", "eventCPUHighDetail": "المعالج: {{ .Detail }}", - "eventLoginFallback": "فشل تسجيل الدخول من {{ .Source }}" + "eventLoginFallback": "فشل تسجيل الدخول من {{ .Source }}", + "memoryThreshold": "استخدام الذاكرة {{ .Percent }}% يتجاوز الحد {{ .Threshold }}%" }, "buttons": { "closeKeyboard": "❌ اقفل الكيبورد", diff --git a/internal/web/translation/en-US.json b/internal/web/translation/en-US.json index 844aff10d..a5558670f 100644 --- a/internal/web/translation/en-US.json +++ b/internal/web/translation/en-US.json @@ -1418,7 +1418,8 @@ "smtpErrorTimeout": "Connection timeout — host unreachable", "smtpErrorRelay": "Server rejects sending from this address", "smtpErrorEof": "Connection closed by server", - "smtpErrorUnknown": "SMTP error: {{ .Error }}" + "smtpErrorUnknown": "SMTP error: {{ .Error }}", + "eventMemoryHigh": "Memory high (%)" }, "xray": { "title": "Xray Configs", @@ -1865,7 +1866,7 @@ "idDesc": "Show your Telegram ID" }, "messages": { - "cpuThreshold": "🔴 CPU Load {{ .Percent }}% exceeds the threshold of {{ .Threshold }}%", + "cpuThreshold": "CPU Load {{ .Percent }}% exceeds the threshold of {{ .Threshold }}%", "selectUserFailed": "❌ Error in user selection!", "userSaved": "✅ Telegram User saved.", "loginSuccess": "✅ Logged in to the panel successfully.\r\n", @@ -1940,7 +1941,8 @@ "eventNodeUp": "Node {{ .Name }} is UP", "eventCPUHigh": "CPU high", "eventCPUHighDetail": "CPU: {{ .Detail }}", - "eventLoginFallback": "Login failed from {{ .Source }}" + "eventLoginFallback": "Login failed from {{ .Source }}", + "memoryThreshold": "Memory Load {{ .Percent }}% exceeds the threshold of {{ .Threshold }}%" }, "buttons": { "closeKeyboard": "❌ Close Keyboard", diff --git a/internal/web/translation/es-ES.json b/internal/web/translation/es-ES.json index c273a82b4..35cab8f0c 100644 --- a/internal/web/translation/es-ES.json +++ b/internal/web/translation/es-ES.json @@ -1309,6 +1309,7 @@ "smtpErrorRelay": "El servidor rechaza el envío desde esta dirección", "smtpErrorEof": "Conexión cerrada por el servidor", "smtpErrorUnknown": "Error de SMTP: {{ .Error }}", + "eventMemoryHigh": "Uso de memoria alto (%)", "remarkTemplate": "Plantilla de notas", "remarkTemplateDesc": "Cuando se define, esto reemplaza el modelo de notas para cada enlace de suscripción — escribe tu propio formato con los tokens de variable (usa el botón para insertarlos). Déjalo vacío para usar el modelo anterior." }, @@ -1865,7 +1866,7 @@ "idDesc": "Mostrar tu ID de Telegram" }, "messages": { - "cpuThreshold": "🔴 El uso de CPU {{ .Percent }}% es mayor que el umbral {{ .Threshold }}%", + "cpuThreshold": "El uso de CPU {{ .Percent }}% es mayor que el umbral {{ .Threshold }}%", "selectUserFailed": "❌ ¡Error al seleccionar usuario!", "userSaved": "✅ Usuario de Telegram guardado.", "loginSuccess": "✅ Has iniciado sesión en el panel con éxito.\r\n", @@ -1940,7 +1941,8 @@ "eventNodeUp": "El nodo {{ .Name }} está ACTIVO", "eventCPUHigh": "CPU alta", "eventCPUHighDetail": "CPU: {{ .Detail }}", - "eventLoginFallback": "Inicio de sesión fallido desde {{ .Source }}" + "eventLoginFallback": "Inicio de sesión fallido desde {{ .Source }}", + "memoryThreshold": "Uso de memoria {{ .Percent }}% supera el umbral de {{ .Threshold }}%" }, "buttons": { "closeKeyboard": "❌ Cerrar Teclado", @@ -2043,4 +2045,4 @@ "statusDown": "CAÍDO", "statusUp": "ACTIVO" } -} \ No newline at end of file +} diff --git a/internal/web/translation/fa-IR.json b/internal/web/translation/fa-IR.json index 7e3873599..b791565a8 100644 --- a/internal/web/translation/fa-IR.json +++ b/internal/web/translation/fa-IR.json @@ -1310,7 +1310,8 @@ "smtpErrorTimeout": "مهلت اتصال به پایان رسید — میزبان در دسترس نیست", "smtpErrorRelay": "سرور ارسال از این آدرس را رد می‌کند", "smtpErrorEof": "اتصال توسط سرور بسته شد", - "smtpErrorUnknown": "خطای SMTP: {{ .Error }}" + "smtpErrorUnknown": "خطای SMTP: {{ .Error }}", + "eventMemoryHigh": "مصرف حافظه بالا (%)" }, "xray": { "title": "پیکربندی ایکس‌ری", @@ -1865,7 +1866,7 @@ "idDesc": "نمایش شناسه تلگرام شما" }, "messages": { - "cpuThreshold": "🔴 بار ‌پردازنده {{ .Percent }}% بیشتر از آستانه است {{ .Threshold }}%", + "cpuThreshold": "بار ‌پردازنده {{ .Percent }}% بیشتر از آستانه است {{ .Threshold }}%", "selectUserFailed": "❌ خطا در انتخاب کاربر!", "userSaved": "✅ کاربر تلگرام ذخیره شد.", "loginSuccess": "✅ با موفقیت به پنل وارد شدید.\r\n", @@ -1940,7 +1941,8 @@ "eventNodeUp": "نود {{ .Name }} وصل است", "eventCPUHigh": "بالا بودن CPU", "eventCPUHighDetail": "CPU: {{ .Detail }}", - "eventLoginFallback": "ورود ناموفق از {{ .Source }}" + "eventLoginFallback": "ورود ناموفق از {{ .Source }}", + "memoryThreshold": "مصرف حافظه {{ .Percent }}% از حد آستانه {{ .Threshold }}% فراتر رفته است" }, "buttons": { "closeKeyboard": "❌ بستن کیبورد", @@ -2043,4 +2045,4 @@ "statusDown": "قطع", "statusUp": "وصل" } -} \ No newline at end of file +} diff --git a/internal/web/translation/id-ID.json b/internal/web/translation/id-ID.json index f838c4e6c..12d25d55c 100644 --- a/internal/web/translation/id-ID.json +++ b/internal/web/translation/id-ID.json @@ -1309,6 +1309,7 @@ "smtpErrorRelay": "Server menolak pengiriman dari alamat ini", "smtpErrorEof": "Koneksi ditutup oleh server", "smtpErrorUnknown": "Kesalahan SMTP: {{ .Error }}", + "eventMemoryHigh": "Penggunaan memori tinggi (%)", "remarkTemplate": "Templat Catatan", "remarkTemplateDesc": "Jika diatur, ini menggantikan model catatan untuk setiap tautan langganan — tulis format Anda sendiri dengan token variabel (gunakan tombol untuk menyisipkannya). Biarkan kosong untuk memakai model di atas." }, @@ -1865,7 +1866,7 @@ "idDesc": "Tampilkan ID Telegram Anda" }, "messages": { - "cpuThreshold": "🔴 Beban CPU {{ .Percent }}% melebihi batas {{ .Threshold }}%", + "cpuThreshold": "Beban CPU {{ .Percent }}% melebihi batas {{ .Threshold }}%", "selectUserFailed": "❌ Kesalahan dalam pemilihan pengguna!", "userSaved": "✅ Pengguna Telegram tersimpan.", "loginSuccess": "✅ Berhasil masuk ke panel.\r\n", @@ -1940,7 +1941,8 @@ "eventNodeUp": "Node {{ .Name }} AKTIF", "eventCPUHigh": "CPU tinggi", "eventCPUHighDetail": "CPU: {{ .Detail }}", - "eventLoginFallback": "Gagal masuk dari {{ .Source }}" + "eventLoginFallback": "Gagal masuk dari {{ .Source }}", + "memoryThreshold": "Penggunaan memori {{ .Percent }}% melebihi ambang batas {{ .Threshold }}%" }, "buttons": { "closeKeyboard": "❌ Tutup Papan Ketik", diff --git a/internal/web/translation/ja-JP.json b/internal/web/translation/ja-JP.json index 317329277..f7359d1e1 100644 --- a/internal/web/translation/ja-JP.json +++ b/internal/web/translation/ja-JP.json @@ -1309,6 +1309,7 @@ "smtpErrorRelay": "サーバーはこのアドレスからの送信を拒否しています", "smtpErrorEof": "サーバーによって接続が閉じられました", "smtpErrorUnknown": "SMTPエラー: {{ .Error }}", + "eventMemoryHigh": "メモリ使用率が高い (%)", "remarkTemplate": "備考テンプレート", "remarkTemplateDesc": "設定すると、すべてのサブスクリプションリンクの備考モデルを置き換えます — 変数トークンを使って独自の形式を記述してください(ボタンで挿入できます)。空欄にすると上記のモデルが使用されます。" }, @@ -1865,7 +1866,7 @@ "idDesc": "Telegram IDを表示" }, "messages": { - "cpuThreshold": "🔴 CPU使用率は{{ .Percent }}%、しきい値{{ .Threshold }}%を超えました", + "cpuThreshold": "CPU使用率は{{ .Percent }}%、しきい値{{ .Threshold }}%を超えました", "selectUserFailed": "❌ ユーザーの選択に失敗しました!", "userSaved": "✅ Telegramユーザーが保存されました。", "loginSuccess": "✅ パネルに正常にログインしました。\r\n", @@ -1940,7 +1941,8 @@ "eventNodeUp": "ノード {{ .Name }} が復旧しました", "eventCPUHigh": "CPU高負荷", "eventCPUHighDetail": "CPU: {{ .Detail }}", - "eventLoginFallback": "{{ .Source }} からのログインに失敗しました" + "eventLoginFallback": "{{ .Source }} からのログインに失敗しました", + "memoryThreshold": "メモリ使用率 {{ .Percent }}% がしきい値 {{ .Threshold }}% を超えました" }, "buttons": { "closeKeyboard": "❌ キーボードを閉じる", @@ -2043,4 +2045,4 @@ "statusDown": "ダウン", "statusUp": "アップ" } -} \ No newline at end of file +} diff --git a/internal/web/translation/pt-BR.json b/internal/web/translation/pt-BR.json index 866623826..bbe864481 100644 --- a/internal/web/translation/pt-BR.json +++ b/internal/web/translation/pt-BR.json @@ -1309,6 +1309,7 @@ "smtpErrorRelay": "O servidor rejeita o envio a partir deste endereço", "smtpErrorEof": "Conexão encerrada pelo servidor", "smtpErrorUnknown": "Erro de SMTP: {{ .Error }}", + "eventMemoryHigh": "Uso de memória alto (%)", "remarkTemplate": "Modelo de Observação", "remarkTemplateDesc": "Quando definido, isto substitui o modelo de observação de cada link de assinatura — escreva seu próprio formato com os tokens de variáveis (use o botão para inseri-los). Deixe vazio para usar o modelo acima." }, @@ -1865,7 +1866,7 @@ "idDesc": "Mostrar seu ID do Telegram" }, "messages": { - "cpuThreshold": "🔴 A carga da CPU {{ .Percent }}% excede o limite de {{ .Threshold }}%", + "cpuThreshold": "A carga da CPU {{ .Percent }}% excede o limite de {{ .Threshold }}%", "selectUserFailed": "❌ Erro na seleção do usuário!", "userSaved": "✅ Usuário do Telegram salvo.", "loginSuccess": "✅ Conectado ao painel com sucesso.\r\n", @@ -1940,7 +1941,8 @@ "eventNodeUp": "O nó {{ .Name }} está ATIVO", "eventCPUHigh": "CPU alta", "eventCPUHighDetail": "CPU: {{ .Detail }}", - "eventLoginFallback": "Falha de login a partir de {{ .Source }}" + "eventLoginFallback": "Falha de login a partir de {{ .Source }}", + "memoryThreshold": "Uso de memória {{ .Percent }}% excede o limite de {{ .Threshold }}%" }, "buttons": { "closeKeyboard": "❌ Fechar teclado", @@ -2043,4 +2045,4 @@ "statusDown": "INATIVO", "statusUp": "ATIVO" } -} \ No newline at end of file +} diff --git a/internal/web/translation/ru-RU.json b/internal/web/translation/ru-RU.json index dfa007bee..36aae20bc 100644 --- a/internal/web/translation/ru-RU.json +++ b/internal/web/translation/ru-RU.json @@ -1309,6 +1309,7 @@ "smtpErrorRelay": "Сервер отклоняет отправку с этого адреса", "smtpErrorEof": "Соединение закрыто сервером", "smtpErrorUnknown": "Ошибка SMTP: {{ .Error }}", + "eventMemoryHigh": "Превышение порога памяти (%)", "remarkTemplate": "Шаблон примечания", "remarkTemplateDesc": "Если задан, заменяет модель примечания для каждой ссылки подписки — задайте собственный формат с помощью токенов переменных (используйте кнопку для их вставки). Оставьте пустым, чтобы использовать модель выше." }, @@ -1865,7 +1866,7 @@ "idDesc": "Показать ваш Telegram ID" }, "messages": { - "cpuThreshold": "🔴 Загрузка процессора составляет {{ .Percent }}%, что превышает пороговое значение {{ .Threshold }}%", + "cpuThreshold": "Загрузка процессора составляет {{ .Percent }}%, что превышает пороговое значение {{ .Threshold }}%", "selectUserFailed": "❌ Ошибка при выборе пользователя.", "userSaved": "✅ Пользователь Telegram сохранен.", "loginSuccess": "✅ Успешный вход в панель.\r\n", @@ -1940,7 +1941,8 @@ "eventNodeUp": "Узел {{ .Name }} В СЕТИ", "eventCPUHigh": "Высокая загрузка CPU", "eventCPUHighDetail": "CPU: {{ .Detail }}", - "eventLoginFallback": "Неудачный вход с {{ .Source }}" + "eventLoginFallback": "Неудачный вход с {{ .Source }}", + "memoryThreshold": "🔴 Использование памяти {{ .Percent }}% превышает пороговое значение {{ .Threshold }}%" }, "buttons": { "closeKeyboard": "❌ Закрыть клавиатуру", @@ -2043,4 +2045,4 @@ "statusDown": "НЕДОСТУПЕН", "statusUp": "РАБОТАЕТ" } -} \ No newline at end of file +} diff --git a/internal/web/translation/tr-TR.json b/internal/web/translation/tr-TR.json index 74916e464..49a0342b1 100644 --- a/internal/web/translation/tr-TR.json +++ b/internal/web/translation/tr-TR.json @@ -1309,6 +1309,7 @@ "smtpErrorRelay": "Sunucu bu adresten gönderimi reddediyor", "smtpErrorEof": "Bağlantı sunucu tarafından kapatıldı", "smtpErrorUnknown": "SMTP hatası: {{ .Error }}", + "eventMemoryHigh": "Bellek kullanımı yüksek (%)", "remarkTemplate": "Açıklama Şablonu", "remarkTemplateDesc": "Ayarlandığında, her abonelik bağlantısının açıklama modelinin yerini alır — değişken belirteçleriyle kendi formatınızı yazın (eklemek için düğmeyi kullanın). Yukarıdaki modeli kullanmak için boş bırakın." }, @@ -1865,7 +1866,7 @@ "idDesc": "Telegram Kimliğinizi gösterir" }, "messages": { - "cpuThreshold": "🔴 CPU Yükü ({{ .Percent }}%), {{ .Threshold }}% eşiğini aşıyor", + "cpuThreshold": "CPU Yükü ({{ .Percent }}%), {{ .Threshold }}% eşiğini aşıyor", "selectUserFailed": "❌ Kullanıcı seçiminde hata!", "userSaved": "✅ Telegram Kullanıcısı kaydedildi.", "loginSuccess": "✅ Panele başarıyla giriş yapıldı.\r\n", @@ -1940,7 +1941,8 @@ "eventNodeUp": "{{ .Name }} düğümü ÇEVRİMİÇİ", "eventCPUHigh": "Yüksek CPU", "eventCPUHighDetail": "CPU: {{ .Detail }}", - "eventLoginFallback": "{{ .Source }} adresinden oturum açma başarısız" + "eventLoginFallback": "{{ .Source }} adresinden oturum açma başarısız", + "memoryThreshold": "Bellek kullanımı {{ .Percent }}% eşiği {{ .Threshold }}% aşıyor" }, "buttons": { "closeKeyboard": "❌ Klavyeyi Kapat", diff --git a/internal/web/translation/uk-UA.json b/internal/web/translation/uk-UA.json index f121ca4e3..5cbf64b33 100644 --- a/internal/web/translation/uk-UA.json +++ b/internal/web/translation/uk-UA.json @@ -1309,6 +1309,7 @@ "smtpErrorRelay": "Сервер відхиляє надсилання з цієї адреси", "smtpErrorEof": "З'єднання закрито сервером", "smtpErrorUnknown": "Помилка SMTP: {{ .Error }}", + "eventMemoryHigh": "Високе використання пам'яті (%)", "remarkTemplate": "Шаблон примітки", "remarkTemplateDesc": "Якщо задано, це замінює модель примітки для кожного посилання підписки — напишіть власний формат із токенами змінних (використовуйте кнопку для їх вставлення). Залиште порожнім, щоб використовувати модель вище." }, @@ -1865,7 +1866,7 @@ "idDesc": "Показати ваш Telegram ID" }, "messages": { - "cpuThreshold": "🔴 Навантаження ЦП {{ .Percent }}% перевищує порогове значення {{ .Threshold }}%", + "cpuThreshold": "Навантаження ЦП {{ .Percent }}% перевищує порогове значення {{ .Threshold }}%", "selectUserFailed": "❌ Помилка під час вибору користувача!", "userSaved": "✅ Користувача Telegram збережено.", "loginSuccess": "✅ Успішно ввійшли в панель\r\n", @@ -1940,7 +1941,8 @@ "eventNodeUp": "Вузол {{ .Name }} ДОСТУПНИЙ", "eventCPUHigh": "Високе навантаження на CPU", "eventCPUHighDetail": "CPU: {{ .Detail }}", - "eventLoginFallback": "Невдала спроба входу з {{ .Source }}" + "eventLoginFallback": "Невдала спроба входу з {{ .Source }}", + "memoryThreshold": "Використання пам'яті {{ .Percent }}% перевищує порогове значення {{ .Threshold }}%" }, "buttons": { "closeKeyboard": "❌ Закрити клавіатуру", @@ -2043,4 +2045,4 @@ "statusDown": "НЕДОСТУПНО", "statusUp": "ДОСТУПНО" } -} \ No newline at end of file +} diff --git a/internal/web/translation/vi-VN.json b/internal/web/translation/vi-VN.json index 087759752..997f98377 100644 --- a/internal/web/translation/vi-VN.json +++ b/internal/web/translation/vi-VN.json @@ -1309,6 +1309,7 @@ "smtpErrorRelay": "Máy chủ từ chối gửi từ địa chỉ này", "smtpErrorEof": "Kết nối đã bị máy chủ đóng", "smtpErrorUnknown": "Lỗi SMTP: {{ .Error }}", + "eventMemoryHigh": "Sử dụng bộ nhớ cao (%)", "remarkTemplate": "Mẫu ghi chú", "remarkTemplateDesc": "Khi được đặt, mục này thay thế mô hình ghi chú cho mọi liên kết đăng ký — hãy viết định dạng riêng của bạn bằng các token biến (dùng nút để chèn chúng). Để trống để dùng mô hình ở trên." }, @@ -1865,7 +1866,7 @@ "idDesc": "Hiển thị ID Telegram của bạn" }, "messages": { - "cpuThreshold": "🔴 Sử dụng CPU {{ .Percent }}% vượt quá ngưỡng {{ .Threshold }}%", + "cpuThreshold": "Sử dụng CPU {{ .Percent }}% vượt quá ngưỡng {{ .Threshold }}%", "selectUserFailed": "❌ Lỗi khi chọn người dùng!", "userSaved": "✅ Người dùng Telegram đã được lưu.", "loginSuccess": "✅ Đăng nhập thành công vào bảng điều khiển.\r\n", @@ -1940,7 +1941,8 @@ "eventNodeUp": "Node {{ .Name }} đã HOẠT ĐỘNG", "eventCPUHigh": "CPU cao", "eventCPUHighDetail": "CPU: {{ .Detail }}", - "eventLoginFallback": "Đăng nhập thất bại từ {{ .Source }}" + "eventLoginFallback": "Đăng nhập thất bại từ {{ .Source }}", + "memoryThreshold": "Sử dụng bộ nhớ {{ .Percent }}% vượt quá ngưỡng {{ .Threshold }}%" }, "buttons": { "closeKeyboard": "❌ Đóng Bàn Phím", @@ -2043,4 +2045,4 @@ "statusDown": "NGỪNG HOẠT ĐỘNG", "statusUp": "HOẠT ĐỘNG" } -} \ No newline at end of file +} diff --git a/internal/web/translation/zh-CN.json b/internal/web/translation/zh-CN.json index 1ca45ac0f..b1c0c75b1 100644 --- a/internal/web/translation/zh-CN.json +++ b/internal/web/translation/zh-CN.json @@ -1309,6 +1309,7 @@ "smtpErrorRelay": "服务器拒绝从此地址发送", "smtpErrorEof": "连接被服务器关闭", "smtpErrorUnknown": "SMTP 错误:{{ .Error }}", + "eventMemoryHigh": "内存使用率高 (%)", "remarkTemplate": "备注模板", "remarkTemplateDesc": "设置后,将替换每个订阅链接的备注模型 — 使用变量标记编写您自己的格式(用按钮插入它们)。留空则使用上方的模型。" }, @@ -1865,7 +1866,7 @@ "idDesc": "显示您的 Telegram ID" }, "messages": { - "cpuThreshold": "🔴 CPU 使用率为 {{ .Percent }}%,超过阈值 {{ .Threshold }}%", + "cpuThreshold": "CPU 使用率为 {{ .Percent }}%,超过阈值 {{ .Threshold }}%", "selectUserFailed": "❌ 用户选择错误!", "userSaved": "✅ 电报用户已保存。", "loginSuccess": "✅ 成功登录到面板。\r\n", @@ -1940,7 +1941,8 @@ "eventNodeUp": "节点 {{ .Name }} 已上线", "eventCPUHigh": "CPU 占用过高", "eventCPUHighDetail": "CPU:{{ .Detail }}", - "eventLoginFallback": "来自 {{ .Source }} 的登录失败" + "eventLoginFallback": "来自 {{ .Source }} 的登录失败", + "memoryThreshold": "内存使用率 {{ .Percent }}% 超过阈值 {{ .Threshold }}%" }, "buttons": { "closeKeyboard": "❌ 关闭键盘", diff --git a/internal/web/translation/zh-TW.json b/internal/web/translation/zh-TW.json index ffbd2277b..bb99a1657 100644 --- a/internal/web/translation/zh-TW.json +++ b/internal/web/translation/zh-TW.json @@ -1309,6 +1309,7 @@ "smtpErrorRelay": "伺服器拒絕從此地址傳送", "smtpErrorEof": "連線已被伺服器關閉", "smtpErrorUnknown": "SMTP 錯誤:{{ .Error }}", + "eventMemoryHigh": "記憶體使用率高 (%)", "remarkTemplate": "備註範本", "remarkTemplateDesc": "設定後,這將取代每個訂閱連結的備註模型——使用變數標記撰寫您自己的格式(使用按鈕來插入)。留空則使用上方的模型。" }, @@ -1865,7 +1866,7 @@ "idDesc": "顯示您的 Telegram ID" }, "messages": { - "cpuThreshold": "🔴 CPU 使用率為 {{ .Percent }}%,超過閾值 {{ .Threshold }}%", + "cpuThreshold": "CPU 使用率為 {{ .Percent }}%,超過閾值 {{ .Threshold }}%", "selectUserFailed": "❌ 使用者選擇錯誤!", "userSaved": "✅ 電報使用者已儲存。", "loginSuccess": "✅ 成功登入到面板。\r\n", @@ -1940,7 +1941,8 @@ "eventNodeUp": "節點 {{ .Name }} 已上線", "eventCPUHigh": "CPU 偏高", "eventCPUHighDetail": "CPU:{{ .Detail }}", - "eventLoginFallback": "來自 {{ .Source }} 的登入失敗" + "eventLoginFallback": "來自 {{ .Source }} 的登入失敗", + "memoryThreshold": "記憶體使用率 {{ .Percent }}% 超過閾值 {{ .Threshold }}%" }, "buttons": { "closeKeyboard": "❌ 關閉鍵盤", @@ -2043,4 +2045,4 @@ "statusDown": "中斷", "statusUp": "恢復" } -} \ No newline at end of file +} diff --git a/internal/web/web.go b/internal/web/web.go index b77683217..ec776847e 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -290,7 +290,8 @@ const ( cadenceCheckHash = "@every 2m" // cpu.Percent samples over a full minute (blocking), so a finer cadence just // stacks overlapping samplers; subscribers rate-limit alerts to 1/min anyway. - cadenceCPUAlarm = "@every 1m" + cadenceCPUAlarm = "@every 1m" + cadenceMemoryAlarm = "@every 1m" ) // startTask schedules background jobs (Xray checks, traffic jobs, cron @@ -385,6 +386,10 @@ func (s *Server) startTask(restartXray bool) { if s.cpuAlarmWanted() { s.cron.AddJob(cadenceCPUAlarm, job.NewCheckCpuJob()) } + // Memory monitor publishes memory.high events; register it whenever any notifier wants them. + if s.memoryAlarmWanted() { + s.cron.AddJob(cadenceMemoryAlarm, job.NewCheckMemJob()) + } } // cpuAlarmWanted reports whether any notifier is configured to receive cpu.high @@ -418,6 +423,36 @@ func (s *Server) cpuAlarmWanted() bool { return false } +// memoryAlarmWanted reports whether any notifier is configured to receive memory.high alerts. +func (s *Server) memoryAlarmWanted() bool { + wants := func(events string, threshold int) bool { + if threshold <= 0 { + return false + } + for _, e := range strings.Split(events, ",") { + if strings.TrimSpace(e) == string(eventbus.EventMemoryHigh) { + return true + } + } + return false + } + if on, _ := s.settingService.GetTgbotEnabled(); on { + events, _ := s.settingService.GetTgEnabledEvents() + mem, _ := s.settingService.GetTgMemory() + if wants(events, mem) { + return true + } + } + if on, _ := s.settingService.GetSmtpEnable(); on { + events, _ := s.settingService.GetSmtpEnabledEvents() + mem, _ := s.settingService.GetSmtpMemory() + if wants(events, mem) { + return true + } + } + return false +} + // Start initializes and starts the web server with configured settings, routes, and background jobs. func (s *Server) Start() (err error) { return s.start(true, true)