import React, { useState, useEffect } from 'react'; import api from '../services/api'; import { format } from 'date-fns'; import { PlusIcon, BellIcon, CheckCircleIcon, XCircleIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline'; import { EditAlertModal, DetectionDetailsModal } from '../components/AlertModals'; const Alerts = () => { const [alertRules, setAlertRules] = useState([]); const [alertLogs, setAlertLogs] = useState([]); const [alertStats, setAlertStats] = useState(null); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState('rules'); const [showCreateModal, setShowCreateModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [editingRule, setEditingRule] = useState(null); const [showDetectionModal, setShowDetectionModal] = useState(false); const [selectedDetection, setSelectedDetection] = useState(null); useEffect(() => { fetchAlertData(); }, []); const fetchAlertData = async () => { try { const [rulesRes, logsRes, statsRes] = await Promise.all([ api.get('/alerts/rules'), api.get('/alerts/logs?limit=50'), api.get('/alerts/stats?hours=24') ]); setAlertRules(rulesRes.data?.data || []); setAlertLogs(logsRes.data?.data || []); setAlertStats(statsRes.data?.data || null); } catch (error) { console.error('Error fetching alert data:', error); // Set default values on error setAlertRules([]); setAlertLogs([]); setAlertStats(null); } finally { setLoading(false); } }; const handleDeleteRule = async (ruleId) => { if (window.confirm('Are you sure you want to delete this alert rule?')) { try { await api.delete(`/alerts/rules/${ruleId}`); fetchAlertData(); } catch (error) { console.error('Error deleting alert rule:', error); } } }; const handleEditRule = (rule) => { setEditingRule(rule); setShowEditModal(true); }; const handleViewDetection = async (detectionId) => { try { const response = await api.get(`/detections/${detectionId}`); setSelectedDetection(response.data.data); setShowDetectionModal(true); } catch (error) { console.error('Error fetching detection details:', error); } }; const getStatusIcon = (status) => { switch (status) { case 'sent': return ; case 'failed': return ; case 'pending': return ; default: return ; } }; const getPriorityColor = (priority) => { switch (priority) { case 'critical': return 'bg-red-100 text-red-800'; case 'high': return 'bg-orange-100 text-orange-800'; case 'medium': return 'bg-yellow-100 text-yellow-800'; case 'low': return 'bg-green-100 text-green-800'; default: return 'bg-gray-100 text-gray-800'; } }; if (loading) { return (
); } return (

Alert Management

Configure and monitor alert rules for drone detections

{/* Alert Stats */} {alertStats && (
{alertStats.total_alerts}
Total Alerts (24h)
{alertStats.sent_alerts}
Sent Successfully
{alertStats.failed_alerts}
Failed
{alertStats.pending_alerts}
Pending
)} {/* Tabs */}
{/* Alert Rules Tab */} {activeTab === 'rules' && (
{(alertRules?.length || 0) === 0 ? (

No alert rules

Get started by creating your first alert rule.

) : (
{(alertRules || []).map((rule) => ( ))}
Name Priority Channels Conditions Status Created Actions
{rule.name}
{rule.description && (
{rule.description}
)}
{rule.priority}
{(rule.alert_channels || []).map((channel, index) => ( {channel} ))}
{rule.min_detections > 1 && (
Min detections: {rule.min_detections}
)} {rule.time_window && (
Time window: {rule.time_window}s
)} {rule.cooldown_period && (
Cooldown: {rule.cooldown_period}s
)} {rule.min_threat_level && (
Min threat: {rule.min_threat_level}
)} {rule.drone_types && rule.drone_types.length > 0 && (
Drone types:
{rule.drone_types.map((typeId, index) => { const droneTypes = { 0: 'Consumer', 1: 'Orlan', 2: 'Professional', 3: 'Racing', 4: 'Unknown' }; return ( {droneTypes[typeId] || 'Unknown'} {typeId === 1 && '⚠️'} ); })}
)}
{rule.is_active ? 'Active' : 'Inactive'}
{format(new Date(rule.created_at), 'MMM dd, yyyy')}
)}
)} {/* Alert Logs Tab */} {activeTab === 'logs' && (
{(alertLogs?.length || 0) === 0 ? (

No alert logs

Alert logs will appear here when alerts are triggered.

) : (
{(alertLogs || []).map((log) => ( ))}
Status Type Recipient Rule Detection Message Sent At
{getStatusIcon(log.status)} {log.status}
{log.alert_type}
{log.recipient}
{log.rule?.name || 'Unknown Rule'}
{log.detection_id ? ( ) : ( N/A )}
{log.message}
{log.sent_at ? format(new Date(log.sent_at), 'MMM dd, HH:mm') : 'Not sent' }
)}
)} {/* Create Alert Rule Modal */} {showCreateModal && ( setShowCreateModal(false)} onSave={() => { setShowCreateModal(false); fetchAlertData(); }} /> )} {showEditModal && editingRule && ( { setShowEditModal(false); setEditingRule(null); }} onSuccess={fetchAlertData} /> )} {showDetectionModal && selectedDetection && ( { setShowDetectionModal(false); setSelectedDetection(null); }} /> )}
); }; const CreateAlertRuleModal = ({ onClose, onSave }) => { const [formData, setFormData] = useState({ name: '', description: '', priority: 'medium', alert_channels: ['sms'], min_detections: 1, time_window: 300, cooldown_period: 600, device_ids: [], drone_types: [], min_rssi: '', max_rssi: '', sms_phone_number: '', webhook_url: '' }); const [saving, setSaving] = useState(false); const [devices, setDevices] = useState([]); const [droneTypes, setDroneTypes] = useState([]); const [loadingData, setLoadingData] = useState(true); useEffect(() => { fetchDevicesAndDroneTypes(); }, []); const fetchDevicesAndDroneTypes = async () => { try { const [devicesResponse, droneTypesResponse] = await Promise.all([ api.get('/devices'), api.get('/drone-types') ]); setDevices(devicesResponse.data.data || []); setDroneTypes(droneTypesResponse.data.data || []); } catch (error) { console.error('Error fetching devices and drone types:', error); } finally { setLoadingData(false); } }; const handleSubmit = async (e) => { e.preventDefault(); setSaving(true); try { const payload = { ...formData }; // Clean up empty values if (!payload.description || payload.description.trim() === '') delete payload.description; if (!payload.min_rssi) delete payload.min_rssi; if (!payload.max_rssi) delete payload.max_rssi; if (!payload.device_ids || payload.device_ids.length === 0) delete payload.device_ids; if (!payload.drone_types || payload.drone_types.length === 0) delete payload.drone_types; // Only include webhook_url if webhook channel is selected if (!payload.alert_channels || !payload.alert_channels.includes('webhook')) { delete payload.webhook_url; } await api.post('/alerts/rules', payload); onSave(); } catch (error) { console.error('Error creating alert rule:', error); } finally { setSaving(false); } }; const handleChange = (e) => { const { name, value, type } = e.target; setFormData(prev => ({ ...prev, [name]: type === 'number' ? parseInt(value) || 0 : value })); }; const handleChannelChange = (channel, checked) => { setFormData(prev => ({ ...prev, alert_channels: checked ? [...prev.alert_channels, channel] : prev.alert_channels.filter(c => c !== channel) })); }; const handleDroneTypeChange = (droneType, checked) => { setFormData(prev => ({ ...prev, drone_types: checked ? [...prev.drone_types, droneType] : prev.drone_types.filter(type => type !== droneType) })); }; const handleDeviceChange = (deviceId, checked) => { setFormData(prev => ({ ...prev, device_ids: checked ? [...prev.device_ids, deviceId] : prev.device_ids.filter(id => id !== deviceId) })); }; return (

Create Alert Rule