Files
3x-ui/frontend/src/components/ui/notifications/EmailNotifications.tsx
T
Sentiago 891d3a8759 feat(memory): add memory threshold alerts (#5366)
* 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
2026-06-21 17:45:33 +02:00

103 lines
3.0 KiB
TypeScript

import { InputNumber } from 'antd';
import { CloudServerOutlined, ThunderboltOutlined, DesktopOutlined, DashboardOutlined, SafetyOutlined } from '@ant-design/icons';
import type { AllSetting } from '@/models/setting';
import { NotificationLayout } from './NotificationLayout';
import { NotificationGroup } from './NotificationGroup';
import type { NotificationGroupConfig } from './types';
const GROUPS: NotificationGroupConfig[] = [
{
icon: <CloudServerOutlined />,
title: 'eventGroupOutbound',
events: [
{ key: 'outbound.down', label: 'eventOutboundDown', settingKey: '' },
{ key: 'outbound.up', label: 'eventOutboundUp', settingKey: '' },
],
},
{
icon: <ThunderboltOutlined />,
title: 'eventGroupXray',
events: [
{ key: 'xray.crash', label: 'eventXrayCrash', settingKey: '' },
],
},
{
icon: <DesktopOutlined />,
title: 'eventGroupNode',
events: [
{ key: 'node.down', label: 'eventNodeDown', settingKey: '' },
{ key: 'node.up', label: 'eventNodeUp', settingKey: '' },
],
},
{
icon: <DashboardOutlined />,
title: 'eventGroupSystem',
events: [
{
key: 'cpu.high',
label: 'eventCPUHigh',
settingKey: 'smtpCpu',
extra: ({ value, onChange }) => (
<InputNumber size="small" min={0} max={100} value={value} onChange={onChange} style={{ width: 80 }} />
),
},
{
key: 'memory.high',
label: 'eventMemoryHigh',
settingKey: 'smtpMemory',
extra: ({ value, onChange }) => (
<InputNumber size="small" min={0} max={100} value={value} onChange={onChange} style={{ width: 80 }} />
),
},
],
},
{
icon: <SafetyOutlined />,
title: 'eventGroupSecurity',
events: [
{ key: 'login.attempt', label: 'eventLoginAttempt', settingKey: '' },
],
},
];
interface Props {
allSetting: AllSetting;
updateSetting: (patch: Partial<AllSetting>) => void;
}
export function EmailNotifications({ allSetting, updateSetting }: Props) {
const events = allSetting.smtpEnabledEvents || '';
const selected = events ? events.split(',').map((s) => s.trim()).filter(Boolean) : [];
function toggle(key: string) {
const next = selected.includes(key)
? selected.filter((e) => e !== key)
: [...selected, key];
updateSetting({ smtpEnabledEvents: next.join(',') });
}
function toggleAll(keys: string[]) {
const allSelected = keys.every((v) => selected.includes(v));
const next = allSelected
? selected.filter((v) => !keys.includes(v))
: [...new Set([...selected, ...keys])];
updateSetting({ smtpEnabledEvents: next.join(',') });
}
return (
<NotificationLayout>
{GROUPS.map((group, i) => (
<NotificationGroup
key={i}
config={group}
selected={selected}
onToggle={toggle}
onToggleAll={toggleAll}
allSetting={allSetting}
updateSetting={updateSetting}
/>
))}
</NotificationLayout>
);
}