diff --git a/frontend/src/pages/ExecutiveDashboardPage.tsx b/frontend/src/pages/ExecutiveDashboardPage.tsx index 5f67d34..6504eb1 100644 --- a/frontend/src/pages/ExecutiveDashboardPage.tsx +++ b/frontend/src/pages/ExecutiveDashboardPage.tsx @@ -155,9 +155,10 @@ export default function ExecutiveDashboardPage() { queryFn: getCoverageByTactic, }); + // Fetch enough actors to rank properly — sort client-side by uncovered techniques const { data: threatActors } = useQuery({ queryKey: ["threat-actors-top"], - queryFn: () => getThreatActors({ limit: 5 }), + queryFn: () => getThreatActors({ limit: 100 }), }); const { data: allTechniques } = useQuery({ @@ -322,29 +323,50 @@ export default function ExecutiveDashboardPage() { {/* Section 3: Top Threat Actors */}
-

+

Top Threat Actors

+

+ Ranked by uncovered techniques (most exposure first) +

- {(threatActors?.items || []).map((actor) => ( + {[...(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) => (
navigate(`/threat-actors/${actor.id}`)} > -
- {actor.country?.slice(0, 2).toUpperCase() || "??"} + {/* Rank badge */} +
+ {idx + 1}

{actor.name}

- {actor.target_sectors?.slice(0, 3).join(", ")} + {Math.round(actor.technique_count * (1 - actor.coverage_pct / 100))} uncovered + {" / "} + {actor.technique_count} techniques

-
+
- - {actor.coverage_pct}% + + {actor.coverage_pct.toFixed(0)}%