diff --git a/frontend/src/pages/ExecutiveDashboardPage.tsx b/frontend/src/pages/ExecutiveDashboardPage.tsx index 3ab7cbf..1e6f5ff 100644 --- a/frontend/src/pages/ExecutiveDashboardPage.tsx +++ b/frontend/src/pages/ExecutiveDashboardPage.tsx @@ -336,53 +336,73 @@ export default function ExecutiveDashboardPage() { - {/* Section 3: Top Threat Actors */} -
-

- Top Threat Actors - -

-

- Ranked by uncovered techniques (most exposure first) -

-
- {[...(threatActors?.items || [])] - // Sort by uncovered technique count DESC (= technique_count × gap%) - // Tiebreak: higher technique_count = broader attack surface - .sort((a, b) => { - const uncoveredA = a.technique_count * (1 - a.coverage_pct / 100); - const uncoveredB = b.technique_count * (1 - b.coverage_pct / 100); - if (uncoveredB !== uncoveredA) return uncoveredB - uncoveredA; - return b.technique_count - a.technique_count; - }) - .slice(0, 5) - .map((actor, idx) => ( + {/* Section 3: Threat Actor Exposure vs Detection Strength */} + {(() => { + const allActors = [...(threatActors?.items || [])].filter( + (a) => a.technique_count > 0, + ); + + // Most vulnerable: highest uncovered technique count + const mostVulnerable = [...allActors] + .sort((a, b) => { + const ua = a.technique_count * (1 - a.coverage_pct / 100); + const ub = b.technique_count * (1 - b.coverage_pct / 100); + return ub !== ua ? ub - ua : b.technique_count - a.technique_count; + }) + .slice(0, 5); + + // Best covered: highest coverage_pct with ≥1 technique + const bestCovered = [...allActors] + .filter((a) => a.coverage_pct > 0) + .sort((a, b) => + b.coverage_pct !== a.coverage_pct + ? b.coverage_pct - a.coverage_pct + : b.technique_count - a.technique_count, + ) + .slice(0, 5); + + const ActorRow = ({ + actor, + idx, + side, + }: { + actor: (typeof allActors)[0]; + idx: number; + side: "risk" | "strength"; + }) => { + const uncovered = Math.round( + actor.technique_count * (1 - actor.coverage_pct / 100), + ); + return (
navigate(`/threat-actors/${actor.id}`)} > - {/* Rank badge */} -
+
{idx + 1}
-

+

{actor.name}

- {Math.round(actor.technique_count * (1 - actor.coverage_pct / 100))} uncovered - {" / "} - {actor.technique_count} techniques + {side === "risk" + ? `${uncovered} uncovered / ${actor.technique_count} techniques` + : `${actor.technique_count} techniques covered`}

-
-
+
+
- + {actor.coverage_pct.toFixed(0)}%
- ))} -
-
+ ); + }; + + return ( +
+ {/* Left: highest exposure */} +
+

+ ⚠ Highest Exposure + +

+

+ These threat actors use techniques we cannot currently detect. + If they targeted us today, most of their attacks would go unnoticed. +

+
+ {mostVulnerable.map((actor, idx) => ( + + ))} + {mostVulnerable.length === 0 && ( +

No data available

+ )} +
+
+ + {/* Right: best detection capability */} +
+

+ ✅ Strongest Detection + +

+

+ For these threat actors we have strong detection coverage. + Their techniques are well tested and our Blue Team would likely identify an intrusion. +

+
+ {bestCovered.map((actor, idx) => ( + + ))} + {bestCovered.length === 0 && ( +

No coverage data yet — run tests to populate

+ )} +
+
+
+ ); + })()} {/* Section 4: Operational KPIs */}