mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-18 23:48:43 -05:00
add webhook
This commit is contained in:
@@ -208,11 +208,12 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder {
|
||||
return breaches;
|
||||
}
|
||||
|
||||
// CPU threshold breach
|
||||
if (monitor.cpuAlertThreshold && hardware.cpu?.usage_percent !== undefined) {
|
||||
const cpuPercent = hardware.cpu.usage_percent;
|
||||
// Note: usage_percent values in hardware payload are decimals (0-1)
|
||||
if (monitor.cpuAlertThreshold !== undefined && monitor.cpuAlertThreshold !== null && hardware.cpu?.usage_percent !== undefined) {
|
||||
const cpuUsageDecimal = hardware.cpu.usage_percent;
|
||||
const cpuPercent = cpuUsageDecimal * 100;
|
||||
const threshold = monitor.cpuAlertThreshold;
|
||||
if (cpuPercent >= threshold) {
|
||||
if (cpuPercent > threshold) {
|
||||
breaches.push({
|
||||
metric: "cpu",
|
||||
currentValue: cpuPercent,
|
||||
@@ -224,10 +225,11 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder {
|
||||
}
|
||||
|
||||
// Memory threshold breach
|
||||
if (monitor.memoryAlertThreshold && hardware.memory?.usage_percent !== undefined) {
|
||||
const memoryPercent = hardware.memory.usage_percent;
|
||||
if (monitor.memoryAlertThreshold !== undefined && monitor.memoryAlertThreshold !== null && hardware.memory?.usage_percent !== undefined) {
|
||||
const memoryUsageDecimal = hardware.memory.usage_percent;
|
||||
const memoryPercent = memoryUsageDecimal * 100;
|
||||
const threshold = monitor.memoryAlertThreshold;
|
||||
if (memoryPercent >= threshold) {
|
||||
if (memoryPercent > threshold) {
|
||||
breaches.push({
|
||||
metric: "memory",
|
||||
currentValue: memoryPercent,
|
||||
@@ -239,28 +241,29 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder {
|
||||
}
|
||||
|
||||
// Disk threshold breach
|
||||
if (monitor.diskAlertThreshold && Array.isArray(hardware.disk)) {
|
||||
if (monitor.diskAlertThreshold !== undefined && monitor.diskAlertThreshold !== null && Array.isArray(hardware.disk)) {
|
||||
// Find the highest disk usage
|
||||
let maxDiskUsage = 0;
|
||||
let maxDiskUsageDecimal = 0;
|
||||
for (const disk of hardware.disk) {
|
||||
if (disk.usage_percent !== undefined && disk.usage_percent > maxDiskUsage) {
|
||||
maxDiskUsage = disk.usage_percent;
|
||||
if (disk.usage_percent !== undefined && disk.usage_percent > maxDiskUsageDecimal) {
|
||||
maxDiskUsageDecimal = disk.usage_percent;
|
||||
}
|
||||
}
|
||||
const maxDiskPercent = maxDiskUsageDecimal * 100;
|
||||
const threshold = monitor.diskAlertThreshold;
|
||||
if (maxDiskUsage >= threshold) {
|
||||
if (maxDiskPercent > threshold) {
|
||||
breaches.push({
|
||||
metric: "disk",
|
||||
currentValue: maxDiskUsage,
|
||||
currentValue: maxDiskPercent,
|
||||
threshold,
|
||||
unit: "%",
|
||||
formattedValue: `${maxDiskUsage.toFixed(1)}%`,
|
||||
formattedValue: `${maxDiskPercent.toFixed(1)}%`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Temperature threshold breach
|
||||
if (monitor.tempAlertThreshold && hardware.cpu?.temperature) {
|
||||
if (monitor.tempAlertThreshold !== undefined && monitor.tempAlertThreshold !== null && hardware.cpu?.temperature) {
|
||||
// Temperature is an array in cpu.temperature
|
||||
const temps = Array.isArray(hardware.cpu.temperature) ? hardware.cpu.temperature : [hardware.cpu.temperature];
|
||||
const maxTemp = Math.max(...temps.filter((t: number) => !isNaN(t)));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Monitor, Notification, Alert, MonitorStatusResponse } from "@/types/index.js";
|
||||
import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
|
||||
import type { NotificationMessage } from "@/types/notificationMessage.js";
|
||||
|
||||
export interface INotificationProvider {
|
||||
sendAlert: (
|
||||
@@ -9,5 +10,6 @@ export interface INotificationProvider {
|
||||
decision: MonitorActionDecision,
|
||||
clientHost: string
|
||||
) => Promise<boolean>;
|
||||
sendMessage?: (notification: Notification, message: NotificationMessage) => Promise<boolean>;
|
||||
sendTestAlert(notification: Notification): Promise<boolean>;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ const SERVICE_NAME = "WebhookProvider";
|
||||
import type { Monitor, Alert, Notification, MonitorStatusResponse } from "@/types/index.js";
|
||||
import { INotificationProvider } from "@/service/index.js";
|
||||
import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
|
||||
import type { NotificationMessage } from "@/types/notificationMessage.js";
|
||||
import {
|
||||
buildHardwareAlerts,
|
||||
buildHardwareNotificationMessage,
|
||||
@@ -75,6 +76,98 @@ export class WebhookProvider implements INotificationProvider {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* New unified message format - builds webhook payload from NotificationMessage
|
||||
*/
|
||||
sendMessage = async (notification: Notification, message: NotificationMessage): Promise<boolean> => {
|
||||
if (!notification.address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build webhook payload from unified message
|
||||
const payload = this.buildWebhookPayload(message);
|
||||
|
||||
try {
|
||||
await got.post(notification.address, {
|
||||
json: payload,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
this.logger.info({
|
||||
message: "[NEW] Webhook notification sent via sendMessage",
|
||||
service: SERVICE_NAME,
|
||||
method: "sendMessage",
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
this.logger.warn({
|
||||
message: "[NEW] Webhook alert failed via sendMessage",
|
||||
service: SERVICE_NAME,
|
||||
method: "sendMessage",
|
||||
stack: err?.stack,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Build webhook payload from NotificationMessage
|
||||
* Format: { text: string, severity: string, monitor: object, details: object }
|
||||
*/
|
||||
private buildWebhookPayload(message: NotificationMessage): object {
|
||||
const lines: string[] = [];
|
||||
|
||||
// Title and summary
|
||||
lines.push(`**${message.content.title}**`);
|
||||
lines.push(message.content.summary);
|
||||
lines.push("");
|
||||
|
||||
// Monitor information
|
||||
lines.push("**Monitor Details:**");
|
||||
lines.push(`- Name: ${message.monitor.name}`);
|
||||
lines.push(`- URL: ${message.monitor.url}`);
|
||||
lines.push(`- Type: ${message.monitor.type}`);
|
||||
lines.push(`- Status: ${message.monitor.status}`);
|
||||
lines.push("");
|
||||
|
||||
// Additional details
|
||||
if (message.content.details && message.content.details.length > 0) {
|
||||
lines.push("**Additional Information:**");
|
||||
message.content.details.forEach((detail) => lines.push(`- ${detail}`));
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
// Threshold breaches (for hardware monitors)
|
||||
if (message.content.thresholds && message.content.thresholds.length > 0) {
|
||||
lines.push("**Threshold Breaches:**");
|
||||
message.content.thresholds.forEach((breach) => {
|
||||
lines.push(`- ${breach.metric.toUpperCase()}: ${breach.formattedValue} (threshold: ${breach.threshold}${breach.unit})`);
|
||||
});
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
// Incident link
|
||||
if (message.content.incident) {
|
||||
lines.push(`[View Incident](${message.clientHost}/infrastructure/${message.monitor.id})`);
|
||||
}
|
||||
|
||||
// Return webhook payload with both text and structured data
|
||||
return {
|
||||
text: lines.join("\n"),
|
||||
severity: message.severity,
|
||||
type: message.type,
|
||||
monitor: {
|
||||
id: message.monitor.id,
|
||||
name: message.monitor.name,
|
||||
url: message.monitor.url,
|
||||
status: message.monitor.status,
|
||||
},
|
||||
timestamp: message.content.timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
sendTestAlert = async (notification: Notification) => {
|
||||
if (!notification.address) {
|
||||
return false;
|
||||
|
||||
@@ -65,11 +65,23 @@ export class NotificationsService implements INotificationsService {
|
||||
notification: Notification,
|
||||
monitor: Monitor,
|
||||
monitorStatusResponse: MonitorStatusResponse,
|
||||
decision: MonitorActionDecision
|
||||
decision: MonitorActionDecision,
|
||||
notificationMessage?: any
|
||||
): Promise<boolean> => {
|
||||
const settings = this.settingsService.getSettings();
|
||||
const clientHost = settings.clientHost || "Host not defined";
|
||||
|
||||
// For webhook notifications, try new sendMessage method if available
|
||||
if (notification.type === "webhook" && this.webhookProvider.sendMessage && notificationMessage) {
|
||||
this.logger.info({
|
||||
message: "[NEW] Using sendMessage for webhook",
|
||||
service: SERVICE_NAME,
|
||||
method: "send",
|
||||
});
|
||||
return await this.webhookProvider.sendMessage(notification, notificationMessage);
|
||||
}
|
||||
|
||||
// Fallback to existing sendAlert for all providers
|
||||
switch (notification.type) {
|
||||
case "email":
|
||||
return await this.emailProvider.sendAlert(notification, monitor, monitorStatusResponse, decision, clientHost);
|
||||
@@ -92,7 +104,12 @@ export class NotificationsService implements INotificationsService {
|
||||
const notificationIds = monitor.notifications ?? [];
|
||||
const notifications = await this.notificationsRepository.findNotificationsByIds(notificationIds);
|
||||
|
||||
const tasks = notifications.map((notification) => this.send(notification, monitor, monitorStatusResponse, decision));
|
||||
// Build notification message once for all notifications
|
||||
const settings = this.settingsService.getSettings();
|
||||
const clientHost = settings.clientHost || "Host not defined";
|
||||
const notificationMessage = this.notificationMessageBuilder.buildMessage(monitor, monitorStatusResponse, decision, clientHost);
|
||||
|
||||
const tasks = notifications.map((notification) => this.send(notification, monitor, monitorStatusResponse, decision, notificationMessage));
|
||||
|
||||
const outcomes = await Promise.all(tasks);
|
||||
const succeeded = outcomes.filter(Boolean).length;
|
||||
@@ -113,10 +130,6 @@ export class NotificationsService implements INotificationsService {
|
||||
return false;
|
||||
}
|
||||
|
||||
const settings = this.settingsService.getSettings();
|
||||
const clientHost = settings.clientHost || "Host not defined";
|
||||
const notificationMessage = this.notificationMessageBuilder.buildMessage(monitor, monitorStatusResponse, decision, clientHost);
|
||||
|
||||
// Send notifications based on decision
|
||||
return await this.sendNotifications(monitor, monitorStatusResponse, decision);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user