feat: add an on-demand node-wide security scan with live progress (#1367)

Add a "Scan this node" action on the Security overview that scans, in one pass,
any combination of three types: image vulnerabilities, image secrets, and
compose misconfigurations. Progress streams live into the deploy-feedback modal.

- TrivyService.scanNode runs the selected scanners across the node's images and,
  for misconfig, every stack's compose file, behind a per-node lock and tolerant
  of per-item failures. The existing scanAllNodeImages becomes a thin vuln-only
  wrapper over the shared image loop, so scheduled scans are unchanged.
- POST /api/security/scan-node (admin, scanner-gated) streams sanitized progress
  to the deploy terminal and returns a combined summary. Secret scans stream
  counts only, never matched values.
- Frontend adds a "scan" action verb and a ScanNodeLauncher wired into the
  overview; the scan stays bound to the node it started on even if the active
  node changes mid-run.
This commit is contained in:
Anso
2026-06-12 19:07:45 -04:00
committed by GitHub
parent ebf66fd92a
commit ef5a3f00a7
11 changed files with 641 additions and 6 deletions
+5 -1
View File
@@ -59,6 +59,8 @@ export function SecurityView({ activeTab, onTabChange }: SecurityViewProps) {
const [summariesError, setSummariesError] = useState(false);
const [trend, setTrend] = useState<SecurityRiskTrendPoint[]>([]);
const [isReplica, setIsReplica] = useState(false);
// Bumped after a node-wide scan completes to refetch the active node's posture.
const [reloadToken, setReloadToken] = useState(0);
const [inspectScanId, setInspectScanId] = useState<number | null>(null);
const [inspectInitialTab, setInspectInitialTab] = useState<ScanDetailTab | undefined>(undefined);
@@ -139,7 +141,7 @@ export function SecurityView({ activeTab, onTabChange }: SecurityViewProps) {
if (!cancelled) setTrend(trend);
})();
return () => { cancelled = true; };
}, [activeNode?.id]);
}, [activeNode?.id, reloadToken]);
// Governance panels (suppressions/acks) are control-governed; probe the local
// fleet role so a replica renders them read-only, mirroring Settings.
@@ -222,6 +224,8 @@ export function SecurityView({ activeTab, onTabChange }: SecurityViewProps) {
trend={trend}
onNavigate={onTabChange}
onInspect={onInspect}
canScan={canScan}
onScanComplete={() => setReloadToken((t) => t + 1)}
/>
</TabsContent>