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.
)}
);
}