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
@@ -4,7 +4,7 @@ import { type ParsedLogRow, parseLogChunk } from '../components/log-rendering/co
import { useDeployFeedbackEnabled } from '../hooks/use-deploy-feedback-enabled';
import { readDeployFeedbackStyle } from '../hooks/use-deploy-feedback-style';
export type ActionVerb = 'deploy' | 'update' | 'down' | 'restart' | 'stop' | 'install';
export type ActionVerb = 'deploy' | 'update' | 'down' | 'restart' | 'stop' | 'install' | 'scan';
// eslint-disable-next-line react-refresh/only-export-components
export const VERB_LABELS: Record<ActionVerb, { present: string; past: string }> = {
@@ -14,6 +14,7 @@ export const VERB_LABELS: Record<ActionVerb, { present: string; past: string }>
restart: { present: 'Restarting', past: 'Restarted' },
stop: { present: 'Stopping', past: 'Stopped' },
install: { present: 'Installing', past: 'Installed' },
scan: { present: 'Scanning', past: 'Scanned' },
};
export interface DeployPanelState {