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;
|