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)