fix: differentiate security action links and add suppression editing (#1500)

Security page UX fixes:

- Stop the CVSS x EPSS scatter chart from painting a full-plot "white
  rectangle" cursor on click (cursor disabled), and prevent click-drag
  selection on charts.
- Differentiate the overview action links: "fixable" links (masthead primary
  action, review-queue blocker, and the Fixable signal tile) now open the
  Images tab pre-filtered to fixable findings; the Stale and Failed signal
  tiles link to the History tab where those scans are listed; Secrets and
  Misconfigs tiles link to their tabs. The Images tab accepts an initialFilter
  and exposes a Fixable option in the severity dropdown.
- Fix the "Secrets / misconfigs" option wrapping and misaligning in the
  severity dropdown (single-line option labels, wider trigger).
- Add Edit for CVE suppressions and misconfig acknowledgements (reason, scope
  pattern, expiry), reusing the existing dialog and the existing PUT endpoints;
  the CVE/rule identity stays fixed.
This commit is contained in:
Anso
2026-06-28 06:17:47 -04:00
committed by GitHub
parent ba57c67048
commit 083442d5ea
15 changed files with 316 additions and 115 deletions
+18 -2
View File
@@ -17,11 +17,13 @@ import { useIsMobile } from '@/hooks/use-is-mobile';
import { Masthead, type Tone } from './mobile/mobile-ui';
import { SecurityMobileTabs, type SecurityMobileTab } from './security/SecurityMobile';
import type { SecurityTab } from '@/lib/events';
import type { ImageFilterValue } from '@/lib/severityStyles';
import type { SecurityOverview, ScanSummary, ScanDetailTab, SecurityRiskTrendPoint, ExploitIntelFinding, FleetRole } from '@/types/security';
import { VulnerabilityScanSheet } from './VulnerabilityScanSheet';
import { SuppressionsPanel } from './settings/SuppressionsPanel';
import { MisconfigAckPanel } from './settings/MisconfigAckPanel';
import { OverviewTab } from './security/OverviewTab';
import { reasonImageFilter } from './security/postureNavigation';
import { ImagesTab } from './security/ImagesTab';
import { FindingsTab } from './security/FindingsTab';
import { ScanPolicyManager } from './security/ScanPolicyManager';
@@ -85,6 +87,16 @@ export function SecurityView({ activeTab, onTabChange, headerActions }: Security
const [inspectScanId, setInspectScanId] = useState<number | null>(null);
const [inspectInitialTab, setInspectInitialTab] = useState<ScanDetailTab | undefined>(undefined);
// Filter to preselect on the Images tab when arriving from an overview link
// (e.g. "fixable findings"). Null leaves the Images tab on its own default.
const [imagesFilter, setImagesFilter] = useState<ImageFilterValue | null>(null);
// Navigate between security tabs, optionally preselecting an Images filter so
// an overview action link lands on exactly the affected images.
const handleNavigate = useCallback((tab: SecurityTab, filter?: ImageFilterValue) => {
if (tab === 'images' && filter) setImagesFilter(filter);
onTabChange(tab);
}, [onTabChange]);
const onInspect = useCallback((scanId: number, initialTab?: ScanDetailTab) => {
setInspectInitialTab(initialTab);
@@ -254,7 +266,7 @@ export function SecurityView({ activeTab, onTabChange, headerActions }: Security
trend={trend}
exploitIntel={exploitIntel}
exploitTruncated={exploitTruncated}
onNavigate={onTabChange}
onNavigate={handleNavigate}
onInspect={onInspect}
canScan={canScan}
onScanComplete={() => setReloadToken((t) => t + 1)}
@@ -272,6 +284,7 @@ export function SecurityView({ activeTab, onTabChange, headerActions }: Security
canScan={canScan}
scanningRef={scanningRef}
onScan={scanImage}
initialFilter={imagesFilter ?? undefined}
/>
</CapabilityGate>
</TabsContent>
@@ -382,7 +395,10 @@ export function SecurityView({ activeTab, onTabChange, headerActions }: Security
{overview?.posture === 'Action needed' && overview.primaryAction ? (
<button
type="button"
onClick={() => onTabChange(overview.primaryAction!.targetTab)}
onClick={() => handleNavigate(
overview.primaryAction!.targetTab,
reasonImageFilter(overview.primaryAction!.kind),
)}
className="text-xs font-medium text-brand hover:underline whitespace-nowrap"
>
{overview.primaryAction.label}