feat(ui): grouped template-graph view for attack paths

Redesigns the Attack Paths graph around the structure of the attack:

- New _lib/template-graph.ts groups concrete nodes by resource type into one synthetic node per type (with a count badge), dedupes inter-group edges, drops intra-group self-loops, and appends a terminal Outcome node wired from the sink representatives. Account and finding nodes are filtered out of this structural view.
- _lib/layout.ts adds 'attackGroup' and 'outcome' node types and a MarkerType.ArrowClosed markerEnd on every edge so attack direction is explicit. 'attackGroup' is intentionally named off the reserved React Flow 'group' type, which otherwise paints a default gray container behind the node.
- New GroupNode (stacked-card visual, type icon, count badge, click-to-expand) and OutcomeNode (severity-colored terminal with Crosshair) components, registered in NODE_TYPES.
- useGraphStore gains templateSource, outcome, expandedTypes and toggleExpandedType; the rendered graph is the collapsed template, recomputed via buildTemplateGraph on every state change.
- attack-paths-page handleNodeClick: grouped type -> expand; expanded concrete resource -> collapse its type; outcome node is inert. A key based on expandedTypes forces React Flow to refit on expansion. Banner copy updated.
- graph-legend.tsx skips the outcome marker label so the legend does not list it as a resource type.
- Unit tests for buildTemplateGraph (grouping, edge dedup, expand, sink->outcome wiring, finding/account drop, empty input) and for the new edge markerEnd in layoutWithDagre.
This commit is contained in:
Hugo P.Brito committed 2026-05-26 09:11:19 +02:00
1 parent 99a11ecfb6
commit e392299d2c
12 files changed
+778 -13

No files matched your search

+7
View File
@@ -212,9 +212,16 @@ export interface AttackPathGraphData {
relationships?: GraphRelationship[];
}
export interface AttackPathOutcome {
label: string;
description: string;
severity: string;
}
export interface QueryResultAttributes {
nodes: GraphNode[];
relationships?: GraphRelationship[];
outcome?: AttackPathOutcome | null;
}
export interface QueryResultData {