"use client" import { useState, useEffect, useCallback } from "react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card" import { Tabs, TabsList, TabsTrigger, TabsContent } from "./ui/tabs" import { Input } from "./ui/input" import { Label } from "./ui/label" import { Badge } from "./ui/badge" import { Checkbox } from "./ui/checkbox" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" import { fetchApi } from "../lib/api-config" import { Bell, BellOff, Send, CheckCircle2, XCircle, Loader2, AlertTriangle, Info, Settings2, Zap, Eye, EyeOff, Trash2, ChevronDown, ChevronUp, TestTube2 } from "lucide-react" interface ChannelConfig { enabled: boolean bot_token?: string chat_id?: string url?: string token?: string webhook_url?: string } interface NotificationConfig { enabled: boolean channels: Record severity_filter: string event_categories: Record ai_enabled: boolean ai_provider: string ai_api_key: string ai_model: string hostname: string } interface ServiceStatus { enabled: boolean running: boolean channels: Record queue_size: number last_sent: string | null total_sent_24h: number } interface HistoryEntry { id: number event_type: string channel: string title: string severity: string sent_at: string success: boolean error_message: string | null } const SEVERITY_OPTIONS = [ { value: "critical", label: "Critical only" }, { value: "warning", label: "Warning + Critical" }, { value: "info", label: "All (Info + Warning + Critical)" }, ] const EVENT_CATEGORIES = [ { key: "system", label: "System", desc: "Startup, shutdown, kernel events" }, { key: "vm_ct", label: "VM / CT", desc: "Start, stop, crash, migration" }, { key: "backup", label: "Backups", desc: "Backup start, complete, fail" }, { key: "resources", label: "Resources", desc: "CPU, memory, temperature" }, { key: "storage", label: "Storage", desc: "Disk space, I/O errors, SMART" }, { key: "network", label: "Network", desc: "Connectivity, bond, latency" }, { key: "security", label: "Security", desc: "Auth failures, fail2ban, firewall" }, { key: "cluster", label: "Cluster", desc: "Quorum, split-brain, HA fencing" }, ] const AI_PROVIDERS = [ { value: "openai", label: "OpenAI" }, { value: "groq", label: "Groq" }, ] const DEFAULT_CONFIG: NotificationConfig = { enabled: false, channels: { telegram: { enabled: false }, gotify: { enabled: false }, discord: { enabled: false }, }, severity_filter: "warning", event_categories: { system: true, vm_ct: true, backup: true, resources: true, storage: true, network: true, security: true, cluster: true, }, ai_enabled: false, ai_provider: "openai", ai_api_key: "", ai_model: "", hostname: "", } export function NotificationSettings() { const [config, setConfig] = useState(DEFAULT_CONFIG) const [status, setStatus] = useState(null) const [history, setHistory] = useState([]) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [saved, setSaved] = useState(false) const [testing, setTesting] = useState(null) const [testResult, setTestResult] = useState<{ channel: string; success: boolean; message: string } | null>(null) const [showHistory, setShowHistory] = useState(false) const [showAdvanced, setShowAdvanced] = useState(false) const [showSecrets, setShowSecrets] = useState>({}) const [editMode, setEditMode] = useState(false) const [hasChanges, setHasChanges] = useState(false) const [originalConfig, setOriginalConfig] = useState(DEFAULT_CONFIG) const loadConfig = useCallback(async () => { try { const data = await fetchApi<{ success: boolean; config: NotificationConfig }>("/api/notifications/settings") if (data.success && data.config) { setConfig(data.config) setOriginalConfig(data.config) } } catch (err) { console.error("Failed to load notification settings:", err) } finally { setLoading(false) } }, []) const loadStatus = useCallback(async () => { try { const data = await fetchApi<{ success: boolean } & ServiceStatus>("/api/notifications/status") if (data.success) { setStatus(data) } } catch { // Service may not be running yet } }, []) const loadHistory = useCallback(async () => { try { const data = await fetchApi<{ success: boolean; history: HistoryEntry[]; total: number }>("/api/notifications/history?limit=20") if (data.success) { setHistory(data.history || []) } } catch { // Ignore } }, []) useEffect(() => { loadConfig() loadStatus() }, [loadConfig, loadStatus]) useEffect(() => { if (showHistory) loadHistory() }, [showHistory, loadHistory]) const updateConfig = (updater: (prev: NotificationConfig) => NotificationConfig) => { setConfig(prev => { const next = updater(prev) setHasChanges(true) return next }) } const updateChannel = (channel: string, field: string, value: string | boolean) => { updateConfig(prev => ({ ...prev, channels: { ...prev.channels, [channel]: { ...prev.channels[channel], [field]: value }, }, })) } const handleSave = async () => { setSaving(true) try { await fetchApi("/api/notifications/settings", { method: "POST", body: JSON.stringify(config), }) setOriginalConfig(config) setHasChanges(false) setEditMode(false) setSaved(true) setTimeout(() => setSaved(false), 3000) loadStatus() } catch (err) { console.error("Failed to save notification settings:", err) } finally { setSaving(false) } } const handleCancel = () => { setConfig(originalConfig) setHasChanges(false) setEditMode(false) } const handleTest = async (channel: string) => { setTesting(channel) setTestResult(null) try { const data = await fetchApi<{ success: boolean; message: string }>("/api/notifications/test", { method: "POST", body: JSON.stringify({ channel }), }) setTestResult({ channel, success: data.success, message: data.message }) } catch (err) { setTestResult({ channel, success: false, message: String(err) }) } finally { setTesting(null) setTimeout(() => setTestResult(null), 5000) } } const handleClearHistory = async () => { try { await fetchApi("/api/notifications/history", { method: "DELETE" }) setHistory([]) } catch { // Ignore } } const toggleSecret = (key: string) => { setShowSecrets(prev => ({ ...prev, [key]: !prev[key] })) } if (loading) { return (
Notifications
) } const activeChannels = Object.entries(config.channels).filter(([, ch]) => ch.enabled).length return (
Notifications {config.enabled && ( Active )}
{saved && ( Saved )} {editMode ? ( <> ) : ( )}
Configure notification channels and event filters. Receive alerts via Telegram, Gotify, or Discord.
{/* ── Service Status ── */} {status && (
{status.running ? "Service running" : "Service stopped"} {status.total_sent_24h > 0 && ( {status.total_sent_24h} sent in last 24h )}
{activeChannels > 0 && ( {activeChannels} channel{activeChannels > 1 ? "s" : ""} )}
)} {/* ── Enable/Disable ── */}
{config.enabled ? ( ) : ( )}
Enable Notifications

Activate the notification service

{config.enabled && ( <> {/* ── Channel Configuration ── */}
Channels
Telegram Gotify Discord {/* Telegram */}
{config.channels.telegram?.enabled && ( <>
updateChannel("telegram", "bot_token", e.target.value)} disabled={!editMode} />
updateChannel("telegram", "chat_id", e.target.value)} disabled={!editMode} />
{!editMode && config.channels.telegram?.bot_token && ( )} )}
{/* Gotify */}
{config.channels.gotify?.enabled && ( <>
updateChannel("gotify", "url", e.target.value)} disabled={!editMode} />
updateChannel("gotify", "token", e.target.value)} disabled={!editMode} />
{!editMode && config.channels.gotify?.url && ( )} )}
{/* Discord */}
{config.channels.discord?.enabled && ( <>
updateChannel("discord", "webhook_url", e.target.value)} disabled={!editMode} />
{!editMode && config.channels.discord?.webhook_url && ( )} )}
{/* Test Result */} {testResult && (
{testResult.success ? ( ) : ( )} {testResult.message}
)}
{/* ── Severity Filter ── */}
Severity Filter
{/* ── Event Categories ── */}
Event Categories
{EVENT_CATEGORIES.map(cat => ( ))}
{/* ── Advanced: AI Enhancement ── */}
{showAdvanced && (
AI-Enhanced Messages

Use AI to generate contextual notification messages

{config.ai_enabled && ( <>
updateConfig(p => ({ ...p, ai_api_key: e.target.value }))} disabled={!editMode} />
updateConfig(p => ({ ...p, ai_model: e.target.value }))} disabled={!editMode} />

AI enhancement is optional. When enabled, notifications include contextual analysis and recommended actions. If the AI service is unavailable, standard templates are used as fallback.

)}
)}
{/* ── Notification History ── */}
{showHistory && (
{history.length === 0 ? (

No notifications sent yet

) : ( <>
{history.map(entry => (
{entry.success ? ( ) : ( )}
{entry.title || entry.event_type} {entry.channel} - {new Date(entry.sent_at).toLocaleString()}
{entry.severity}
))}
)}
)}
)} {/* ── Footer info ── */}

{config.enabled ? "Notifications are active. Events matching your severity filter and category selection will be sent to configured channels." : "Enable notifications to receive alerts about system events, health status changes, and security incidents via Telegram, Gotify, or Discord."}

) }