Refactor Webhook to use alert_configurations and Add Signal API endpoint field

- This field will be used to specify the API endpoint for Signal notifications.
- This change aligns the frontend with the backend's data structure for webhooks.
This commit is contained in:
Tola Leng
2025-08-14 04:54:15 +07:00
parent 47a38d3c64
commit 58242c33f8
2 changed files with 78 additions and 67 deletions
@@ -9,7 +9,6 @@ import {
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { AlertConfiguration, alertConfigService } from "@/services/alertConfigService"; import { AlertConfiguration, alertConfigService } from "@/services/alertConfigService";
import { WebhookConfiguration, webhookService } from "@/services/webhookService";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { z } from "zod"; import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
@@ -62,6 +61,7 @@ const slackSchema = baseSchema.extend({
const signalSchema = baseSchema.extend({ const signalSchema = baseSchema.extend({
notification_type: z.literal("signal"), notification_type: z.literal("signal"),
signal_number: z.string().min(1, "Signal number is required"), signal_number: z.string().min(1, "Signal number is required"),
signal_api_endpoint: z.string().url("Must be a valid API endpoint URL"),
}); });
const googleChatSchema = baseSchema.extend({ const googleChatSchema = baseSchema.extend({
@@ -107,19 +107,19 @@ const notificationTypeOptions = [
value: "discord", value: "discord",
label: "Discord", label: "Discord",
description: "Send notifications to Discord webhook", description: "Send notifications to Discord webhook",
icon: "/upload/notification/discord.png" icon: "/upload/notification/discord.png"
}, },
{ {
value: "slack", value: "slack",
label: "Slack", label: "Slack",
description: "Send notifications to Slack webhook", description: "Send notifications to Slack webhook",
icon: "/upload/notification/slack.png" icon: "/upload/notification/slack.png"
}, },
{ {
value: "signal", value: "signal",
label: "Signal", label: "Signal",
description: "Send notifications via Signal", description: "Send notifications via Signal",
icon: "/upload/notification/signal.png" icon: "/upload/notification/signal.png"
}, },
{ {
value: "google_chat", value: "google_chat",
@@ -234,14 +234,6 @@ export const NotificationChannelDialog = ({
onClose(false); onClose(false);
}; };
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
toast({
title: "Copied",
description: "Template copied to clipboard",
});
};
const insertTemplate = (template: string) => { const insertTemplate = (template: string) => {
setValue("webhook_payload_template", template); setValue("webhook_payload_template", template);
}; };
@@ -249,39 +241,16 @@ export const NotificationChannelDialog = ({
const onSubmit = async (values: FormValues) => { const onSubmit = async (values: FormValues) => {
setIsSubmitting(true); setIsSubmitting(true);
try { try {
if (values.notification_type === "webhook") { // Handle all notification types including webhook through alert_configurations
// Handle webhook creation/update with simplified fields const configData = {
const webhookData: Omit<WebhookConfiguration, 'id' | 'collectionId' | 'collectionName' | 'created' | 'updated'> = { ...values,
name: values.notify_name, service_id: values.service_id || "global",
url: values.webhook_url, };
enabled: values.enabled ? "on" : "off",
method: "POST", if (isEditing && editingConfig?.id) {
secret: "", await alertConfigService.updateAlertConfiguration(editingConfig.id, configData);
headers: "",
description: "",
payload_template: values.webhook_payload_template || defaultPayloadTemplate,
retry_count: "3",
trigger_events: "all",
user_id: "global",
};
if (isEditing && editingConfig?.id) {
await webhookService.updateWebhook(editingConfig.id, webhookData);
} else {
await webhookService.createWebhook(webhookData);
}
} else { } else {
// Handle all other notification types including Google Chat and Email await alertConfigService.createAlertConfiguration(configData as any);
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 onClose(true); // Close with refresh
@@ -435,22 +404,40 @@ export const NotificationChannelDialog = ({
)} )}
{notificationType === "signal" && ( {notificationType === "signal" && (
<FormField <>
control={form.control} <FormField
name="signal_number" control={form.control}
render={({ field }) => ( name="signal_number"
<FormItem> render={({ field }) => (
<FormLabel>Signal Number</FormLabel> <FormItem>
<FormControl> <FormLabel>Signal Number</FormLabel>
<Input placeholder="+1234567890" {...field} /> <FormControl>
</FormControl> <Input placeholder="+1234567890" {...field} />
<FormDescription> </FormControl>
Signal phone number to send notifications to <FormDescription>
</FormDescription> Signal phone number to send notifications to
<FormMessage /> </FormDescription>
</FormItem> <FormMessage />
)} </FormItem>
/> )}
/>
<FormField
control={form.control}
name="signal_api_endpoint"
render={({ field }) => (
<FormItem>
<FormLabel>Signal API Endpoint</FormLabel>
<FormControl>
<Input placeholder="https://your-signal-api.com/v2/send" {...field} />
</FormControl>
<FormDescription>
The Rest API endpoint for your Signal service
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</>
)} )}
{notificationType === "google_chat" && ( {notificationType === "google_chat" && (
+31 -7
View File
@@ -11,6 +11,7 @@ export interface AlertConfiguration {
telegram_chat_id?: string; telegram_chat_id?: string;
discord_webhook_url?: string; discord_webhook_url?: string;
signal_number?: string; signal_number?: string;
signal_api_endpoint?: string;
notify_name: string; notify_name: string;
bot_token?: string; bot_token?: string;
template_id?: string; template_id?: string;
@@ -27,10 +28,14 @@ export interface AlertConfiguration {
smtp_password?: string; smtp_password?: string;
webhook_id?: string; webhook_id?: string;
channel_id?: string; channel_id?: string;
// Webhook fields for alert_configurations
webhook_url?: string;
webhook_payload_template?: string;
} }
export const alertConfigService = { export const alertConfigService = {
async getAlertConfigurations(): Promise<AlertConfiguration[]> { async getAlertConfigurations(): Promise<AlertConfiguration[]> {
try { try {
const response = await pb.collection('alert_configurations').getList(1, 50); const response = await pb.collection('alert_configurations').getList(1, 50);
return response.items as AlertConfiguration[]; return response.items as AlertConfiguration[];
@@ -45,7 +50,7 @@ export const alertConfigService = {
}, },
async createAlertConfiguration(config: Omit<AlertConfiguration, 'id' | 'collectionId' | 'collectionName' | 'created' | 'updated'>): Promise<AlertConfiguration | null> { async createAlertConfiguration(config: Omit<AlertConfiguration, 'id' | 'collectionId' | 'collectionName' | 'created' | 'updated'>): Promise<AlertConfiguration | null> {
try { try {
// Build the configuration object with proper field mapping // Build the configuration object with proper field mapping
const cleanConfig: any = { const cleanConfig: any = {
@@ -55,7 +60,7 @@ export const alertConfigService = {
enabled: config.enabled, enabled: config.enabled,
template_id: config.template_id || "", template_id: config.template_id || "",
}; };
// Add type-specific fields based on notification type // Add type-specific fields based on notification type
if (config.notification_type === "telegram") { if (config.notification_type === "telegram") {
cleanConfig.telegram_chat_id = config.telegram_chat_id || ""; cleanConfig.telegram_chat_id = config.telegram_chat_id || "";
@@ -66,24 +71,33 @@ export const alertConfigService = {
cleanConfig.slack_webhook_url = config.slack_webhook_url || ""; cleanConfig.slack_webhook_url = config.slack_webhook_url || "";
} else if (config.notification_type === "signal") { } else if (config.notification_type === "signal") {
cleanConfig.signal_number = config.signal_number || ""; cleanConfig.signal_number = config.signal_number || "";
cleanConfig.signal_api_endpoint = config.signal_api_endpoint || "";
} else if (config.notification_type === "google_chat") { } else if (config.notification_type === "google_chat") {
cleanConfig.google_chat_webhook_url = config.google_chat_webhook_url || ""; cleanConfig.google_chat_webhook_url = config.google_chat_webhook_url || "";
} else if (config.notification_type === "email") {
} else if (config.notification_type === "email") {
cleanConfig.email_address = config.email_address || ""; cleanConfig.email_address = config.email_address || "";
cleanConfig.email_sender_name = config.email_sender_name || ""; cleanConfig.email_sender_name = config.email_sender_name || "";
cleanConfig.smtp_server = config.smtp_server || ""; cleanConfig.smtp_server = config.smtp_server || "";
cleanConfig.smtp_port = config.smtp_port || ""; cleanConfig.smtp_port = config.smtp_port || "";
cleanConfig.smtp_password = config.smtp_password || ""; cleanConfig.smtp_password = config.smtp_password || "";
} } else if (config.notification_type === "webhook") {
cleanConfig.webhook_url = config.webhook_url || "";
cleanConfig.webhook_payload_template = config.webhook_payload_template || "";
}
const result = await pb.collection('alert_configurations').create(cleanConfig); const result = await pb.collection('alert_configurations').create(cleanConfig);
toast({ toast({
title: "Success", title: "Success",
description: "Notification channel created successfully", description: "Notification channel created successfully",
}); });
return result as AlertConfiguration; return result as AlertConfiguration;
} catch (error) { } catch (error) {
// Try to get more details from the error // Try to get more details from the error
if (error && typeof error === 'object') { if (error && typeof error === 'object') {
} }
@@ -99,7 +113,17 @@ export const alertConfigService = {
async updateAlertConfiguration(id: string, config: Partial<AlertConfiguration>): Promise<AlertConfiguration | null> { async updateAlertConfiguration(id: string, config: Partial<AlertConfiguration>): Promise<AlertConfiguration | null> {
try { try {
const result = await pb.collection('alert_configurations').update(id, config); // Build the update config with proper field mapping
const updateConfig: any = {};
// Copy all provided fields
Object.keys(config).forEach(key => {
if (config[key as keyof AlertConfiguration] !== undefined) {
updateConfig[key] = config[key as keyof AlertConfiguration];
}
});
const result = await pb.collection('alert_configurations').update(id, updateConfig);
toast({ toast({
title: "Success", title: "Success",
description: "Notification channel updated successfully", description: "Notification channel updated successfully",