Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions openaev-front/src/admin/components/findings/FindingList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,24 @@ const FindingList = ({ searchDistinctFindings, filterLocalStorageKey, contextId,

const visibleHeaders = headers.filter(h => !hiddenFields.includes(h.field));

// The column widths above are percentages of the full six-column table: hiding columns would
// otherwise leave the remainder crammed on the left with dead space on the right (the assets chips
// truncating while the row is half empty). Re-spread the hidden columns' share over the visible
// ones, keeping their relative proportions.
const visibleStyles: Record<string, CSSProperties> = (() => {
const widthOf = (field: string) => Number.parseFloat(String(inlineStyles[field]?.width ?? '0'));
const totalWidth = visibleHeaders.reduce((sum, h) => sum + widthOf(h.field), 0);
if (totalWidth <= 0 || totalWidth >= 100) {
return inlineStyles;
}
return Object.fromEntries(
visibleHeaders.map(h => [h.field, {
...inlineStyles[h.field],
width: `${(widthOf(h.field) / totalWidth) * 100}%`,
}]),
);
})();

return (
<>
<PaginationComponentV2
Expand All @@ -201,14 +219,14 @@ const FindingList = ({ searchDistinctFindings, filterLocalStorageKey, contextId,
primary={(
<SortHeadersComponentV2
headers={visibleHeaders}
inlineStylesHeaders={inlineStyles}
inlineStylesHeaders={visibleStyles}
sortHelpers={queryableHelpers.sortHelpers}
/>
)}
/>
</ListItem>
{loading
? <PaginatedListLoader Icon={Binoculars} headers={visibleHeaders} headerStyles={inlineStyles} />
? <PaginatedListLoader Icon={Binoculars} headers={visibleHeaders} headerStyles={visibleStyles} />
: findings.map(finding => (
<ListItem
key={finding.finding_id}
Expand All @@ -233,7 +251,7 @@ const FindingList = ({ searchDistinctFindings, filterLocalStorageKey, contextId,
key={header.field}
style={{
...bodyItemsStyles.bodyItem,
...inlineStyles[header.field],
...visibleStyles[header.field],
}}
>
{header.value && header.value(finding)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1802,6 +1802,46 @@ const SimulationAttackPath = ({ scenarioExerciseIds, scenarioId }: SimulationAtt
// Focus a chokepoint endpoint: redraw the graph as the focused endpoint path (injectors -> endpoint
// -> its finding clusters) and open its side panel (findings + executions). Uses the endpoint-focus
// mode of buildFindingPathFlow (empty finding type/value).
// Entering an endpoint focus, pre-expand every finding-type cluster of that endpoint: the analyst
// came here to read the findings, so making them click each cluster open adds a step with no
// information gain. One fetch feeds every cluster (they all read the same endpoint).
const expandFocusedEndpointClusters = useCallback((endpointNodeId: string, ref: string) => {
const counts = (dto?.attackPathNodes ?? []).find(n => n.id === endpointNodeId)?.findingCounts ?? {};
const types = Object.entries(counts).filter(([, v]) => (v ?? 0) > 0).map(([type]) => type);
if (types.length === 0) {
return;
}
const clusterIdOf = (type: string) => `path-cl-type|${type}|${ref}`;
setExpandedFindingClusters(new Set(types.map(clusterIdOf)));
setFindingBatch((prev) => {
const next = new Map(prev);
types.forEach(type => next.set(clusterIdOf(type), FINDING_BATCH_SIZE));
return next;
});
fetchEndpointFindings(simulationId, ref)
.then((r) => {
const byType = new Map<string, AttackPathNodeDTO[]>();
const seen = new Set<string>();
for (const f of r.data.findings ?? []) {
const type = f.typeFindings ?? '';
const key = `${type}|${f.value ?? f.id ?? ''}`;
if (seen.has(key)) {
continue;
}
seen.add(key);
const list = byType.get(type) ?? [];
list.push(f);
byType.set(type, list);
}
setFindingsByCluster((prev) => {
const next = new Map(prev);
byType.forEach((list, type) => next.set(clusterIdOf(type), list));
return next;
});
})
.catch(() => undefined);
}, [dto, simulationId]);

const focusChokepoint = (c: {
nodeId: string;
ref: string;
Expand All @@ -1824,6 +1864,7 @@ const SimulationAttackPath = ({ scenarioExerciseIds, scenarioId }: SimulationAtt
});
setFitNonce(n => n + 1);
onEndpointClick(c.nodeId, c.ref, c.label);
expandFocusedEndpointClusters(c.nodeId, c.ref);
};

// Keep the selected value in the options so MUI does not warn when the current simulation has no
Expand Down
Loading