diff --git a/frontend-modern/scripts/canonical-platform-audit.mjs b/frontend-modern/scripts/canonical-platform-audit.mjs
index 120933ab4..b0b9c43e7 100644
--- a/frontend-modern/scripts/canonical-platform-audit.mjs
+++ b/frontend-modern/scripts/canonical-platform-audit.mjs
@@ -31,6 +31,7 @@ const ALLOWLIST = new Set([
'src/components/Storage/storageSourceOptions.ts',
'src/components/Storage/useStorageExpansionState.ts',
'src/components/Storage/useStorageResourceHighlight.ts',
+ 'src/features/alerts/useAlertGroupExpansion.ts',
'src/components/Infrastructure/resourceBadges.ts',
'src/components/Settings/reportingResourceTypes.ts',
'src/utils/canonicalResourceTypes.ts',
diff --git a/frontend-modern/src/features/alerts/AlertOverviewActiveAlertsSection.tsx b/frontend-modern/src/features/alerts/AlertOverviewActiveAlertsSection.tsx
index 460698490..2233b6298 100644
--- a/frontend-modern/src/features/alerts/AlertOverviewActiveAlertsSection.tsx
+++ b/frontend-modern/src/features/alerts/AlertOverviewActiveAlertsSection.tsx
@@ -12,6 +12,7 @@ import {
} from '@/utils/alertOverviewPresentation';
import { AlertOverviewAlertCard } from './AlertOverviewAlertCard';
+import { useAlertGroupExpansion } from './useAlertGroupExpansion';
import type { AlertIncidentTimelineState } from './useAlertIncidentTimelineState';
import type { AlertOverviewState } from './useAlertOverviewState';
@@ -25,6 +26,7 @@ interface AlertOverviewActiveAlertsSectionProps {
}
export function AlertOverviewActiveAlertsSection(props: AlertOverviewActiveAlertsSectionProps) {
+ const { isGroupExpanded, toggleGroup } = useAlertGroupExpansion();
return (
@@ -116,13 +118,41 @@ export function AlertOverviewActiveAlertsSection(props: AlertOverviewActiveAlert
{getAlertListEmptyState(props.showAcknowledged)}
-
- {(alert) => (
-
+
+ {(group) => (
+
+
+
0}>
+
+
+
+
+
+ {(alert) => (
+
+ )}
+
+
+
+
+
+
)}
diff --git a/frontend-modern/src/features/alerts/useAlertGroupExpansion.ts b/frontend-modern/src/features/alerts/useAlertGroupExpansion.ts
new file mode 100644
index 000000000..bb12909dd
--- /dev/null
+++ b/frontend-modern/src/features/alerts/useAlertGroupExpansion.ts
@@ -0,0 +1,18 @@
+import { createSignal } from 'solid-js';
+
+export function useAlertGroupExpansion() {
+ const [expandedGroups, setExpandedGroups] = createSignal>(new Set());
+
+ const toggleGroup = (key: string) => {
+ setExpandedGroups((prev) => {
+ const next = new Set(prev);
+ if (next.has(key)) next.delete(key);
+ else next.add(key);
+ return next;
+ });
+ };
+
+ const isGroupExpanded = (key: string) => expandedGroups().has(key);
+
+ return { isGroupExpanded, toggleGroup };
+}
diff --git a/frontend-modern/src/features/alerts/useAlertOverviewState.ts b/frontend-modern/src/features/alerts/useAlertOverviewState.ts
index d2852f85b..6a46eba96 100644
--- a/frontend-modern/src/features/alerts/useAlertOverviewState.ts
+++ b/frontend-modern/src/features/alerts/useAlertOverviewState.ts
@@ -5,6 +5,21 @@ import type { Alert } from '@/types/api';
import type { Override } from './types';
import { useAlertAcknowledgementState } from './useAlertAcknowledgementState';
+export interface AlertGroup {
+ key: string;
+ primary: Alert;
+ related: Alert[];
+}
+
+export function computeAlertGroupKey(alert: Alert): string {
+ const rid = alert.resourceId ?? '';
+ const segments = rid.split('/');
+ if (segments.length > 2) {
+ return segments.slice(0, -1).join('/');
+ }
+ return rid;
+}
+
export interface UseAlertOverviewStateProps {
activeAlerts: Accessor>;
overrides: Accessor;
@@ -66,9 +81,29 @@ export function useAlertOverviewState(props: UseAlertOverviewStateProps) {
}),
);
+ const groupedAlerts = createMemo(() => {
+ const sorted = filteredAlerts();
+ const groups = new Map();
+ for (const alert of sorted) {
+ const key = computeAlertGroupKey(alert);
+ const existing = groups.get(key);
+ if (existing) {
+ existing.push(alert);
+ } else {
+ groups.set(key, [alert]);
+ }
+ }
+ const result: AlertGroup[] = [];
+ for (const [key, alerts] of groups) {
+ result.push({ key, primary: alerts[0], related: alerts.slice(1) });
+ }
+ return result;
+ });
+
return {
alertStats,
filteredAlerts,
+ groupedAlerts,
unacknowledgedAlerts,
processingAlerts,
bulkAckProcessing,