diff --git a/client/src/pages/Alerts.jsx b/client/src/pages/Alerts.jsx index 61f7d01..4f4a8c3 100644 --- a/client/src/pages/Alerts.jsx +++ b/client/src/pages/Alerts.jsx @@ -9,7 +9,8 @@ import { XCircleIcon, ExclamationTriangleIcon, ChevronDownIcon, - ChevronRightIcon + ChevronRightIcon, + XMarkIcon } from '@heroicons/react/24/outline'; import { EditAlertModal, DetectionDetailsModal } from '../components/AlertModals'; import { useDroneTypes } from '../hooks/useDroneTypes'; @@ -28,6 +29,8 @@ const Alerts = () => { const [editingRule, setEditingRule] = useState(null); const [showDetectionModal, setShowDetectionModal] = useState(false); const [selectedDetection, setSelectedDetection] = useState(null); + const [showAlertDetailsModal, setShowAlertDetailsModal] = useState(false); + const [selectedAlertForDetails, setSelectedAlertForDetails] = useState(null); const [expandedGroups, setExpandedGroups] = useState(new Set()); useEffect(() => { @@ -132,6 +135,71 @@ const Alerts = () => { } }; + // Parse error message JSON to extract meaningful information + const parseErrorMessage = (errorMessage) => { + if (!errorMessage) return null; + + try { + // Check if it's a success message + if (errorMessage.startsWith('Success:')) { + const jsonPart = errorMessage.substring(8).trim(); + const successData = JSON.parse(jsonPart); + return { + type: 'success', + data: successData + }; + } + + // Check if it's a webhook network error + if (errorMessage.startsWith('Webhook network error:')) { + const jsonPart = errorMessage.substring(22).trim(); + const errorData = JSON.parse(jsonPart); + return { + type: 'webhook_error', + data: errorData + }; + } + + // Check if it's an SMS error + if (errorMessage.startsWith('SMS error:')) { + const jsonPart = errorMessage.substring(10).trim(); + const errorData = JSON.parse(jsonPart); + return { + type: 'sms_error', + data: errorData + }; + } + + // Check if it's an email error + if (errorMessage.startsWith('Email error:')) { + const jsonPart = errorMessage.substring(12).trim(); + const errorData = JSON.parse(jsonPart); + return { + type: 'email_error', + data: errorData + }; + } + + // Try to parse as direct JSON + const parsed = JSON.parse(errorMessage); + return { + type: 'parsed_json', + data: parsed + }; + } catch (error) { + // If parsing fails, return the raw message + return { + type: 'raw', + data: { message: errorMessage } + }; + } + }; + + const handleViewAlertDetails = (alert) => { + setSelectedAlertForDetails(alert); + setShowAlertDetailsModal(true); + }; + const getStatusIcon = (status) => { switch (status) { case 'sent': @@ -417,6 +485,7 @@ const Alerts = () => { Drone Type {t('alerts.droneId')} {t('alerts.detection')} + Alert Details {t('alerts.message')} {t('alerts.sentAt')} Event ID @@ -525,6 +594,14 @@ const Alerts = () => { {t('alerts.na')} )} + + +
@@ -614,6 +691,14 @@ const Alerts = () => { {t('alerts.na')} )} + + +
@@ -681,6 +766,17 @@ const Alerts = () => { }} /> )} + + {showAlertDetailsModal && selectedAlertForDetails && ( + { + setShowAlertDetailsModal(false); + setSelectedAlertForDetails(null); + }} + /> + )}
); }; @@ -1041,4 +1137,285 @@ const CreateAlertRuleModal = ({ onClose, onSave }) => { ); }; +// Alert Details Modal Component +const AlertDetailsModal = ({ alert, parseErrorMessage, onClose }) => { + if (!alert) return null; + + const errorInfo = parseErrorMessage(alert.error_message); + + const getStatusBadge = (status) => { + const statusClasses = { + sent: 'bg-green-100 text-green-800', + failed: 'bg-red-100 text-red-800', + pending: 'bg-yellow-100 text-yellow-800' + }; + + return ( + + {status.charAt(0).toUpperCase() + status.slice(1)} + + ); + }; + + const renderSuccessDetails = (data) => ( +
+

✅ Success Details

+
+ {data.recipient && ( +
+ Recipient: +
{data.recipient}
+
+ )} + {data.messageLength && ( +
+ Message Length: +
{data.messageLength} characters
+
+ )} + {data.timestamp && ( +
+ Processed At: +
{format(new Date(data.timestamp), 'MMM dd, yyyy HH:mm:ss')}
+
+ )} + {data.simulatedDelivery !== undefined && ( +
+ Delivery Mode: +
{data.simulatedDelivery ? 'Simulated' : 'Real'}
+
+ )} + {data.ruleId && ( +
+ Rule ID: +
{data.ruleId}
+
+ )} + {data.detectionId && ( +
+ Detection ID: +
{data.detectionId}
+
+ )} +
+
+ ); + + const renderWebhookError = (data) => { + let webhookDetails = {}; + + // Parse the nested webhook error details + if (data.errorMessage && data.errorMessage.includes('Webhook HTTP')) { + try { + const match = data.errorMessage.match(/Webhook HTTP \d+: (.+)/); + if (match) { + webhookDetails = JSON.parse(match[1]); + } + } catch (e) { + console.error('Error parsing webhook details:', e); + } + } + + return ( +
+

❌ Webhook Error Details

+
+ {webhookDetails.httpStatus && ( +
+
+ HTTP Status: +
{webhookDetails.httpStatus} {webhookDetails.httpStatusText}
+
+
+ Response Time: +
{webhookDetails.responseTime}
+
+
+ URL: +
{webhookDetails.url}
+
+
+ Payload Size: +
{webhookDetails.requestPayload}
+
+ {webhookDetails.timestamp && ( +
+ Failed At: +
{format(new Date(webhookDetails.timestamp), 'MMM dd, yyyy HH:mm:ss')}
+
+ )} +
+ )} + + {webhookDetails.responseBody && ( +
+ Response Body: +
+ {webhookDetails.responseBody} +
+
+ )} + + {webhookDetails.responseHeaders && ( +
+ Response Headers: +
+ {JSON.stringify(webhookDetails.responseHeaders, null, 2)} +
+
+ )} +
+
+ ); + }; + + const renderErrorDetails = (data, type) => { + if (type === 'webhook_error') { + return renderWebhookError(data); + } + + // Generic error display for SMS and email errors + const errorTypeNames = { + sms_error: '📱 SMS Error Details', + email_error: '📧 Email Error Details' + }; + + return ( +
+

+ {errorTypeNames[type] || '❌ Error Details'} +

+
+
+            {JSON.stringify(data, null, 2)}
+          
+
+
+ ); + }; + + return ( +
+
+
+

Alert Details

+ +
+ +
+ {/* Basic Alert Information */} +
+

📋 Basic Information

+
+
+ Alert ID: +
{alert.id}
+
+
+ Status: +
{getStatusBadge(alert.status)}
+
+
+ Type: +
{alert.alert_type}
+
+
+ Priority: +
{alert.priority}
+
+
+ Recipient: +
{alert.recipient}
+
+
+ Retry Count: +
{alert.retry_count}
+
+
+ Created At: +
{format(new Date(alert.created_at), 'MMM dd, yyyy HH:mm:ss')}
+
+ {alert.sent_at && ( +
+ Sent At: +
{format(new Date(alert.sent_at), 'MMM dd, yyyy HH:mm:ss')}
+
+ )} + {alert.alert_event_id && ( +
+ Event ID: +
{alert.alert_event_id}
+
+ )} +
+
+ + {/* Alert Message */} +
+

💬 Alert Message

+
+ {alert.message} +
+
+ + {/* Error/Success Details */} + {errorInfo && ( + <> + {errorInfo.type === 'success' && renderSuccessDetails(errorInfo.data)} + {(errorInfo.type === 'webhook_error' || errorInfo.type === 'sms_error' || errorInfo.type === 'email_error') && + renderErrorDetails(errorInfo.data, errorInfo.type)} + {errorInfo.type === 'raw' && ( +
+

📝 Raw Error Message

+
+ {errorInfo.data.message} +
+
+ )} + + )} + + {/* Rule and Detection Info */} + {(alert.rule || alert.detection) && ( +
+

🔗 Related Information

+
+ {alert.rule && ( +
+ Alert Rule: +
{alert.rule.name}
+
ID: {alert.rule.id}
+
+ )} + {alert.detection && ( +
+ Detection: +
Drone ID: {alert.detection.drone_id}
+
Type: {alert.detection.drone_type}
+
RSSI: {alert.detection.rssi} dBm
+
+ )} +
+
+ )} +
+ +
+ +
+
+
+ ); +}; + export default Alerts;