import { useState, useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; import { Loader2, AlertCircle, Filter, X, Grid3X3, List } from "lucide-react"; import { getTechniques, type TechniqueSummary } from "../api/techniques"; import AttackMatrix from "../components/AttackMatrix"; import type { TechniqueStatus } from "../types/models"; import { useNavigate } from "react-router-dom"; const STATUS_OPTIONS: { value: TechniqueStatus | "all"; label: string; color: string }[] = [ { value: "all", label: "All Statuses", color: "text-gray-400" }, { value: "validated", label: "Validated", color: "text-green-400" }, { value: "partial", label: "Partial", color: "text-yellow-400" }, { value: "in_progress", label: "In Progress", color: "text-blue-400" }, { value: "not_covered", label: "Not Covered", color: "text-red-400" }, { value: "not_evaluated", label: "Not Evaluated", color: "text-gray-400" }, ]; const PLATFORM_OPTIONS = ["all", "windows", "linux", "macos", "cloud", "network"] as const; const statusBadgeColors: Record = { validated: "bg-green-900/50 text-green-400 border-green-500/30", partial: "bg-yellow-900/50 text-yellow-400 border-yellow-500/30", in_progress: "bg-blue-900/50 text-blue-400 border-blue-500/30", not_covered: "bg-red-900/50 text-red-400 border-red-500/30", not_evaluated: "bg-gray-800/50 text-gray-400 border-gray-600/30", review_required: "bg-orange-900/50 text-orange-400 border-orange-500/30", }; export default function TechniquesPage() { const navigate = useNavigate(); const [viewMode, setViewMode] = useState<"matrix" | "list">("matrix"); const [statusFilter, setStatusFilter] = useState("all"); const [platformFilter, setPlatformFilter] = useState("all"); const [tacticFilter, setTacticFilter] = useState("all"); const { data: techniques, isLoading, error, } = useQuery({ queryKey: ["techniques"], queryFn: () => getTechniques(), }); // Extract unique tactics from techniques const availableTactics = useMemo(() => { if (!techniques) return []; const tactics = new Set(); for (const tech of techniques) { if (tech.tactic) { tech.tactic.split(",").forEach((t) => tactics.add(t.trim().toLowerCase())); } } return Array.from(tactics).sort(); }, [techniques]); // Apply filters const filteredTechniques = useMemo(() => { if (!techniques) return []; return techniques.filter((tech: TechniqueSummary) => { // Status filter if (statusFilter !== "all" && tech.status_global !== statusFilter) { return false; } // Tactic filter if (tacticFilter !== "all") { const techTactics = tech.tactic?.split(",").map((t) => t.trim().toLowerCase()) || []; if (!techTactics.includes(tacticFilter)) { return false; } } return true; }); }, [techniques, statusFilter, tacticFilter]); const hasActiveFilters = statusFilter !== "all" || tacticFilter !== "all" || platformFilter !== "all"; const clearFilters = () => { setStatusFilter("all"); setPlatformFilter("all"); setTacticFilter("all"); }; if (isLoading) { return (
); } if (error) { return (

Failed to load techniques

); } return (
{/* Header */}

ATT&CK Techniques

MITRE ATT&CK coverage matrix — click any technique for details

{/* Filters */}
Filters:
{/* Status filter */} {/* Tactic filter */} {/* Platform filter */} {hasActiveFilters && ( )}
Showing {filteredTechniques.length} of {techniques?.length || 0} techniques
{/* Matrix or List View */} {viewMode === "matrix" ? ( ) : (
{filteredTechniques.map((tech) => ( navigate(`/techniques/${tech.mitre_id}`)} className="cursor-pointer border-b border-gray-800/50 hover:bg-gray-800/50 transition-colors" > ))}
MITRE ID Name Tactic Status
{tech.mitre_id} {tech.name} {tech.tactic?.replace(/-/g, " ") || "—"} {tech.status_global.replace(/_/g, " ")}
{filteredTechniques.length === 0 && (
No techniques found matching your filters
)}
)} {/* Legend */}
Legend: {STATUS_OPTIONS.filter((s) => s.value !== "all").map((status) => (
{status.label}
))}
); }