Files
sencho/backend/src/services/NotificationService.ts
T
SaelixCode c6eb9d76e7 feat: add Notification Settings Modal and Stack Alert Sheet components
- Implemented NotificationSettingsModal for configuring notification agents (Discord, Slack, Webhook) and global settings.
- Added StackAlertSheet for managing stack-specific alert rules with metrics, thresholds, and cooldowns.
- Introduced reusable UI components: DropdownMenu, Popover, Select, Sheet, Slider, and Switch.
- Enhanced Tabs component by removing unnecessary client directive.
2026-02-26 23:09:47 -05:00

130 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { DatabaseService } from './DatabaseService';
export class NotificationService {
private static instance: NotificationService;
private dbService: DatabaseService;
private constructor() {
this.dbService = DatabaseService.getInstance();
}
public static getInstance(): NotificationService {
if (!NotificationService.instance) {
NotificationService.instance = new NotificationService();
}
return NotificationService.instance;
}
public async dispatchAlert(level: 'info' | 'warning' | 'error', message: string) {
// 1. Log to history
this.dbService.addNotificationHistory({
level,
message,
timestamp: Date.now()
});
// 2. Fetch enabled agents
const agents = this.dbService.getEnabledAgents();
if (agents.length === 0) {
console.log('No active notification agents found. Skipping external dispatch.');
return;
}
// 3. Dispatch to each agent
for (const agent of agents) {
try {
if (agent.type === 'discord') {
await this.sendDiscordWebhook(agent.url, level, message);
} else if (agent.type === 'slack') {
await this.sendSlackWebhook(agent.url, level, message);
} else if (agent.type === 'webhook') {
await this.sendCustomWebhook(agent.url, level, message);
}
} catch (error) {
console.error(`Failed to dispatch notification to ${agent.type}:`, error);
}
}
}
public async testDispatch(type: 'discord' | 'slack' | 'webhook', url: string) {
const level = 'info';
const message = '🔌 Test Notification from Sencho!';
if (type === 'discord') {
await this.sendDiscordWebhook(url, level, message);
} else if (type === 'slack') {
await this.sendSlackWebhook(url, level, message);
} else if (type === 'webhook') {
await this.sendCustomWebhook(url, level, message);
}
}
private async sendDiscordWebhook(url: string, level: 'info' | 'warning' | 'error', message: string) {
const colorMap = {
info: 3447003, // Blue
warning: 16776960, // Yellow
error: 15158332 // Red
};
const payload = {
embeds: [{
title: `Sencho Alert [${level.toUpperCase()}]`,
description: message,
color: colorMap[level],
timestamp: new Date().toISOString()
}]
};
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`Discord Webhook responded with ${response.status}`);
}
}
private async sendSlackWebhook(url: string, level: 'info' | 'warning' | 'error', message: string) {
const emojiMap = {
info: '️',
warning: '⚠️',
error: '🚨'
};
const payload = {
text: `${emojiMap[level]} *Sencho Alert [${level.toUpperCase()}]*\n${message}`
};
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`Slack Webhook responded with ${response.status}`);
}
}
private async sendCustomWebhook(url: string, level: 'info' | 'warning' | 'error', message: string) {
const payload = {
level,
message,
timestamp: new Date().toISOString(),
source: 'sencho'
};
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`Custom Webhook responded with ${response.status}`);
}
}
}