import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { useNavigate, useSearchParams, useParams } from "react-router-dom"; import { Loader2, Search, BookOpen, Filter, ChevronLeft, ChevronRight, FlaskConical, X, } from "lucide-react"; import { getTemplates } from "../api/test-templates"; import TestFromTemplateForm from "../components/TestFromTemplateForm"; import type { TestTemplateSummary } from "../types/models"; // ── Constants ────────────────────────────────────────────────────── const PAGE_SIZE = 12; const SOURCE_OPTIONS = [ { value: "", label: "All Sources" }, { value: "atomic_red_team", label: "Atomic Red Team" }, { value: "custom", label: "Custom" }, ]; const SEVERITY_OPTIONS = [ { value: "", label: "All Severities" }, { value: "low", label: "Low" }, { value: "medium", label: "Medium" }, { value: "high", label: "High" }, { value: "critical", label: "Critical" }, ]; const PLATFORM_OPTIONS = [ { value: "", label: "All Platforms" }, { value: "windows", label: "Windows" }, { value: "linux", label: "Linux" }, { value: "macos", label: "macOS" }, { value: "azure-ad", label: "Azure AD" }, { value: "office-365", label: "Office 365" }, { value: "containers", label: "Containers" }, ]; const SOURCE_BADGE: Record = { atomic_red_team: "bg-red-900/50 text-red-400 border-red-500/30", custom: "bg-cyan-900/50 text-cyan-400 border-cyan-500/30", }; const SEVERITY_BADGE: Record = { low: "bg-blue-900/50 text-blue-400 border-blue-500/30", medium: "bg-yellow-900/50 text-yellow-400 border-yellow-500/30", high: "bg-orange-900/50 text-orange-400 border-orange-500/30", critical: "bg-red-900/50 text-red-400 border-red-500/30", }; // ── Component ────────────────────────────────────────────────────── export default function TestCatalogPage() { const navigate = useNavigate(); const { templateId } = useParams<{ templateId: string }>(); const [searchParams, setSearchParams] = useSearchParams(); const [search, setSearch] = useState(searchParams.get("search") || ""); const [source, setSource] = useState(searchParams.get("source") || ""); const [platform, setPlatform] = useState(searchParams.get("platform") || ""); const [severity, setSeverity] = useState(searchParams.get("severity") || ""); const [page, setPage] = useState(0); // Build filters const filters = { search: search || undefined, source: source || undefined, platform: platform || undefined, severity: severity || undefined, offset: page * PAGE_SIZE, limit: PAGE_SIZE, }; const { data: templates = [], isLoading } = useQuery({ queryKey: ["test-templates", filters], queryFn: () => getTemplates(filters), }); // ── Filter handlers ────────────────────────────────────────────── const applyFilters = () => { setPage(0); const params = new URLSearchParams(); if (search) params.set("search", search); if (source) params.set("source", source); if (platform) params.set("platform", platform); if (severity) params.set("severity", severity); setSearchParams(params); }; const clearFilters = () => { setSearch(""); setSource(""); setPlatform(""); setSeverity(""); setPage(0); setSearchParams({}); }; const hasActiveFilters = search || source || platform || severity; // ── Render ─────────────────────────────────────────────────────── return (
{/* Page header */}

Test Catalog

Browse available test templates. Use a template to quickly create a security test.

{/* Filters bar */}
{/* Search */}
setSearch(e.target.value)} onKeyDown={(e) => e.key === "Enter" && applyFilters()} placeholder="Search templates..." className="w-full rounded-lg border border-gray-700 bg-gray-800 py-2 pl-9 pr-3 text-sm text-gray-200 placeholder-gray-500 focus:border-cyan-500 focus:outline-none focus:ring-1 focus:ring-cyan-500" />
{/* Source */}
{/* Platform */}
{/* Severity */}
{/* Buttons */} {hasActiveFilters && ( )}
{/* Results */} {isLoading ? (
) : templates.length === 0 ? (

No templates found

{hasActiveFilters ? "Try adjusting your filters or search term." : "Import Atomic Red Team tests from the System page to populate the catalog."}

) : ( <> {/* Template Grid */}
{templates.map((tpl) => ( navigate(`/test-catalog/${tpl.id}/use`)} /> ))}
{/* Pagination */}

Showing {page * PAGE_SIZE + 1} {" - "} {page * PAGE_SIZE + templates.length}

Page {page + 1}
)} {/* Template instantiation modal */} {templateId && ( navigate("/test-catalog")} /> )}
); } // ── Template Card ────────────────────────────────────────────────── function TemplateCard({ template, onUse, }: { template: TestTemplateSummary; onUse: () => void; }) { return (
{/* Header */}

{template.name}

{/* Badges */}
{/* Technique */} {template.mitre_technique_id} {/* Source */} {template.source === "atomic_red_team" ? "Atomic" : template.source} {/* Platform */} {template.platform && ( {template.platform} )} {/* Severity */} {template.severity && ( {template.severity} )}
{/* Spacer */}
{/* Action */}
); }