feat(dashboards): hover tooltips on all metric cards
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled

New MetricTooltip component — a small ⓘ icon showing an executive-
friendly explanation panel on hover (CSS, no JS, instant).

DashboardPage: tooltips on all 6 coverage summary cards (Total
Techniques, Validated, Partial, In Progress, Not Covered, Not
Evaluated), Coverage Evolution chart, Test Pipeline funnel,
Team Activity and Validation Rate section headers.

ExecutiveDashboardPage: tooltips on all 4 sub-scores (Coverage,
Detection, Critical, Response), Score Trend, Top Threat Actors,
4 KPIs (MTTD, MTTR, Detection Efficacy, Validation Throughput),
Coverage by Tactic, Critical Gaps table, and all 6 team metrics
(Red/Blue Tests Done, Avg Time, Rejection).

Each tooltip explains what the metric measures, what a good/bad
value looks like, and what action to take — written for non-
technical executives.
This commit is contained in:
kitos
2026-06-03 09:49:58 +02:00
parent d896f2761d
commit 757d99d22a
4 changed files with 114 additions and 16 deletions

View File

@@ -23,6 +23,7 @@ import {
Cell,
} from "recharts";
import { getOrganizationScore, getScoreHistory } from "../api/scores";
import MetricTooltip from "../components/MetricTooltip";
import {
getOperationalMetrics,
getMetricsByTeam,
@@ -87,11 +88,13 @@ function KPICard({
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"
@@ -109,7 +112,10 @@ function KPICard({
return (
<div className="rounded-xl border border-gray-800 bg-gray-900 p-4">
<p className="text-xs text-gray-500 uppercase tracking-wider">{label}</p>
<p className="text-xs text-gray-500 uppercase tracking-wider flex items-center gap-0.5">
{label}
{tooltip && <MetricTooltip title={label} description={tooltip.description} context={tooltip.context} />}
</p>
<div className="mt-2 flex items-end justify-between">
<div>
<span className="text-2xl font-bold text-white">
@@ -256,33 +262,34 @@ export default function ExecutiveDashboardPage() {
<p className="text-lg font-bold text-white">
{orgScore?.total_coverage ?? 0}
</p>
<p className="text-[10px] text-gray-500">Coverage</p>
<p className="text-[10px] text-gray-500 flex items-center justify-center gap-0.5">Coverage<MetricTooltip title="Coverage Score" description="Breadth of attack technique coverage — how many of the known MITRE ATT&CK techniques have been tested and detected. Scale 0100." position="above" /></p>
</div>
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
<p className="text-lg font-bold text-white">
{orgScore?.detection_maturity ?? 0}
</p>
<p className="text-[10px] text-gray-500">Detection</p>
<p className="text-[10px] text-gray-500 flex items-center justify-center gap-0.5">Detection<MetricTooltip title="Detection Score" description="Quality and maturity of detection capabilities — based on detection rules, validated tests and Blue Team confirmation rate. Scale 0100." position="above" /></p>
</div>
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
<p className="text-lg font-bold text-white">
{orgScore?.critical_coverage ?? 0}
</p>
<p className="text-[10px] text-gray-500">Critical</p>
<p className="text-[10px] text-gray-500 flex items-center justify-center gap-0.5">Critical<MetricTooltip title="Critical Coverage Score" description="Coverage specifically for high-risk and critical techniques used by known threat actors targeting your sector. Scale 0100." position="above" /></p>
</div>
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
<p className="text-lg font-bold text-white">
{orgScore?.response_readiness ?? 0}
</p>
<p className="text-[10px] text-gray-500">Response</p>
<p className="text-[10px] text-gray-500 flex items-center justify-center gap-0.5">Response<MetricTooltip title="Response Readiness Score" description="How quickly and efficiently tests flow through the Red/Blue validation process. High score = fast, smooth validation workflow. Scale 0100." position="above" /></p>
</div>
</div>
</div>
{/* Section 2: Trend Chart */}
<div className="rounded-xl border border-gray-800 bg-gray-900 p-4 lg:col-span-3">
<h2 className="mb-3 text-sm font-semibold text-gray-300">
<h2 className="mb-3 text-sm font-semibold text-gray-300 flex items-center gap-1">
Score Trend (90 days)
<MetricTooltip title="Score Trend" description="How the overall security posture score has evolved over the past 90 days. An upward trend indicates improving security coverage and maturity." context="Aim for a steady upward trend. Sudden drops may indicate new uncovered threats discovered." />
</h2>
<ResponsiveContainer width="100%" height={220}>
<LineChart data={scoreHistory || []}>
@@ -323,8 +330,9 @@ export default function ExecutiveDashboardPage() {
{/* Section 3: Top Threat Actors */}
<div className="rounded-xl border border-gray-800 bg-gray-900 p-4">
<h2 className="mb-1 text-sm font-semibold text-gray-300">
<h2 className="mb-1 text-sm font-semibold text-gray-300 flex items-center gap-1">
Top Threat Actors
<MetricTooltip title="Top Threat Actors" description="The 5 most dangerous known adversary groups ranked by how many of their attack techniques we cannot currently detect. Higher uncovered count = greater exposure to that group." context="These are real APT groups (e.g. APT28, FIN7) with documented attack playbooks. Focus testing on their techniques." />
</h2>
<p className="mb-3 text-[10px] text-gray-500">
Ranked by uncovered techniques (most exposure first)
@@ -395,29 +403,34 @@ export default function ExecutiveDashboardPage() {
label="MTTD"
value={opMetrics?.mttd?.mean_hours ?? "N/A"}
unit={opMetrics?.mttd ? "hrs" : undefined}
tooltip={{ description: "Mean Time To Detect — average hours from attack execution to Blue Team raising an alert or detecting the intrusion.", context: "Lower is better. Industry benchmark: < 24h." }}
/>
<KPICard
label="MTTR"
value={opMetrics?.mttr?.mean_hours ?? "N/A"}
unit={opMetrics?.mttr ? "hrs" : undefined}
tooltip={{ description: "Mean Time To Respond — average hours for a test to go from execution through the full Red/Blue validation and reach a final result.", context: "Lower is better. Reflects process efficiency and team responsiveness." }}
/>
<KPICard
label="Detection Efficacy"
value={opMetrics?.detection_efficacy?.percentage ?? 0}
unit="%"
tooltip={{ description: "Percentage of executed attacks that Blue Team successfully detected. 100% means every simulated attack triggered an alert.", context: "Higher is better. Below 60% indicates significant detection gaps." }}
/>
<KPICard
label="Validation Throughput"
value={opMetrics?.validation_throughput?.tests_per_week ?? 0}
unit="/week"
trend={opMetrics?.validation_throughput?.trend}
tooltip={{ description: "Number of tests fully validated (approved by Red + Blue leads) per week. Shows how productive the security testing programme is.", context: "Increasing trend = programme is accelerating. Decreasing may indicate process bottlenecks." }}
/>
</div>
{/* Section 5: Coverage by Tactic */}
<div className="rounded-xl border border-gray-800 bg-gray-900 p-4">
<h2 className="mb-3 text-sm font-semibold text-gray-300">
<h2 className="mb-3 text-sm font-semibold text-gray-300 flex items-center gap-1">
Coverage by Tactic
<MetricTooltip title="Coverage by Tactic" description="How well each MITRE ATT&CK tactic (attack phase) is covered by validated tests. Tactics represent different stages of an attack, from Initial Access to Impact." context="Red bars (low coverage) are highest priority. Focus testing on those tactics first." />
</h2>
<ResponsiveContainer width="100%" height={300}>
<BarChart
@@ -462,8 +475,9 @@ export default function ExecutiveDashboardPage() {
{/* Section 6: Critical Gaps */}
<div className="rounded-xl border border-gray-800 bg-gray-900 p-4">
<div className="mb-3 flex items-center justify-between">
<h2 className="text-sm font-semibold text-gray-300">
<h2 className="text-sm font-semibold text-gray-300 flex items-center gap-1">
Critical Gaps Top 10 by Risk Priority
<MetricTooltip title="Critical Gaps" description="The 10 most dangerous attack techniques with no detection coverage, ranked by a composite risk score based on: how many threat actors use this technique, recent threat intelligence, and test history." context="These are your highest-priority gaps — real adversaries actively use these techniques and you cannot currently detect them." />
</h2>
<button
onClick={() => computeMutation.mutate()}
@@ -581,13 +595,14 @@ export default function ExecutiveDashboardPage() {
<h2 className="mb-3 flex items-center gap-2 text-sm font-semibold text-red-400">
<div className="h-2 w-2 rounded-full bg-red-500" />
Red Team
<MetricTooltip title="Red Team Performance" description="Metrics for the offensive security team that simulates attacks. Tests Done = completed executions; Avg Time = mean execution duration; Rejection = % of results rejected by leads." context="Lower rejection rate = higher quality test documentation." />
</h2>
<div className="grid grid-cols-3 gap-3">
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
<p className="text-lg font-bold text-white">
{teamMetrics.red_team.tests_completed}
</p>
<p className="text-[10px] text-gray-500">Tests Done</p>
<p className="text-[10px] text-gray-500 flex items-center justify-center gap-0.5">Tests Done<MetricTooltip title="Tests Done (Red)" description="Total number of attack simulations completed by Red Team and submitted to Blue Team for evaluation." /></p>
</div>
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
<p className="text-lg font-bold text-white">
@@ -595,13 +610,13 @@ export default function ExecutiveDashboardPage() {
? `${teamMetrics.red_team.avg_completion_hours}h`
: "N/A"}
</p>
<p className="text-[10px] text-gray-500">Avg Time</p>
<p className="text-[10px] text-gray-500 flex items-center justify-center gap-0.5">Avg Time<MetricTooltip title="Avg Execution Time (Red)" description="Average hours Red Team spends executing and documenting each attack simulation." /></p>
</div>
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
<p className="text-lg font-bold text-white">
{teamMetrics.red_team.rejection_rate}%
</p>
<p className="text-[10px] text-gray-500">Rejection</p>
<p className="text-[10px] text-gray-500 flex items-center justify-center gap-0.5">Rejection<MetricTooltip title="Rejection Rate (Red)" description="Percentage of Red Team submissions rejected by the Red Lead during validation review. High rejection = quality issues in test documentation." /></p>
</div>
</div>
</div>
@@ -611,13 +626,14 @@ export default function ExecutiveDashboardPage() {
<h2 className="mb-3 flex items-center gap-2 text-sm font-semibold text-blue-400">
<div className="h-2 w-2 rounded-full bg-blue-500" />
Blue Team
<MetricTooltip title="Blue Team Performance" description="Metrics for the defensive security team that analyses attacks and validates detections. Tests Done = evaluations completed; Avg Time = mean evaluation duration; Rejection = % rejected by Blue Lead." context="Lower rejection rate = consistent, high-quality detection analysis." />
</h2>
<div className="grid grid-cols-3 gap-3">
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
<p className="text-lg font-bold text-white">
{teamMetrics.blue_team.tests_completed}
</p>
<p className="text-[10px] text-gray-500">Tests Done</p>
<p className="text-[10px] text-gray-500 flex items-center justify-center gap-0.5">Tests Done<MetricTooltip title="Tests Done (Blue)" description="Total attack simulations evaluated by Blue Team — verified whether security controls detected or missed the attack." /></p>
</div>
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
<p className="text-lg font-bold text-white">
@@ -625,13 +641,13 @@ export default function ExecutiveDashboardPage() {
? `${teamMetrics.blue_team.avg_completion_hours}h`
: "N/A"}
</p>
<p className="text-[10px] text-gray-500">Avg Time</p>
<p className="text-[10px] text-gray-500 flex items-center justify-center gap-0.5">Avg Time<MetricTooltip title="Avg Evaluation Time (Blue)" description="Average hours Blue Team takes to evaluate an attack simulation and document the detection result." /></p>
</div>
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
<p className="text-lg font-bold text-white">
{teamMetrics.blue_team.rejection_rate}%
</p>
<p className="text-[10px] text-gray-500">Rejection</p>
<p className="text-[10px] text-gray-500 flex items-center justify-center gap-0.5">Rejection<MetricTooltip title="Rejection Rate (Blue)" description="Percentage of Blue Team evaluations rejected by the Blue Lead. May indicate unclear detection documentation or disagreement on results." /></p>
</div>
</div>
</div>