Files
Aegis/frontend/src/components/heatmap/HeatmapLayerSelector.tsx
T

121 lines
4.2 KiB
TypeScript

import { useState, useEffect } from "react";
import { useQuery } from "@tanstack/react-query";
import { Shield, User, Search, ClipboardList } from "lucide-react";
import { getThreatActors, type ThreatActorSummary } from "../../api/threat-actors";
import { listCampaigns, type CampaignSummary } from "../../api/campaigns";
export type LayerType = "coverage" | "threat-actor" | "detection-rules" | "campaign";
interface HeatmapLayerSelectorProps {
activeLayer: LayerType;
onLayerChange: (layer: LayerType) => void;
selectedActorId: string | null;
onActorChange: (actorId: string | null) => void;
selectedCampaignId: string | null;
onCampaignChange: (campaignId: string | null) => void;
}
const LAYERS: {
id: LayerType;
label: string;
icon: React.FC<{ className?: string }>;
}[] = [
{ id: "coverage", label: "Coverage", icon: Shield },
{ id: "threat-actor", label: "Threat Actor", icon: User },
{ id: "detection-rules", label: "Detection Rules", icon: Search },
{ id: "campaign", label: "Campaign", icon: ClipboardList },
];
export default function HeatmapLayerSelector({
activeLayer,
onLayerChange,
selectedActorId,
onActorChange,
selectedCampaignId,
onCampaignChange,
}: HeatmapLayerSelectorProps) {
// Fetch actors for dropdown
const { data: actorsData } = useQuery({
queryKey: ["threat-actors-selector"],
queryFn: () => getThreatActors({ limit: 200 }),
enabled: activeLayer === "threat-actor",
});
// Fetch campaigns for dropdown
const { data: campaignsData } = useQuery({
queryKey: ["campaigns-selector"],
queryFn: () => listCampaigns({ limit: 200 }),
enabled: activeLayer === "campaign",
});
const actors: ThreatActorSummary[] = actorsData?.items || [];
const campaigns: CampaignSummary[] = campaignsData?.items || [];
// Auto-select first actor/campaign if none selected
useEffect(() => {
if (activeLayer === "threat-actor" && !selectedActorId && actors.length > 0) {
onActorChange(actors[0].id);
}
}, [activeLayer, actors, selectedActorId, onActorChange]);
useEffect(() => {
if (activeLayer === "campaign" && !selectedCampaignId && campaigns.length > 0) {
onCampaignChange(campaigns[0].id);
}
}, [activeLayer, campaigns, selectedCampaignId, onCampaignChange]);
return (
<div className="flex flex-wrap items-center gap-3">
{/* Layer type tabs */}
<div className="flex rounded-lg border border-gray-700 bg-gray-900 p-0.5">
{LAYERS.map((layer) => (
<button
key={layer.id}
onClick={() => onLayerChange(layer.id)}
className={`flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors ${
activeLayer === layer.id
? "bg-cyan-500/20 text-cyan-400"
: "text-gray-400 hover:bg-gray-800 hover:text-gray-200"
}`}
>
<layer.icon className="h-3.5 w-3.5" />
{layer.label}
</button>
))}
</div>
{/* Actor dropdown */}
{activeLayer === "threat-actor" && (
<select
value={selectedActorId || ""}
onChange={(e) => onActorChange(e.target.value || null)}
className="rounded-lg border border-gray-700 bg-gray-800 px-3 py-1.5 text-sm text-gray-200 focus:border-cyan-500 focus:outline-none"
>
<option value="">Select Threat Actor...</option>
{actors.map((actor) => (
<option key={actor.id} value={actor.id}>
{actor.name} {actor.country ? `(${actor.country})` : ""}
</option>
))}
</select>
)}
{/* Campaign dropdown */}
{activeLayer === "campaign" && (
<select
value={selectedCampaignId || ""}
onChange={(e) => onCampaignChange(e.target.value || null)}
className="rounded-lg border border-gray-700 bg-gray-800 px-3 py-1.5 text-sm text-gray-200 focus:border-cyan-500 focus:outline-none"
>
<option value="">Select Campaign...</option>
{campaigns.map((campaign) => (
<option key={campaign.id} value={campaign.id}>
{campaign.name} ({campaign.status})
</option>
))}
</select>
)}
</div>
);
}