inject settings service, extract client host

This commit is contained in:
Alex Holliday committed 2026-02-16 00:39:34 +00:00
1 parent 412a3b7cc9
commit a8ecd44abf
9 files changed
+73 -28

No files matched your search

+1
View File
@@ -182,6 +182,7 @@ export const initializeServices = async ({
discordProvider,
pagerDutyProvider,
matrixProvider,
settingsService,
logger
);
@@ -6,7 +6,8 @@ export interface INotificationProvider {
notification: Notification,
monitor: Monitor,
monitorStatusResponse: MonitorStatusResponse,
decision: MonitorActionDecision
decision: MonitorActionDecision,
clientHost: string
) => Promise<boolean>;
sendTestAlert(notification: Notification): Promise<boolean>;
}
@@ -11,20 +11,31 @@ export class DiscordProvider implements INotificationProvider {
constructor(logger: any) {
this.logger = logger;
}
private getHardwareContent = (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse, decision: MonitorActionDecision) => {
private getHardwareContent = (
clientHost: string,
monitor: Monitor,
monitorStatusResponse: MonitorStatusResponse,
decision: MonitorActionDecision
) => {
// For status changes (recovery), use standard format
if (decision.notificationReason === "status_change") {
return buildDiscordBody(monitor, monitorStatusResponse);
}
// For threshold breaches, use hardware alert format
const { discordPayload } = buildHardwareAlerts("HOST_PLACEHOLDER", monitor, monitorStatusResponse);
const { discordPayload } = buildHardwareAlerts(clientHost, monitor, monitorStatusResponse);
return discordPayload;
};
sendAlert = async (notification: Notification, monitor: Monitor, monitorStatusResponse: MonitorStatusResponse, decision: MonitorActionDecision) => {
sendAlert = async (
notification: Notification,
monitor: Monitor,
monitorStatusResponse: MonitorStatusResponse,
decision: MonitorActionDecision,
clientHost: string
) => {
let body;
if (monitor.type === "hardware") {
body = this.getHardwareContent(monitor, monitorStatusResponse, decision);
body = this.getHardwareContent(clientHost, monitor, monitorStatusResponse, decision);
} else {
body = buildDiscordBody(monitor, monitorStatusResponse);
}
@@ -13,13 +13,18 @@ export class EmailProvider implements INotificationProvider {
this.logger = logger;
}
private buildHardwareEmail = async (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse, decision: MonitorActionDecision) => {
private buildHardwareEmail = async (
clientHost: string,
monitor: Monitor,
monitorStatusResponse: MonitorStatusResponse,
decision: MonitorActionDecision
) => {
// For status changes (recovery), use standard email format
if (decision.notificationReason === "status_change") {
return await buildEmail(this.emailService, monitor);
}
// For threshold breaches, use hardware alert format
const { alertsToSend } = buildHardwareAlerts("HOST_PLACEHOLDER", monitor, monitorStatusResponse);
const { alertsToSend } = buildHardwareAlerts(clientHost, monitor, monitorStatusResponse);
const html = buildHardwareEmail(this.emailService, monitor, alertsToSend);
return html;
};
@@ -28,7 +33,8 @@ export class EmailProvider implements INotificationProvider {
notification: Notification,
monitor: Monitor,
monitorStatusResponse: MonitorStatusResponse,
decision: MonitorActionDecision
decision: MonitorActionDecision,
clientHost: string
): Promise<boolean> {
// For grouped notifications (identified by ":" in name), customize subject to indicate multiple services.
// Example: "2 services: Service A, Service B" becomes "Alert: 2 services are down"
@@ -48,7 +54,7 @@ export class EmailProvider implements INotificationProvider {
let html;
if (monitor.type === "hardware") {
html = await this.buildHardwareEmail(monitor, monitorStatusResponse, decision);
html = await this.buildHardwareEmail(clientHost, monitor, monitorStatusResponse, decision);
} else {
html = await buildEmail(this.emailService, monitor);
}
@@ -27,7 +27,7 @@ export class MatrixProvider implements INotificationProvider {
return buildWebhookBody(monitor, monitorStatusResponse);
}
// For threshold breaches, use hardware alert format
const { alertsToSend } = buildHardwareAlerts("HOST_PLACEHOLDER", monitor, monitorStatusResponse);
const { alertsToSend } = buildHardwareAlerts(clientHost, monitor, monitorStatusResponse);
const body = buildHardwareNotificationMessage(clientHost, alertsToSend, monitor);
return body;
};
@@ -37,12 +37,18 @@ export class MatrixProvider implements INotificationProvider {
return body;
};
sendAlert = async (notification: Notification, monitor: Monitor, monitorStatusResponse: MonitorStatusResponse, decision: MonitorActionDecision) => {
sendAlert = async (
notification: Notification,
monitor: Monitor,
monitorStatusResponse: MonitorStatusResponse,
decision: MonitorActionDecision,
clientHost: string
) => {
const { homeserverUrl, accessToken, roomId } = notification;
let content;
if (monitor.type === "hardware") {
content = this.getHardwareContent("HOST_PLACEHOLDER", monitor, monitorStatusResponse, decision);
content = this.getHardwareContent(clientHost, monitor, monitorStatusResponse, decision);
} else {
content = this.getContent(monitor, monitorStatusResponse);
}
@@ -27,7 +27,7 @@ export class PagerDutyProvider implements INotificationProvider {
return buildWebhookBody(monitor, monitorStatusResponse);
}
// For threshold breaches, use hardware alert format
const { alertsToSend } = buildHardwareAlerts("HOST_PLACEHOLDER", monitor, monitorStatusResponse);
const { alertsToSend } = buildHardwareAlerts(clientHost, monitor, monitorStatusResponse);
const body = buildHardwareNotificationMessage(clientHost, alertsToSend, monitor);
return body;
};
@@ -41,11 +41,12 @@ export class PagerDutyProvider implements INotificationProvider {
notification: Notification,
monitor: Monitor,
monitorStatusResponse: MonitorStatusResponse,
decision: MonitorActionDecision
decision: MonitorActionDecision,
clientHost: string
): Promise<boolean> {
let body;
if (monitor.type === "hardware") {
body = this.getHardwareContent("HOST_PLACEHOLDER", monitor, monitorStatusResponse, decision);
body = this.getHardwareContent(clientHost, monitor, monitorStatusResponse, decision);
} else {
body = this.getContent(monitor, monitorStatusResponse);
}
@@ -27,7 +27,7 @@ export class SlackProvider implements INotificationProvider {
return buildWebhookBody(monitor, monitorStatusResponse);
}
// For threshold breaches, use hardware alert format
const { alertsToSend } = buildHardwareAlerts("HOST_PLACEHOLDER", monitor, monitorStatusResponse);
const { alertsToSend } = buildHardwareAlerts(clientHost, monitor, monitorStatusResponse);
const body = buildHardwareNotificationMessage(clientHost, alertsToSend, monitor);
return body;
};
@@ -41,11 +41,12 @@ export class SlackProvider implements INotificationProvider {
notification: Notification,
monitor: Monitor,
monitorStatusResponse: MonitorStatusResponse,
decision: MonitorActionDecision
decision: MonitorActionDecision,
clientHost: string
): Promise<boolean> {
let body;
if (monitor.type === "hardware") {
body = this.getHardwareContent("HOST_PLACEHOLDER", monitor, monitorStatusResponse, decision);
body = this.getHardwareContent(clientHost, monitor, monitorStatusResponse, decision);
} else {
body = this.getContent(monitor, monitorStatusResponse);
}
@@ -27,7 +27,7 @@ export class WebhookProvider implements INotificationProvider {
return buildWebhookBody(monitor, monitorStatusResponse);
}
// For threshold breaches, use hardware alert format
const { alertsToSend } = buildHardwareAlerts("HOST_PLACEHOLDER", monitor, monitorStatusResponse);
const { alertsToSend } = buildHardwareAlerts(clientHost, monitor, monitorStatusResponse);
const body = buildHardwareNotificationMessage(clientHost, alertsToSend, monitor);
return body;
};
@@ -37,10 +37,16 @@ export class WebhookProvider implements INotificationProvider {
return body;
};
sendAlert = async (notification: Notification, monitor: Monitor, monitorStatusResponse: MonitorStatusResponse, decision: MonitorActionDecision) => {
sendAlert = async (
notification: Notification,
monitor: Monitor,
monitorStatusResponse: MonitorStatusResponse,
decision: MonitorActionDecision,
clientHost: string
) => {
let body;
if (monitor.type === "hardware") {
body = this.getHardwareContent("HOST_PLACEHOLDER", monitor, monitorStatusResponse, decision);
body = this.getHardwareContent(clientHost, monitor, monitorStatusResponse, decision);
} else {
body = this.getContent(monitor, monitorStatusResponse);
}
@@ -2,6 +2,7 @@ import type { HardwareStatusPayload, Monitor, MonitorStatusResponse, Notificatio
import { IMonitorsRepository, INotificationsRepository } from "@/repositories/index.js";
import { INotificationProvider } from "./notificationProviders/INotificationProvider.js";
import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
import type { ISettingsService } from "@/service/system/settingsService.js";
export interface INotificationsService {
createNotification: (notificationData: Partial<Notification>) => Promise<Notification>;
@@ -29,6 +30,7 @@ export class NotificationsService implements INotificationsService {
private pagerDutyProvider: INotificationProvider;
private matrixProvider: INotificationProvider;
private logger: any;
private settingsService: ISettingsService;
// Email grouping (batching) configuration
private emailGroupingWindowMs: number;
@@ -52,6 +54,7 @@ export class NotificationsService implements INotificationsService {
discordProvider: INotificationProvider,
pagerDutyProvider: INotificationProvider,
matrixProvider: INotificationProvider,
settingsService: ISettingsService,
logger: any
) {
this.notificationsRepository = notificationsRepository;
@@ -62,6 +65,7 @@ export class NotificationsService implements INotificationsService {
this.discordProvider = discordProvider;
this.pagerDutyProvider = pagerDutyProvider;
this.matrixProvider = matrixProvider;
this.settingsService = settingsService;
this.logger = logger;
// Configure email grouping window (in milliseconds).
@@ -82,19 +86,21 @@ export class NotificationsService implements INotificationsService {
}
private send = async (notification: Notification, monitor: Monitor, monitorStatusResponse: MonitorStatusResponse): Promise<boolean> => {
const settings = this.settingsService.getSettings();
const clientHost = settings.clientHost || "Host not defined";
switch (notification.type) {
case "email":
return await this.emailProvider.sendAlert(notification, monitor, monitorStatusResponse, this.currentDecision!);
return await this.emailProvider.sendAlert(notification, monitor, monitorStatusResponse, this.currentDecision!, clientHost);
case "slack":
return await this.slackProvider.sendAlert(notification, monitor, monitorStatusResponse, this.currentDecision!);
return await this.slackProvider.sendAlert(notification, monitor, monitorStatusResponse, this.currentDecision!, clientHost);
case "discord":
return await this.discordProvider.sendAlert(notification, monitor, monitorStatusResponse, this.currentDecision!);
return await this.discordProvider.sendAlert(notification, monitor, monitorStatusResponse, this.currentDecision!, clientHost);
case "pager_duty":
return await this.pagerDutyProvider.sendAlert(notification, monitor, monitorStatusResponse, this.currentDecision!);
return await this.pagerDutyProvider.sendAlert(notification, monitor, monitorStatusResponse, this.currentDecision!, clientHost);
case "matrix":
return await this.matrixProvider.sendAlert(notification, monitor, monitorStatusResponse, this.currentDecision!);
return await this.matrixProvider.sendAlert(notification, monitor, monitorStatusResponse, this.currentDecision!, clientHost);
case "webhook":
return await this.webhookProvider.sendAlert(notification, monitor, monitorStatusResponse, this.currentDecision!);
return await this.webhookProvider.sendAlert(notification, monitor, monitorStatusResponse, this.currentDecision!, clientHost);
default:
return false;
}
@@ -215,6 +221,12 @@ export class NotificationsService implements INotificationsService {
return false;
}
const { clientHost } = this.settingsService.getSettings();
if (!clientHost) {
this.logger.warn({ message: "CLIENT_HOST not configured", service: SERVICE_NAME, method: "flushEmailGroup" });
return false;
}
// Build a combined monitor name listing all affected services.
// Example: "Service A, Service B" (2 services) or "Service A" (1 service)
const uniqueNames = Array.from(new Set(monitors.map((m) => m.name)));
@@ -235,7 +247,7 @@ export class NotificationsService implements INotificationsService {
};
// Reuse existing email provider to send grouped notification.
return await this.emailProvider.sendAlert(notification, syntheticMonitor, baseStatus, decision);
return await this.emailProvider.sendAlert(notification, syntheticMonitor, baseStatus, decision, clientHost);
};
handleNotifications = async (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse, decision: MonitorActionDecision) => {