pms_md 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -0
- package/node-monitor/ARCHITECTURE.md +341 -0
- package/node-monitor/CHANGELOG.md +105 -0
- package/node-monitor/CONTRIBUTING.md +96 -0
- package/node-monitor/DESIGN_IMPROVEMENTS.md +286 -0
- package/node-monitor/FILTER_BUTTONS_FIX.md +303 -0
- package/node-monitor/GETTING_STARTED.md +416 -0
- package/node-monitor/INSTALLATION.md +470 -0
- package/node-monitor/LICENSE +22 -0
- package/node-monitor/PUBLISHING_GUIDE.md +331 -0
- package/node-monitor/QUICK_REFERENCE.md +252 -0
- package/node-monitor/README.md +458 -0
- package/node-monitor/READY_TO_PUBLISH.md +272 -0
- package/node-monitor/SETUP_GUIDE.md +479 -0
- package/node-monitor/examples/EMAIL_SETUP_GUIDE.md +282 -0
- package/node-monitor/examples/ERROR_LOGGING_GUIDE.md +405 -0
- package/node-monitor/examples/GET_APP_PASSWORD.md +145 -0
- package/node-monitor/examples/LOG_FILES_REFERENCE.md +336 -0
- package/node-monitor/examples/QUICK_START_EMAIL.md +126 -0
- package/node-monitor/examples/express-app.js +499 -0
- package/node-monitor/examples/package-lock.json +1295 -0
- package/node-monitor/examples/package.json +18 -0
- package/node-monitor/examples/public/css/style.css +718 -0
- package/node-monitor/examples/public/js/dashboard.js +207 -0
- package/node-monitor/examples/public/js/health.js +114 -0
- package/node-monitor/examples/public/js/main.js +89 -0
- package/node-monitor/examples/public/js/metrics.js +225 -0
- package/node-monitor/examples/public/js/theme.js +138 -0
- package/node-monitor/examples/views/dashboard.ejs +20 -0
- package/node-monitor/examples/views/error-logs.ejs +1129 -0
- package/node-monitor/examples/views/health.ejs +21 -0
- package/node-monitor/examples/views/home.ejs +341 -0
- package/node-monitor/examples/views/layout.ejs +50 -0
- package/node-monitor/examples/views/metrics.ejs +16 -0
- package/node-monitor/examples/views/partials/footer.ejs +16 -0
- package/node-monitor/examples/views/partials/header.ejs +35 -0
- package/node-monitor/examples/views/partials/nav.ejs +23 -0
- package/node-monitor/examples/views/status.ejs +390 -0
- package/node-monitor/package-lock.json +4300 -0
- package/node-monitor/package.json +76 -0
- package/node-monitor/pre-publish-check.js +200 -0
- package/node-monitor/src/config/monitoringConfig.js +255 -0
- package/node-monitor/src/index.js +300 -0
- package/node-monitor/src/logger/errorLogger.js +297 -0
- package/node-monitor/src/monitors/apiErrorMonitor.js +156 -0
- package/node-monitor/src/monitors/dbConnectionMonitor.js +389 -0
- package/node-monitor/src/monitors/serverHealthMonitor.js +320 -0
- package/node-monitor/src/monitors/systemResourceMonitor.js +357 -0
- package/node-monitor/src/notifiers/emailNotifier.js +248 -0
- package/node-monitor/src/notifiers/notificationManager.js +96 -0
- package/node-monitor/src/notifiers/slackNotifier.js +209 -0
- package/node-monitor/src/views/dashboard.html +530 -0
- package/node-monitor/src/views/health.html +399 -0
- package/node-monitor/src/views/metrics.html +406 -0
- package/package.json +22 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Notifier
|
|
3
|
+
* Sends email notifications using Nodemailer
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const nodemailer = require('nodemailer');
|
|
7
|
+
|
|
8
|
+
class EmailNotifier {
|
|
9
|
+
constructor(config, logger) {
|
|
10
|
+
this.config = config.notifications.email;
|
|
11
|
+
this.appConfig = config.app;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
this.transporter = null;
|
|
14
|
+
this.lastSent = new Map(); // Track last sent time per alert type
|
|
15
|
+
this.cooldown = config.notifications.cooldown;
|
|
16
|
+
|
|
17
|
+
if (this.config.enabled) {
|
|
18
|
+
this.initialize();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initialize email transporter
|
|
24
|
+
*/
|
|
25
|
+
initialize() {
|
|
26
|
+
try {
|
|
27
|
+
this.transporter = nodemailer.createTransport({
|
|
28
|
+
host: this.config.host,
|
|
29
|
+
port: this.config.port,
|
|
30
|
+
secure: this.config.secure,
|
|
31
|
+
auth: {
|
|
32
|
+
user: this.config.auth.user,
|
|
33
|
+
pass: this.config.auth.pass
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Verify connection
|
|
38
|
+
this.transporter.verify((error) => {
|
|
39
|
+
if (error) {
|
|
40
|
+
this.logger.logWarning('email_config', 'Email transporter verification failed', { error: error.message });
|
|
41
|
+
} else {
|
|
42
|
+
this.logger.logInfo('Email notifier initialized successfully');
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
} catch (error) {
|
|
46
|
+
this.logger.logWarning('email_config', 'Failed to initialize email notifier', { error: error.message });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check if cooldown period has passed
|
|
52
|
+
*/
|
|
53
|
+
canSend(alertType) {
|
|
54
|
+
const lastSentTime = this.lastSent.get(alertType);
|
|
55
|
+
if (!lastSentTime) return true;
|
|
56
|
+
|
|
57
|
+
return Date.now() - lastSentTime >= this.cooldown;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Send email notification
|
|
62
|
+
*/
|
|
63
|
+
async send(alertType, subject, message, details = {}) {
|
|
64
|
+
if (!this.config.enabled || !this.transporter) {
|
|
65
|
+
return { success: false, reason: 'Email notifications not enabled or configured' };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!this.canSend(alertType)) {
|
|
69
|
+
return { success: false, reason: 'Cooldown period active' };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const htmlContent = this.generateHtmlEmail(alertType, subject, message, details);
|
|
74
|
+
const textContent = this.generateTextEmail(alertType, subject, message, details);
|
|
75
|
+
|
|
76
|
+
const mailOptions = {
|
|
77
|
+
from: this.config.from || this.config.auth.user,
|
|
78
|
+
to: this.config.recipients.join(', '),
|
|
79
|
+
subject: `[${this.appConfig.name}] ${subject}`,
|
|
80
|
+
text: textContent,
|
|
81
|
+
html: htmlContent
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const info = await this.transporter.sendMail(mailOptions);
|
|
85
|
+
|
|
86
|
+
this.lastSent.set(alertType, Date.now());
|
|
87
|
+
this.logger.logInfo('Email notification sent', { alertType, messageId: info.messageId });
|
|
88
|
+
|
|
89
|
+
return { success: true, messageId: info.messageId };
|
|
90
|
+
} catch (error) {
|
|
91
|
+
this.logger.logWarning('email_send_failed', 'Failed to send email notification', {
|
|
92
|
+
error: error.message,
|
|
93
|
+
alertType
|
|
94
|
+
});
|
|
95
|
+
return { success: false, reason: error.message };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Generate HTML email content
|
|
101
|
+
*/
|
|
102
|
+
generateHtmlEmail(alertType, subject, message, details) {
|
|
103
|
+
const severity = this.getSeverityColor(alertType);
|
|
104
|
+
const timestamp = new Date().toLocaleString();
|
|
105
|
+
|
|
106
|
+
let detailsHtml = '';
|
|
107
|
+
if (Object.keys(details).length > 0) {
|
|
108
|
+
detailsHtml = '<h3>Details:</h3><table style="border-collapse: collapse; width: 100%;">';
|
|
109
|
+
for (const [key, value] of Object.entries(details)) {
|
|
110
|
+
detailsHtml += `
|
|
111
|
+
<tr>
|
|
112
|
+
<td style="padding: 8px; border: 1px solid #ddd; font-weight: bold;">${this.formatKey(key)}</td>
|
|
113
|
+
<td style="padding: 8px; border: 1px solid #ddd;">${this.formatValue(value)}</td>
|
|
114
|
+
</tr>
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
117
|
+
detailsHtml += '</table>';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return `
|
|
121
|
+
<!DOCTYPE html>
|
|
122
|
+
<html>
|
|
123
|
+
<head>
|
|
124
|
+
<style>
|
|
125
|
+
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
|
126
|
+
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
127
|
+
.header { background-color: ${severity}; color: white; padding: 20px; border-radius: 5px 5px 0 0; }
|
|
128
|
+
.content { background-color: #f9f9f9; padding: 20px; border: 1px solid #ddd; border-top: none; }
|
|
129
|
+
.footer { background-color: #333; color: white; padding: 10px; text-align: center; font-size: 12px; border-radius: 0 0 5px 5px; }
|
|
130
|
+
table { margin-top: 10px; }
|
|
131
|
+
</style>
|
|
132
|
+
</head>
|
|
133
|
+
<body>
|
|
134
|
+
<div class="container">
|
|
135
|
+
<div class="header">
|
|
136
|
+
<h2 style="margin: 0;">🚨 ${subject}</h2>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="content">
|
|
139
|
+
<p><strong>Message:</strong> ${message}</p>
|
|
140
|
+
<p><strong>Application:</strong> ${this.appConfig.name} (v${this.appConfig.version})</p>
|
|
141
|
+
<p><strong>Environment:</strong> ${this.appConfig.environment}</p>
|
|
142
|
+
<p><strong>Hostname:</strong> ${this.appConfig.hostname}</p>
|
|
143
|
+
<p><strong>Time:</strong> ${timestamp}</p>
|
|
144
|
+
${detailsHtml}
|
|
145
|
+
</div>
|
|
146
|
+
<div class="footer">
|
|
147
|
+
Node Monitor - Automated Alert System
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</body>
|
|
151
|
+
</html>
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Generate plain text email content
|
|
157
|
+
*/
|
|
158
|
+
generateTextEmail(alertType, subject, message, details) {
|
|
159
|
+
const timestamp = new Date().toLocaleString();
|
|
160
|
+
let text = `
|
|
161
|
+
🚨 ${subject}
|
|
162
|
+
|
|
163
|
+
Message: ${message}
|
|
164
|
+
Application: ${this.appConfig.name} (v${this.appConfig.version})
|
|
165
|
+
Environment: ${this.appConfig.environment}
|
|
166
|
+
Hostname: ${this.appConfig.hostname}
|
|
167
|
+
Time: ${timestamp}
|
|
168
|
+
`;
|
|
169
|
+
|
|
170
|
+
if (Object.keys(details).length > 0) {
|
|
171
|
+
text += '\nDetails:\n';
|
|
172
|
+
for (const [key, value] of Object.entries(details)) {
|
|
173
|
+
text += ` ${this.formatKey(key)}: ${this.formatValue(value)}\n`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
text += '\n---\nNode Monitor - Automated Alert System';
|
|
178
|
+
return text;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get severity color based on alert type
|
|
183
|
+
*/
|
|
184
|
+
getSeverityColor(alertType) {
|
|
185
|
+
const type = alertType.toLowerCase();
|
|
186
|
+
if (type.includes('critical') || type.includes('down') || type.includes('failed')) {
|
|
187
|
+
return '#dc3545'; // Red
|
|
188
|
+
}
|
|
189
|
+
if (type.includes('warning') || type.includes('high')) {
|
|
190
|
+
return '#ffc107'; // Yellow
|
|
191
|
+
}
|
|
192
|
+
if (type.includes('recovery') || type.includes('success')) {
|
|
193
|
+
return '#28a745'; // Green
|
|
194
|
+
}
|
|
195
|
+
return '#007bff'; // Blue (info)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Format key for display
|
|
200
|
+
*/
|
|
201
|
+
formatKey(key) {
|
|
202
|
+
return key
|
|
203
|
+
.replace(/([A-Z])/g, ' $1')
|
|
204
|
+
.replace(/^./, str => str.toUpperCase())
|
|
205
|
+
.trim();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Format value for display
|
|
210
|
+
*/
|
|
211
|
+
formatValue(value) {
|
|
212
|
+
if (typeof value === 'object') {
|
|
213
|
+
return JSON.stringify(value, null, 2);
|
|
214
|
+
}
|
|
215
|
+
return String(value);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Send critical alert
|
|
220
|
+
*/
|
|
221
|
+
async sendCritical(subject, message, details) {
|
|
222
|
+
return this.send('critical', `🔴 CRITICAL: ${subject}`, message, details);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Send warning alert
|
|
227
|
+
*/
|
|
228
|
+
async sendWarning(subject, message, details) {
|
|
229
|
+
return this.send('warning', `⚠️ WARNING: ${subject}`, message, details);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Send info alert
|
|
234
|
+
*/
|
|
235
|
+
async sendInfo(subject, message, details) {
|
|
236
|
+
return this.send('info', `ℹ️ INFO: ${subject}`, message, details);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Send recovery notification
|
|
241
|
+
*/
|
|
242
|
+
async sendRecovery(subject, message, details) {
|
|
243
|
+
return this.send('recovery', `✅ RECOVERY: ${subject}`, message, details);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
module.exports = EmailNotifier;
|
|
248
|
+
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notification Manager
|
|
3
|
+
* Coordinates all notification channels
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const EmailNotifier = require('./emailNotifier');
|
|
7
|
+
const SlackNotifier = require('./slackNotifier');
|
|
8
|
+
|
|
9
|
+
class NotificationManager {
|
|
10
|
+
constructor(config, logger) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
this.notifiers = [];
|
|
14
|
+
|
|
15
|
+
this.initialize();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Initialize all enabled notifiers
|
|
20
|
+
*/
|
|
21
|
+
initialize() {
|
|
22
|
+
if (this.config.notifications.email.enabled) {
|
|
23
|
+
this.notifiers.push(new EmailNotifier(this.config, this.logger));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (this.config.notifications.slack.enabled) {
|
|
27
|
+
this.notifiers.push(new SlackNotifier(this.config, this.logger));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// SMS notifier can be added here when implemented
|
|
31
|
+
// if (this.config.notifications.sms.enabled) {
|
|
32
|
+
// this.notifiers.push(new SmsNotifier(this.config, this.logger));
|
|
33
|
+
// }
|
|
34
|
+
|
|
35
|
+
this.logger.logInfo(`Notification manager initialized with ${this.notifiers.length} notifier(s)`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Send notification through all enabled channels
|
|
40
|
+
*/
|
|
41
|
+
async notify(alertType, subject, message, details = {}) {
|
|
42
|
+
if (!this.config.notifications.enabled || this.notifiers.length === 0) {
|
|
43
|
+
return { success: false, reason: 'No notifiers enabled' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const results = await Promise.allSettled(
|
|
47
|
+
this.notifiers.map(notifier =>
|
|
48
|
+
notifier.send(alertType, subject, message, details)
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const summary = {
|
|
53
|
+
total: results.length,
|
|
54
|
+
successful: results.filter(r => r.status === 'fulfilled' && r.value.success).length,
|
|
55
|
+
failed: results.filter(r => r.status === 'rejected' || !r.value.success).length,
|
|
56
|
+
results: results
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return summary;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Send critical alert
|
|
64
|
+
*/
|
|
65
|
+
async sendCritical(subject, message, details = {}) {
|
|
66
|
+
this.logger.logSystemError('critical_alert', subject, details);
|
|
67
|
+
return this.notify('critical', subject, message, details);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Send warning alert
|
|
72
|
+
*/
|
|
73
|
+
async sendWarning(subject, message, details = {}) {
|
|
74
|
+
this.logger.logWarning('warning_alert', subject, details);
|
|
75
|
+
return this.notify('warning', subject, message, details);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Send info notification
|
|
80
|
+
*/
|
|
81
|
+
async sendInfo(subject, message, details = {}) {
|
|
82
|
+
this.logger.logInfo(subject, details);
|
|
83
|
+
return this.notify('info', subject, message, details);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Send recovery notification
|
|
88
|
+
*/
|
|
89
|
+
async sendRecovery(subject, message, details = {}) {
|
|
90
|
+
this.logger.logInfo(`Recovery: ${subject}`, details);
|
|
91
|
+
return this.notify('recovery', subject, message, details);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = NotificationManager;
|
|
96
|
+
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack Notifier
|
|
3
|
+
* Sends notifications to Slack using Incoming Webhooks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { IncomingWebhook } = require('@slack/webhook');
|
|
7
|
+
|
|
8
|
+
class SlackNotifier {
|
|
9
|
+
constructor(config, logger) {
|
|
10
|
+
this.config = config.notifications.slack;
|
|
11
|
+
this.appConfig = config.app;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
this.webhook = null;
|
|
14
|
+
this.lastSent = new Map();
|
|
15
|
+
this.cooldown = config.notifications.cooldown;
|
|
16
|
+
|
|
17
|
+
if (this.config.enabled) {
|
|
18
|
+
this.initialize();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initialize Slack webhook
|
|
24
|
+
*/
|
|
25
|
+
initialize() {
|
|
26
|
+
try {
|
|
27
|
+
this.webhook = new IncomingWebhook(this.config.webhook);
|
|
28
|
+
this.logger.logInfo('Slack notifier initialized successfully');
|
|
29
|
+
} catch (error) {
|
|
30
|
+
this.logger.logWarning('slack_config', 'Failed to initialize Slack notifier', {
|
|
31
|
+
error: error.message
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if cooldown period has passed
|
|
38
|
+
*/
|
|
39
|
+
canSend(alertType) {
|
|
40
|
+
const lastSentTime = this.lastSent.get(alertType);
|
|
41
|
+
if (!lastSentTime) return true;
|
|
42
|
+
|
|
43
|
+
return Date.now() - lastSentTime >= this.cooldown;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Send Slack notification
|
|
48
|
+
*/
|
|
49
|
+
async send(alertType, subject, message, details = {}) {
|
|
50
|
+
if (!this.config.enabled || !this.webhook) {
|
|
51
|
+
return { success: false, reason: 'Slack notifications not enabled or configured' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!this.canSend(alertType)) {
|
|
55
|
+
return { success: false, reason: 'Cooldown period active' };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const payload = this.buildPayload(alertType, subject, message, details);
|
|
60
|
+
await this.webhook.send(payload);
|
|
61
|
+
|
|
62
|
+
this.lastSent.set(alertType, Date.now());
|
|
63
|
+
this.logger.logInfo('Slack notification sent', { alertType });
|
|
64
|
+
|
|
65
|
+
return { success: true };
|
|
66
|
+
} catch (error) {
|
|
67
|
+
this.logger.logWarning('slack_send_failed', 'Failed to send Slack notification', {
|
|
68
|
+
error: error.message,
|
|
69
|
+
alertType
|
|
70
|
+
});
|
|
71
|
+
return { success: false, reason: error.message };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build Slack message payload
|
|
77
|
+
*/
|
|
78
|
+
buildPayload(alertType, subject, message, details) {
|
|
79
|
+
const color = this.getSeverityColor(alertType);
|
|
80
|
+
const emoji = this.getSeverityEmoji(alertType);
|
|
81
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
82
|
+
|
|
83
|
+
const fields = [
|
|
84
|
+
{
|
|
85
|
+
title: 'Application',
|
|
86
|
+
value: `${this.appConfig.name} (v${this.appConfig.version})`,
|
|
87
|
+
short: true
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
title: 'Environment',
|
|
91
|
+
value: this.appConfig.environment,
|
|
92
|
+
short: true
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
title: 'Hostname',
|
|
96
|
+
value: this.appConfig.hostname,
|
|
97
|
+
short: true
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
title: 'Alert Type',
|
|
101
|
+
value: alertType.toUpperCase(),
|
|
102
|
+
short: true
|
|
103
|
+
}
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
// Add custom details as fields
|
|
107
|
+
for (const [key, value] of Object.entries(details)) {
|
|
108
|
+
if (typeof value !== 'object') {
|
|
109
|
+
fields.push({
|
|
110
|
+
title: this.formatKey(key),
|
|
111
|
+
value: String(value),
|
|
112
|
+
short: true
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
username: this.config.username,
|
|
119
|
+
channel: this.config.channel,
|
|
120
|
+
icon_emoji: emoji,
|
|
121
|
+
attachments: [
|
|
122
|
+
{
|
|
123
|
+
color: color,
|
|
124
|
+
title: subject,
|
|
125
|
+
text: message,
|
|
126
|
+
fields: fields,
|
|
127
|
+
footer: 'Node Monitor',
|
|
128
|
+
footer_icon: 'https://platform.slack-edge.com/img/default_application_icon.png',
|
|
129
|
+
ts: timestamp
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get severity color for Slack
|
|
137
|
+
*/
|
|
138
|
+
getSeverityColor(alertType) {
|
|
139
|
+
const type = alertType.toLowerCase();
|
|
140
|
+
if (type.includes('critical') || type.includes('down') || type.includes('failed')) {
|
|
141
|
+
return 'danger'; // Red
|
|
142
|
+
}
|
|
143
|
+
if (type.includes('warning') || type.includes('high')) {
|
|
144
|
+
return 'warning'; // Yellow
|
|
145
|
+
}
|
|
146
|
+
if (type.includes('recovery') || type.includes('success')) {
|
|
147
|
+
return 'good'; // Green
|
|
148
|
+
}
|
|
149
|
+
return '#007bff'; // Blue (info)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get severity emoji
|
|
154
|
+
*/
|
|
155
|
+
getSeverityEmoji(alertType) {
|
|
156
|
+
const type = alertType.toLowerCase();
|
|
157
|
+
if (type.includes('critical') || type.includes('down') || type.includes('failed')) {
|
|
158
|
+
return ':rotating_light:';
|
|
159
|
+
}
|
|
160
|
+
if (type.includes('warning') || type.includes('high')) {
|
|
161
|
+
return ':warning:';
|
|
162
|
+
}
|
|
163
|
+
if (type.includes('recovery') || type.includes('success')) {
|
|
164
|
+
return ':white_check_mark:';
|
|
165
|
+
}
|
|
166
|
+
return ':information_source:';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Format key for display
|
|
171
|
+
*/
|
|
172
|
+
formatKey(key) {
|
|
173
|
+
return key
|
|
174
|
+
.replace(/([A-Z])/g, ' $1')
|
|
175
|
+
.replace(/^./, str => str.toUpperCase())
|
|
176
|
+
.trim();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Send critical alert
|
|
181
|
+
*/
|
|
182
|
+
async sendCritical(subject, message, details) {
|
|
183
|
+
return this.send('critical', `🔴 CRITICAL: ${subject}`, message, details);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Send warning alert
|
|
188
|
+
*/
|
|
189
|
+
async sendWarning(subject, message, details) {
|
|
190
|
+
return this.send('warning', `⚠️ WARNING: ${subject}`, message, details);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Send info alert
|
|
195
|
+
*/
|
|
196
|
+
async sendInfo(subject, message, details) {
|
|
197
|
+
return this.send('info', `ℹ️ INFO: ${subject}`, message, details);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Send recovery notification
|
|
202
|
+
*/
|
|
203
|
+
async sendRecovery(subject, message, details) {
|
|
204
|
+
return this.send('recovery', `✅ RECOVERY: ${subject}`, message, details);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = SlackNotifier;
|
|
209
|
+
|