slack
This commit is contained in:
1 parent
e18681606c
commit
0e1dc3e3fe
3 files changed
+173
-5
No files matched your search
@@ -62,8 +62,8 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder {
|
||||
return "threshold_breach";
|
||||
}
|
||||
|
||||
// Recovery from threshold breach
|
||||
if (decision.notificationReason === "status_change" && monitor.status === "up") {
|
||||
// Recovery from threshold breach (only for hardware monitors)
|
||||
if (decision.notificationReason === "status_change" && monitor.status === "up" && monitor.type === "hardware") {
|
||||
return "threshold_resolved";
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder {
|
||||
clientHost: string
|
||||
): NotificationContent {
|
||||
const title = `Threshold Breach: ${monitor.name}`;
|
||||
const summary = `Hardware monitor "${monitor.name}" has breached one or more thresholds.`;
|
||||
const summary = `Monitor "${monitor.name}" has breached one or more thresholds.`;
|
||||
const details = [`URL: ${monitor.url}`, `Status: Breached`, `Type: ${monitor.type}`];
|
||||
|
||||
const thresholds = this.extractThresholdBreaches(monitor, monitorStatusResponse);
|
||||
@@ -172,7 +172,7 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder {
|
||||
|
||||
private buildThresholdResolvedContent(monitor: Monitor, clientHost: string): NotificationContent {
|
||||
const title = `Thresholds Resolved: ${monitor.name}`;
|
||||
const summary = `Hardware monitor "${monitor.name}" thresholds have returned to normal.`;
|
||||
const summary = `Monitor "${monitor.name}" thresholds have returned to normal.`;
|
||||
const details = [`URL: ${monitor.url}`, `Status: Up`, `Type: ${monitor.type}`];
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,6 +2,7 @@ const SERVICE_NAME = "SlackProvider";
|
||||
import type { Monitor, 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,
|
||||
@@ -99,4 +100,162 @@ export class SlackProvider implements INotificationProvider {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* New unified message format - builds Slack Block Kit payload from NotificationMessage
|
||||
*/
|
||||
async sendMessage(notification: Notification, message: NotificationMessage): Promise<boolean> {
|
||||
if (!notification.address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const payload = this.buildSlackPayload(message);
|
||||
|
||||
try {
|
||||
await got.post(notification.address, {
|
||||
json: payload,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
this.logger.info({
|
||||
message: "[NEW] Slack notification sent via sendMessage",
|
||||
service: SERVICE_NAME,
|
||||
method: "sendMessage",
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
this.logger.warn({
|
||||
message: "[NEW] Slack alert failed via sendMessage",
|
||||
service: SERVICE_NAME,
|
||||
method: "sendMessage",
|
||||
stack: err?.stack,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Slack Block Kit payload from NotificationMessage
|
||||
* Uses Slack's rich formatting with sections, fields, and context blocks
|
||||
*/
|
||||
private buildSlackPayload(message: NotificationMessage): object {
|
||||
const blocks: any[] = [];
|
||||
|
||||
// Determine color based on severity
|
||||
const colorMap = {
|
||||
critical: "#FF0000", // Red
|
||||
warning: "#FFA500", // Orange
|
||||
success: "#00FF00", // Green
|
||||
info: "#0000FF", // Blue
|
||||
};
|
||||
const color = colorMap[message.severity] || "#808080";
|
||||
|
||||
// Header block with title
|
||||
blocks.push({
|
||||
type: "header",
|
||||
text: {
|
||||
type: "plain_text",
|
||||
text: message.content.title,
|
||||
emoji: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Summary section
|
||||
blocks.push({
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: message.content.summary,
|
||||
},
|
||||
});
|
||||
|
||||
// Monitor details as fields
|
||||
const monitorFields = [
|
||||
{ type: "mrkdwn", text: `*Name:*\n${message.monitor.name}` },
|
||||
{ type: "mrkdwn", text: `*Type:*\n${message.monitor.type}` },
|
||||
{ type: "mrkdwn", text: `*Status:*\n${message.monitor.status}` },
|
||||
{ type: "mrkdwn", text: `*URL:*\n${message.monitor.url}` },
|
||||
];
|
||||
|
||||
blocks.push({
|
||||
type: "section",
|
||||
fields: monitorFields,
|
||||
});
|
||||
|
||||
// Divider
|
||||
blocks.push({ type: "divider" });
|
||||
|
||||
// Threshold breaches (if any)
|
||||
if (message.content.thresholds && message.content.thresholds.length > 0) {
|
||||
const thresholdText = message.content.thresholds
|
||||
.map((breach) => `• *${breach.metric.toUpperCase()}:* ${breach.formattedValue} (threshold: ${breach.threshold}${breach.unit})`)
|
||||
.join("\n");
|
||||
|
||||
blocks.push({
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `*Threshold Breaches:*\n${thresholdText}`,
|
||||
},
|
||||
});
|
||||
|
||||
blocks.push({ type: "divider" });
|
||||
}
|
||||
|
||||
// Additional details (if any)
|
||||
if (message.content.details && message.content.details.length > 0) {
|
||||
const detailsText = message.content.details.map((detail) => `• ${detail}`).join("\n");
|
||||
|
||||
blocks.push({
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `*Additional Information:*\n${detailsText}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Incident link button (if incident exists)
|
||||
if (message.content.incident) {
|
||||
blocks.push({
|
||||
type: "actions",
|
||||
elements: [
|
||||
{
|
||||
type: "button",
|
||||
text: {
|
||||
type: "plain_text",
|
||||
text: "View Incident",
|
||||
emoji: true,
|
||||
},
|
||||
url: `${message.clientHost}/infrastructure/${message.monitor.id}`,
|
||||
style: "primary",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// Context footer with timestamp
|
||||
blocks.push({
|
||||
type: "context",
|
||||
elements: [
|
||||
{
|
||||
type: "mrkdwn",
|
||||
text: `Checkmate | ${new Date(message.content.timestamp).toUTCString()}`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Return Slack payload with blocks and attachment color
|
||||
return {
|
||||
blocks,
|
||||
attachments: [
|
||||
{
|
||||
color,
|
||||
blocks: [], // Empty blocks in attachment for color bar
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ export class NotificationsService implements INotificationsService {
|
||||
const settings = this.settingsService.getSettings();
|
||||
const clientHost = settings.clientHost || "Host not defined";
|
||||
|
||||
// For webhook notifications, try new sendMessage method if available
|
||||
// Route to new sendMessage method if provider supports it
|
||||
if (notification.type === "webhook" && this.webhookProvider.sendMessage && notificationMessage) {
|
||||
this.logger.info({
|
||||
message: "[NEW] Using sendMessage for webhook",
|
||||
@@ -81,6 +81,15 @@ export class NotificationsService implements INotificationsService {
|
||||
return await this.webhookProvider.sendMessage(notification, notificationMessage);
|
||||
}
|
||||
|
||||
if (notification.type === "slack" && this.slackProvider.sendMessage && notificationMessage) {
|
||||
this.logger.info({
|
||||
message: "[NEW] Using sendMessage for slack",
|
||||
service: SERVICE_NAME,
|
||||
method: "send",
|
||||
});
|
||||
return await this.slackProvider.sendMessage(notification, notificationMessage);
|
||||
}
|
||||
|
||||
// Fallback to existing sendAlert for all providers
|
||||
switch (notification.type) {
|
||||
case "email":
|
||||
|
||||
Reference in new issue
Block a user