import { useState, useEffect } from 'react'; import { Tabs, TabsContent, TabsList, TabsTrigger, TabsHighlight, TabsHighlightItem } from '@/components/ui/tabs'; import { springs } from '@/lib/motion'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { Badge } from '@/components/ui/badge'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { toast } from '@/components/ui/toast-store'; import { apiFetch } from '@/lib/api'; import { useNodes } from '@/context/NodeContext'; import { RefreshCw, Info } from 'lucide-react'; import type { Agent } from './types'; export function NotificationsSection() { const { activeNode } = useNodes(); const isRemote = activeNode?.type === 'remote'; const [notifTab, setNotifTab] = useState<'discord' | 'slack' | 'webhook'>('discord'); const [agents, setAgents] = useState>({ discord: { type: 'discord', url: '', enabled: false }, slack: { type: 'slack', url: '', enabled: false }, webhook: { type: 'webhook', url: '', enabled: false }, }); const [isSavingAgent, setIsSavingAgent] = useState>({}); const [isTestingAgent, setIsTestingAgent] = useState>({}); const fetchAgents = async () => { try { const res = await apiFetch('/agents'); if (res.ok) { const data: Agent[] = await res.json(); setAgents(prev => { const next = { ...prev }; data.forEach(a => { next[a.type] = a; }); return next; }); } } catch (e) { console.error('Failed to fetch agents', e); } }; useEffect(() => { fetchAgents(); }, [activeNode?.id]); const handleAgentChange = (type: string, field: keyof Agent, value: Agent[keyof Agent]) => { setAgents(prev => ({ ...prev, [type]: { ...prev[type], [field]: value }, })); }; const saveAgent = async (type: string) => { setIsSavingAgent(prev => ({ ...prev, [type]: true })); try { const res = await apiFetch('/agents', { method: 'POST', body: JSON.stringify(agents[type]), }); if (res.ok) { toast.success(`${type.charAt(0).toUpperCase() + type.slice(1)} settings saved.`); } else { const err = await res.json().catch(() => ({})); toast.error(err?.error || err?.message || 'Something went wrong.'); } } catch (e: unknown) { toast.error((e as Error)?.message || 'Network error.'); } finally { setIsSavingAgent(prev => ({ ...prev, [type]: false })); } }; const testAgent = async (type: string) => { if (!agents[type].url) { toast.error('Please enter a webhook URL first.'); return; } setIsTestingAgent(prev => ({ ...prev, [type]: true })); try { const res = await apiFetch('/notifications/test', { method: 'POST', body: JSON.stringify({ type, url: agents[type].url }), }); if (res.ok) { toast.success('Test notification sent!'); } else { const err = await res.json().catch(() => ({})); toast.error(err?.details || err?.error || 'Test failed.'); } } catch (e: unknown) { toast.error((e as Error)?.message || 'Network error.'); } finally { setIsTestingAgent(prev => ({ ...prev, [type]: false })); } }; const renderAgentTab = (type: 'discord' | 'slack' | 'webhook', title: string) => (
handleAgentChange(type, 'enabled', c)} />
handleAgentChange(type, 'url', e.target.value)} />
); return (

Notifications & Alerts

{isRemote ? <>Configuring notification channels on {activeNode!.name}. Alerts from this remote node will dispatch via these channels. : 'Configure external integrations for crash alerts.' }

{isRemote && ( Remote These channels are saved on the remote Sencho instance and used when it dispatches alerts. )}
setNotifTab(v as 'discord' | 'slack' | 'webhook')} className="w-full"> Discord Slack Webhook {renderAgentTab('discord', 'Discord')} {renderAgentTab('slack', 'Slack')} {renderAgentTab('webhook', 'Custom Webhook')}
); }