feat(phase-27): add advanced ATT&CK Navigator-style heatmap with layers, filters and export (T-221 to T-223)

This commit is contained in:
2026-02-09 17:16:59 +01:00
parent 57b47c296d
commit a911ddeb52
14 changed files with 2024 additions and 171 deletions
@@ -0,0 +1,79 @@
interface HeatmapLegendProps {
layerType: "coverage" | "threat-actor" | "detection-rules" | "campaign";
}
const LEGENDS: Record<
string,
{ label: string; colors: { color: string; label: string }[] }
> = {
coverage: {
label: "Coverage Status",
colors: [
{ color: "#d3d3d3", label: "Not Evaluated (0)" },
{ color: "#ff6666", label: "Not Covered (10)" },
{ color: "#ff9933", label: "In Progress (30)" },
{ color: "#ffff66", label: "Partial (60)" },
{ color: "#66ff66", label: "Validated (100)" },
],
},
"threat-actor": {
label: "Threat Actor Coverage",
colors: [
{ color: "#d3d3d3", label: "Not Used by Actor" },
{ color: "#ff6666", label: "Not Covered (10)" },
{ color: "#ff9933", label: "In Progress (30)" },
{ color: "#ffff66", label: "Partial (60)" },
{ color: "#66ff66", label: "Covered (100)" },
],
},
"detection-rules": {
label: "Detection Rules Coverage",
colors: [
{ color: "#d3d3d3", label: "No Rules (0)" },
{ color: "#ff6666", label: "Few Rules (<25)" },
{ color: "#ff9933", label: "Some Rules (25-50)" },
{ color: "#ffff66", label: "Good Coverage (50-75)" },
{ color: "#66ff66", label: "Full Coverage (75-100)" },
],
},
campaign: {
label: "Campaign Progress",
colors: [
{ color: "#ff6666", label: "Draft / Rejected" },
{ color: "#ff9933", label: "Red Executing (30)" },
{ color: "#ffff66", label: "Blue Evaluating (50)" },
{ color: "#66ff66", label: "Validated (100)" },
],
},
};
export default function HeatmapLegend({ layerType }: HeatmapLegendProps) {
const legend = LEGENDS[layerType] || LEGENDS.coverage;
return (
<div className="flex flex-wrap items-center gap-4 rounded-xl border border-gray-800 bg-gray-900 p-4">
<span className="text-sm font-medium text-gray-400">{legend.label}:</span>
{/* Gradient bar */}
<div className="flex items-center gap-1">
<div
className="h-3 w-40 rounded"
style={{
background: `linear-gradient(to right, ${legend.colors.map((c) => c.color).join(", ")})`,
}}
/>
</div>
{/* Individual labels */}
{legend.colors.map((item) => (
<div key={item.label} className="flex items-center gap-1.5">
<div
className="h-3 w-3 rounded border border-gray-700"
style={{ backgroundColor: item.color }}
/>
<span className="text-xs text-gray-400">{item.label}</span>
</div>
))}
</div>
);
}