feat: comprehensive alert system simplification and UI improvements

- Simplified alert rules: removed warning/critical levels, now one rule per metric type
- Unified alert editing: all rules (built-in and custom) use same modal template
- Enhanced UI: show actual thresholds/conditions in rules list without clicking edit
- Fixed Create Rule button: changed from event listener to direct onclick approach
- Fixed disabled rules visibility: disabled rules now stay in list with toggle off
- Removed redundant labels: eliminated "Enabled/Disabled" badges (toggle shows state)
- Updated alert summary cards: replaced Critical/Warning/Info with Active/Acknowledged/Rules/Status
- Improved UX: consistent editing experience across all alert types
- Cleaned up duplicate code: removed separate modal templates for system vs custom alerts

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
rcourtman
2025-06-10 18:17:08 +01:00
parent e37963e4ff
commit d34ebeac53
4 changed files with 1912 additions and 565 deletions
+218 -293
View File
@@ -1,6 +1,6 @@
{
"guest_down_endpoint_2_pi_102": {
"id": "alert_1749383901524_ii0r1wvj6",
"id": "alert_1749506759853_8lji2gh42",
"rule": {
"id": "guest_down",
"name": "Guest System Down",
@@ -18,110 +18,77 @@
"group": "availability_alerts",
"escalationTime": 600000,
"autoResolve": true,
"suppressionTime": 120000,
"notificationChannels": [
"default",
"urgent"
]
"suppressionTime": 120000
},
"guest": {
"maxmem": 536870912,
"status": "stopped",
"mem": 0,
"cpus": 1,
"swap": 0,
"tags": "proxmox-helper-scripts",
"cpu": 0,
"diskwrite": 0,
"netout": 0,
"vmid": 102,
"maxswap": 536870912,
"type": "lxc",
"diskread": 0,
"uptime": 0,
"netin": 0,
"maxdisk": 4401922048,
"disk": 0,
"name": "pi-docker",
"node": "pi",
"endpointId": "endpoint_2",
"id": "endpoint_2-pi-102",
"nodeDisplayName": "pimox"
},
"startTime": 1749383901523,
"lastUpdate": 1749383916078,
"triggeredAt": 1749383901523,
"currentValue": "stopped",
"effectiveThreshold": "stopped",
"state": "active",
"escalated": false,
"acknowledged": true,
"acknowledgedBy": "web-user",
"acknowledgedAt": 1749383916175,
"acknowledgeNote": "Acknowledged via web interface",
"note": "Acknowledged via web interface"
},
"guest_down_primary_desktop_400": {
"id": "alert_1749383901523_u7e6l7r1v",
"rule": {
"id": "guest_down",
"name": "Guest System Down",
"description": "Virtual machine or container has stopped unexpectedly",
"metric": "status",
"condition": "equals",
"threshold": "stopped",
"duration": 60000,
"severity": "critical",
"enabled": true,
"tags": [
"availability",
"guest"
],
"group": "availability_alerts",
"escalationTime": 600000,
"autoResolve": true,
"suppressionTime": 120000,
"notificationChannels": [
"default",
"urgent"
]
},
"guest": {
"diskread": 0,
"name": "ubuntu-gpu-vm",
"maxmem": 2147483648,
"diskwrite": 0,
"disk": 0,
"mem": 0,
"vmid": 400,
"node": "pi",
"vmid": 102,
"name": "pi-docker",
"type": "lxc",
"status": "stopped",
"cpus": 2,
"uptime": 0,
"netin": 0,
"maxdisk": 34359738368,
"cpu": 0,
"netout": 0,
"node": "desktop",
"endpointId": "primary",
"type": "qemu",
"id": "primary-desktop-400",
"nodeDisplayName": "homelab - desktop"
"maxmem": 536870912,
"maxdisk": 4401922048
},
"startTime": 1749383901523,
"lastUpdate": 1749383922611,
"triggeredAt": 1749383901523,
"startTime": 1749506759851,
"lastUpdate": 1749506768059,
"triggeredAt": 1749506759851,
"currentValue": "stopped",
"effectiveThreshold": "stopped",
"state": "active",
"escalated": false,
"acknowledged": true,
"acknowledgedBy": "bulk-operation",
"acknowledgedAt": 1749383923304,
"acknowledgedAt": 1749506768114,
"acknowledgeNote": "Bulk acknowledged via dropdown",
"note": "Bulk acknowledged via dropdown"
},
"guest_down_primary_desktop_400": {
"id": "alert_1749506759852_1huz9md0u",
"rule": {
"id": "guest_down",
"name": "Guest System Down",
"description": "Virtual machine or container has stopped unexpectedly",
"metric": "status",
"condition": "equals",
"threshold": "stopped",
"duration": 60000,
"severity": "critical",
"enabled": true,
"tags": [
"availability",
"guest"
],
"group": "availability_alerts",
"escalationTime": 600000,
"autoResolve": true,
"suppressionTime": 120000
},
"guest": {
"endpointId": "primary",
"node": "desktop",
"vmid": 400,
"name": "ubuntu-gpu-vm",
"type": "qemu",
"status": "stopped",
"maxmem": 2147483648,
"maxdisk": 34359738368
},
"startTime": 1749506759851,
"lastUpdate": 1749506768059,
"triggeredAt": 1749506759851,
"currentValue": "stopped",
"effectiveThreshold": "stopped",
"state": "active",
"escalated": false,
"acknowledged": true,
"acknowledgedBy": "bulk-operation",
"acknowledgedAt": 1749506768076,
"acknowledgeNote": "Bulk acknowledged via dropdown",
"note": "Bulk acknowledged via dropdown"
},
"guest_down_primary_desktop_102": {
"id": "alert_1749383901524_5sdux0hpp",
"id": "alert_1749569747925_cr09csaa4",
"rule": {
"id": "guest_down",
"name": "Guest System Down",
@@ -139,49 +106,33 @@
"group": "availability_alerts",
"escalationTime": 600000,
"autoResolve": true,
"suppressionTime": 120000,
"notificationChannels": [
"default",
"urgent"
]
"suppressionTime": 120000
},
"guest": {
"diskwrite": 0,
"name": "windows11",
"maxmem": 8589934592,
"diskread": 0,
"vmid": 102,
"disk": 0,
"mem": 0,
"cpus": 4,
"status": "stopped",
"serial": 1,
"cpu": 0,
"netout": 0,
"maxdisk": 68719476736,
"netin": 0,
"uptime": 0,
"node": "desktop",
"endpointId": "primary",
"node": "desktop",
"vmid": 102,
"name": "windows11",
"type": "qemu",
"id": "primary-desktop-102",
"nodeDisplayName": "homelab - desktop"
"status": "stopped",
"maxmem": 8589934592,
"maxdisk": 68719476736
},
"startTime": 1749383901523,
"lastUpdate": 1749383922611,
"triggeredAt": 1749383901523,
"startTime": 1749569747925,
"lastUpdate": 1749569831486,
"currentValue": "stopped",
"effectiveThreshold": "stopped",
"state": "active",
"escalated": false,
"acknowledged": true,
"acknowledgedBy": "bulk-operation",
"acknowledgedAt": 1749383923312,
"acknowledgeNote": "Bulk acknowledged via dropdown",
"note": "Bulk acknowledged via dropdown"
"triggeredAt": 1749569809584,
"acknowledgedBy": "web-user",
"acknowledgedAt": 1749569832402,
"acknowledgeNote": "Acknowledged via web interface",
"note": "Acknowledged via web interface"
},
"guest_down_primary_desktop_200": {
"id": "alert_1749383901524_wc8c7l96a",
"id": "alert_1749569747925_g5yxjcnmv",
"rule": {
"id": "guest_down",
"name": "Guest System Down",
@@ -199,48 +150,33 @@
"group": "availability_alerts",
"escalationTime": 600000,
"autoResolve": true,
"suppressionTime": 120000,
"notificationChannels": [
"default",
"urgent"
]
"suppressionTime": 120000
},
"guest": {
"diskread": 0,
"maxmem": 8589934592,
"name": "UnraidServer",
"diskwrite": 0,
"mem": 0,
"disk": 0,
"vmid": 200,
"cpus": 6,
"status": "stopped",
"uptime": 0,
"netin": 0,
"maxdisk": 0,
"netout": 0,
"cpu": 0,
"node": "desktop",
"endpointId": "primary",
"node": "desktop",
"vmid": 200,
"name": "UnraidServer",
"type": "qemu",
"id": "primary-desktop-200",
"nodeDisplayName": "homelab - desktop"
"status": "stopped",
"maxmem": 8589934592,
"maxdisk": 0
},
"startTime": 1749383901523,
"lastUpdate": 1749383922611,
"triggeredAt": 1749383901523,
"startTime": 1749569747925,
"lastUpdate": 1749569829318,
"currentValue": "stopped",
"effectiveThreshold": "stopped",
"state": "active",
"escalated": false,
"acknowledged": true,
"acknowledgedBy": "bulk-operation",
"acknowledgedAt": 1749383923314,
"acknowledgeNote": "Bulk acknowledged via dropdown",
"note": "Bulk acknowledged via dropdown"
"triggeredAt": 1749569809584,
"acknowledgedBy": "web-user",
"acknowledgedAt": 1749569829548,
"acknowledgeNote": "Acknowledged via web interface",
"note": "Acknowledged via web interface"
},
"guest_down_primary_desktop_109": {
"id": "alert_1749383901524_884o95b7f",
"id": "alert_1749506759852_h7fxa1ycd",
"rule": {
"id": "guest_down",
"name": "Guest System Down",
@@ -258,51 +194,33 @@
"group": "availability_alerts",
"escalationTime": 600000,
"autoResolve": true,
"suppressionTime": 120000,
"notificationChannels": [
"default",
"urgent"
]
"suppressionTime": 120000
},
"guest": {
"vmid": 109,
"mem": 0,
"disk": 0,
"type": "lxc",
"maxswap": 536870912,
"diskwrite": 0,
"maxmem": 4294967296,
"name": "pbs2",
"diskread": 0,
"netout": 0,
"cpu": 0,
"maxdisk": 10737418240,
"tags": "backup;community-script",
"netin": 0,
"uptime": 0,
"status": "stopped",
"cpus": 4,
"swap": 0,
"node": "desktop",
"endpointId": "primary",
"id": "primary-desktop-109",
"nodeDisplayName": "homelab - desktop"
"node": "desktop",
"vmid": 109,
"name": "pbs2",
"type": "lxc",
"status": "stopped",
"maxmem": 4294967296,
"maxdisk": 10737418240
},
"startTime": 1749383901523,
"lastUpdate": 1749383922611,
"triggeredAt": 1749383901523,
"startTime": 1749506759851,
"lastUpdate": 1749506768059,
"triggeredAt": 1749506759851,
"currentValue": "stopped",
"effectiveThreshold": "stopped",
"state": "active",
"escalated": false,
"acknowledged": true,
"acknowledgedBy": "bulk-operation",
"acknowledgedAt": 1749383923317,
"acknowledgedAt": 1749506768101,
"acknowledgeNote": "Bulk acknowledged via dropdown",
"note": "Bulk acknowledged via dropdown"
},
"guest_down_primary_desktop_112": {
"id": "alert_1749383901524_e6h94me1n",
"id": "alert_1749506759852_xx9hn6622",
"rule": {
"id": "guest_down",
"name": "Guest System Down",
@@ -320,51 +238,33 @@
"group": "availability_alerts",
"escalationTime": 600000,
"autoResolve": true,
"suppressionTime": 120000,
"notificationChannels": [
"default",
"urgent"
]
"suppressionTime": 120000
},
"guest": {
"status": "stopped",
"cpus": 1,
"swap": 0,
"tags": "community-script;monitoring;proxmox",
"maxdisk": 4294967296,
"cpu": 0,
"netout": 0,
"uptime": 0,
"netin": 0,
"diskwrite": 0,
"maxswap": 536870912,
"type": "lxc",
"diskread": 0,
"name": "pulse",
"maxmem": 1073741824,
"disk": 0,
"mem": 0,
"vmid": 112,
"node": "desktop",
"endpointId": "primary",
"id": "primary-desktop-112",
"nodeDisplayName": "homelab - desktop"
"node": "desktop",
"vmid": 112,
"name": "pulse",
"type": "lxc",
"status": "stopped",
"maxmem": 1073741824,
"maxdisk": 4294967296
},
"startTime": 1749383901523,
"lastUpdate": 1749383922611,
"triggeredAt": 1749383901523,
"startTime": 1749506759851,
"lastUpdate": 1749506768059,
"triggeredAt": 1749506759851,
"currentValue": "stopped",
"effectiveThreshold": "stopped",
"state": "active",
"escalated": false,
"acknowledged": true,
"acknowledgedBy": "bulk-operation",
"acknowledgedAt": 1749383923320,
"acknowledgedAt": 1749506768098,
"acknowledgeNote": "Bulk acknowledged via dropdown",
"note": "Bulk acknowledged via dropdown"
},
"guest_down_endpoint_2_pi_101": {
"id": "alert_1749383901524_ew3vhj38x",
"id": "alert_1749506759853_fqm539ogq",
"rule": {
"id": "guest_down",
"name": "Guest System Down",
@@ -382,51 +282,33 @@
"group": "availability_alerts",
"escalationTime": 600000,
"autoResolve": true,
"suppressionTime": 120000,
"notificationChannels": [
"default",
"urgent"
]
"suppressionTime": 120000
},
"guest": {
"diskwrite": 0,
"cpu": 0,
"tags": "alpine;community-script;os",
"swap": 0,
"cpus": 1,
"mem": 0,
"maxmem": 536870912,
"status": "stopped",
"disk": 0,
"name": "pi-influxdb",
"maxdisk": 2254438400,
"netin": 0,
"diskread": 0,
"uptime": 0,
"type": "lxc",
"maxswap": 536870912,
"vmid": 101,
"netout": 0,
"node": "pi",
"endpointId": "endpoint_2",
"id": "endpoint_2-pi-101",
"nodeDisplayName": "pimox"
"node": "pi",
"vmid": 101,
"name": "pi-influxdb",
"type": "lxc",
"status": "stopped",
"maxmem": 536870912,
"maxdisk": 2254438400
},
"startTime": 1749383901523,
"lastUpdate": 1749383922611,
"triggeredAt": 1749383901523,
"startTime": 1749506759851,
"lastUpdate": 1749506768059,
"triggeredAt": 1749506759851,
"currentValue": "stopped",
"effectiveThreshold": "stopped",
"state": "active",
"escalated": false,
"acknowledged": true,
"acknowledgedBy": "bulk-operation",
"acknowledgedAt": 1749383923322,
"acknowledgedAt": 1749506768110,
"acknowledgeNote": "Bulk acknowledged via dropdown",
"note": "Bulk acknowledged via dropdown"
},
"guest_down_endpoint_2_pi_100": {
"id": "alert_1749383901524_z3xuybdjm",
"id": "alert_1749506759853_efv9jpkmk",
"rule": {
"id": "guest_down",
"name": "Guest System Down",
@@ -444,46 +326,28 @@
"group": "availability_alerts",
"escalationTime": 600000,
"autoResolve": true,
"suppressionTime": 120000,
"notificationChannels": [
"default",
"urgent"
]
"suppressionTime": 120000
},
"guest": {
"tags": "proxmox-helper-scripts",
"swap": 0,
"cpus": 1,
"mem": 0,
"endpointId": "endpoint_2",
"node": "pi",
"vmid": 100,
"name": "pihole",
"type": "lxc",
"status": "stopped",
"maxmem": 536870912,
"diskwrite": 0,
"cpu": 0,
"type": "lxc",
"maxswap": 536870912,
"vmid": 100,
"netout": 0,
"disk": 0,
"name": "pihole",
"maxdisk": 6442450944,
"netin": 0,
"diskread": 0,
"uptime": 0,
"node": "pi",
"endpointId": "endpoint_2",
"id": "endpoint_2-pi-100",
"nodeDisplayName": "pimox"
"maxdisk": 6442450944
},
"startTime": 1749383901523,
"lastUpdate": 1749383922611,
"triggeredAt": 1749383901523,
"startTime": 1749506759851,
"lastUpdate": 1749506768059,
"triggeredAt": 1749506759851,
"currentValue": "stopped",
"effectiveThreshold": "stopped",
"state": "active",
"escalated": false,
"acknowledged": true,
"acknowledgedBy": "bulk-operation",
"acknowledgedAt": 1749383923326,
"acknowledgedAt": 1749506768116,
"acknowledgeNote": "Bulk acknowledged via dropdown",
"note": "Bulk acknowledged via dropdown"
},
@@ -1100,7 +964,7 @@
"note": "Bulk acknowledged via dropdown"
},
"compound_1749471240848_s7tobb06w_primary_desktop_100": {
"id": "alert_1749502225872_hi8xpxptm",
"id": "alert_1749506911287_7s6evevo5",
"rule": {
"id": "compound_1749471240848_s7tobb06w",
"name": "Custom Alert",
@@ -1134,10 +998,10 @@
"maxmem": 2147483648,
"maxdisk": 10464022528
},
"startTime": 1749502225871,
"lastUpdate": 1749502234638,
"startTime": 1749506911287,
"lastUpdate": 1749506917939,
"currentValue": {
"cpu": 9.7
"cpu": 4.6
},
"effectiveThreshold": [
{
@@ -1149,10 +1013,10 @@
"state": "resolved",
"escalated": false,
"acknowledged": true,
"triggeredAt": 1749502232450,
"resolvedAt": 1749502236824,
"triggeredAt": 1749506917939,
"resolvedAt": 1749506920164,
"acknowledgedBy": "bulk-operation",
"acknowledgedAt": 1749503811808,
"acknowledgedAt": 1749506987457,
"acknowledgeNote": "Bulk acknowledged via dropdown",
"note": "Bulk acknowledged via dropdown"
},
@@ -1267,8 +1131,7 @@
"note": "Bulk acknowledged via dropdown"
},
"compound_1749471240848_s7tobb06w_primary_desktop_111": {
"id": "alert_1749502224406_5r7jkmoga",
"ruleId": "compound_1749471240848_s7tobb06w",
"id": "alert_1749568238371_r7qnu045w",
"rule": {
"id": "compound_1749471240848_s7tobb06w",
"name": "Custom Alert",
@@ -1302,21 +1165,83 @@
"maxmem": 2147483648,
"maxdisk": 4152066048
},
"severity": "warning",
"message": "Dynamic threshold rule \"Custom Alert\" triggered for debian: CPU: 10.5% (≥ 4%)",
"startTime": 1749502224405,
"lastUpdate": 1749502224405,
"triggeredAt": 1749502224405,
"startTime": 1749568238370,
"lastUpdate": 1749568244956,
"currentValue": {
"cpu": 10.5
"cpu": 4
},
"effectiveThreshold": [
{
"metric": "cpu",
"condition": "greater_than",
"threshold": 4
}
],
"state": "resolved",
"escalated": false,
"acknowledged": true,
"resolvedAt": 1749502225871,
"acknowledgedBy": "bulk-operation",
"acknowledgedAt": 1749503811814,
"acknowledgeNote": "Bulk acknowledged via dropdown",
"note": "Bulk acknowledged via dropdown"
"triggeredAt": 1749568244956,
"resolvedAt": 1749568247148,
"acknowledgedBy": "web-user",
"acknowledgedAt": 1749568251499,
"acknowledgeNote": "Acknowledged via web interface",
"note": "Acknowledged via web interface"
},
"compound_1749471240848_s7tobb06w_primary_delly_101": {
"id": "alert_1749505855170_5srfusl2c",
"rule": {
"id": "compound_1749471240848_s7tobb06w",
"name": "Custom Alert",
"condition": "greater_than",
"duration": 5000,
"severity": "warning",
"enabled": true,
"tags": [],
"group": "compound_threshold",
"escalationTime": 900000,
"autoResolve": false,
"suppressionTime": 300000,
"type": "compound_threshold",
"thresholds": [
{
"metric": "cpu",
"condition": "greater_than",
"threshold": 4
}
],
"sendEmail": true,
"sendWebhook": false
},
"guest": {
"endpointId": "primary",
"node": "delly",
"vmid": 101,
"name": "homeassistant",
"type": "lxc",
"status": "running",
"maxmem": 2147483648,
"maxdisk": 14687412224
},
"startTime": 1749505855169,
"lastUpdate": 1749505863903,
"currentValue": {
"cpu": 4.4
},
"effectiveThreshold": [
{
"metric": "cpu",
"condition": "greater_than",
"threshold": 4
}
],
"state": "resolved",
"escalated": false,
"acknowledged": true,
"triggeredAt": 1749505861716,
"resolvedAt": 1749505866083,
"acknowledgedBy": "web-user",
"acknowledgedAt": 1749506285411,
"acknowledgeNote": "Acknowledged via web interface",
"note": "Acknowledged via web interface"
}
}
+131 -79
View File
@@ -130,9 +130,9 @@ class AlertManager extends EventEmitter {
const defaultRules = [
{
id: 'high_cpu',
name: 'High CPU Usage',
description: 'Triggers when CPU usage exceeds threshold for specified duration',
id: 'cpu',
name: 'CPU Usage',
description: 'Monitors CPU usage across all VMs and containers',
metric: 'cpu',
condition: 'greater_than',
threshold: cpuThreshold,
@@ -146,25 +146,9 @@ class AlertManager extends EventEmitter {
suppressionTime: 300000, // 5 minutes
},
{
id: 'critical_cpu',
name: 'Critical CPU Usage',
description: 'Critical CPU usage requiring immediate attention',
metric: 'cpu',
condition: 'greater_than',
threshold: this.parseEnvInt('ALERT_CPU_CRITICAL_THRESHOLD', 95, 1, 100),
duration: this.parseEnvInt('ALERT_CPU_CRITICAL_DURATION', 60000, 1000),
severity: 'critical',
enabled: process.env.ALERT_CPU_CRITICAL_ENABLED !== 'false',
tags: ['performance', 'cpu', 'critical'],
group: 'critical_alerts',
escalationTime: 300000, // 5 minutes
autoResolve: true,
suppressionTime: 0, // No suppression for critical
},
{
id: 'high_memory',
name: 'High Memory Usage',
description: 'Memory usage exceeds safe operating levels',
id: 'memory',
name: 'Memory Usage',
description: 'Monitors memory usage across all VMs and containers',
metric: 'memory',
condition: 'greater_than',
threshold: memThreshold,
@@ -178,25 +162,9 @@ class AlertManager extends EventEmitter {
suppressionTime: 300000,
},
{
id: 'critical_memory',
name: 'Critical Memory Usage',
description: 'Memory usage at critical levels - system stability at risk',
metric: 'memory',
condition: 'greater_than',
threshold: this.parseEnvInt('ALERT_MEMORY_CRITICAL_THRESHOLD', 98, 1, 100),
duration: this.parseEnvInt('ALERT_MEMORY_CRITICAL_DURATION', 120000, 1000),
severity: 'critical',
enabled: process.env.ALERT_MEMORY_CRITICAL_ENABLED !== 'false',
tags: ['performance', 'memory', 'critical'],
group: 'critical_alerts',
escalationTime: 300000, // 5 minutes
autoResolve: true,
suppressionTime: 0,
},
{
id: 'disk_space_warning',
name: 'Low Disk Space',
description: 'Disk usage approaching capacity limits',
id: 'disk',
name: 'Disk Usage',
description: 'Monitors disk space usage across all VMs and containers',
metric: 'disk',
condition: 'greater_than',
threshold: diskThreshold,
@@ -210,25 +178,9 @@ class AlertManager extends EventEmitter {
suppressionTime: 600000, // 10 minutes
},
{
id: 'disk_space_critical',
name: 'Critical Disk Space',
description: 'Disk space critically low - immediate action required',
metric: 'disk',
condition: 'greater_than',
threshold: this.parseEnvInt('ALERT_DISK_CRITICAL_THRESHOLD', 95, 1, 100),
duration: this.parseEnvInt('ALERT_DISK_CRITICAL_DURATION', 60000, 1000),
severity: 'critical',
enabled: process.env.ALERT_DISK_CRITICAL_ENABLED !== 'false',
tags: ['storage', 'disk', 'critical'],
group: 'critical_alerts',
escalationTime: 300000, // 5 minutes
autoResolve: true,
suppressionTime: 0,
},
{
id: 'guest_down',
name: 'Guest System Down',
description: 'Virtual machine or container has stopped unexpectedly',
id: 'down',
name: 'System Availability',
description: 'Monitors VM/container availability and uptime',
metric: 'status',
condition: 'equals',
threshold: 'stopped',
@@ -240,29 +192,13 @@ class AlertManager extends EventEmitter {
escalationTime: 600000, // 10 minutes
autoResolve: true,
suppressionTime: 120000, // 2 minutes
},
{
id: 'network_anomaly',
name: 'Network Traffic Anomaly',
description: 'Unusual network traffic patterns detected',
metric: 'network_combined',
condition: 'anomaly',
threshold: 'auto', // Machine learning based
duration: 180000, // 3 minutes
severity: 'info',
enabled: process.env.ALERT_NETWORK_ANOMALY_ENABLED === 'true',
tags: ['network', 'anomaly'],
group: 'network_alerts',
escalationTime: 1800000, // 30 minutes
autoResolve: true,
suppressionTime: 900000, // 15 minutes
}
];
defaultRules.forEach(rule => {
if (rule.enabled) {
this.alertRules.set(rule.id, rule);
}
// Always add rules to the map, regardless of enabled state
// This allows them to be displayed and toggled in the UI
this.alertRules.set(rule.id, rule);
});
}
@@ -2442,6 +2378,122 @@ This alert was generated by Pulse monitoring system.
this.emailTransporter.close();
}
}
async sendTestEmail() {
try {
console.log('[AlertManager] Sending test email...');
if (!this.emailTransporter) {
return { success: false, error: 'Email transporter not configured' };
}
const config = await this.loadEmailConfig();
if (!config.to) {
return { success: false, error: 'No recipient email address configured' };
}
return await this.sendTestEmailWithConfig({
ALERT_FROM_EMAIL: config.from,
ALERT_TO_EMAIL: config.to,
SMTP_HOST: config.host,
SMTP_PORT: config.port,
SMTP_USER: config.user,
SMTP_SECURE: config.secure
});
} catch (error) {
console.error('[AlertManager] Error sending test email:', error);
return { success: false, error: error.message };
}
}
async sendTestEmailWithConfig(config) {
try {
console.log('[AlertManager] Sending test email with provided config...');
if (!this.emailTransporter) {
return { success: false, error: 'Email transporter not configured' };
}
if (!config.ALERT_TO_EMAIL) {
return { success: false, error: 'No recipient email address configured' };
}
const testEmailOptions = {
from: config.ALERT_FROM_EMAIL || 'noreply@pulse.local',
to: config.ALERT_TO_EMAIL,
subject: 'Pulse Alert System - Test Email',
text: `This is a test email from your Pulse monitoring system.
Sent at: ${new Date().toISOString()}
From: ${require('os').hostname()}
If you received this email, your email configuration is working correctly!
Configuration used:
- SMTP Host: ${config.SMTP_HOST}
- SMTP Port: ${config.SMTP_PORT}
- From: ${config.ALERT_FROM_EMAIL}
- To: ${config.ALERT_TO_EMAIL}
Best regards,
Pulse Monitoring System`,
html: `<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2 style="color: #3b82f6;">Pulse Alert System - Test Email</h2>
<p>This is a test email from your Pulse monitoring system.</p>
<div style="background-color: #f3f4f6; padding: 15px; border-radius: 5px; margin: 20px 0;">
<strong>Test Details:</strong><br>
<strong>Sent at:</strong> ${new Date().toISOString()}<br>
<strong>From:</strong> ${require('os').hostname()}<br><br>
<strong>Configuration used:</strong><br>
<strong>SMTP Host:</strong> ${config.SMTP_HOST}<br>
<strong>SMTP Port:</strong> ${config.SMTP_PORT}<br>
<strong>From:</strong> ${config.ALERT_FROM_EMAIL}<br>
<strong>To:</strong> ${config.ALERT_TO_EMAIL}
</div>
<p style="color: #16a34a;"><strong>✅ Success!</strong> If you received this email, your email configuration is working correctly!</p>
<hr style="margin: 30px 0; border: none; border-top: 1px solid #e5e7eb;">
<p style="color: #6b7280; font-size: 12px;">
Best regards,<br>
Pulse Monitoring System
</p>
</div>`
};
await this.emailTransporter.sendMail(testEmailOptions);
console.log('[AlertManager] Test email sent successfully');
return { success: true };
} catch (error) {
console.error('[AlertManager] Error sending test email:', error);
return { success: false, error: error.message };
}
}
async loadEmailConfig() {
try {
// Load email configuration from environment or config
const { loadConfiguration } = require('./configLoader');
const config = await loadConfiguration();
console.log('[AlertManager] Loading email config, ALERT_TO_EMAIL:', config.ALERT_TO_EMAIL);
return {
from: config.ALERT_FROM_EMAIL,
to: config.ALERT_TO_EMAIL,
host: config.SMTP_HOST,
port: config.SMTP_PORT,
user: config.SMTP_USER,
secure: config.SMTP_SECURE === 'true'
};
} catch (error) {
console.error('[AlertManager] Error loading email config:', error);
return {};
}
}
}
module.exports = AlertManager;
+41
View File
@@ -466,6 +466,47 @@ app.get('/api/alerts/metrics', (req, res) => {
}
});
// Test email configuration
app.post('/api/alerts/test-email', async (req, res) => {
try {
console.log('[Test Email] Sending test email...');
// Get email configuration from process.env (loaded by configLoader)
const config = {
ALERT_TO_EMAIL: process.env.ALERT_TO_EMAIL,
ALERT_FROM_EMAIL: process.env.ALERT_FROM_EMAIL,
SMTP_HOST: process.env.SMTP_HOST,
SMTP_PORT: process.env.SMTP_PORT,
SMTP_USER: process.env.SMTP_USER,
SMTP_SECURE: process.env.SMTP_SECURE
};
console.log('[Test Email] Config loaded, ALERT_TO_EMAIL:', config.ALERT_TO_EMAIL);
if (!config.ALERT_TO_EMAIL) {
return res.status(400).json({ success: false, error: 'No recipient email address configured' });
}
if (!config.SMTP_HOST) {
return res.status(400).json({ success: false, error: 'SMTP server not configured' });
}
// Use the alert manager to send a test email with the config
const testResult = await stateManager.alertManager.sendTestEmailWithConfig(config);
if (testResult.success) {
console.log('[Test Email] Test email sent successfully');
res.json({ success: true, message: 'Test email sent successfully' });
} else {
console.error('[Test Email] Failed to send test email:', testResult.error);
res.status(400).json({ success: false, error: testResult.error || 'Failed to send test email' });
}
} catch (error) {
console.error('[Test Email] Error sending test email:', error);
res.status(500).json({ success: false, error: 'Internal server error while sending test email' });
}
});
// Alert rules management with filtering
app.get('/api/alerts/rules', (req, res) => {
try {
File diff suppressed because it is too large Load Diff