'use client' import { useState, useEffect, useCallback } from 'react' import { useTranslations } from 'next-intl' import { Button } from '@/components/ui/button' interface AlertRule { id: number name: string description: string | null enabled: number entity_type: string condition_field: string condition_operator: string condition_value: string action_type: string action_config: string cooldown_minutes: number last_triggered_at: number | null trigger_count: number created_by: string created_at: number updated_at: number } interface EvalResult { rule_id: number rule_name: string triggered: boolean reason?: string } const ENTITY_FIELDS: Record = { agent: ['status', 'role', 'name', 'last_seen', 'last_activity'], task: ['status', 'priority', 'assigned_to', 'title'], session: ['status'], activity: ['type', 'actor', 'entity_type'], } const OPERATORS = [ { value: 'equals', label: '=' }, { value: 'not_equals', label: '!=' }, { value: 'greater_than', label: '>' }, { value: 'less_than', label: '<' }, { value: 'contains', label: 'contains' }, { value: 'count_above', label: 'count >' }, { value: 'count_below', label: 'count <' }, { value: 'age_minutes_above', label: 'age (min) >' }, ] const ENTITY_COLORS: Record = { agent: 'bg-purple-500/20 text-purple-400 border-purple-500/30', task: 'bg-blue-500/20 text-blue-400 border-blue-500/30', session: 'bg-green-500/20 text-green-400 border-green-500/30', activity: 'bg-amber-500/20 text-amber-400 border-amber-500/30', } export function AlertRulesPanel() { const t = useTranslations('alertRules') const [rules, setRules] = useState([]) const [loading, setLoading] = useState(true) const [showCreate, setShowCreate] = useState(false) const [evalResults, setEvalResults] = useState(null) const [evaluating, setEvaluating] = useState(false) const fetchRules = useCallback(async () => { try { const res = await fetch('/api/alerts') const data = await res.json() setRules(data.rules || []) } catch { /* ignore */ } setLoading(false) }, []) useEffect(() => { fetchRules() }, [fetchRules]) const toggleRule = async (rule: AlertRule) => { await fetch('/api/alerts', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: rule.id, enabled: rule.enabled ? 0 : 1 }), }) fetchRules() } const deleteRule = async (id: number) => { await fetch('/api/alerts', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id }), }) fetchRules() } const evaluateAll = async () => { setEvaluating(true) try { const res = await fetch('/api/alerts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'evaluate' }), }) const data = await res.json() setEvalResults(data.results || []) } catch { /* ignore */ } setEvaluating(false) fetchRules() // refresh trigger counts } const enabledCount = rules.filter(r => r.enabled).length const totalTriggers = rules.reduce((sum, r) => sum + r.trigger_count, 0) return (
{/* Header */}

{t('title')}

{t('description')}

{/* Stats */}
{t('statTotalRules')}
{rules.length}
{t('statActive')}
{enabledCount}
{t('statTotalTriggers')}
{totalTriggers}
{/* Eval Results */} {evalResults && (

{t('evalResultsTitle')}

{evalResults.map(r => (
{r.rule_name} {r.triggered ? t('triggered') : r.reason}
))} {evalResults.length === 0 && (
{t('noRulesToEvaluate')}
)}
)} {/* Create Form */} {showCreate && ( { fetchRules(); setShowCreate(false) }} onCancel={() => setShowCreate(false)} /> )} {/* Rules List */} {loading ? (
{t('loadingRules')}
) : rules.length === 0 ? (

{t('noRulesConfigured')}

{t('createRuleHint')}

) : (
{rules.map(rule => ( toggleRule(rule)} onDelete={() => deleteRule(rule.id)} /> ))}
)}
) } function RuleCard({ rule, onToggle, onDelete }: { rule: AlertRule; onToggle: () => void; onDelete: () => void }) { const t = useTranslations('alertRules') const operator = OPERATORS.find(o => o.value === rule.condition_operator) const lastTriggered = rule.last_triggered_at ? new Date(rule.last_triggered_at * 1000).toLocaleString() : t('never') return (
{rule.entity_type}

{rule.name}

{rule.description && (

{rule.description}

)}
{rule.condition_field} {operator?.label || rule.condition_operator} {rule.condition_value} {t('cooldown', { minutes: rule.cooldown_minutes })} {t('triggerCount', { count: rule.trigger_count })} {t('lastTriggered', { time: lastTriggered })}
) } function CreateRuleForm({ onCreated, onCancel }: { onCreated: () => void; onCancel: () => void }) { const t = useTranslations('alertRules') const [form, setForm] = useState({ name: '', description: '', entity_type: 'agent', condition_field: 'status', condition_operator: 'equals', condition_value: '', cooldown_minutes: 60, recipient: 'system', }) const [saving, setSaving] = useState(false) const [error, setError] = useState('') const fields = ENTITY_FIELDS[form.entity_type] || [] const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setError('') setSaving(true) try { const res = await fetch('/api/alerts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: form.name, description: form.description || null, entity_type: form.entity_type, condition_field: form.condition_field, condition_operator: form.condition_operator, condition_value: form.condition_value, cooldown_minutes: form.cooldown_minutes, action_type: 'notification', action_config: { recipient: form.recipient }, }), }) const data = await res.json() if (!res.ok) { setError(data.error || t('failedToCreate')) return } onCreated() } catch { setError(t('networkError')) } finally { setSaving(false) } } return (

{t('newRuleTitle')}

setForm({ ...form, name: e.target.value })} placeholder={t('ruleNamePlaceholder')} className="w-full h-8 px-2.5 rounded-md bg-secondary border border-border text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-primary" required />
setForm({ ...form, description: e.target.value })} placeholder={t('optionalDescription')} className="w-full h-8 px-2.5 rounded-md bg-secondary border border-border text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-primary" />
setForm({ ...form, condition_value: e.target.value })} placeholder={t('valuePlaceholder')} className="w-full h-8 px-2.5 rounded-md bg-secondary border border-border text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-primary" required />
setForm({ ...form, cooldown_minutes: parseInt(e.target.value) || 60 })} min={1} className="w-full h-8 px-2.5 rounded-md bg-secondary border border-border text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-primary" />
setForm({ ...form, recipient: e.target.value })} placeholder="system" className="w-full h-8 px-2.5 rounded-md bg-secondary border border-border text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-primary" />
{error &&

{error}

}
) } function PlayIcon() { return ( ) }