import { useState, useEffect } from 'react'; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from '@/components/ui/sheet'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Trash2, HelpCircle } from 'lucide-react'; import { toast } from 'sonner'; import { apiFetch } from '@/lib/api'; interface StackAlert { id?: number; stack_name: string; metric: string; operator: string; threshold: number; duration_mins: number; cooldown_mins: number; } interface StackAlertSheetProps { isOpen: boolean; onClose: () => void; stackName: string; } export function StackAlertSheet({ isOpen, onClose, stackName }: StackAlertSheetProps) { const [alerts, setAlerts] = useState([]); const [isLoading, setIsLoading] = useState(false); // New Alert Form State const [metric, setMetric] = useState('cpu_percent'); const [operator, setOperator] = useState('>'); const [threshold, setThreshold] = useState(''); const [duration, setDuration] = useState('5'); const [cooldown, setCooldown] = useState('60'); useEffect(() => { if (isOpen && stackName) { fetchAlerts(); } }, [isOpen, stackName]); const fetchAlerts = async () => { try { const res = await apiFetch(`/alerts?stackName=${stackName}`); if (res.ok) { const data = await res.json(); setAlerts(data); } } catch (e) { console.error('Failed to fetch alerts', e); } }; const addAlert = async () => { if (!threshold) { toast.error('Please enter a threshold.'); return; } setIsLoading(true); const newAlert = { stack_name: stackName, metric, operator, threshold: parseFloat(threshold), duration_mins: parseInt(duration, 10), cooldown_mins: parseInt(cooldown, 10) }; try { const res = await apiFetch('/alerts', { method: 'POST', body: JSON.stringify(newAlert) }); if (res.ok) { toast.success('Alert rule added.'); setThreshold(''); fetchAlerts(); } else { toast.error('Failed to add alert rule.'); } } catch (e) { toast.error('Network error.'); } finally { setIsLoading(false); } }; const deleteAlert = async (id: number) => { setIsLoading(true); try { const res = await apiFetch(`/alerts/${id}`, { method: 'DELETE' }); if (res.ok) { toast.success('Alert rule deleted.'); fetchAlerts(); } else { toast.error('Failed to delete alert rule.'); } } catch (e) { toast.error('Network error.'); } finally { setIsLoading(false); } }; const metricLabels: Record = { cpu_percent: 'CPU Usage (%)', memory_percent: 'Memory Usage (%)', memory_mb: 'Memory Usage (MB)', net_rx: 'Network In (MB)', net_tx: 'Network Out (MB)', restart_count: 'Restart Count' }; return ( !open && onClose()}> Stack Alerts: {stackName} Configure metric thresholds to trigger notifications for this stack.
{/* List Existing Alerts */}

Existing Rules

{alerts.length === 0 ? (
No active alert rules for this stack.
) : ( alerts.map(alert => (
{metricLabels[alert.metric] || alert.metric} {alert.operator} {alert.threshold}
Trigger after {alert.duration_mins}m • Cooldown: {alert.cooldown_mins}m
)) )}

{/* Add New Alert Form */}

Add New Rule

The system resource or metric to monitor. Select from CPU, Memory, Network I/O, or Restarts.

The comparison condition to trigger the alert against the threshold.

The numerical value the metric needs to breach to trigger the conditions.

{ let val = e.target.value; if (val !== '' && Number(val) < 0) val = '0'; setThreshold(val); }} placeholder="e.g. 90" />

How long the metric must stay in breach of the threshold before sending an alert.

{ let val = e.target.value; if (val !== '' && Number(val) < 0) val = '0'; setDuration(val); }} />

How long to wait before sending another alert if the stack continues to breach.

{ let val = e.target.value; if (val !== '' && Number(val) < 0) val = '0'; setCooldown(val); }} />
); }