diff --git a/frontend/src/components/MotivationBadge.tsx b/frontend/src/components/MotivationBadge.tsx new file mode 100644 index 0000000..d1f703c --- /dev/null +++ b/frontend/src/components/MotivationBadge.tsx @@ -0,0 +1,103 @@ +/** + * MotivationBadge — renders a threat actor motivation pill with a + * CSS-only hover tooltip explaining what the motivation means. + */ + +const COLORS: Record = { + espionage: "border-purple-500/30 bg-purple-900/50 text-purple-400", + financial: "border-yellow-500/30 bg-yellow-900/50 text-yellow-400", + destruction: "border-red-500/30 bg-red-900/50 text-red-400", + hacktivism: "border-cyan-500/30 bg-cyan-900/50 text-cyan-400", +}; + +const DEFAULT_COLOR = "border-gray-600/30 bg-gray-800/50 text-gray-400"; + +interface TooltipLine { label: string; text: string } + +const TOOLTIPS: Record = { + espionage: { + heading: "🕵️ Espionage", + lines: [ + { label: "Goal", text: "Steal sensitive data, intellectual property, or government secrets for intelligence purposes." }, + { label: "Typical", text: "Nation-state APTs, long-duration intrusions, low detection priority." }, + { label: "Example", text: "APT28, APT29, Turla, Lazarus Group (intelligence ops)." }, + ], + }, + financial: { + heading: "💰 Financial", + lines: [ + { label: "Goal", text: "Monetary gain through fraud, ransomware, cryptocurrency theft, or banking trojans." }, + { label: "Typical", text: "Opportunistic attacks, ransomware deployment, data exfiltration for sale." }, + { label: "Example", text: "FIN7, Carbanak, Cobalt Group, Wizard Spider." }, + ], + }, + destruction: { + heading: "💥 Destruction", + lines: [ + { label: "Goal", text: "Disrupt or destroy critical infrastructure, systems, or data — often geopolitically motivated." }, + { label: "Typical", text: "Wiper malware, ICS/SCADA attacks, denial of service campaigns." }, + { label: "Example", text: "Sandworm, APT37 (destructive ops), Cleaver." }, + ], + }, + hacktivism: { + heading: "✊ Hacktivism", + lines: [ + { label: "Goal", text: "Promote political or ideological causes through defacement, leaks, or disruptive attacks." }, + { label: "Typical", text: "Website defacement, data leaks, DDoS campaigns, public statements." }, + { label: "Example", text: "Anonymous Sudan, various politically motivated collectives." }, + ], + }, +}; + +interface MotivationBadgeProps { + motivation: string; + size?: "sm" | "md"; + /** Show icon before label */ + showIcon?: boolean; + className?: string; +} + +export default function MotivationBadge({ + motivation, + size = "md", + className = "", +}: MotivationBadgeProps) { + const key = motivation.toLowerCase(); + const color = COLORS[key] ?? DEFAULT_COLOR; + const tip = TOOLTIPS[key]; + const pill = size === "sm" ? "px-2 py-0.5 text-[10px]" : "px-2 py-0.5 text-[11px]"; + + return ( + + {/* Badge */} + + {motivation} + + + {/* Tooltip — below badge */} + {tip && ( + + {/* Arrow */} + +

{tip.heading}

+ {tip.lines.map(({ label, text }) => ( +
+ + {label} + +

{text}

+
+ ))} +
+ )} +
+ ); +} diff --git a/frontend/src/pages/ThreatActorDetailPage.tsx b/frontend/src/pages/ThreatActorDetailPage.tsx index f4a602f..77b434f 100644 --- a/frontend/src/pages/ThreatActorDetailPage.tsx +++ b/frontend/src/pages/ThreatActorDetailPage.tsx @@ -1,5 +1,6 @@ import { useParams, useNavigate } from "react-router-dom"; import MarkdownText from "../components/MarkdownText"; +import MotivationBadge from "../components/MotivationBadge"; import { useQuery } from "@tanstack/react-query"; import { Loader2, @@ -218,10 +219,7 @@ export default function ThreatActorDetailPage() {
)} {actor.motivation && ( - - - {actor.motivation} - + )} {actor.sophistication && ( diff --git a/frontend/src/pages/ThreatActorsPage.tsx b/frontend/src/pages/ThreatActorsPage.tsx index 65e6383..33e2d49 100644 --- a/frontend/src/pages/ThreatActorsPage.tsx +++ b/frontend/src/pages/ThreatActorsPage.tsx @@ -35,20 +35,7 @@ function coverageBg(pct: number) { } /** Motivation badge colour. */ -function motivationColor(m: string | null) { - switch (m?.toLowerCase()) { - case "espionage": - return "border-purple-500/30 bg-purple-900/50 text-purple-400"; - case "financial": - return "border-yellow-500/30 bg-yellow-900/50 text-yellow-400"; - case "destruction": - return "border-red-500/30 bg-red-900/50 text-red-400"; - case "hacktivism": - return "border-cyan-500/30 bg-cyan-900/50 text-cyan-400"; - default: - return "border-gray-600/30 bg-gray-800/50 text-gray-400"; - } -} +import MotivationBadge from "../components/MotivationBadge"; export default function ThreatActorsPage() { const navigate = useNavigate(); @@ -161,9 +148,7 @@ export default function ThreatActorsPage() { )} {actor.motivation && ( - - {actor.motivation} - + )}