feat: Support multiple notification channels
- Update the Notification Settings dashboard to allow users to select multiple notification channels for alerts. - Add webhook to notification channels: Add webhook as a selectable option in the notification channel type dropdown. - Implement template type selection in the Add Template dialog. Based on the selected type.
This commit is contained in:
@@ -18,6 +18,13 @@ export interface AlertConfiguration {
|
||||
enabled: boolean;
|
||||
created?: string;
|
||||
updated?: string;
|
||||
// Email specific fields
|
||||
email_address?: string;
|
||||
email_sender_name?: string;
|
||||
smtp_server?: string;
|
||||
smtp_port?: string;
|
||||
webhook_id?: string;
|
||||
channel_id?: string;
|
||||
}
|
||||
|
||||
export const alertConfigService = {
|
||||
@@ -25,7 +32,7 @@ export const alertConfigService = {
|
||||
// console.info("Fetching alert configurations");
|
||||
try {
|
||||
const response = await pb.collection('alert_configurations').getList(1, 50);
|
||||
// console.info("Alert configurations response:", response);
|
||||
// console.info("Alert configurations response:", response);
|
||||
return response.items as AlertConfiguration[];
|
||||
} catch (error) {
|
||||
// console.error("Error fetching alert configurations:", error);
|
||||
@@ -42,17 +49,17 @@ export const alertConfigService = {
|
||||
// console.info("Creating alert configuration:", config);
|
||||
try {
|
||||
const result = await pb.collection('alert_configurations').create(config);
|
||||
// console.info("Alert configuration created:", result);
|
||||
// console.info("Alert configuration created:", result);
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Notification settings saved successfully",
|
||||
description: "Notification channel created successfully",
|
||||
});
|
||||
return result as AlertConfiguration;
|
||||
} catch (error) {
|
||||
// console.error("Error creating alert configuration:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to save notification settings",
|
||||
description: "Failed to create notification channel",
|
||||
variant: "destructive"
|
||||
});
|
||||
return null;
|
||||
@@ -60,20 +67,20 @@ export const alertConfigService = {
|
||||
},
|
||||
|
||||
async updateAlertConfiguration(id: string, config: Partial<AlertConfiguration>): Promise<AlertConfiguration | null> {
|
||||
// console.info(`Updating alert configuration ${id}:`, config);
|
||||
// console.info(`Updating alert configuration ${id}:`, config);
|
||||
try {
|
||||
const result = await pb.collection('alert_configurations').update(id, config);
|
||||
// console.info("Alert configuration updated:", result);
|
||||
// console.info("Alert configuration updated:", result);
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Notification settings updated successfully",
|
||||
description: "Notification channel updated successfully",
|
||||
});
|
||||
return result as AlertConfiguration;
|
||||
} catch (error) {
|
||||
// console.error("Error updating alert configuration:", error);
|
||||
// console.error("Error updating alert configuration:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to update notification settings",
|
||||
description: "Failed to update notification channel",
|
||||
variant: "destructive"
|
||||
});
|
||||
return null;
|
||||
@@ -81,17 +88,17 @@ export const alertConfigService = {
|
||||
},
|
||||
|
||||
async deleteAlertConfiguration(id: string): Promise<boolean> {
|
||||
// console.info(`Deleting alert configuration ${id}`);
|
||||
// console.info(`Deleting alert configuration ${id}`);
|
||||
try {
|
||||
await pb.collection('alert_configurations').delete(id);
|
||||
// console.info("Alert configuration deleted");
|
||||
// console.info("Alert configuration deleted");
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Notification channel removed",
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
// console.error("Error deleting alert configuration:", error);
|
||||
// console.error("Error deleting alert configuration:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to remove notification channel",
|
||||
@@ -100,4 +107,4 @@ export const alertConfigService = {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
|
||||
import { pb } from '@/lib/pocketbase';
|
||||
import { uptimeService } from '@/services/uptimeService';
|
||||
import { notificationService } from '@/services/notification'; // Import from the main notification service
|
||||
import { prepareServiceForNotification } from '../utils/httpUtils';
|
||||
import { UptimeData } from '@/types/service.types';
|
||||
|
||||
@@ -9,7 +8,7 @@ import { UptimeData } from '@/types/service.types';
|
||||
* Handle a service that is determined to be UP
|
||||
*/
|
||||
export async function handleServiceUp(service: any, responseTime: number, formattedTime: string): Promise<void> {
|
||||
console.log(`Service ${service.name} is UP! Response time: ${responseTime}ms`);
|
||||
// console.log(`Service ${service.name} is UP! Response time: ${responseTime}ms`);
|
||||
|
||||
// Create a history record of this check with a more accurate timestamp
|
||||
const uptimeData: UptimeData = {
|
||||
@@ -41,47 +40,18 @@ export async function handleServiceUp(service: any, responseTime: number, format
|
||||
try {
|
||||
await uptimeService.recordUptimeData(uptimeData);
|
||||
} catch (error) {
|
||||
console.error("Failed to record uptime data on first try, retrying...", error);
|
||||
// console.error("Failed to record uptime data on first try, retrying...", error);
|
||||
// Wait a short time and retry once
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
await uptimeService.recordUptimeData(uptimeData);
|
||||
}
|
||||
|
||||
// Reset notification count if service is recovered from DOWN status
|
||||
if (previousStatus === "down") {
|
||||
console.log(`Service ${service.name} recovered from DOWN status - resetting notification count`);
|
||||
notificationService.resetNotificationCount(service.id);
|
||||
}
|
||||
|
||||
// Send notification if status changed from down to up
|
||||
// Status change logging (notification logic removed - will be handled by backend)
|
||||
if (statusChanged) {
|
||||
console.log(`Status changed from ${previousStatus} to UP - sending notification`);
|
||||
|
||||
// Convert PocketBase record to Service type for notification
|
||||
const serviceForNotification = prepareServiceForNotification(service, "up", responseTime);
|
||||
|
||||
// Check if alerts are muted - STRICT check for "muted" string value
|
||||
if (service.alerts === "muted" || serviceForNotification.alerts === "muted") {
|
||||
console.log(`Alerts are MUTED for service ${service.name}, SKIPPING UP notification`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Send notification through main notification service
|
||||
await notificationService.sendNotification({
|
||||
service: serviceForNotification,
|
||||
status: "up",
|
||||
responseTime: responseTime,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
console.log("UP notification sent successfully");
|
||||
} catch (error) {
|
||||
console.error("Error sending UP notification:", error);
|
||||
}
|
||||
// console.log(`Status changed from ${previousStatus} to UP - notification will be handled by backend`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error handling service UP state:", error);
|
||||
// console.error("Error handling service UP state:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +59,7 @@ export async function handleServiceUp(service: any, responseTime: number, format
|
||||
* Handle a service that is determined to be DOWN
|
||||
*/
|
||||
export async function handleServiceDown(service: any, formattedTime: string): Promise<void> {
|
||||
console.log(`Service ${service.name} is DOWN!`);
|
||||
// console.log(`Service ${service.name} is DOWN!`);
|
||||
|
||||
// Create a history record of this check
|
||||
const uptimeData: UptimeData = {
|
||||
@@ -106,7 +76,7 @@ export async function handleServiceDown(service: any, formattedTime: string): Pr
|
||||
const previousStatus = service.status;
|
||||
const statusChanged = previousStatus !== "down";
|
||||
|
||||
console.log(`Service ${service.name} previous status: ${previousStatus}, statusChanged: ${statusChanged}`);
|
||||
// console.log(`Service ${service.name} previous status: ${previousStatus}, statusChanged: ${statusChanged}`);
|
||||
|
||||
try {
|
||||
// Update service status
|
||||
@@ -123,44 +93,15 @@ export async function handleServiceDown(service: any, formattedTime: string): Pr
|
||||
try {
|
||||
await uptimeService.recordUptimeData(uptimeData);
|
||||
} catch (error) {
|
||||
console.error("Failed to record uptime data on first try, retrying...", error);
|
||||
// console.error("Failed to record uptime data on first try, retrying...", error);
|
||||
// Wait a short time and retry once
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
await uptimeService.recordUptimeData(uptimeData);
|
||||
}
|
||||
|
||||
// Convert PocketBase record to Service type for notification
|
||||
const serviceForNotification = prepareServiceForNotification(service, "down");
|
||||
|
||||
// Check if alerts are muted - STRICT check for "muted" string value
|
||||
if (service.alerts === "muted" || serviceForNotification.alerts === "muted") {
|
||||
console.log(`Alerts are MUTED for service ${service.name}, SKIPPING DOWN notification`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Attempting to send DOWN notification for service:", service.name);
|
||||
console.log("Service notification data:", {
|
||||
name: serviceForNotification.name,
|
||||
notificationChannel: serviceForNotification.notificationChannel,
|
||||
alertTemplate: serviceForNotification.alertTemplate,
|
||||
retries: serviceForNotification.retries || 3,
|
||||
alerts: serviceForNotification.alerts
|
||||
});
|
||||
|
||||
try {
|
||||
// Use the main notification service - this handles retries internally
|
||||
const result = await notificationService.sendNotification({
|
||||
service: serviceForNotification,
|
||||
status: "down",
|
||||
responseTime: 0,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
console.log("DOWN notification sent result:", result);
|
||||
} catch (error) {
|
||||
console.error("Error sending DOWN notification:", error);
|
||||
}
|
||||
// Status change logging (notification logic removed - will be handled by backend)
|
||||
// console.log("Service DOWN status recorded - notification will be handled by backend");
|
||||
} catch (error) {
|
||||
console.error("Error handling service DOWN state:", error);
|
||||
// console.error("Error handling service DOWN state:", error);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
|
||||
import { pb } from '@/lib/pocketbase';
|
||||
import { monitoringIntervals } from '../monitoringIntervals';
|
||||
import { notificationService } from '@/services/notificationService';
|
||||
import { Service } from '@/types/service.types';
|
||||
import { startMonitoringService } from './startMonitoring';
|
||||
|
||||
@@ -16,7 +15,7 @@ export async function resumeMonitoring(serviceId: string): Promise<void> {
|
||||
// Fetch the current service to get its name for better logging
|
||||
const service = await pb.collection('services').getOne(serviceId);
|
||||
|
||||
// console.log(`Resuming service ${service.name} at ${now}`);
|
||||
// console.log(`Resuming service ${service.name} at ${now}`);
|
||||
|
||||
// First, clear any existing interval just to be safe
|
||||
const existingInterval = monitoringIntervals.get(serviceId);
|
||||
@@ -32,50 +31,14 @@ export async function resumeMonitoring(serviceId: string): Promise<void> {
|
||||
last_checked: now
|
||||
});
|
||||
|
||||
// Convert PocketBase record to Service type for notification
|
||||
const serviceForNotification: Service = {
|
||||
id: service.id,
|
||||
name: service.name,
|
||||
url: service.url || "",
|
||||
host: service.host || "", // Include host property
|
||||
type: service.service_type || service.type || "HTTP",
|
||||
status: "up",
|
||||
responseTime: service.response_time || 0,
|
||||
uptime: service.uptime || 0,
|
||||
lastChecked: now,
|
||||
interval: service.heartbeat_interval || 60,
|
||||
retries: service.max_retries || 3,
|
||||
notificationChannel: service.notification_id,
|
||||
alertTemplate: service.template_id,
|
||||
muteAlerts: service.mute_alerts || false,
|
||||
alerts: service.alerts || "unmuted",
|
||||
muteChangedAt: service.mute_changed_at,
|
||||
};
|
||||
|
||||
// Double check if alerts are muted before sending notification
|
||||
// Ensure we're using the correct field - alerts should be "muted" or "unmuted"
|
||||
const alertsMuted = service.alerts === "muted" || serviceForNotification.alerts === "muted";
|
||||
|
||||
if (!alertsMuted) {
|
||||
// console.log(`Alerts NOT muted for service ${service.name}, sending resume notification`);
|
||||
// Send notification that service has been resumed
|
||||
await notificationService.sendNotification({
|
||||
service: serviceForNotification,
|
||||
status: "up",
|
||||
timestamp: now
|
||||
});
|
||||
} else {
|
||||
// console.log(`Alerts muted for service ${service.name}, skipping resume notification`);
|
||||
}
|
||||
|
||||
// IMPORTANT: Wait a brief moment to ensure the status update is processed
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// Now start the service monitoring with a clean slate
|
||||
await startMonitoringService(serviceId);
|
||||
|
||||
console.log(`Service ${service.name} resumed and ready for monitoring`);
|
||||
// console.log(`Service ${service.name} resumed and ready for monitoring`);
|
||||
} catch (error) {
|
||||
// console.error("Error resuming service:", error);
|
||||
// console.error("Error resuming service:", error);
|
||||
}
|
||||
}
|
||||
@@ -1,234 +1,6 @@
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { AlertConfiguration } from "../alertConfigService";
|
||||
import { NotificationTemplate } from "../templateService";
|
||||
import { NotificationPayload } from "./types";
|
||||
import { processTemplate, generateDefaultMessage } from "./templateProcessor";
|
||||
import { sendTelegramNotification } from "./telegramService";
|
||||
import { sendSignalNotification } from "./signalService";
|
||||
|
||||
// Track last notification times for services to implement cooldown
|
||||
const lastNotifications: Record<string, {
|
||||
timestamp: Date;
|
||||
count: number;
|
||||
lastMessageTime: Date; // Track when the last message was actually sent
|
||||
}> = {};
|
||||
// Re-export template processor only
|
||||
export { processTemplate, generateDefaultMessage } from './templateProcessor';
|
||||
|
||||
// Cooldown period in milliseconds (5 minutes)
|
||||
const NOTIFICATION_COOLDOWN = 5 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Notification service responsible for sending notifications based on service status changes
|
||||
*/
|
||||
export const notificationService = {
|
||||
/**
|
||||
* Send notification based on service status change
|
||||
*/
|
||||
async sendNotification(payload: NotificationPayload): Promise<boolean> {
|
||||
try {
|
||||
console.log("=== NOTIFICATION SERVICE TRIGGER ===");
|
||||
console.log("Sending notification for service:", payload.service.name, "Status:", payload.status);
|
||||
console.log("Service type:", payload.service.type);
|
||||
console.log("Service retries:", payload.service.retries || 3);
|
||||
console.log("Alerts status:", payload.service.alerts);
|
||||
|
||||
// First check: Strictly check if alerts are muted for this specific service
|
||||
if (payload.service.alerts === "muted") {
|
||||
console.log(`BLOCKING NOTIFICATION: Alerts are explicitly muted for service ${payload.service.name} (ID: ${payload.service.id})`);
|
||||
return true; // Return true as this is expected behavior, not a failure
|
||||
}
|
||||
|
||||
// For DOWN status, implement the cooldown and max retries logic
|
||||
if (payload.status === "down") {
|
||||
const serviceId = payload.service.id;
|
||||
// Get max retries from service config or default to 3
|
||||
const maxRetries = typeof payload.service.retries === 'number' ? payload.service.retries : 3;
|
||||
const now = new Date();
|
||||
|
||||
console.log(`DOWN notification for ${payload.service.name} - Max retries: ${maxRetries}`);
|
||||
|
||||
if (lastNotifications[serviceId]) {
|
||||
const lastNotif = lastNotifications[serviceId];
|
||||
const timeSinceLastNotif = now.getTime() - lastNotif.timestamp.getTime();
|
||||
|
||||
console.log(`Time since last notification tracking for ${payload.service.name}: ${Math.round(timeSinceLastNotif/1000)}s`);
|
||||
console.log(`Current notification count: ${lastNotif.count}/${maxRetries}`);
|
||||
|
||||
// Check if we're within the cooldown period
|
||||
if (timeSinceLastNotif < NOTIFICATION_COOLDOWN) {
|
||||
// If max retries is set to 1, we should only send one notification during the cooldown period
|
||||
if (maxRetries === 1) {
|
||||
console.log(`Max retries is 1 - BLOCKING duplicate DOWN notification for ${payload.service.name}`);
|
||||
return true; // Skip notification but return success
|
||||
}
|
||||
|
||||
// Check if we've reached max retries
|
||||
if (lastNotif.count >= maxRetries) {
|
||||
console.log(`DOWN notification for ${payload.service.name} skipped: Max retries (${maxRetries}) reached. Next notification after cooldown.`);
|
||||
return true; // Skip notification but return success
|
||||
}
|
||||
|
||||
// Increment count since we're sending another notification
|
||||
console.log(`DOWN notification for ${payload.service.name}: ${lastNotif.count + 1}/${maxRetries}`);
|
||||
lastNotifications[serviceId].count += 1;
|
||||
lastNotifications[serviceId].lastMessageTime = now;
|
||||
} else {
|
||||
// Reset count after cooldown period
|
||||
console.log(`Cooldown period elapsed for ${payload.service.name}. Resetting notification count.`);
|
||||
lastNotifications[serviceId] = { timestamp: now, count: 1, lastMessageTime: now };
|
||||
}
|
||||
} else {
|
||||
// First notification for this service
|
||||
console.log(`First DOWN notification for ${payload.service.name}: 1/${maxRetries}`);
|
||||
lastNotifications[serviceId] = { timestamp: now, count: 1, lastMessageTime: now };
|
||||
}
|
||||
}
|
||||
|
||||
// Check if service has notification channel configured
|
||||
const notificationChannel = payload.service.notificationChannel;
|
||||
|
||||
if (!notificationChannel || notificationChannel === "none") {
|
||||
console.log("No notification channel configured for service", payload.service.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log("Using notification channel ID:", notificationChannel);
|
||||
|
||||
try {
|
||||
// Get the alert configuration details
|
||||
console.log("Fetching alert configuration with ID:", notificationChannel);
|
||||
const alertConfig = await pb.collection('alert_configurations').getOne(notificationChannel);
|
||||
console.log("Found alert configuration:", JSON.stringify({
|
||||
...alertConfig,
|
||||
bot_token: alertConfig.bot_token ? "[REDACTED]" : undefined
|
||||
}, null, 2));
|
||||
|
||||
// Check enabled status - skip if explicitly disabled
|
||||
if (alertConfig.enabled === false || alertConfig.enabled === "false") {
|
||||
console.log("Alert configuration is disabled, skipping notification");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prepare message content
|
||||
let messageContent = await this.prepareMessageContent(
|
||||
payload.service,
|
||||
payload.status,
|
||||
payload.responseTime,
|
||||
payload.message
|
||||
);
|
||||
|
||||
// For DOWN status, add retry information to the message
|
||||
if (payload.status === "down" && lastNotifications[payload.service.id]) {
|
||||
const retryInfo = lastNotifications[payload.service.id];
|
||||
const maxRetries = typeof payload.service.retries === 'number' ? payload.service.retries : 3;
|
||||
if (maxRetries > 1) {
|
||||
messageContent += `\n\nAlert ${retryInfo.count}/${maxRetries}`;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Prepared message for ${payload.status} notification:`, messageContent);
|
||||
|
||||
// Send notification based on type
|
||||
if (alertConfig.notification_type === "telegram") {
|
||||
console.log("Sending Telegram notification for service status change");
|
||||
return await sendTelegramNotification(alertConfig as AlertConfiguration, messageContent);
|
||||
} else if (alertConfig.notification_type === "signal") {
|
||||
console.log("Sending Signal notification with message:", messageContent);
|
||||
return await sendSignalNotification(alertConfig as AlertConfiguration, messageContent);
|
||||
}
|
||||
|
||||
// Add other notification types here
|
||||
console.log("Notification type not supported:", alertConfig.notification_type);
|
||||
toast({
|
||||
title: "Notification Error",
|
||||
description: `Notification type '${alertConfig.notification_type}' not supported`,
|
||||
variant: "destructive"
|
||||
});
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error("Error processing notification channel:", error);
|
||||
console.error("Error details:", error instanceof Error ? error.message : "Unknown error");
|
||||
|
||||
toast({
|
||||
title: "Notification Error",
|
||||
description: `Error processing notification: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
variant: "destructive"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error sending notification:", error);
|
||||
toast({
|
||||
title: "Notification Error",
|
||||
description: `Failed to send notification: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
variant: "destructive"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare message content for notification
|
||||
*/
|
||||
async prepareMessageContent(
|
||||
service: any,
|
||||
status: string,
|
||||
responseTime?: number,
|
||||
defaultMessage?: string
|
||||
): Promise<string> {
|
||||
// Use provided message if available
|
||||
if (defaultMessage) {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
// Check for a template
|
||||
let templateId = service.alertTemplate;
|
||||
|
||||
if (templateId && templateId !== "default") {
|
||||
try {
|
||||
console.log("Using specified template ID:", templateId);
|
||||
const template = await pb.collection('notification_templates').getOne(templateId);
|
||||
console.log("Template data:", template);
|
||||
return processTemplate(
|
||||
template as unknown as NotificationTemplate,
|
||||
service,
|
||||
status,
|
||||
responseTime
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error fetching template:", error);
|
||||
// Fall back to default message format
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Using default message template");
|
||||
return generateDefaultMessage(service.name, status, responseTime);
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a test notification for a specific service status change
|
||||
*/
|
||||
async testServiceStatusNotification(serviceName: string, status: "up" | "down", responseTime?: number): Promise<boolean> {
|
||||
const emoji = status === "up" ? "🟢" : "🔴";
|
||||
const rtText = responseTime ? ` (Response time: ${responseTime}ms)` : "";
|
||||
const message = `${emoji} Test notification: Service ${serviceName} is ${status.toUpperCase()}${rtText}`;
|
||||
|
||||
console.log("Test notification would be sent:", message);
|
||||
// Instead of calling testTelegramNotification which no longer exists, just log and return success
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset notification count for a service
|
||||
* This is called when a service recovers from DOWN to UP
|
||||
*/
|
||||
resetNotificationCount(serviceId: string): void {
|
||||
if (lastNotifications[serviceId]) {
|
||||
console.log(`Resetting notification count for service ${serviceId}`);
|
||||
delete lastNotifications[serviceId];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Re-export the types for easier imports
|
||||
export * from "./types";
|
||||
// Export types from template services
|
||||
export type { AnyTemplate } from '../templateService';
|
||||
@@ -1,42 +1,42 @@
|
||||
|
||||
import { NotificationTemplate } from "../templateService";
|
||||
import { AnyTemplate } from "../templateService";
|
||||
|
||||
/**
|
||||
* Process a notification template with service data
|
||||
*/
|
||||
export function processTemplate(
|
||||
template: NotificationTemplate,
|
||||
template: AnyTemplate,
|
||||
service: any,
|
||||
status: string,
|
||||
responseTime?: number
|
||||
): string {
|
||||
try {
|
||||
console.log(`Processing template for status: ${status}`);
|
||||
// console.log(`Processing template for status: ${status}`);
|
||||
|
||||
let templateText = "";
|
||||
|
||||
// Select the appropriate message template based on status
|
||||
if (status === "up") {
|
||||
templateText = template.up_message || `Service ${service.name} is now UP`;
|
||||
templateText = (template as any).up_message || `Service ${service.name} is now UP`;
|
||||
} else if (status === "down") {
|
||||
templateText = template.down_message || `Service ${service.name} is DOWN`;
|
||||
templateText = (template as any).down_message || `Service ${service.name} is DOWN`;
|
||||
} else if (status === "warning") {
|
||||
templateText = template.incident_message || `Warning: Service ${service.name} has an incident`;
|
||||
templateText = (template as any).incident_message || `Warning: Service ${service.name} has an incident`;
|
||||
} else if (status === "maintenance" || status === "paused") {
|
||||
templateText = template.maintenance_message || `Service ${service.name} is in maintenance mode`;
|
||||
templateText = (template as any).maintenance_message || `Service ${service.name} is in maintenance mode`;
|
||||
} else if (status === "resolved") {
|
||||
templateText = template.resolved_message || `Issue with service ${service.name} has been resolved`;
|
||||
templateText = (template as any).resolved_message || `Issue with service ${service.name} has been resolved`;
|
||||
} else {
|
||||
templateText = `Service ${service.name} status changed to: ${status}`;
|
||||
}
|
||||
|
||||
// Skip replacement if template is empty
|
||||
if (!templateText) {
|
||||
console.log("Empty template for status:", status);
|
||||
// console.log("Empty template for status:", status);
|
||||
return generateDefaultMessage(service.name, status, responseTime);
|
||||
}
|
||||
|
||||
console.log("Using template text:", templateText);
|
||||
// console.log("Using template text:", templateText);
|
||||
|
||||
// Replace placeholders with actual values
|
||||
let message = templateText
|
||||
@@ -56,10 +56,10 @@ export function processTemplate(
|
||||
.replace(/\${url}/g, service.url || 'N/A')
|
||||
.replace(/\${time}/g, new Date().toLocaleString());
|
||||
|
||||
console.log("Processed template message:", message);
|
||||
// console.log("Processed template message:", message);
|
||||
return message;
|
||||
} catch (error) {
|
||||
console.error("Error processing template:", error);
|
||||
// console.error("Error processing template:", error);
|
||||
return generateDefaultMessage(service.name, status, responseTime);
|
||||
}
|
||||
}
|
||||
@@ -83,4 +83,4 @@ export function generateDefaultMessage(
|
||||
message += `. Time: ${new Date().toLocaleString()}`;
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
|
||||
import { Service } from "@/types/service.types";
|
||||
import { processTemplate, generateDefaultMessage } from "./notification/templateProcessor";
|
||||
import { sendTelegramNotification, testSendTelegramMessage } from "./notification/telegramService";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import { templateService } from "./templateService";
|
||||
import { AlertConfiguration } from "./alertConfigService";
|
||||
|
||||
interface NotificationData {
|
||||
service: Service;
|
||||
status: string;
|
||||
responseTime?: number;
|
||||
timestamp: string;
|
||||
_notificationSource?: string;
|
||||
}
|
||||
|
||||
// Track last notification times for services to implement cooldown
|
||||
const lastNotifications: Record<string, {
|
||||
timestamp: Date;
|
||||
count: number;
|
||||
}> = {};
|
||||
|
||||
// Cooldown period in milliseconds (5 minutes)
|
||||
const NOTIFICATION_COOLDOWN = 5 * 60 * 1000;
|
||||
|
||||
export const notificationService = {
|
||||
/**
|
||||
* Send a notification for a service status change
|
||||
*/
|
||||
async sendNotification(data: NotificationData): Promise<boolean> {
|
||||
try {
|
||||
const { service, status, responseTime } = data;
|
||||
|
||||
// console.log(`Preparing to send notification for service: ${service.name}, status: ${status}`);
|
||||
// console.log(`Service alerts status: ${service.alerts}`);
|
||||
|
||||
// First check if alerts are muted for this service
|
||||
// STRICT equality check against "muted" string value
|
||||
if (service.alerts === "muted") {
|
||||
// console.log(`NOTIFICATION BLOCKED: Alerts are muted for service: ${service.name}`);
|
||||
return true; // Return true as this is expected behavior
|
||||
}
|
||||
|
||||
// For paused status, check if this is a duplicate notification from another source
|
||||
// This helps prevent the double-notification issue
|
||||
if (status === "paused" && data._notificationSource === "duplicate_check") {
|
||||
// console.log("NOTIFICATION BLOCKED: Duplicate pause notification detected");
|
||||
return true; // Return true as this is expected behavior
|
||||
}
|
||||
|
||||
// For DOWN status, implement the cooldown and max retries logic
|
||||
if (status === "down") {
|
||||
const serviceId = service.id;
|
||||
const maxRetries = service.retries || 3; // Default to 3 if not specified
|
||||
const now = new Date();
|
||||
|
||||
if (lastNotifications[serviceId]) {
|
||||
const lastNotif = lastNotifications[serviceId];
|
||||
const timeSinceLastNotif = now.getTime() - lastNotif.timestamp.getTime();
|
||||
|
||||
// Check if we're within the cooldown period
|
||||
if (timeSinceLastNotif < NOTIFICATION_COOLDOWN) {
|
||||
// Increment count only if we haven't reached max retries
|
||||
if (lastNotif.count < maxRetries) {
|
||||
// console.log(`DOWN notification for ${service.name}: ${lastNotif.count + 1}/${maxRetries}`);
|
||||
lastNotifications[serviceId].count += 1;
|
||||
} else {
|
||||
// console.log(`DOWN notification for ${service.name} skipped: Max retries (${maxRetries}) reached. Next notification after cooldown.`);
|
||||
return true; // Skip notification but return success
|
||||
}
|
||||
} else {
|
||||
// Reset count after cooldown period
|
||||
// console.log(`Cooldown period elapsed for ${service.name}. Resetting notification count.`);
|
||||
lastNotifications[serviceId] = { timestamp: now, count: 1 };
|
||||
}
|
||||
} else {
|
||||
// First notification for this service
|
||||
// console.log(`First DOWN notification for ${service.name}: 1/${maxRetries}`);
|
||||
lastNotifications[serviceId] = { timestamp: now, count: 1 };
|
||||
}
|
||||
|
||||
// Update timestamp for the current notification
|
||||
lastNotifications[serviceId].timestamp = now;
|
||||
}
|
||||
|
||||
// Check if notification channel is set
|
||||
if (!service.notificationChannel) {
|
||||
// console.log(`No notification channel set for service: ${service.name}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fetch the notification configuration
|
||||
const alertConfigRecord = await pb.collection('alert_configurations').getOne(service.notificationChannel);
|
||||
|
||||
if (!alertConfigRecord) {
|
||||
// console.error(`Alert configuration not found for ID: ${service.notificationChannel}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!alertConfigRecord.enabled) {
|
||||
// console.log(`Alert configuration is disabled for service: ${service.name}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert PocketBase record to AlertConfiguration
|
||||
const alertConfig: AlertConfiguration = {
|
||||
id: alertConfigRecord.id,
|
||||
collectionId: alertConfigRecord.collectionId,
|
||||
collectionName: alertConfigRecord.collectionName,
|
||||
service_id: alertConfigRecord.service_id || "",
|
||||
notification_type: alertConfigRecord.notification_type,
|
||||
telegram_chat_id: alertConfigRecord.telegram_chat_id,
|
||||
discord_webhook_url: alertConfigRecord.discord_webhook_url,
|
||||
signal_number: alertConfigRecord.signal_number,
|
||||
notify_name: alertConfigRecord.notify_name,
|
||||
bot_token: alertConfigRecord.bot_token,
|
||||
template_id: alertConfigRecord.template_id,
|
||||
slack_webhook_url: alertConfigRecord.slack_webhook_url,
|
||||
enabled: alertConfigRecord.enabled,
|
||||
created: alertConfigRecord.created,
|
||||
updated: alertConfigRecord.updated
|
||||
};
|
||||
|
||||
// Select template if one is specified
|
||||
let template;
|
||||
if (service.alertTemplate) {
|
||||
try {
|
||||
template = await templateService.getTemplate(service.alertTemplate);
|
||||
} catch (error) {
|
||||
// console.error(`Error fetching template for ID: ${service.alertTemplate}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the notification message
|
||||
let message;
|
||||
if (template) {
|
||||
message = processTemplate(template, service, status, responseTime);
|
||||
} else {
|
||||
message = generateDefaultMessage(service.name, status, responseTime);
|
||||
}
|
||||
|
||||
// For DOWN status, add retry information to the message
|
||||
if (status === "down" && lastNotifications[service.id]) {
|
||||
const retryInfo = lastNotifications[service.id];
|
||||
const maxRetries = service.retries || 3;
|
||||
message += `\n\nAlert ${retryInfo.count}/${maxRetries}`;
|
||||
}
|
||||
|
||||
// console.log(`Prepared notification message: ${message}`);
|
||||
|
||||
// Send notification based on notification type
|
||||
const notificationType = alertConfig.notification_type;
|
||||
|
||||
if (notificationType === 'telegram') {
|
||||
return await sendTelegramNotification(alertConfig, message);
|
||||
}
|
||||
|
||||
// For other types like discord, slack, etc. (not implemented yet)
|
||||
// console.log(`Notification type ${notificationType} not implemented yet`);
|
||||
return false;
|
||||
} catch (error) {
|
||||
// console.error("Error sending notification:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a test notification for a service status change
|
||||
*/
|
||||
async testServiceStatusNotification(serviceName: string, status: string, responseTime?: number): Promise<boolean> {
|
||||
try {
|
||||
const message = status === 'up'
|
||||
? `Service ${serviceName} is UP${responseTime ? ` (Response time: ${responseTime}ms)` : ''}`
|
||||
: `Service ${serviceName} is DOWN`;
|
||||
|
||||
// console.log(`Test notification would have been sent: ${message}`);
|
||||
return true; // Just log, don't actually send
|
||||
} catch (error) {
|
||||
// console.error("Error in test notification:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset notification count for a service
|
||||
* This can be called when a service recovers from DOWN to UP
|
||||
*/
|
||||
resetNotificationCount(serviceId: string): void {
|
||||
if (lastNotifications[serviceId]) {
|
||||
// console.log(`Resetting notification count for service ${serviceId}`);
|
||||
delete lastNotifications[serviceId];
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,103 @@
|
||||
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
|
||||
export interface ServerNotificationTemplate {
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
name: string;
|
||||
ram_message: string;
|
||||
cpu_message: string;
|
||||
disk_message: string;
|
||||
network_message: string;
|
||||
up_message: string;
|
||||
down_message: string;
|
||||
notification_id: string;
|
||||
warning_message: string;
|
||||
paused_message: string;
|
||||
cpu_temp_message: string;
|
||||
disk_io_message: string;
|
||||
placeholder: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
export interface CreateUpdateServerNotificationTemplateData {
|
||||
name: string;
|
||||
ram_message: string;
|
||||
cpu_message: string;
|
||||
disk_message: string;
|
||||
network_message: string;
|
||||
up_message: string;
|
||||
down_message: string;
|
||||
notification_id: string;
|
||||
warning_message: string;
|
||||
paused_message: string;
|
||||
cpu_temp_message: string;
|
||||
disk_io_message: string;
|
||||
placeholder: string;
|
||||
}
|
||||
|
||||
export const serverNotificationTemplateService = {
|
||||
async getTemplates(): Promise<ServerNotificationTemplate[]> {
|
||||
try {
|
||||
// console.log("Fetching server notification templates");
|
||||
const response = await pb.collection('server_notification_templates').getList(1, 50, {
|
||||
sort: '-created',
|
||||
});
|
||||
// console.log("Server notification templates response:", response);
|
||||
return response.items as unknown as ServerNotificationTemplate[];
|
||||
} catch (error) {
|
||||
// console.error("Error fetching server notification templates:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async getTemplate(id: string): Promise<ServerNotificationTemplate> {
|
||||
try {
|
||||
// console.log(`Fetching server notification template with id: ${id}`);
|
||||
const response = await pb.collection('server_notification_templates').getOne(id);
|
||||
// console.log("Server notification template response:", response);
|
||||
return response as unknown as ServerNotificationTemplate;
|
||||
} catch (error) {
|
||||
/// console.error(`Error fetching server notification template with id ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async createTemplate(data: CreateUpdateServerNotificationTemplateData): Promise<ServerNotificationTemplate> {
|
||||
try {
|
||||
// console.log("Creating new server notification template with data:", data);
|
||||
const response = await pb.collection('server_notification_templates').create(data);
|
||||
// console.log("Create server notification template response:", response);
|
||||
return response as unknown as ServerNotificationTemplate;
|
||||
} catch (error) {
|
||||
// console.error("Error creating server notification template:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async updateTemplate(id: string, data: Partial<CreateUpdateServerNotificationTemplateData>): Promise<ServerNotificationTemplate> {
|
||||
try {
|
||||
// console.log(`Updating server notification template with id: ${id}`, data);
|
||||
const response = await pb.collection('server_notification_templates').update(id, data);
|
||||
// console.log("Update server notification template response:", response);
|
||||
return response as unknown as ServerNotificationTemplate;
|
||||
} catch (error) {
|
||||
// console.error(`Error updating server notification template with id ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async deleteTemplate(id: string): Promise<boolean> {
|
||||
try {
|
||||
// console.log(`Deleting server notification template with id: ${id}`);
|
||||
await pb.collection('server_notification_templates').delete(id);
|
||||
// console.log("Server notification template deleted successfully");
|
||||
return true;
|
||||
} catch (error) {
|
||||
// console.error(`Error deleting server notification template with id ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
|
||||
export interface ServiceNotificationTemplate {
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
name: string;
|
||||
up_message: string;
|
||||
down_message: string;
|
||||
maintenance_message: string;
|
||||
incident_message: string;
|
||||
resolved_message: string;
|
||||
placeholder: string;
|
||||
warning_message: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
export interface CreateUpdateServiceNotificationTemplateData {
|
||||
name: string;
|
||||
up_message: string;
|
||||
down_message: string;
|
||||
maintenance_message: string;
|
||||
incident_message: string;
|
||||
resolved_message: string;
|
||||
placeholder: string;
|
||||
warning_message: string;
|
||||
}
|
||||
|
||||
export const serviceNotificationTemplateService = {
|
||||
async getTemplates(): Promise<ServiceNotificationTemplate[]> {
|
||||
try {
|
||||
// console.log("Fetching service notification templates");
|
||||
const response = await pb.collection('service_notification_templates').getList(1, 50, {
|
||||
sort: '-created',
|
||||
});
|
||||
// console.log("Service notification templates response:", response);
|
||||
return response.items as unknown as ServiceNotificationTemplate[];
|
||||
} catch (error) {
|
||||
// console.error("Error fetching service notification templates:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async getTemplate(id: string): Promise<ServiceNotificationTemplate> {
|
||||
try {
|
||||
// console.log(`Fetching service notification template with id: ${id}`);
|
||||
const response = await pb.collection('service_notification_templates').getOne(id);
|
||||
// console.log("Service notification template response:", response);
|
||||
return response as unknown as ServiceNotificationTemplate;
|
||||
} catch (error) {
|
||||
// console.error(`Error fetching service notification template with id ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async createTemplate(data: CreateUpdateServiceNotificationTemplateData): Promise<ServiceNotificationTemplate> {
|
||||
try {
|
||||
// console.log("Creating new service notification template with data:", data);
|
||||
const response = await pb.collection('service_notification_templates').create(data);
|
||||
// console.log("Create service notification template response:", response);
|
||||
return response as unknown as ServiceNotificationTemplate;
|
||||
} catch (error) {
|
||||
// console.error("Error creating service notification template:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async updateTemplate(id: string, data: Partial<CreateUpdateServiceNotificationTemplateData>): Promise<ServiceNotificationTemplate> {
|
||||
try {
|
||||
// console.log(`Updating service notification template with id: ${id}`, data);
|
||||
const response = await pb.collection('service_notification_templates').update(id, data);
|
||||
// console.log("Update service notification template response:", response);
|
||||
return response as unknown as ServiceNotificationTemplate;
|
||||
} catch (error) {
|
||||
// console.error(`Error updating service notification template with id ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async deleteTemplate(id: string): Promise<boolean> {
|
||||
try {
|
||||
// console.log(`Deleting service notification template with id: ${id}`);
|
||||
await pb.collection('service_notification_templates').delete(id);
|
||||
// console.log("Service notification template deleted successfully");
|
||||
return true;
|
||||
} catch (error) {
|
||||
// console.error(`Error deleting service notification template with id ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
|
||||
export interface SslNotificationTemplate {
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
name: string;
|
||||
expired: string;
|
||||
exiring_soon: string;
|
||||
warning: string;
|
||||
placeholder: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
export interface CreateUpdateSslNotificationTemplateData {
|
||||
name: string;
|
||||
expired: string;
|
||||
exiring_soon: string;
|
||||
warning: string;
|
||||
placeholder: string;
|
||||
}
|
||||
|
||||
export const sslNotificationTemplateService = {
|
||||
async getTemplates(): Promise<SslNotificationTemplate[]> {
|
||||
try {
|
||||
// console.log("Fetching SSL notification templates");
|
||||
const response = await pb.collection('ssl_notification_templates').getList(1, 50, {
|
||||
sort: '-created',
|
||||
});
|
||||
// console.log("SSL notification templates response:", response);
|
||||
return response.items as unknown as SslNotificationTemplate[];
|
||||
} catch (error) {
|
||||
// console.error("Error fetching SSL notification templates:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async getTemplate(id: string): Promise<SslNotificationTemplate> {
|
||||
try {
|
||||
// console.log(`Fetching SSL notification template with id: ${id}`);
|
||||
const response = await pb.collection('ssl_notification_templates').getOne(id);
|
||||
// console.log("SSL notification template response:", response);
|
||||
return response as unknown as SslNotificationTemplate;
|
||||
} catch (error) {
|
||||
// console.error(`Error fetching SSL notification template with id ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async createTemplate(data: CreateUpdateSslNotificationTemplateData): Promise<SslNotificationTemplate> {
|
||||
try {
|
||||
// console.log("Creating new SSL notification template with data:", data);
|
||||
const response = await pb.collection('ssl_notification_templates').create(data);
|
||||
// console.log("Create SSL notification template response:", response);
|
||||
return response as unknown as SslNotificationTemplate;
|
||||
} catch (error) {
|
||||
// console.error("Error creating SSL notification template:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async updateTemplate(id: string, data: Partial<CreateUpdateSslNotificationTemplateData>): Promise<SslNotificationTemplate> {
|
||||
try {
|
||||
// console.log(`Updating SSL notification template with id: ${id}`, data);
|
||||
const response = await pb.collection('ssl_notification_templates').update(id, data);
|
||||
// console.log("Update SSL notification template response:", response);
|
||||
return response as unknown as SslNotificationTemplate;
|
||||
} catch (error) {
|
||||
// console.error(`Error updating SSL notification template with id ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async deleteTemplate(id: string): Promise<boolean> {
|
||||
try {
|
||||
// console.log(`Deleting SSL notification template with id: ${id}`);
|
||||
await pb.collection('ssl_notification_templates').delete(id);
|
||||
// console.log("SSL notification template deleted successfully");
|
||||
return true;
|
||||
} catch (error) {
|
||||
// console.error(`Error deleting SSL notification template with id ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,100 +1,120 @@
|
||||
|
||||
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import {
|
||||
serverNotificationTemplateService,
|
||||
ServerNotificationTemplate,
|
||||
CreateUpdateServerNotificationTemplateData
|
||||
} from "./serverNotificationTemplateService";
|
||||
import {
|
||||
serviceNotificationTemplateService,
|
||||
ServiceNotificationTemplate,
|
||||
CreateUpdateServiceNotificationTemplateData
|
||||
} from "./serviceNotificationTemplateService";
|
||||
import {
|
||||
sslNotificationTemplateService,
|
||||
SslNotificationTemplate,
|
||||
CreateUpdateSslNotificationTemplateData
|
||||
} from "./sslNotificationTemplateService";
|
||||
|
||||
export interface NotificationTemplate {
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
name: string;
|
||||
up_message: string;
|
||||
down_message: string;
|
||||
service_name_placeholder: string;
|
||||
response_time_placeholder: string;
|
||||
status_placeholder: string;
|
||||
threshold_placeholder: string;
|
||||
type: string;
|
||||
maintenance_message: string;
|
||||
incident_message: string;
|
||||
resolved_message: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
export type TemplateType = 'server' | 'service' | 'ssl';
|
||||
|
||||
export interface CreateUpdateTemplateData {
|
||||
name: string;
|
||||
up_message: string;
|
||||
down_message: string;
|
||||
service_name_placeholder: string;
|
||||
response_time_placeholder: string;
|
||||
status_placeholder: string;
|
||||
threshold_placeholder: string;
|
||||
type: string;
|
||||
maintenance_message: string;
|
||||
incident_message: string;
|
||||
resolved_message: string;
|
||||
}
|
||||
export type AnyTemplate = ServerNotificationTemplate | ServiceNotificationTemplate | SslNotificationTemplate;
|
||||
export type AnyTemplateData = CreateUpdateServerNotificationTemplateData | CreateUpdateServiceNotificationTemplateData | CreateUpdateSslNotificationTemplateData;
|
||||
|
||||
// Export individual template types
|
||||
export type { ServerNotificationTemplate, ServiceNotificationTemplate, SslNotificationTemplate };
|
||||
export type { CreateUpdateServerNotificationTemplateData, CreateUpdateServiceNotificationTemplateData, CreateUpdateSslNotificationTemplateData };
|
||||
|
||||
export const templateService = {
|
||||
async getTemplates(): Promise<NotificationTemplate[]> {
|
||||
try {
|
||||
// console.log("Fetching server notification templates");
|
||||
const response = await pb.collection('server_notification_templates').getList(1, 50, {
|
||||
sort: '-created',
|
||||
});
|
||||
// console.log("Server templates response:", response);
|
||||
return response.items as unknown as NotificationTemplate[];
|
||||
} catch (error) {
|
||||
// console.error("Error fetching server templates:", error);
|
||||
throw error;
|
||||
async getTemplates(type: TemplateType): Promise<AnyTemplate[]> {
|
||||
switch (type) {
|
||||
case 'server':
|
||||
return serverNotificationTemplateService.getTemplates();
|
||||
case 'service':
|
||||
return serviceNotificationTemplateService.getTemplates();
|
||||
case 'ssl':
|
||||
return sslNotificationTemplateService.getTemplates();
|
||||
default:
|
||||
throw new Error(`Unknown template type: ${type}`);
|
||||
}
|
||||
},
|
||||
|
||||
async getTemplate(id: string): Promise<NotificationTemplate> {
|
||||
try {
|
||||
// console.log(`Fetching server template with id: ${id}`);
|
||||
const response = await pb.collection('server_notification_templates').getOne(id);
|
||||
// console.log("Server template response:", response);
|
||||
return response as unknown as NotificationTemplate;
|
||||
} catch (error) {
|
||||
// console.error(`Error fetching server template with id ${id}:`, error);
|
||||
throw error;
|
||||
async getTemplate(id: string, type: TemplateType): Promise<AnyTemplate> {
|
||||
switch (type) {
|
||||
case 'server':
|
||||
return serverNotificationTemplateService.getTemplate(id);
|
||||
case 'service':
|
||||
return serviceNotificationTemplateService.getTemplate(id);
|
||||
case 'ssl':
|
||||
return sslNotificationTemplateService.getTemplate(id);
|
||||
default:
|
||||
throw new Error(`Unknown template type: ${type}`);
|
||||
}
|
||||
},
|
||||
|
||||
async createTemplate(data: CreateUpdateTemplateData): Promise<NotificationTemplate> {
|
||||
try {
|
||||
// console.log("Creating new server template with data:", data);
|
||||
const response = await pb.collection('server_notification_templates').create(data);
|
||||
// console.log("Create server template response:", response);
|
||||
return response as unknown as NotificationTemplate;
|
||||
} catch (error) {
|
||||
// console.error("Error creating server template:", error);
|
||||
throw error;
|
||||
async createTemplate(data: AnyTemplateData, type: TemplateType): Promise<AnyTemplate> {
|
||||
switch (type) {
|
||||
case 'server':
|
||||
return serverNotificationTemplateService.createTemplate(data as CreateUpdateServerNotificationTemplateData);
|
||||
case 'service':
|
||||
return serviceNotificationTemplateService.createTemplate(data as CreateUpdateServiceNotificationTemplateData);
|
||||
case 'ssl':
|
||||
return sslNotificationTemplateService.createTemplate(data as CreateUpdateSslNotificationTemplateData);
|
||||
default:
|
||||
throw new Error(`Unknown template type: ${type}`);
|
||||
}
|
||||
},
|
||||
|
||||
async updateTemplate(id: string, data: Partial<CreateUpdateTemplateData>): Promise<NotificationTemplate> {
|
||||
try {
|
||||
// console.log(`Updating server template with id: ${id}`, data);
|
||||
const response = await pb.collection('server_notification_templates').update(id, data);
|
||||
// console.log("Update server template response:", response);
|
||||
return response as unknown as NotificationTemplate;
|
||||
} catch (error) {
|
||||
// console.error(`Error updating server template with id ${id}:`, error);
|
||||
throw error;
|
||||
async updateTemplate(id: string, data: Partial<AnyTemplateData>, type: TemplateType): Promise<AnyTemplate> {
|
||||
switch (type) {
|
||||
case 'server':
|
||||
return serverNotificationTemplateService.updateTemplate(id, data as Partial<CreateUpdateServerNotificationTemplateData>);
|
||||
case 'service':
|
||||
return serviceNotificationTemplateService.updateTemplate(id, data as Partial<CreateUpdateServiceNotificationTemplateData>);
|
||||
case 'ssl':
|
||||
return sslNotificationTemplateService.updateTemplate(id, data as Partial<CreateUpdateSslNotificationTemplateData>);
|
||||
default:
|
||||
throw new Error(`Unknown template type: ${type}`);
|
||||
}
|
||||
},
|
||||
|
||||
async deleteTemplate(id: string): Promise<boolean> {
|
||||
try {
|
||||
// console.log(`Deleting server template with id: ${id}`);
|
||||
await pb.collection('server_notification_templates').delete(id);
|
||||
// console.log("Server template deleted successfully");
|
||||
return true;
|
||||
} catch (error) {
|
||||
// console.error(`Error deleting server template with id ${id}:`, error);
|
||||
throw error;
|
||||
async deleteTemplate(id: string, type: TemplateType): Promise<boolean> {
|
||||
switch (type) {
|
||||
case 'server':
|
||||
return serverNotificationTemplateService.deleteTemplate(id);
|
||||
case 'service':
|
||||
return serviceNotificationTemplateService.deleteTemplate(id);
|
||||
case 'ssl':
|
||||
return sslNotificationTemplateService.deleteTemplate(id);
|
||||
default:
|
||||
throw new Error(`Unknown template type: ${type}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Template type configurations
|
||||
export const templateTypeConfigs = {
|
||||
server: {
|
||||
label: 'Server Monitoring',
|
||||
description: 'Templates for server resource monitoring alerts',
|
||||
placeholders: [
|
||||
'${server_name}', '${cpu_usage}', '${ram_usage}', '${disk_usage}',
|
||||
'${network_usage}', '${cpu_temp}', '${disk_io}', '${threshold}', '${time}'
|
||||
]
|
||||
},
|
||||
service: {
|
||||
label: 'Service Uptime',
|
||||
description: 'Templates for service uptime monitoring alerts',
|
||||
placeholders: [
|
||||
'${service_name}', '${status}', '${response_time}', '${url}',
|
||||
'${uptime}', '${downtime}', '${time}'
|
||||
]
|
||||
},
|
||||
ssl: {
|
||||
label: 'SSL Certificate',
|
||||
description: 'Templates for SSL certificate monitoring alerts',
|
||||
placeholders: [
|
||||
'${domain}', '${certificate_name}', '${expiry_date}', '${days_left}',
|
||||
'${issuer}', '${serial_number}', '${time}'
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
|
||||
export interface WebhookConfiguration {
|
||||
id?: string;
|
||||
collectionId?: string;
|
||||
collectionName?: string;
|
||||
user_id?: string;
|
||||
url: string;
|
||||
enabled: string;
|
||||
secret?: string;
|
||||
headers?: string;
|
||||
retry_count?: string;
|
||||
trigger_events?: string;
|
||||
description?: string;
|
||||
name: string;
|
||||
method?: string;
|
||||
payload_template?: string;
|
||||
created?: string;
|
||||
updated?: string;
|
||||
}
|
||||
|
||||
export const webhookService = {
|
||||
async createWebhook(config: Omit<WebhookConfiguration, 'id' | 'collectionId' | 'collectionName' | 'created' | 'updated'>): Promise<WebhookConfiguration | null> {
|
||||
// console.info("Creating webhook configuration:", config);
|
||||
try {
|
||||
const result = await pb.collection('webhook').create(config);
|
||||
// console.info("Webhook configuration created:", result);
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Webhook created successfully",
|
||||
});
|
||||
return result as WebhookConfiguration;
|
||||
} catch (error) {
|
||||
// console.error("Error creating webhook configuration:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to create webhook",
|
||||
variant: "destructive"
|
||||
});
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async updateWebhook(id: string, config: Partial<WebhookConfiguration>): Promise<WebhookConfiguration | null> {
|
||||
// console.info(`Updating webhook configuration ${id}:`, config);
|
||||
try {
|
||||
const result = await pb.collection('webhook').update(id, config);
|
||||
// console.info("Webhook configuration updated:", result);
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Webhook updated successfully",
|
||||
});
|
||||
return result as WebhookConfiguration;
|
||||
} catch (error) {
|
||||
// console.error("Error updating webhook configuration:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to update webhook",
|
||||
variant: "destructive"
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user