diff --git a/frontend/src/app/routes/index.tsx b/frontend/src/app/routes/index.tsx index 6412f0e88..d7621d67d 100644 --- a/frontend/src/app/routes/index.tsx +++ b/frontend/src/app/routes/index.tsx @@ -105,6 +105,7 @@ export function AppRoutes() { } /> } /> } /> + } /> } /> } /> diff --git a/frontend/src/features/adversaries/pages/AdversariesPage.tsx b/frontend/src/features/adversaries/pages/AdversariesPage.tsx index baed2637f..c5e087ec9 100644 --- a/frontend/src/features/adversaries/pages/AdversariesPage.tsx +++ b/frontend/src/features/adversaries/pages/AdversariesPage.tsx @@ -45,7 +45,7 @@ export function AdversariesPage() { const visibleItems = filtered.slice(0, visible) return ( -
+
diff --git a/frontend/src/features/alerting-rules/pages/AlertingRulesPage.tsx b/frontend/src/features/alerting-rules/pages/AlertingRulesPage.tsx index ffa3ca4ae..ff88673bc 100644 --- a/frontend/src/features/alerting-rules/pages/AlertingRulesPage.tsx +++ b/frontend/src/features/alerting-rules/pages/AlertingRulesPage.tsx @@ -175,7 +175,7 @@ export function AlertingRulesPage() { } return ( -
+
diff --git a/frontend/src/features/alerts/components/alert-row.tsx b/frontend/src/features/alerts/components/alert-row.tsx index 4bbbbda68..1df71b9b2 100644 --- a/frontend/src/features/alerts/components/alert-row.tsx +++ b/frontend/src/features/alerts/components/alert-row.tsx @@ -111,7 +111,12 @@ export function AlertRow({
e.stopPropagation()}> - + onCreateRule(a)} + /> {a.technique || '—'} diff --git a/frontend/src/features/alerts/components/status-change-menu.tsx b/frontend/src/features/alerts/components/status-change-menu.tsx index 878c65ff8..404d552c7 100644 --- a/frontend/src/features/alerts/components/status-change-menu.tsx +++ b/frontend/src/features/alerts/components/status-change-menu.tsx @@ -14,10 +14,12 @@ export function StatusChangeMenu({ status, onStatus, variant, + onCreateRule, }: { status: StatusKey onStatus: (status: number, observation: string, fp: boolean) => void variant: Variant + onCreateRule?: () => void }) { const { t } = useTranslation() const [open, setOpen] = useState(false) @@ -77,7 +79,7 @@ export function StatusChangeMenu({ {open && (
e.stopPropagation()} - className="absolute left-0 top-full z-30 mt-1 w-48 rounded-md border border-border bg-popover py-1 shadow-lg" + className="absolute left-0 top-full z-30 mt-1 w-max min-w-[12rem] rounded-md border border-border bg-popover py-1 shadow-lg" > {(['open', 'in_review', 'completed'] as StatusKey[]).map((k) => ( + {onCreateRule && ( + + )}
)} {pending && ( diff --git a/frontend/src/features/alerts/pages/AlertsPage.tsx b/frontend/src/features/alerts/pages/AlertsPage.tsx index 818209403..0cecccb00 100644 --- a/frontend/src/features/alerts/pages/AlertsPage.tsx +++ b/frontend/src/features/alerts/pages/AlertsPage.tsx @@ -1,6 +1,6 @@ import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { AlertTriangle, Loader2 } from 'lucide-react' -import { useLocation, useNavigate } from 'react-router-dom' +import { useLocation, useNavigate, useParams } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { Button } from '@/shared/components/ui/button' import { InfiniteScrollSentinel } from '@/shared/components/ui/infinite-scroll' @@ -65,7 +65,25 @@ export function AlertsPage() { // SOC-AI chat navigation: seed the filters + time window the agent emitted. const location = useLocation() const navigate = useNavigate() + const { id: routeAlertName } = useParams<{ id: string }>() + const pendingOpenNameRef = useRef(null) const seededRef = useRef(false) + + // Deep-link (/threat-management/alerts/:alertName): seed as name filter + open drawer once loaded. + useEffect(() => { + if (!routeAlertName) return + const decoded = decodeURIComponent(routeAlertName) + pendingOpenNameRef.current = decoded + setCustomFilters((c) => + c.some((f) => f.field === 'name' && f.value === decoded) + ? c + : [...c, { field: 'name', label: 'name', operator: 'IS', value: decoded }], + ) + setPage(0) + // Drop the name from the URL so future edits to filters aren't fought by re-seeding. + navigate('/threat-management/alerts', { replace: true }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [routeAlertName]) useEffect(() => { const state = location.state as { socaiFilters?: FilterType[]; socaiTime?: string } | null if (!state?.socaiFilters?.length || seededRef.current) return @@ -117,6 +135,15 @@ export function AlertsPage() { }, [scopeFilters, statusTab, severity]) const { alerts, total, loading, error, refresh: refreshList } = useAlertsList(page, pageSize, listFilters) + + useEffect(() => { + const name = pendingOpenNameRef.current + if (!name) return + const match = alerts.find((a) => a.name === name) + if (!match) return + pendingOpenNameRef.current = null + setOpenAlert(match) + }, [alerts]) const { sevCounts, statusCounts, timeline, openCount, refresh: refreshStats } = useAlertStats(scopeFilters, range.interval) const { tagCatalog, createTag, updateTag, deleteTag } = useAlertTagCatalog((deletedName) => setTagFilter((tf) => tf.filter((tn) => tn !== deletedName)) @@ -162,7 +189,7 @@ export function AlertsPage() { } return ( -
+
diff --git a/frontend/src/features/alerts/pages/TaggingRulesPage.tsx b/frontend/src/features/alerts/pages/TaggingRulesPage.tsx index 387db46ea..547f57ce4 100644 --- a/frontend/src/features/alerts/pages/TaggingRulesPage.tsx +++ b/frontend/src/features/alerts/pages/TaggingRulesPage.tsx @@ -137,7 +137,7 @@ export function TaggingRulesPage() { } return ( -
+
diff --git a/frontend/src/features/audit/pages/AuditPage.tsx b/frontend/src/features/audit/pages/AuditPage.tsx index bd17a9151..c990d6840 100644 --- a/frontend/src/features/audit/pages/AuditPage.tsx +++ b/frontend/src/features/audit/pages/AuditPage.tsx @@ -129,7 +129,7 @@ export function AuditPage() { })) return ( -
+
load(filters)} />
diff --git a/frontend/src/features/billing/pages/LicensePage.tsx b/frontend/src/features/billing/pages/LicensePage.tsx index e199f95ad..433c5fda7 100644 --- a/frontend/src/features/billing/pages/LicensePage.tsx +++ b/frontend/src/features/billing/pages/LicensePage.tsx @@ -40,7 +40,7 @@ export function LicensePage() { const showError = !license && error return ( -
+

diff --git a/frontend/src/features/branding/pages/BrandingPage.tsx b/frontend/src/features/branding/pages/BrandingPage.tsx index ce8979cf3..c41c5b4c3 100644 --- a/frontend/src/features/branding/pages/BrandingPage.tsx +++ b/frontend/src/features/branding/pages/BrandingPage.tsx @@ -93,7 +93,7 @@ export function BrandingPage() { } return ( -
+

diff --git a/frontend/src/features/compliance/pages/CompliancePage.tsx b/frontend/src/features/compliance/pages/CompliancePage.tsx index 59b83789e..eef9ed32f 100644 --- a/frontend/src/features/compliance/pages/CompliancePage.tsx +++ b/frontend/src/features/compliance/pages/CompliancePage.tsx @@ -34,7 +34,7 @@ export function CompliancePage() { const [tab, setTab] = useState('frameworks') return ( -
+
setTab('frameworks')} icon={ShieldCheck} label={t('compliance.tabs.frameworks')} /> diff --git a/frontend/src/features/dashboard/components/VisualizationEditor.tsx b/frontend/src/features/dashboard/components/VisualizationEditor.tsx index 6915360c4..cc321bfdc 100644 --- a/frontend/src/features/dashboard/components/VisualizationEditor.tsx +++ b/frontend/src/features/dashboard/components/VisualizationEditor.tsx @@ -180,7 +180,7 @@ export function VisualizationEditor({ initial, initialChartType }: Visualization } return ( -
+

diff --git a/frontend/src/features/dashboard/pages/DashboardPage.tsx b/frontend/src/features/dashboard/pages/DashboardPage.tsx index 07f36945f..2b439a3fe 100644 --- a/frontend/src/features/dashboard/pages/DashboardPage.tsx +++ b/frontend/src/features/dashboard/pages/DashboardPage.tsx @@ -320,7 +320,7 @@ export function DashboardPage() { previewDashboard != null && !previewDashboard.systemOwner && !editor.editing return ( -
+
{inPreview && previewDashboard && ( +
+
-
+ ))}
) diff --git a/frontend/src/features/incidents/pages/IncidentsPage.tsx b/frontend/src/features/incidents/pages/IncidentsPage.tsx index a5ca98100..f8750a497 100644 --- a/frontend/src/features/incidents/pages/IncidentsPage.tsx +++ b/frontend/src/features/incidents/pages/IncidentsPage.tsx @@ -66,7 +66,7 @@ export function IncidentsPage() { } return ( -
+
diff --git a/frontend/src/features/integrations/pages/IntegrationsPage.tsx b/frontend/src/features/integrations/pages/IntegrationsPage.tsx index 61fa8cc6f..85bd4f071 100644 --- a/frontend/src/features/integrations/pages/IntegrationsPage.tsx +++ b/frontend/src/features/integrations/pages/IntegrationsPage.tsx @@ -193,7 +193,7 @@ export function IntegrationsPage() { const openLive = open ? displayList.find((d) => d.id === open.id) ?? open : null return ( -
+
{/* Loading overlay */} {integrations.isLoading && (
diff --git a/frontend/src/features/notifications/pages/NotificationsPage.tsx b/frontend/src/features/notifications/pages/NotificationsPage.tsx index 5ec95c6b6..c650a6203 100644 --- a/frontend/src/features/notifications/pages/NotificationsPage.tsx +++ b/frontend/src/features/notifications/pages/NotificationsPage.tsx @@ -85,7 +85,7 @@ export function NotificationsPage() { } return ( -
+

diff --git a/frontend/src/features/parsing-filters/pages/ParsingFiltersPage.tsx b/frontend/src/features/parsing-filters/pages/ParsingFiltersPage.tsx index 2b3e99005..92503ac33 100644 --- a/frontend/src/features/parsing-filters/pages/ParsingFiltersPage.tsx +++ b/frontend/src/features/parsing-filters/pages/ParsingFiltersPage.tsx @@ -124,7 +124,7 @@ export function ParsingFiltersPage() { } return ( -
+
diff --git a/frontend/src/features/profile/pages/ProfilePage.tsx b/frontend/src/features/profile/pages/ProfilePage.tsx index 6d768a1f7..e3a08e2fa 100644 --- a/frontend/src/features/profile/pages/ProfilePage.tsx +++ b/frontend/src/features/profile/pages/ProfilePage.tsx @@ -211,7 +211,7 @@ export function ProfilePage() { } return ( -
+
{/* Hero */}
diff --git a/frontend/src/features/regex-patterns/pages/RegexPatternsPage.tsx b/frontend/src/features/regex-patterns/pages/RegexPatternsPage.tsx index 2d45dc023..fb81fad24 100644 --- a/frontend/src/features/regex-patterns/pages/RegexPatternsPage.tsx +++ b/frontend/src/features/regex-patterns/pages/RegexPatternsPage.tsx @@ -66,7 +66,7 @@ export function RegexPatternsPage() { }, [load]) return ( -
+
diff --git a/frontend/src/features/settings/pages/AboutPage.tsx b/frontend/src/features/settings/pages/AboutPage.tsx index a4b9f69c6..a718162bc 100644 --- a/frontend/src/features/settings/pages/AboutPage.tsx +++ b/frontend/src/features/settings/pages/AboutPage.tsx @@ -98,7 +98,7 @@ export function AboutPage() { const isEnterprise = edition === 'enterprise' return ( -
+

diff --git a/frontend/src/features/settings/pages/ConnectionKeyPage.tsx b/frontend/src/features/settings/pages/ConnectionKeyPage.tsx index 29af607b6..ab0bcb896 100644 --- a/frontend/src/features/settings/pages/ConnectionKeyPage.tsx +++ b/frontend/src/features/settings/pages/ConnectionKeyPage.tsx @@ -78,7 +78,7 @@ export function ConnectionKeyPage() { const hasToken = token !== '' return ( -
+
diff --git a/frontend/src/features/settings/pages/DataRetentionPage.tsx b/frontend/src/features/settings/pages/DataRetentionPage.tsx index 86edb6475..6494c9d58 100644 --- a/frontend/src/features/settings/pages/DataRetentionPage.tsx +++ b/frontend/src/features/settings/pages/DataRetentionPage.tsx @@ -105,7 +105,7 @@ export function DataRetentionPage() { } return ( -
+

diff --git a/frontend/src/features/settings/pages/DateFormatPage.tsx b/frontend/src/features/settings/pages/DateFormatPage.tsx index 8dc17a6af..a9a38c874 100644 --- a/frontend/src/features/settings/pages/DateFormatPage.tsx +++ b/frontend/src/features/settings/pages/DateFormatPage.tsx @@ -90,7 +90,7 @@ export function DateFormatPage() { const zoneOptions = useMemo(() => ['system', ...ALL_ZONES], []) return ( -
+

diff --git a/frontend/src/features/settings/pages/EmailConfigurationPage.tsx b/frontend/src/features/settings/pages/EmailConfigurationPage.tsx index 6540b5cb8..5668649bd 100644 --- a/frontend/src/features/settings/pages/EmailConfigurationPage.tsx +++ b/frontend/src/features/settings/pages/EmailConfigurationPage.tsx @@ -157,7 +157,7 @@ export function EmailConfigurationPage() { } return ( -
+

diff --git a/frontend/src/features/settings/pages/IdentityProvidersPage.tsx b/frontend/src/features/settings/pages/IdentityProvidersPage.tsx index f54cc4228..563770d47 100644 --- a/frontend/src/features/settings/pages/IdentityProvidersPage.tsx +++ b/frontend/src/features/settings/pages/IdentityProvidersPage.tsx @@ -27,7 +27,7 @@ import type { IdentityProvider, IdentityProviderRequest } from '../types/idp.typ function EnterpriseGate() { const { t } = useTranslation() return ( -
+
@@ -130,7 +130,7 @@ export function IdentityProvidersPage() { } return ( -
+
setDialog({ mode: 'create' })} />
diff --git a/frontend/src/features/settings/pages/IndicesPage.tsx b/frontend/src/features/settings/pages/IndicesPage.tsx index 8d81f199c..9e0e40e5e 100644 --- a/frontend/src/features/settings/pages/IndicesPage.tsx +++ b/frontend/src/features/settings/pages/IndicesPage.tsx @@ -34,7 +34,7 @@ export function IndicesPage() { const { t } = useTranslation() const [tab, setTab] = useState('dataViews') return ( -
+
diff --git a/frontend/src/features/settings/pages/LanguagePage.tsx b/frontend/src/features/settings/pages/LanguagePage.tsx index fd974d781..9cc160a40 100644 --- a/frontend/src/features/settings/pages/LanguagePage.tsx +++ b/frontend/src/features/settings/pages/LanguagePage.tsx @@ -54,7 +54,7 @@ export function LanguagePage() { } return ( -
+

diff --git a/frontend/src/features/settings/pages/SocAiSettingsPage.tsx b/frontend/src/features/settings/pages/SocAiSettingsPage.tsx index dea9d538f..946870eb4 100644 --- a/frontend/src/features/settings/pages/SocAiSettingsPage.tsx +++ b/frontend/src/features/settings/pages/SocAiSettingsPage.tsx @@ -268,7 +268,7 @@ export function SocAiSettingsPage() { const hasModelList = def.models.length > 0 return ( -
+

diff --git a/frontend/src/features/soar/pages/FlowsPage.tsx b/frontend/src/features/soar/pages/FlowsPage.tsx index b88a93e00..aa6de3abd 100644 --- a/frontend/src/features/soar/pages/FlowsPage.tsx +++ b/frontend/src/features/soar/pages/FlowsPage.tsx @@ -19,7 +19,7 @@ export function FlowsPage() { const [tab, setTab] = useState('flows') return ( -
+
setTab('flows')} icon={Workflow} label={t('soar.tabs.flows')} /> diff --git a/frontend/src/features/team/pages/TeamPage.tsx b/frontend/src/features/team/pages/TeamPage.tsx index 4b9db36ce..f12db9f77 100644 --- a/frontend/src/features/team/pages/TeamPage.tsx +++ b/frontend/src/features/team/pages/TeamPage.tsx @@ -80,7 +80,7 @@ export function TeamPage() { }, [t]) return ( -
+
diff --git a/frontend/src/features/threat-intel/pages/ThreatIntelPage.tsx b/frontend/src/features/threat-intel/pages/ThreatIntelPage.tsx index f521d7315..6093169dd 100644 --- a/frontend/src/features/threat-intel/pages/ThreatIntelPage.tsx +++ b/frontend/src/features/threat-intel/pages/ThreatIntelPage.tsx @@ -297,7 +297,7 @@ export function ThreatIntelPage() { const totalMatches = matchedIocs.reduce((s, i) => s + i.matchesInEnv, 0) return ( -
+
diff --git a/frontend/src/features/user-auditor/pages/UserAuditorPage.tsx b/frontend/src/features/user-auditor/pages/UserAuditorPage.tsx index dc11a3a46..dcf496e15 100644 --- a/frontend/src/features/user-auditor/pages/UserAuditorPage.tsx +++ b/frontend/src/features/user-auditor/pages/UserAuditorPage.tsx @@ -146,7 +146,7 @@ export function UserAuditorPage() { } return ( -
+