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:
+247
-70
@@ -1,4 +1,3 @@
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -8,8 +7,9 @@ import {
|
||||
DialogFooter
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { AlertConfiguration, alertConfigService } from "@/services/alertConfigService";
|
||||
import { WebhookConfiguration, webhookService } from "@/services/webhookService";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
@@ -36,7 +36,7 @@ const baseSchema = z.object({
|
||||
notify_name: z.string().min(1, "Name is required"),
|
||||
notification_type: z.enum(["telegram", "discord", "slack", "signal", "email"]),
|
||||
enabled: z.boolean().default(true),
|
||||
service_id: z.string().default("global"), // Assuming global for now, could be linked to specific services
|
||||
service_id: z.string().default("global"),
|
||||
template_id: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -63,7 +63,19 @@ const signalSchema = baseSchema.extend({
|
||||
|
||||
const emailSchema = baseSchema.extend({
|
||||
notification_type: z.literal("email"),
|
||||
// Email specific fields could be added here
|
||||
email_address: z.string().email("Valid email is required"),
|
||||
email_sender_name: z.string().min(1, "Sender name is required"),
|
||||
smtp_server: z.string().min(1, "SMTP server is required"),
|
||||
smtp_port: z.string().min(1, "SMTP port is required"),
|
||||
});
|
||||
|
||||
const webhookSchema = baseSchema.extend({
|
||||
notification_type: z.literal("webhook"),
|
||||
webhook_url: z.string().url("Must be a valid URL"),
|
||||
webhook_method: z.enum(["GET", "POST", "PUT", "PATCH"]).default("POST"),
|
||||
webhook_secret: z.string().optional(),
|
||||
webhook_headers: z.string().optional(),
|
||||
webhook_description: z.string().optional(),
|
||||
});
|
||||
|
||||
const formSchema = z.discriminatedUnion("notification_type", [
|
||||
@@ -71,11 +83,21 @@ const formSchema = z.discriminatedUnion("notification_type", [
|
||||
discordSchema,
|
||||
slackSchema,
|
||||
signalSchema,
|
||||
emailSchema
|
||||
emailSchema,
|
||||
webhookSchema
|
||||
]);
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
const notificationTypeOptions = [
|
||||
{ value: "telegram", label: "Telegram", description: "Send notifications via Telegram bot" },
|
||||
{ value: "discord", label: "Discord", description: "Send notifications to Discord webhook" },
|
||||
{ value: "slack", label: "Slack", description: "Send notifications to Slack webhook" },
|
||||
{ value: "signal", label: "Signal", description: "Send notifications via Signal" },
|
||||
{ value: "email", label: "Email", description: "Send notifications via email" },
|
||||
{ value: "webhook", label: "Webhook", description: "Send notifications to custom webhook" },
|
||||
];
|
||||
|
||||
export const NotificationChannelDialog = ({
|
||||
open,
|
||||
onClose,
|
||||
@@ -126,16 +148,36 @@ export const NotificationChannelDialog = ({
|
||||
const onSubmit = async (values: FormValues) => {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
// Ensure service_id is always present
|
||||
const configData = {
|
||||
...values,
|
||||
service_id: values.service_id || "global",
|
||||
};
|
||||
|
||||
if (isEditing && editingConfig?.id) {
|
||||
await alertConfigService.updateAlertConfiguration(editingConfig.id, configData);
|
||||
if (values.notification_type === "webhook") {
|
||||
// Handle webhook creation/update separately
|
||||
const webhookData: Omit<WebhookConfiguration, 'id' | 'collectionId' | 'collectionName' | 'created' | 'updated'> = {
|
||||
name: values.notify_name,
|
||||
url: values.webhook_url,
|
||||
enabled: values.enabled ? "on" : "off",
|
||||
method: values.webhook_method || "POST",
|
||||
secret: values.webhook_secret || "",
|
||||
headers: values.webhook_headers || "",
|
||||
description: values.webhook_description || "",
|
||||
user_id: "global", // or get current user id
|
||||
};
|
||||
|
||||
if (isEditing && editingConfig?.id) {
|
||||
await webhookService.updateWebhook(editingConfig.id, webhookData);
|
||||
} else {
|
||||
await webhookService.createWebhook(webhookData);
|
||||
}
|
||||
} else {
|
||||
await alertConfigService.createAlertConfiguration(configData as any);
|
||||
// Handle other notification types with existing service
|
||||
const configData = {
|
||||
...values,
|
||||
service_id: values.service_id || "global",
|
||||
};
|
||||
|
||||
if (isEditing && editingConfig?.id) {
|
||||
await alertConfigService.updateAlertConfiguration(editingConfig.id, configData);
|
||||
} else {
|
||||
await alertConfigService.createAlertConfiguration(configData as any);
|
||||
}
|
||||
}
|
||||
onClose(true); // Close with refresh
|
||||
} finally {
|
||||
@@ -159,7 +201,7 @@ export const NotificationChannelDialog = ({
|
||||
name="notify_name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>Channel Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="My Notification Channel" {...field} />
|
||||
</FormControl>
|
||||
@@ -175,57 +217,25 @@ export const NotificationChannelDialog = ({
|
||||
control={form.control}
|
||||
name="notification_type"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-3">
|
||||
<FormItem>
|
||||
<FormLabel>Channel Type</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
className="flex flex-col space-y-1"
|
||||
>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="telegram" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Telegram
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="discord" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Discord
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="slack" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Slack
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="signal" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Signal
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="email" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Email
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<Select onValueChange={field.onChange} value={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select notification type" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{notificationTypeOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{option.label}</span>
|
||||
<span className="text-xs text-muted-foreground">{option.description}</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -243,6 +253,9 @@ export const NotificationChannelDialog = ({
|
||||
<FormControl>
|
||||
<Input placeholder="Telegram Chat ID" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The Telegram chat ID to send notifications to
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -256,6 +269,9 @@ export const NotificationChannelDialog = ({
|
||||
<FormControl>
|
||||
<Input placeholder="Telegram Bot Token" {...field} type="password" />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Your Telegram bot token from @BotFather
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -271,8 +287,11 @@ export const NotificationChannelDialog = ({
|
||||
<FormItem>
|
||||
<FormLabel>Webhook URL</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Discord Webhook URL" {...field} />
|
||||
<Input placeholder="https://discord.com/api/webhooks/..." {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Discord webhook URL from your server settings
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -287,8 +306,11 @@ export const NotificationChannelDialog = ({
|
||||
<FormItem>
|
||||
<FormLabel>Webhook URL</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Slack Webhook URL" {...field} />
|
||||
<Input placeholder="https://hooks.slack.com/services/..." {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Slack incoming webhook URL
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -305,11 +327,166 @@ export const NotificationChannelDialog = ({
|
||||
<FormControl>
|
||||
<Input placeholder="+1234567890" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Signal phone number to send notifications to
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{notificationType === "email" && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email_address"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email Address</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="notifications@example.com" {...field} type="email" />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Email address to send notifications to
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email_sender_name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Sender Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Alert System" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Display name for outgoing emails
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="smtp_server"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>SMTP Server</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="smtp.gmail.com" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="smtp_port"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>SMTP Port</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="587" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{notificationType === "webhook" && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="webhook_url"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Webhook URL</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="https://api.example.com/webhook" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The URL where webhook notifications will be sent
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="webhook_method"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>HTTP Method</FormLabel>
|
||||
<Select onValueChange={field.onChange} value={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select method" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="POST">POST</SelectItem>
|
||||
<SelectItem value="PUT">PUT</SelectItem>
|
||||
<SelectItem value="PATCH">PATCH</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="webhook_secret"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Secret (Optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="webhook_secret_key" {...field} type="password" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="webhook_headers"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Custom Headers (Optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder='{"Authorization": "Bearer token"}' {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
JSON format for additional headers
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="webhook_description"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Description (Optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Description of this webhook" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -319,7 +496,7 @@ export const NotificationChannelDialog = ({
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Enabled</FormLabel>
|
||||
<FormDescription>
|
||||
Turn notifications on or off
|
||||
Enable or disable this notification channel
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
@@ -338,7 +515,7 @@ export const NotificationChannelDialog = ({
|
||||
</Button>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{isEditing ? "Update" : "Create"}
|
||||
{isEditing ? "Update Channel" : "Create Channel"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
@@ -346,4 +523,4 @@ export const NotificationChannelDialog = ({
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user