'use client'; import { useState, useEffect } from 'react'; import Link from 'next/link'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Switch } from '@/components/ui/switch'; import { ArrowLeft, Plus, Trash2, Send, Bell, MessageSquare, Globe, CheckCircle2, XCircle, Loader2, Pencil, } from 'lucide-react'; interface Channel { id: number; name: string; channel_type: string; config: Record; is_active: boolean; created_at: string; updated_at: string; } const CHANNEL_TYPES = [ { value: 'teams', label: 'Microsoft Teams', icon: MessageSquare, color: 'bg-indigo-100 text-indigo-700', fields: [ { key: 'webhook_url', label: 'Webhook URL', type: 'url', placeholder: 'https://...webhook.office.com/...' }, ]}, { value: 'telegram', label: 'Telegram', icon: Send, color: 'bg-blue-100 text-blue-700', fields: [ { key: 'bot_token', label: 'Bot Token', type: 'password', placeholder: '123456:ABC-DEF...' }, { key: 'chat_id', label: 'Chat ID', type: 'text', placeholder: '-1001234567890' }, { key: 'parse_mode', label: 'Parse Mode', type: 'select', options: ['HTML', 'Markdown', 'MarkdownV2'] }, ]}, { value: 'ntfy', label: 'ntfy', icon: Bell, color: 'bg-green-100 text-green-700', fields: [ { key: 'server_url', label: 'Server URL', type: 'url', placeholder: 'https://ntfy.sh' }, { key: 'topic', label: 'Topic', type: 'text', placeholder: 'pulse-alerts' }, { key: 'auth_token', label: 'Auth Token (optional)', type: 'password', placeholder: 'tk_...' }, { key: 'default_priority', label: 'Default Priority', type: 'select', options: ['min', 'low', 'default', 'high', 'urgent'] }, ]}, { value: 'webhook', label: 'Generic Webhook', icon: Globe, color: 'bg-gray-100 text-gray-700', fields: [ { key: 'url', label: 'URL', type: 'url', placeholder: 'https://...' }, { key: 'method', label: 'Method', type: 'select', options: ['POST', 'PUT', 'PATCH'] }, ]}, ]; export default function ChannelsPage() { const [channels, setChannels] = useState([]); const [isLoading, setIsLoading] = useState(true); const [showCreate, setShowCreate] = useState(false); const [editingId, setEditingId] = useState(null); const [formType, setFormType] = useState('teams'); const [formName, setFormName] = useState(''); const [formConfig, setFormConfig] = useState>({}); const [testStatus, setTestStatus] = useState>({}); useEffect(() => { loadChannels(); }, []); const loadChannels = async () => { setIsLoading(true); try { const res = await fetch('/api/notification-channels'); if (res.ok) { const data = await res.json(); setChannels(data.data || []); } } catch (err) { console.error('Failed to load channels:', err); } finally { setIsLoading(false); } }; const saveChannel = async () => { if (!formName) return; try { const payload = { name: formName, channel_type: formType, config: formConfig, is_active: true }; if (editingId) { await fetch(`/api/notification-channels/${editingId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); } else { await fetch('/api/notification-channels', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); } resetForm(); loadChannels(); } catch (err) { console.error('Failed to save channel:', err); } }; const deleteChannel = async (id: number) => { if (!confirm('Delete this notification channel?')) return; try { await fetch(`/api/notification-channels/${id}`, { method: 'DELETE' }); setChannels(prev => prev.filter(c => c.id !== id)); } catch (err) { console.error('Failed to delete channel:', err); } }; const toggleChannel = async (id: number, active: boolean) => { try { await fetch(`/api/notification-channels/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ is_active: active }), }); setChannels(prev => prev.map(c => c.id === id ? { ...c, is_active: active } : c)); } catch (err) { console.error('Failed to toggle channel:', err); } }; const testChannel = async (id: number) => { setTestStatus(prev => ({ ...prev, [id]: 'testing' })); try { const res = await fetch(`/api/notification-channels/${id}/test`, { method: 'POST' }); const data = await res.json(); setTestStatus(prev => ({ ...prev, [id]: data.success ? 'success' : 'error' })); setTimeout(() => setTestStatus(prev => ({ ...prev, [id]: 'idle' })), 3000); } catch { setTestStatus(prev => ({ ...prev, [id]: 'error' })); setTimeout(() => setTestStatus(prev => ({ ...prev, [id]: 'idle' })), 3000); } }; const editChannel = (channel: Channel) => { setEditingId(channel.id); setFormType(channel.channel_type); setFormName(channel.name); setFormConfig(channel.config); setShowCreate(true); }; const resetForm = () => { setShowCreate(false); setEditingId(null); setFormType('teams'); setFormName(''); setFormConfig({}); }; const typeDef = CHANNEL_TYPES.find(t => t.value === formType); return (

Notification Channels

Configure Teams, Telegram, ntfy, and webhook destinations

{showCreate && ( {editingId ? 'Edit Channel' : 'Create Channel'}
setFormName(e.target.value)} />
{typeDef && (
{typeDef.fields.map(field => (
{field.type === 'select' ? ( ) : ( setFormConfig(prev => ({ ...prev, [field.key]: e.target.value }))} /> )}
))}
)}
)} {isLoading ? (
Loading channels...
) : channels.length === 0 && !showCreate ? (

No notification channels configured yet.

) : (
{channels.map(channel => { const cType = CHANNEL_TYPES.find(t => t.value === channel.channel_type); const Icon = cType?.icon || Globe; const status = testStatus[channel.id] || 'idle'; return (
toggleChannel(channel.id, checked)} />
{channel.name} {cType?.label || channel.channel_type}

{channel.channel_type === 'teams' && channel.config.webhook_url && `URL: ${channel.config.webhook_url.substring(0, 50)}...`} {channel.channel_type === 'telegram' && `Chat: ${channel.config.chat_id || 'not set'}`} {channel.channel_type === 'ntfy' && `Topic: ${channel.config.topic || 'not set'} @ ${channel.config.server_url || 'ntfy.sh'}`} {channel.channel_type === 'webhook' && `${channel.config.method || 'POST'} ${channel.config.url || 'not set'}`}

); })}
)}
); }