424eef70c5
Before: score = (rules/max_rules)*50 + (evaluated/rules)*50 -> everything red because relative to the 1 technique with most rules After: score = min(rules/4 * 100, 100) — absolute thresholds 0 rules = gray (not covered) 1 rule = red (25 — minimal) 2 rules = orange (50 — some) 3 rules = yellow (75 — good) 4+ rules = green (100 — well covered) Also update HeatmapLegend labels to show actual rule counts instead of meaningless percentage ranges.
80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
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: "0 rules" },
|
|
{ color: "#ff6666", label: "1 rule" },
|
|
{ color: "#ff9933", label: "2 rules" },
|
|
{ color: "#ffff66", label: "3 rules" },
|
|
{ color: "#66ff66", label: "4+ rules" },
|
|
],
|
|
},
|
|
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>
|
|
);
|
|
}
|