diff --git a/frontend-modern/src/pages/Alerts.tsx b/frontend-modern/src/pages/Alerts.tsx index fbf6a69..a4383a3 100644 --- a/frontend-modern/src/pages/Alerts.tsx +++ b/frontend-modern/src/pages/Alerts.tsx @@ -28,7 +28,7 @@ import History from 'lucide-solid/icons/history'; import Gauge from 'lucide-solid/icons/gauge'; import Send from 'lucide-solid/icons/send'; import Calendar from 'lucide-solid/icons/calendar'; -import { getPatrolStatus, getFindings, getFindingsHistory, getPatrolRunHistory, forcePatrol, subscribeToPatrolStream, dismissFinding, suppressFinding, type Finding, type PatrolStatus, type PatrolRunRecord, severityColors, formatTimestamp } from '@/api/patrol'; +import { getPatrolStatus, getFindings, getFindingsHistory, getPatrolRunHistory, forcePatrol, subscribeToPatrolStream, dismissFinding, suppressFinding, getSuppressionRules, addSuppressionRule, deleteSuppressionRule, type Finding, type PatrolStatus, type PatrolRunRecord, type SuppressionRule, severityColors, formatTimestamp, categoryLabels } from '@/api/patrol'; import { aiChatStore } from '@/stores/aiChat'; type AlertTab = 'overview' | 'thresholds' | 'destinations' | 'schedule' | 'history'; @@ -2050,6 +2050,13 @@ function OverviewTab(props: { const [forcePatrolLoading, setForcePatrolLoading] = createSignal(false); const [expandedRunId, setExpandedRunId] = createSignal(null); const [historyTimeFilter, setHistoryTimeFilter] = createSignal<'24h' | '7d' | 'all'>('all'); + // Suppression rules management + const [suppressionRules, setSuppressionRules] = createSignal([]); + const [showSuppressionRules, setShowSuppressionRules] = createSignal(false); + const [showAddRuleForm, setShowAddRuleForm] = createSignal(false); + const [newRuleResource, setNewRuleResource] = createSignal(''); + const [newRuleCategory, setNewRuleCategory] = createSignal(''); + const [newRuleDescription, setNewRuleDescription] = createSignal(''); // Live streaming state for running patrol const [expandedLiveStream, setExpandedLiveStream] = createSignal(false); // Track streaming blocks for sequential display (like AI chat) @@ -2146,10 +2153,11 @@ function OverviewTab(props: { // Fetch AI data - extracted for reuse const fetchAiData = async () => { try { - const [status, findings, runHistory] = await Promise.all([ + const [status, findings, runHistory, rules] = await Promise.all([ getPatrolStatus(), getFindings(), - getPatrolRunHistory(50) // Fetch more for filtering + getPatrolRunHistory(50), // Fetch more for filtering + getSuppressionRules().catch(() => []) // May not be available ]); // Check if a new patrol has completed - if so, clear pending fix findings @@ -2165,6 +2173,7 @@ function OverviewTab(props: { setPatrolStatus(status); setAiFindings(findings); setPatrolRunHistory(runHistory); + setSuppressionRules(rules); // Auto-expand history if most recent run found issues if (runHistory.length > 0 && runHistory[0].status !== 'healthy') { @@ -2743,6 +2752,157 @@ function OverviewTab(props: { + {/* Suppression Rules - What's being ignored */} +
+
+ + +
+ + {/* Add rule form */} + +
+

+ Add a suppression rule to prevent alerts +

+
+ setNewRuleResource(e.currentTarget.value)} + /> + + setNewRuleDescription(e.currentTarget.value)} + /> +
+
+ + +
+
+
+ + {/* Rules list */} + +
+ +

+ No suppression rules. Dismiss findings or add rules to prevent unwanted alerts. +

+
+ + {(rule) => ( +
+
+
+ + {rule.resource_name || rule.resource_id || 'Any resource'} + + + + {categoryLabels[rule.category!] || rule.category} + + + + + Any category + + + + {rule.created_from === 'finding' ? 'From Finding' : 'Manual'} + +
+

+ {rule.description || 'No description'} +

+
+ +
+ )} +
+
+
+
+ {/* Patrol Check History */}