import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/dialog"; import { Button } from "@/components/button"; import { Input } from "@/components/input"; import { toast } from "sonner"; import { createNotificationChannel, updateNotificationChannel, type NotificationChannel, } from "@/api/alerts-api"; interface NotificationChannelDialogProps { open: boolean; onOpenChange: (open: boolean) => void; channel: NotificationChannel | null; onSaved: () => void; } type ChannelType = "webhook" | "ntfy" | "discord"; interface WebhookConfig { url: string; headers?: Record; } interface NtfyConfig { url: string; topic: string; token?: string; } interface DiscordConfig { url: string; username?: string; avatar_url?: string; } export function NotificationChannelDialog({ open, onOpenChange, channel, onSaved, }: NotificationChannelDialogProps) { const { t } = useTranslation(); const [name, setName] = useState(""); const [type, setType] = useState("webhook"); const [webhookUrl, setWebhookUrl] = useState(""); const [ntfyUrl, setNtfyUrl] = useState("https://ntfy.sh"); const [ntfyTopic, setNtfyTopic] = useState(""); const [ntfyToken, setNtfyToken] = useState(""); const [discordUsername, setDiscordUsername] = useState(""); const [discordAvatarUrl, setDiscordAvatarUrl] = useState(""); const [saving, setSaving] = useState(false); useEffect(() => { if (!open) return; if (channel) { setName(channel.name); setType(channel.type); try { const cfg = JSON.parse(channel.config); if (channel.type === "webhook") { setWebhookUrl((cfg as WebhookConfig).url ?? ""); } else if (channel.type === "discord") { const dc = cfg as DiscordConfig; setWebhookUrl(dc.url ?? ""); setDiscordUsername(dc.username ?? ""); setDiscordAvatarUrl(dc.avatar_url ?? ""); } else { const nc = cfg as NtfyConfig; setNtfyUrl(nc.url ?? "https://ntfy.sh"); setNtfyTopic(nc.topic ?? ""); setNtfyToken(nc.token ?? ""); } } catch { // ignore malformed config } } else { setName(""); setType("webhook"); setWebhookUrl(""); setNtfyUrl("https://ntfy.sh"); setNtfyTopic(""); setNtfyToken(""); } }, [open, channel]); async function handleSave() { if (!name.trim()) { toast.error(t("alerts.channelNameRequired", "Name is required")); return; } let config: WebhookConfig | NtfyConfig | DiscordConfig; if (type === "webhook") { if (!webhookUrl.trim()) { toast.error(t("alerts.webhookUrlRequired", "Webhook URL is required")); return; } config = { url: webhookUrl.trim(), } satisfies WebhookConfig; } else if (type === "discord") { const trimmedUrl = webhookUrl.trim(); if (!trimmedUrl) { toast.error(t("alerts.webhookUrlRequired", "Webhook URL is required")); return; } if ( !/^https:\/\/(?:canary\.|ptb\.)?(?:discord\.com|discordapp\.com)\/api\/webhooks\/.+/i.test( trimmedUrl, ) ) { toast.error( t( "alerts.discordUrlInvalid", "Discord URL must be a valid Discord webhook URL", ), ); return; } const dc: DiscordConfig = { url: trimmedUrl }; if (discordUsername.trim()) dc.username = discordUsername.trim(); if (discordAvatarUrl.trim()) dc.avatar_url = discordAvatarUrl.trim(); config = dc; } else { if (!ntfyTopic.trim()) { toast.error(t("alerts.ntfyTopicRequired", "Topic is required")); return; } const cfg: NtfyConfig = { url: ntfyUrl.trim(), topic: ntfyTopic.trim() }; if (ntfyToken.trim()) cfg.token = ntfyToken.trim(); config = cfg; } setSaving(true); try { if (channel) { await updateNotificationChannel(channel.id, { name: name.trim(), type, config, }); } else { await createNotificationChannel({ name: name.trim(), type, config }); } onSaved(); } catch { toast.error(t("alerts.channelSaveFailed", "Failed to save channel")); } finally { setSaving(false); } } return ( {channel ? t("alerts.editChannel", "Edit Channel") : t("alerts.addChannel", "Add Channel")}
setName(e.target.value)} className="text-sm" />
{(["webhook", "ntfy", "discord"] as ChannelType[]).map((ct) => ( ))}
{type === "webhook" && (
setWebhookUrl(e.target.value)} placeholder="https://example.com/webhook" className="text-sm font-mono" /> {t( "alerts.webhookDesc", "POST JSON payload to this URL on each alert firing", )}
)} {type === "discord" && (
setWebhookUrl(e.target.value)} placeholder="https://discord.com/api/webhooks/..." className="text-sm font-mono" /> {t( "alerts.discordWebhookDesc", "POST a Discord embed to this webhook URL on each alert firing", )}
setDiscordUsername(e.target.value)} placeholder="Termix" className="text-sm" />
setDiscordAvatarUrl(e.target.value)} placeholder="https://example.com/avatar.png" className="text-sm font-mono" />
)} {type === "ntfy" && ( <>
setNtfyUrl(e.target.value)} placeholder="https://ntfy.sh" className="text-sm font-mono" />
setNtfyTopic(e.target.value)} placeholder="my-alerts" className="text-sm font-mono" />
setNtfyToken(e.target.value)} placeholder="tk_..." className="text-sm font-mono" />
)}
); }