feat(dashboards): hover tooltips on all metric cards
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
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:
@@ -1,4 +1,5 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
import MetricTooltip from "./MetricTooltip";
|
||||||
|
|
||||||
interface CoverageSummaryCardProps {
|
interface CoverageSummaryCardProps {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -7,6 +8,8 @@ interface CoverageSummaryCardProps {
|
|||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
colorClass: string;
|
colorClass: string;
|
||||||
bgClass: string;
|
bgClass: string;
|
||||||
|
/** Optional tooltip explaining this metric to non-technical users */
|
||||||
|
tooltip?: { description: string; context?: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CoverageSummaryCard({
|
export default function CoverageSummaryCard({
|
||||||
@@ -16,6 +19,7 @@ export default function CoverageSummaryCard({
|
|||||||
icon,
|
icon,
|
||||||
colorClass,
|
colorClass,
|
||||||
bgClass,
|
bgClass,
|
||||||
|
tooltip,
|
||||||
}: CoverageSummaryCardProps) {
|
}: CoverageSummaryCardProps) {
|
||||||
const percentage = total && total > 0 ? ((value / total) * 100).toFixed(1) : null;
|
const percentage = total && total > 0 ? ((value / total) * 100).toFixed(1) : null;
|
||||||
|
|
||||||
@@ -23,7 +27,16 @@ export default function CoverageSummaryCard({
|
|||||||
<div className={`rounded-xl border border-gray-800 ${bgClass} p-5`}>
|
<div className={`rounded-xl border border-gray-800 ${bgClass} p-5`}>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-400">{title}</p>
|
<p className="text-sm font-medium text-gray-400 flex items-center">
|
||||||
|
{title}
|
||||||
|
{tooltip && (
|
||||||
|
<MetricTooltip
|
||||||
|
title={title}
|
||||||
|
description={tooltip.description}
|
||||||
|
context={tooltip.context}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
<p className={`mt-1 text-3xl font-bold ${colorClass}`}>{value}</p>
|
<p className={`mt-1 text-3xl font-bold ${colorClass}`}>{value}</p>
|
||||||
{percentage !== null && (
|
{percentage !== null && (
|
||||||
<p className="mt-1 text-xs text-gray-500">{percentage}% of total</p>
|
<p className="mt-1 text-xs text-gray-500">{percentage}% of total</p>
|
||||||
|
|||||||
58
frontend/src/components/MetricTooltip.tsx
Normal file
58
frontend/src/components/MetricTooltip.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* MetricTooltip — a small ⓘ icon that shows a plain-language explanation
|
||||||
|
* of a metric on hover. Designed for executive dashboards where not everyone
|
||||||
|
* knows what "MTTD" or "partial coverage" means.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface MetricTooltipProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
/** Optional extra context line (e.g. "Why it matters") */
|
||||||
|
context?: string;
|
||||||
|
/** Positioning hint — defaults to 'below' */
|
||||||
|
position?: "below" | "above" | "left";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MetricTooltip({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
context,
|
||||||
|
position = "below",
|
||||||
|
}: MetricTooltipProps) {
|
||||||
|
const posClass =
|
||||||
|
position === "above"
|
||||||
|
? "bottom-full mb-2 left-0"
|
||||||
|
: position === "left"
|
||||||
|
? "right-full mr-2 top-0"
|
||||||
|
: "top-full mt-2 left-0";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="relative group inline-flex items-center ml-1.5 align-middle">
|
||||||
|
{/* The ⓘ icon */}
|
||||||
|
<span className="flex h-4 w-4 cursor-help items-center justify-center rounded-full border border-gray-600 bg-gray-800 text-[9px] font-bold text-gray-400 hover:border-cyan-500/50 hover:text-cyan-400 transition-colors select-none">
|
||||||
|
i
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Tooltip panel */}
|
||||||
|
<span
|
||||||
|
className={`
|
||||||
|
pointer-events-none absolute ${posClass} z-50 w-64
|
||||||
|
rounded-xl border border-gray-700 bg-gray-900 p-3 shadow-xl
|
||||||
|
opacity-0 transition-opacity duration-150 group-hover:opacity-100
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{/* Arrow */}
|
||||||
|
{position !== "above" && position !== "left" && (
|
||||||
|
<span className="absolute bottom-full left-3 border-4 border-transparent border-b-gray-700" />
|
||||||
|
)}
|
||||||
|
<p className="mb-1.5 text-xs font-semibold text-white">{title}</p>
|
||||||
|
<p className="text-[11px] leading-snug text-gray-300">{description}</p>
|
||||||
|
{context && (
|
||||||
|
<p className="mt-1.5 text-[10px] leading-snug text-cyan-400/80 border-t border-gray-800 pt-1.5">
|
||||||
|
{context}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -42,6 +42,7 @@ import {
|
|||||||
import { getCoverageEvolution } from "../api/snapshots";
|
import { getCoverageEvolution } from "../api/snapshots";
|
||||||
import CoverageSummaryCard from "../components/CoverageSummaryCard";
|
import CoverageSummaryCard from "../components/CoverageSummaryCard";
|
||||||
import TacticCoverageChart from "../components/TacticCoverageChart";
|
import TacticCoverageChart from "../components/TacticCoverageChart";
|
||||||
|
import MetricTooltip from "../components/MetricTooltip";
|
||||||
import type { TestState } from "../types/models";
|
import type { TestState } from "../types/models";
|
||||||
|
|
||||||
/* ── Badge colours (reused from TestsPage) ─────────────────────────── */
|
/* ── Badge colours (reused from TestsPage) ─────────────────────────── */
|
||||||
@@ -199,6 +200,7 @@ export default function DashboardPage() {
|
|||||||
icon={<Shield className="h-6 w-6 text-cyan-400" />}
|
icon={<Shield className="h-6 w-6 text-cyan-400" />}
|
||||||
colorClass="text-cyan-400"
|
colorClass="text-cyan-400"
|
||||||
bgClass="bg-gray-900"
|
bgClass="bg-gray-900"
|
||||||
|
tooltip={{ description: "Total number of MITRE ATT&CK attack techniques tracked in the platform. This is the full universe of known attack methods we measure coverage against.", context: "A higher total means broader threat visibility." }}
|
||||||
/>
|
/>
|
||||||
<CoverageSummaryCard
|
<CoverageSummaryCard
|
||||||
title="Validated"
|
title="Validated"
|
||||||
@@ -207,6 +209,7 @@ export default function DashboardPage() {
|
|||||||
icon={<CheckCircle className="h-6 w-6 text-green-400" />}
|
icon={<CheckCircle className="h-6 w-6 text-green-400" />}
|
||||||
colorClass="text-green-400"
|
colorClass="text-green-400"
|
||||||
bgClass="bg-green-950/20"
|
bgClass="bg-green-950/20"
|
||||||
|
tooltip={{ description: "Techniques confirmed as detectable by Blue Team with ≥2 successful tests. These attacks would be caught if a real adversary used them.", context: "Goal: maximise this number. Higher = stronger defence." }}
|
||||||
/>
|
/>
|
||||||
<CoverageSummaryCard
|
<CoverageSummaryCard
|
||||||
title="Partial"
|
title="Partial"
|
||||||
@@ -215,6 +218,7 @@ export default function DashboardPage() {
|
|||||||
icon={<AlertTriangle className="h-6 w-6 text-yellow-400" />}
|
icon={<AlertTriangle className="h-6 w-6 text-yellow-400" />}
|
||||||
colorClass="text-yellow-400"
|
colorClass="text-yellow-400"
|
||||||
bgClass="bg-yellow-950/20"
|
bgClass="bg-yellow-950/20"
|
||||||
|
tooltip={{ description: "Techniques with incomplete coverage: only 1 validated test, mixed detection results, or tests still in progress.", context: "These need attention — detection is not yet reliable." }}
|
||||||
/>
|
/>
|
||||||
<CoverageSummaryCard
|
<CoverageSummaryCard
|
||||||
title="In Progress"
|
title="In Progress"
|
||||||
@@ -223,6 +227,7 @@ export default function DashboardPage() {
|
|||||||
icon={<Clock className="h-6 w-6 text-blue-400" />}
|
icon={<Clock className="h-6 w-6 text-blue-400" />}
|
||||||
colorClass="text-blue-400"
|
colorClass="text-blue-400"
|
||||||
bgClass="bg-blue-950/20"
|
bgClass="bg-blue-950/20"
|
||||||
|
tooltip={{ description: "Techniques with active tests currently being executed or awaiting review by leads. Not yet counted as covered.", context: "Indicates active testing work underway." }}
|
||||||
/>
|
/>
|
||||||
<CoverageSummaryCard
|
<CoverageSummaryCard
|
||||||
title="Not Covered"
|
title="Not Covered"
|
||||||
@@ -231,6 +236,7 @@ export default function DashboardPage() {
|
|||||||
icon={<XCircle className="h-6 w-6 text-red-400" />}
|
icon={<XCircle className="h-6 w-6 text-red-400" />}
|
||||||
colorClass="text-red-400"
|
colorClass="text-red-400"
|
||||||
bgClass="bg-red-950/20"
|
bgClass="bg-red-950/20"
|
||||||
|
tooltip={{ description: "Techniques that were tested but Blue Team failed to detect the attack. These are confirmed security gaps — a real attack would go unnoticed.", context: "High priority: invest in detection rules and monitoring." }}
|
||||||
/>
|
/>
|
||||||
<CoverageSummaryCard
|
<CoverageSummaryCard
|
||||||
title="Not Evaluated"
|
title="Not Evaluated"
|
||||||
@@ -239,6 +245,7 @@ export default function DashboardPage() {
|
|||||||
icon={<HelpCircle className="h-6 w-6 text-gray-400" />}
|
icon={<HelpCircle className="h-6 w-6 text-gray-400" />}
|
||||||
colorClass="text-gray-400"
|
colorClass="text-gray-400"
|
||||||
bgClass="bg-gray-900"
|
bgClass="bg-gray-900"
|
||||||
|
tooltip={{ description: "Techniques with no tests created yet. We have no data on whether these attacks would be detected or not.", context: "Unknown risk — prioritise testing the most common ones." }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -248,6 +255,7 @@ export default function DashboardPage() {
|
|||||||
<h2 className="mb-4 text-lg font-semibold text-white flex items-center gap-2">
|
<h2 className="mb-4 text-lg font-semibold text-white flex items-center gap-2">
|
||||||
<TrendingUp className="h-5 w-5 text-cyan-400" />
|
<TrendingUp className="h-5 w-5 text-cyan-400" />
|
||||||
Coverage Evolution (6 months)
|
Coverage Evolution (6 months)
|
||||||
|
<MetricTooltip title="Coverage Evolution" description="How overall coverage % and organisation score have changed over the last 6 months. An upward trend means the security posture is improving." context="Org Score reflects depth of testing; Coverage % reflects breadth." />
|
||||||
</h2>
|
</h2>
|
||||||
{evolutionLoading ? (
|
{evolutionLoading ? (
|
||||||
<div className="flex h-48 items-center justify-center">
|
<div className="flex h-48 items-center justify-center">
|
||||||
@@ -310,6 +318,7 @@ export default function DashboardPage() {
|
|||||||
<h2 className="text-lg font-semibold text-white flex items-center gap-2">
|
<h2 className="text-lg font-semibold text-white flex items-center gap-2">
|
||||||
<TrendingUp className="h-5 w-5 text-cyan-400" />
|
<TrendingUp className="h-5 w-5 text-cyan-400" />
|
||||||
Test Pipeline
|
Test Pipeline
|
||||||
|
<MetricTooltip title="Test Pipeline" description="Shows how many security tests are at each stage of the Red/Blue workflow: Draft → Red Executing → Blue Evaluating → In Review → Validated / Rejected." context="A healthy pipeline has tests flowing through to Validated. Backlogs in any stage indicate bottlenecks." />
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate("/tests")}
|
onClick={() => navigate("/tests")}
|
||||||
@@ -337,6 +346,7 @@ export default function DashboardPage() {
|
|||||||
<h2 className="mb-4 text-lg font-semibold text-white flex items-center gap-2">
|
<h2 className="mb-4 text-lg font-semibold text-white flex items-center gap-2">
|
||||||
<Users className="h-5 w-5 text-cyan-400" />
|
<Users className="h-5 w-5 text-cyan-400" />
|
||||||
Team Activity
|
Team Activity
|
||||||
|
<MetricTooltip title="Team Activity" description="Shows Red Team (attackers) and Blue Team (defenders) productivity. 'Completed' = tests passed through their phase; 'Pending' = tests waiting for action." context="Low completion rate may indicate resource constraints or bottlenecks." />
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{teamLoading ? (
|
{teamLoading ? (
|
||||||
@@ -389,6 +399,7 @@ export default function DashboardPage() {
|
|||||||
<h2 className="mb-4 text-lg font-semibold text-white flex items-center gap-2">
|
<h2 className="mb-4 text-lg font-semibold text-white flex items-center gap-2">
|
||||||
<CheckCircle className="h-5 w-5 text-cyan-400" />
|
<CheckCircle className="h-5 w-5 text-cyan-400" />
|
||||||
Validation Rate
|
Validation Rate
|
||||||
|
<MetricTooltip title="Validation Rate" description="How many test results Red Lead and Blue Lead have reviewed and approved vs rejected. High approval = good quality tests and clear detection results." context="High rejection rate may indicate unclear test procedures or disagreement on results." />
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{validationLoading ? (
|
{validationLoading ? (
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
Cell,
|
Cell,
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { getOrganizationScore, getScoreHistory } from "../api/scores";
|
import { getOrganizationScore, getScoreHistory } from "../api/scores";
|
||||||
|
import MetricTooltip from "../components/MetricTooltip";
|
||||||
import {
|
import {
|
||||||
getOperationalMetrics,
|
getOperationalMetrics,
|
||||||
getMetricsByTeam,
|
getMetricsByTeam,
|
||||||
@@ -87,11 +88,13 @@ function KPICard({
|
|||||||
value,
|
value,
|
||||||
unit,
|
unit,
|
||||||
trend,
|
trend,
|
||||||
|
tooltip,
|
||||||
}: {
|
}: {
|
||||||
label: string;
|
label: string;
|
||||||
value: string | number;
|
value: string | number;
|
||||||
unit?: string;
|
unit?: string;
|
||||||
trend?: "improving" | "declining" | "stable" | null;
|
trend?: "improving" | "declining" | "stable" | null;
|
||||||
|
tooltip?: { description: string; context?: string };
|
||||||
}) {
|
}) {
|
||||||
const TrendIcon =
|
const TrendIcon =
|
||||||
trend === "improving"
|
trend === "improving"
|
||||||
@@ -109,7 +112,10 @@ function KPICard({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-gray-800 bg-gray-900 p-4">
|
<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 className="mt-2 flex items-end justify-between">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-2xl font-bold text-white">
|
<span className="text-2xl font-bold text-white">
|
||||||
@@ -256,33 +262,34 @@ export default function ExecutiveDashboardPage() {
|
|||||||
<p className="text-lg font-bold text-white">
|
<p className="text-lg font-bold text-white">
|
||||||
{orgScore?.total_coverage ?? 0}
|
{orgScore?.total_coverage ?? 0}
|
||||||
</p>
|
</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 0–100." position="above" /></p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
||||||
<p className="text-lg font-bold text-white">
|
<p className="text-lg font-bold text-white">
|
||||||
{orgScore?.detection_maturity ?? 0}
|
{orgScore?.detection_maturity ?? 0}
|
||||||
</p>
|
</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 0–100." position="above" /></p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
||||||
<p className="text-lg font-bold text-white">
|
<p className="text-lg font-bold text-white">
|
||||||
{orgScore?.critical_coverage ?? 0}
|
{orgScore?.critical_coverage ?? 0}
|
||||||
</p>
|
</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 0–100." position="above" /></p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
||||||
<p className="text-lg font-bold text-white">
|
<p className="text-lg font-bold text-white">
|
||||||
{orgScore?.response_readiness ?? 0}
|
{orgScore?.response_readiness ?? 0}
|
||||||
</p>
|
</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 0–100." position="above" /></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Section 2: Trend Chart */}
|
{/* Section 2: Trend Chart */}
|
||||||
<div className="rounded-xl border border-gray-800 bg-gray-900 p-4 lg:col-span-3">
|
<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)
|
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>
|
</h2>
|
||||||
<ResponsiveContainer width="100%" height={220}>
|
<ResponsiveContainer width="100%" height={220}>
|
||||||
<LineChart data={scoreHistory || []}>
|
<LineChart data={scoreHistory || []}>
|
||||||
@@ -323,8 +330,9 @@ export default function ExecutiveDashboardPage() {
|
|||||||
|
|
||||||
{/* Section 3: Top Threat Actors */}
|
{/* Section 3: Top Threat Actors */}
|
||||||
<div className="rounded-xl border border-gray-800 bg-gray-900 p-4">
|
<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
|
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>
|
</h2>
|
||||||
<p className="mb-3 text-[10px] text-gray-500">
|
<p className="mb-3 text-[10px] text-gray-500">
|
||||||
Ranked by uncovered techniques (most exposure first)
|
Ranked by uncovered techniques (most exposure first)
|
||||||
@@ -395,29 +403,34 @@ export default function ExecutiveDashboardPage() {
|
|||||||
label="MTTD"
|
label="MTTD"
|
||||||
value={opMetrics?.mttd?.mean_hours ?? "N/A"}
|
value={opMetrics?.mttd?.mean_hours ?? "N/A"}
|
||||||
unit={opMetrics?.mttd ? "hrs" : undefined}
|
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
|
<KPICard
|
||||||
label="MTTR"
|
label="MTTR"
|
||||||
value={opMetrics?.mttr?.mean_hours ?? "N/A"}
|
value={opMetrics?.mttr?.mean_hours ?? "N/A"}
|
||||||
unit={opMetrics?.mttr ? "hrs" : undefined}
|
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
|
<KPICard
|
||||||
label="Detection Efficacy"
|
label="Detection Efficacy"
|
||||||
value={opMetrics?.detection_efficacy?.percentage ?? 0}
|
value={opMetrics?.detection_efficacy?.percentage ?? 0}
|
||||||
unit="%"
|
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
|
<KPICard
|
||||||
label="Validation Throughput"
|
label="Validation Throughput"
|
||||||
value={opMetrics?.validation_throughput?.tests_per_week ?? 0}
|
value={opMetrics?.validation_throughput?.tests_per_week ?? 0}
|
||||||
unit="/week"
|
unit="/week"
|
||||||
trend={opMetrics?.validation_throughput?.trend}
|
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>
|
</div>
|
||||||
|
|
||||||
{/* Section 5: Coverage by Tactic */}
|
{/* Section 5: Coverage by Tactic */}
|
||||||
<div className="rounded-xl border border-gray-800 bg-gray-900 p-4">
|
<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
|
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>
|
</h2>
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
<ResponsiveContainer width="100%" height={300}>
|
||||||
<BarChart
|
<BarChart
|
||||||
@@ -462,8 +475,9 @@ export default function ExecutiveDashboardPage() {
|
|||||||
{/* Section 6: Critical Gaps */}
|
{/* Section 6: Critical Gaps */}
|
||||||
<div className="rounded-xl border border-gray-800 bg-gray-900 p-4">
|
<div className="rounded-xl border border-gray-800 bg-gray-900 p-4">
|
||||||
<div className="mb-3 flex items-center justify-between">
|
<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
|
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>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={() => computeMutation.mutate()}
|
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">
|
<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" />
|
<div className="h-2 w-2 rounded-full bg-red-500" />
|
||||||
Red Team
|
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>
|
</h2>
|
||||||
<div className="grid grid-cols-3 gap-3">
|
<div className="grid grid-cols-3 gap-3">
|
||||||
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
||||||
<p className="text-lg font-bold text-white">
|
<p className="text-lg font-bold text-white">
|
||||||
{teamMetrics.red_team.tests_completed}
|
{teamMetrics.red_team.tests_completed}
|
||||||
</p>
|
</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>
|
||||||
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
||||||
<p className="text-lg font-bold text-white">
|
<p className="text-lg font-bold text-white">
|
||||||
@@ -595,13 +610,13 @@ export default function ExecutiveDashboardPage() {
|
|||||||
? `${teamMetrics.red_team.avg_completion_hours}h`
|
? `${teamMetrics.red_team.avg_completion_hours}h`
|
||||||
: "N/A"}
|
: "N/A"}
|
||||||
</p>
|
</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>
|
||||||
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
||||||
<p className="text-lg font-bold text-white">
|
<p className="text-lg font-bold text-white">
|
||||||
{teamMetrics.red_team.rejection_rate}%
|
{teamMetrics.red_team.rejection_rate}%
|
||||||
</p>
|
</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>
|
</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">
|
<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" />
|
<div className="h-2 w-2 rounded-full bg-blue-500" />
|
||||||
Blue Team
|
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>
|
</h2>
|
||||||
<div className="grid grid-cols-3 gap-3">
|
<div className="grid grid-cols-3 gap-3">
|
||||||
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
||||||
<p className="text-lg font-bold text-white">
|
<p className="text-lg font-bold text-white">
|
||||||
{teamMetrics.blue_team.tests_completed}
|
{teamMetrics.blue_team.tests_completed}
|
||||||
</p>
|
</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>
|
||||||
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
||||||
<p className="text-lg font-bold text-white">
|
<p className="text-lg font-bold text-white">
|
||||||
@@ -625,13 +641,13 @@ export default function ExecutiveDashboardPage() {
|
|||||||
? `${teamMetrics.blue_team.avg_completion_hours}h`
|
? `${teamMetrics.blue_team.avg_completion_hours}h`
|
||||||
: "N/A"}
|
: "N/A"}
|
||||||
</p>
|
</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>
|
||||||
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
<div className="rounded-lg bg-gray-800 px-3 py-2 text-center">
|
||||||
<p className="text-lg font-bold text-white">
|
<p className="text-lg font-bold text-white">
|
||||||
{teamMetrics.blue_team.rejection_rate}%
|
{teamMetrics.blue_team.rejection_rate}%
|
||||||
</p>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user