import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { useNavigate } from "react-router-dom"; import { Loader2, AlertCircle, Search, Users, Shield, ChevronLeft, ChevronRight, Globe, Target, Crosshair, } from "lucide-react"; import { getThreatActors, type ThreatActorSummary, type ListThreatActorsParams, } from "../api/threat-actors"; /** Coverage colour based on percentage. */ function coverageColor(pct: number) { if (pct >= 80) return "text-green-400"; if (pct >= 50) return "text-yellow-400"; if (pct >= 20) return "text-orange-400"; return "text-red-400"; } function coverageBg(pct: number) { if (pct >= 80) return "bg-green-500"; if (pct >= 50) return "bg-yellow-500"; if (pct >= 20) return "bg-orange-500"; return "bg-red-500"; } /** Motivation badge colour. */ import MotivationBadge from "../components/MotivationBadge"; export default function ThreatActorsPage() { const navigate = useNavigate(); const [search, setSearch] = useState(""); const [motivation, setMotivation] = useState(""); const [page, setPage] = useState(0); const limit = 24; const params: ListThreatActorsParams = { offset: page * limit, limit, ...(search ? { search } : {}), ...(motivation ? { motivation } : {}), }; const { data, isLoading, error } = useQuery({ queryKey: ["threat-actors", params], queryFn: () => getThreatActors(params), }); const totalPages = data ? Math.ceil(data.total / limit) : 0; return (
{/* Header */}

Threat Actors

APT groups and threat actor profiles from MITRE ATT&CK with coverage analysis

{/* Filters */}
{/* Search */}
{ setSearch(e.target.value); setPage(0); }} className="w-full rounded-lg border border-gray-700 bg-gray-800 py-2 pl-10 pr-4 text-sm text-gray-300 placeholder-gray-500 focus:border-cyan-500 focus:outline-none" />
{/* Motivation filter */}
{/* Loading */} {isLoading && (
)} {/* Error */} {error && (

Failed to load threat actors: {(error as Error)?.message}

)} {/* Grid */} {data && data.items.length > 0 && ( <>
{data.items.map((actor: ThreatActorSummary) => ( ))}
{/* Pagination */} {totalPages > 1 && (
Showing {page * limit + 1}–{Math.min((page + 1) * limit, data.total)} of{" "} {data.total}
Page {page + 1} of {totalPages}
)} )} {/* Empty */} {data && data.items.length === 0 && (

No Threat Actors Found

Import threat actors from MITRE CTI via the Data Sources panel.

)}
); }