diff --git a/client/src/pages/Alerts.jsx b/client/src/pages/Alerts.jsx index 197b4ea..24fa682 100644 --- a/client/src/pages/Alerts.jsx +++ b/client/src/pages/Alerts.jsx @@ -49,6 +49,33 @@ const Alerts = () => { } }; + // Group alerts by alert_event_id to show related alerts together + const groupAlertsByEvent = (logs) => { + const grouped = {}; + const ungrouped = []; + + logs.forEach(log => { + if (log.alert_event_id) { + if (!grouped[log.alert_event_id]) { + grouped[log.alert_event_id] = []; + } + grouped[log.alert_event_id].push(log); + } else { + ungrouped.push(log); + } + }); + + // Convert grouped object to array of arrays, sorted by most recent alert in each group + const groupedArrays = Object.values(grouped).map(group => + group.sort((a, b) => new Date(b.sent_at) - new Date(a.sent_at)) + ).sort((a, b) => new Date(b[0].sent_at) - new Date(a[0].sent_at)); + + // Add ungrouped alerts as individual groups + ungrouped.forEach(log => groupedArrays.push([log])); + + return groupedArrays; + }; + const handleDeleteRule = async (ruleId) => { if (window.confirm('Are you sure you want to delete this alert rule?')) { try { @@ -356,72 +383,183 @@ const Alerts = () => { {t('alerts.detection')} {t('alerts.message')} {t('alerts.sentAt')} + Event ID - {(alertLogs || []).map((log) => ( - - -
- {getStatusIcon(log.status)} - - {log.status} - -
- - - - {log.alert_type} - - - -
- {log.recipient} -
- - -
- {log.rule?.name || t('alerts.unknownRule')} -
- - -
- {log.detection?.drone_id ? ( - - {log.detection.drone_id} - - ) : ( - {t('alerts.na')} - )} -
- - - {log.detection_id ? ( - - ) : ( - {t('alerts.na')} - )} - - -
- {log.message} -
- - -
- {log.sent_at - ? format(new Date(log.sent_at), 'MMM dd, HH:mm') - : 'Not sent' - } -
- - - ))} + {groupAlertsByEvent(alertLogs || []).map((alertGroup, groupIndex) => { + // Display the primary alert (first in group) + const primaryAlert = alertGroup[0]; + const relatedAlerts = alertGroup.slice(1); + + return ( + + + +
+ {getStatusIcon(primaryAlert.status)} + + {primaryAlert.status} + + {relatedAlerts.length > 0 && ( + + +{relatedAlerts.length} more + + )} +
+ + +
+ + {primaryAlert.alert_type} + + {relatedAlerts.map((alert, idx) => ( + + {alert.alert_type} + + ))} +
+ + +
+ {primaryAlert.recipient} + {relatedAlerts.length > 0 && relatedAlerts.some(a => a.recipient !== primaryAlert.recipient) && ( +
+ +{relatedAlerts.filter(a => a.recipient !== primaryAlert.recipient).length} others +
+ )} +
+ + +
+ {primaryAlert.rule?.name || t('alerts.unknownRule')} +
+ + +
+ {primaryAlert.detection?.drone_id ? ( + + {primaryAlert.detection.drone_id} + + ) : ( + {t('alerts.na')} + )} +
+ + + {primaryAlert.detection_id ? ( + + ) : ( + {t('alerts.na')} + )} + + +
+
+ {primaryAlert.message} +
+
+ + +
+ {format(new Date(primaryAlert.sent_at), 'MMM dd, HH:mm')} +
+ + +
+ {primaryAlert.alert_event_id ? ( + + {primaryAlert.alert_event_id.substring(0, 8)}... + + ) : ( + - + )} +
+ + + + {/* Show related alerts as sub-rows if any */} + {relatedAlerts.map((alert, alertIndex) => ( + + +
+ {getStatusIcon(alert.status)} + + {alert.status} + +
+ + + + {alert.alert_type} + + + +
+ {alert.recipient} +
+ + +
+ {alert.rule?.name || t('alerts.unknownRule')} +
+ + +
+ {alert.detection?.drone_id ? ( + + {alert.detection.drone_id} + + ) : ( + {t('alerts.na')} + )} +
+ + + {alert.detection_id ? ( + + ) : ( + {t('alerts.na')} + )} + + +
+
+ {alert.message} +
+
+ + +
+ {format(new Date(alert.sent_at), 'MMM dd, HH:mm')} +
+ + +
+ {alert.alert_event_id ? ( + + {alert.alert_event_id.substring(0, 8)}... + + ) : ( + - + )} +
+ + + ))} +
+ ); + })}