import { useEffect } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useNavigate } from "react-router-dom"; import { Loader2, AlertCircle, TrendingUp, TrendingDown, Minus, ArrowRight, RefreshCw, } from "lucide-react"; import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell, } from "recharts"; import { getOrganizationScore, getScoreHistory } from "../api/scores"; import MetricTooltip from "../components/MetricTooltip"; import { getOperationalMetrics, getMetricsByTeam, } from "../api/operational-metrics"; import { getCoverageByTactic } from "../api/metrics"; import { getThreatActors } from "../api/threat-actors"; import { getTechniques, type TechniqueSummary } from "../api/techniques"; import { getRiskProfiles, computeRiskScores, type RiskProfile } from "../api/risk"; // ── Score Gauge Component ──────────────────────────────────────────── function ScoreGauge({ score, label }: { score: number; label: string }) { const getColor = (s: number) => { if (s < 30) return "#ef4444"; if (s < 50) return "#f97316"; if (s < 70) return "#eab308"; return "#22c55e"; }; const color = getColor(score); const circumference = 2 * Math.PI * 54; const strokeDasharray = `${(score / 100) * circumference} ${circumference}`; return (
{Math.round(score)} / 100
{label}
); } // ── KPI Card Component ────────────────────────────────────────────── function KPICard({ label, value, unit, trend, tooltip, }: { label: string; value: string | number; unit?: string; trend?: "improving" | "declining" | "stable" | null; tooltip?: { description: string; context?: string }; }) { const TrendIcon = trend === "improving" ? TrendingUp : trend === "declining" ? TrendingDown : Minus; const trendColor = trend === "improving" ? "text-green-400" : trend === "declining" ? "text-red-400" : "text-gray-500"; return (

{label} {tooltip && }

{value === null || value === undefined ? "N/A" : value} {unit && {unit}}
{trend && ( )}
); } // ── Main Component ────────────────────────────────────────────────── export default function ExecutiveDashboardPage() { const navigate = useNavigate(); const { data: orgScore, isLoading: loadingScore } = useQuery({ queryKey: ["org-score"], queryFn: getOrganizationScore, }); const { data: scoreHistory } = useQuery({ queryKey: ["score-history", "90d"], queryFn: () => getScoreHistory("90d"), }); const { data: opMetrics, isLoading: loadingMetrics } = useQuery({ queryKey: ["operational-metrics"], queryFn: getOperationalMetrics, }); const { data: teamMetrics } = useQuery({ queryKey: ["team-metrics"], queryFn: getMetricsByTeam, }); const { data: tacticCoverage } = useQuery({ queryKey: ["tactic-coverage"], 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: 100 }), }); const { data: allTechniques } = useQuery({ queryKey: ["techniques-exec"], queryFn: () => getTechniques(), }); const queryClient = useQueryClient(); const { data: riskProfiles, isLoading: loadingRisk } = useQuery({ queryKey: ["risk-profiles-exec"], queryFn: () => getRiskProfiles({ limit: 500 }), }); const computeMutation = useMutation({ mutationFn: computeRiskScores, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["risk-profiles-exec"] }); }, }); // Auto-compute on first load if no profiles exist useEffect(() => { if (!loadingRisk && riskProfiles !== undefined && riskProfiles.length === 0) { computeMutation.mutate(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [loadingRisk, riskProfiles?.length]); const isLoading = loadingScore || loadingMetrics; if (isLoading) { return (
); } // Build a lookup: techniqueId → risk profile (already sorted by risk_score DESC server-side) const riskByTechniqueId = new Map( (riskProfiles || []).map((p) => [p.technique_id, p]) ); // Critical gaps: not_covered or not_evaluated, sorted by risk_score DESC // Techniques with a risk profile rank higher; ties broken by status (not_covered first) const criticalGaps: TechniqueSummary[] = (allTechniques || []) .filter((t) => t.status_global === "not_covered" || t.status_global === "not_evaluated") .sort((a, b) => { const scoreA = riskByTechniqueId.get(a.id)?.risk_score ?? -1; const scoreB = riskByTechniqueId.get(b.id)?.risk_score ?? -1; if (scoreB !== scoreA) return scoreB - scoreA; // same score: not_covered before not_evaluated if (a.status_global === "not_covered" && b.status_global !== "not_covered") return -1; if (b.status_global === "not_covered" && a.status_global !== "not_covered") return 1; return 0; }) .slice(0, 10); // Coverage by tactic for bar chart const tacticData = (tacticCoverage || []).map((tc) => ({ name: tc.tactic .split("-") .map((w: string) => w.charAt(0).toUpperCase() + w.slice(1)) .join(" "), coverage: tc.total > 0 ? Math.round(((tc.validated + tc.partial) / tc.total) * 100) : 0, })); const getBarColor = (coverage: number) => { if (coverage < 30) return "#ef4444"; if (coverage < 50) return "#f97316"; if (coverage < 70) return "#eab308"; return "#22c55e"; }; return (
{/* Header */}

Executive Dashboard

Organization security posture overview

{/* Section 1: Score Card + Sub-scores */}

{orgScore?.total_coverage ?? 0}

Coverage

{orgScore?.detection_maturity ?? 0}

Detection

{orgScore?.critical_coverage ?? 0}

Critical

{orgScore?.response_readiness ?? 0}

Response

{/* Section 2: Trend Chart */}

Score Trend (90 days)

{ const d = new Date(val); return `${d.getMonth() + 1}/${d.getDate()}`; }} />
{/* 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) => (
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

70 ? "#22c55e" : actor.coverage_pct > 40 ? "#eab308" : "#ef4444", }} />
{actor.coverage_pct.toFixed(0)}%
))}
{/* Section 4: Operational KPIs */}
{/* Section 5: Coverage by Tactic */}

Coverage by Tactic

`${v}%`} /> [`${value}%`, "Coverage"]} /> {tacticData.map((entry, index) => ( ))}
{/* Section 6: Critical Gaps */}

Critical Gaps — Top 10 by Risk Priority

{criticalGaps.map((tech, idx) => { const profile = riskByTechniqueId.get(tech.id); const riskScore = profile?.risk_score; const riskLevel = profile?.risk_level; const riskLevelColors: Record = { critical: "bg-red-500/20 text-red-400 border border-red-500/30", high: "bg-orange-500/20 text-orange-400 border border-orange-500/30", medium: "bg-yellow-500/20 text-yellow-400 border border-yellow-500/30", low: "bg-blue-500/20 text-blue-400 border border-blue-500/30", info: "bg-gray-500/20 text-gray-400 border border-gray-500/30", }; const scoreColor = riskScore === undefined ? "text-gray-600" : riskScore >= 75 ? "text-red-400" : riskScore >= 50 ? "text-orange-400" : riskScore >= 25 ? "text-yellow-400" : "text-blue-400"; return ( navigate(`/techniques/${tech.mitre_id}`)} > ); })} {criticalGaps.length === 0 && ( )}
# Risk MITRE ID Name Tactic Status
{idx + 1}
{riskScore !== undefined ? Math.round(riskScore) : "—"} {riskLevel && ( {riskLevel} )}
{tech.mitre_id} {tech.name} {tech.tactic ?.split(",")[0] .trim() .split("-") .map((w: string) => w.charAt(0).toUpperCase() + w.slice(1)) .join(" ")} {tech.status_global?.replace(/_/g, " ")}
No critical gaps found
{/* Section 7: Team Performance */} {teamMetrics && (
{/* Red Team */}

Red Team

{teamMetrics.red_team.tests_completed}

Tests Done

{teamMetrics.red_team.avg_completion_hours ? `${teamMetrics.red_team.avg_completion_hours}h` : "N/A"}

Avg Time

{teamMetrics.red_team.rejection_rate}%

Rejection

{/* Blue Team */}

Blue Team

{teamMetrics.blue_team.tests_completed}

Tests Done

{teamMetrics.blue_team.avg_completion_hours ? `${teamMetrics.blue_team.avg_completion_hours}h` : "N/A"}

Avg Time

{teamMetrics.blue_team.rejection_rate}%

Rejection

)}
); }