1
1
const EventEmitter = require ( 'events' ) ;
2
2
const fs = require ( 'fs' ) . promises ;
3
3
const path = require ( 'path' ) ;
4
+ const nodemailer = require ( 'nodemailer' ) ;
4
5
const customThresholdManager = require ( './customThresholds' ) ;
5
6
6
7
class AlertManager extends EventEmitter {
@@ -36,6 +37,10 @@ class AlertManager extends EventEmitter {
36
37
// Initialize custom threshold manager
37
38
this . initializeCustomThresholds ( ) ;
38
39
40
+ // Initialize email transporter
41
+ this . emailTransporter = null ;
42
+ this . initializeEmailTransporter ( ) ;
43
+
39
44
// Cleanup timer for resolved alerts
40
45
this . cleanupInterval = setInterval ( ( ) => {
41
46
this . cleanupResolvedAlerts ( ) ;
@@ -544,18 +549,27 @@ class AlertManager extends EventEmitter {
544
549
} ) ;
545
550
}
546
551
547
- sendToChannel ( channel , alert ) {
548
- // This would implement actual notification sending
549
- // For now, just log it
550
- console . log ( `[NOTIFICATION] Sending to ${ channel . name } :` , {
551
- channel : channel . type ,
552
- alert : alert . rule . name ,
553
- severity : alert . rule . severity ,
554
- guest : alert . guest . name
555
- } ) ;
556
-
557
- // Emit event for external handlers
558
- this . emit ( 'notification' , { channel, alert } ) ;
552
+ async sendToChannel ( channel , alert ) {
553
+ try {
554
+ console . log ( `[NOTIFICATION] Sending to ${ channel . name } :` , {
555
+ channel : channel . type ,
556
+ alert : alert . rule . name ,
557
+ severity : alert . rule . severity ,
558
+ guest : alert . guest . name
559
+ } ) ;
560
+
561
+ if ( channel . type === 'email' ) {
562
+ await this . sendEmailNotification ( channel , alert ) ;
563
+ } else if ( channel . type === 'webhook' ) {
564
+ await this . sendWebhookNotification ( channel , alert ) ;
565
+ }
566
+
567
+ // Emit event for external handlers
568
+ this . emit ( 'notification' , { channel, alert } ) ;
569
+ } catch ( error ) {
570
+ console . error ( `[NOTIFICATION ERROR] Failed to send to ${ channel . name } :` , error ) ;
571
+ this . emit ( 'notificationError' , { channel, alert, error } ) ;
572
+ }
559
573
}
560
574
561
575
updateMetrics ( ) {
@@ -991,6 +1005,146 @@ class AlertManager extends EventEmitter {
991
1005
}
992
1006
}
993
1007
1008
+ /**
1009
+ * Initialize email transporter for sending notifications
1010
+ */
1011
+ initializeEmailTransporter ( ) {
1012
+ if ( process . env . SMTP_HOST ) {
1013
+ try {
1014
+ this . emailTransporter = nodemailer . createTransporter ( {
1015
+ host : process . env . SMTP_HOST ,
1016
+ port : parseInt ( process . env . SMTP_PORT ) || 587 ,
1017
+ secure : process . env . SMTP_SECURE === 'true' , // true for 465, false for other ports
1018
+ auth : {
1019
+ user : process . env . SMTP_USER ,
1020
+ pass : process . env . SMTP_PASS
1021
+ }
1022
+ } ) ;
1023
+ console . log ( '[AlertManager] Email transporter initialized' ) ;
1024
+ } catch ( error ) {
1025
+ console . error ( '[AlertManager] Failed to initialize email transporter:' , error ) ;
1026
+ }
1027
+ } else {
1028
+ console . log ( '[AlertManager] SMTP not configured, email notifications disabled' ) ;
1029
+ }
1030
+ }
1031
+
1032
+ /**
1033
+ * Send email notification
1034
+ */
1035
+ async sendEmailNotification ( channel , alert ) {
1036
+ if ( ! this . emailTransporter ) {
1037
+ throw new Error ( 'Email transporter not configured' ) ;
1038
+ }
1039
+
1040
+ const recipients = channel . config . to ;
1041
+ if ( ! recipients || recipients . length === 0 ) {
1042
+ throw new Error ( 'No email recipients configured' ) ;
1043
+ }
1044
+
1045
+ const severityEmoji = {
1046
+ 'info' : '💙' ,
1047
+ 'warning' : '⚠️' ,
1048
+ 'critical' : '🚨'
1049
+ } ;
1050
+
1051
+ const subject = `${ severityEmoji [ alert . rule . severity ] || '📢' } Pulse Alert: ${ alert . rule . name } ` ;
1052
+
1053
+ const html = `
1054
+ <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
1055
+ <div style="background: ${ alert . rule . severity === 'critical' ? '#dc2626' : alert . rule . severity === 'warning' ? '#ea580c' : '#2563eb' } ; color: white; padding: 20px; border-radius: 8px 8px 0 0;">
1056
+ <h1 style="margin: 0; font-size: 24px;">${ severityEmoji [ alert . rule . severity ] || '📢' } ${ alert . rule . name } </h1>
1057
+ <p style="margin: 5px 0 0 0; opacity: 0.9; font-size: 16px;">Severity: ${ alert . rule . severity . toUpperCase ( ) } </p>
1058
+ </div>
1059
+
1060
+ <div style="background: #f9fafb; padding: 20px; border-left: 4px solid ${ alert . rule . severity === 'critical' ? '#dc2626' : alert . rule . severity === 'warning' ? '#ea580c' : '#2563eb' } ;">
1061
+ <h2 style="margin: 0 0 15px 0; color: #374151;">Alert Details</h2>
1062
+
1063
+ <table style="width: 100%; border-collapse: collapse;">
1064
+ <tr>
1065
+ <td style="padding: 8px 0; font-weight: bold; color: #374151; width: 120px;">VM/LXC:</td>
1066
+ <td style="padding: 8px 0; color: #6b7280;">${ alert . guest . name } (${ alert . guest . type } ${ alert . guest . id } )</td>
1067
+ </tr>
1068
+ <tr>
1069
+ <td style="padding: 8px 0; font-weight: bold; color: #374151;">Node:</td>
1070
+ <td style="padding: 8px 0; color: #6b7280;">${ alert . guest . node } </td>
1071
+ </tr>
1072
+ <tr>
1073
+ <td style="padding: 8px 0; font-weight: bold; color: #374151;">Metric:</td>
1074
+ <td style="padding: 8px 0; color: #6b7280;">${ alert . rule . metric . toUpperCase ( ) } </td>
1075
+ </tr>
1076
+ <tr>
1077
+ <td style="padding: 8px 0; font-weight: bold; color: #374151;">Current Value:</td>
1078
+ <td style="padding: 8px 0; color: #6b7280;">${ alert . value } %</td>
1079
+ </tr>
1080
+ <tr>
1081
+ <td style="padding: 8px 0; font-weight: bold; color: #374151;">Threshold:</td>
1082
+ <td style="padding: 8px 0; color: #6b7280;">${ alert . threshold } %</td>
1083
+ </tr>
1084
+ <tr>
1085
+ <td style="padding: 8px 0; font-weight: bold; color: #374151;">Status:</td>
1086
+ <td style="padding: 8px 0; color: #6b7280;">${ alert . guest . status } </td>
1087
+ </tr>
1088
+ <tr>
1089
+ <td style="padding: 8px 0; font-weight: bold; color: #374151;">Time:</td>
1090
+ <td style="padding: 8px 0; color: #6b7280;">${ new Date ( alert . timestamp ) . toLocaleString ( ) } </td>
1091
+ </tr>
1092
+ </table>
1093
+ </div>
1094
+
1095
+ <div style="background: white; padding: 20px; border-radius: 0 0 8px 8px; border-top: 1px solid #e5e7eb;">
1096
+ <p style="margin: 0; color: #6b7280; font-size: 14px;">
1097
+ <strong>Description:</strong> ${ alert . rule . description }
1098
+ </p>
1099
+ <p style="margin: 15px 0 0 0; color: #9ca3af; font-size: 12px;">
1100
+ This alert was generated by Pulse monitoring system.
1101
+ Please check your Proxmox dashboard for more details.
1102
+ </p>
1103
+ </div>
1104
+ </div>
1105
+ ` ;
1106
+
1107
+ const text = `
1108
+ PULSE ALERT: ${ alert . rule . name }
1109
+
1110
+ Severity: ${ alert . rule . severity . toUpperCase ( ) }
1111
+ VM/LXC: ${ alert . guest . name } (${ alert . guest . type } ${ alert . guest . id } )
1112
+ Node: ${ alert . guest . node }
1113
+ Metric: ${ alert . rule . metric . toUpperCase ( ) }
1114
+ Current Value: ${ alert . value } %
1115
+ Threshold: ${ alert . threshold } %
1116
+ Status: ${ alert . guest . status }
1117
+ Time: ${ new Date ( alert . timestamp ) . toLocaleString ( ) }
1118
+
1119
+ Description: ${ alert . rule . description }
1120
+
1121
+ This alert was generated by Pulse monitoring system.
1122
+ ` ;
1123
+
1124
+ const mailOptions = {
1125
+ from : channel . config . from ,
1126
+ to : recipients . join ( ', ' ) ,
1127
+ subject : subject ,
1128
+ text : text ,
1129
+ html : html
1130
+ } ;
1131
+
1132
+ await this . emailTransporter . sendMail ( mailOptions ) ;
1133
+ console . log ( `[EMAIL] Alert sent to: ${ recipients . join ( ', ' ) } ` ) ;
1134
+ }
1135
+
1136
+ /**
1137
+ * Send webhook notification (placeholder for future implementation)
1138
+ */
1139
+ async sendWebhookNotification ( channel , alert ) {
1140
+ if ( ! channel . config . url ) {
1141
+ throw new Error ( 'Webhook URL not configured' ) ;
1142
+ }
1143
+
1144
+ // Placeholder for webhook implementation
1145
+ console . log ( `[WEBHOOK] Would send to: ${ channel . config . url } ` ) ;
1146
+ }
1147
+
994
1148
destroy ( ) {
995
1149
if ( this . cleanupInterval ) {
996
1150
clearInterval ( this . cleanupInterval ) ;
@@ -1003,6 +1157,11 @@ class AlertManager extends EventEmitter {
1003
1157
this . alertRules . clear ( ) ;
1004
1158
this . acknowledgedAlerts . clear ( ) ;
1005
1159
this . suppressedAlerts . clear ( ) ;
1160
+
1161
+ // Close email transporter
1162
+ if ( this . emailTransporter ) {
1163
+ this . emailTransporter . close ( ) ;
1164
+ }
1006
1165
}
1007
1166
}
1008
1167
0 commit comments