import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Loader2, AlertCircle, Plus, Search, Crosshair, Zap, Filter, Target, } from "lucide-react"; import { listCampaigns, createCampaign, generateCampaignFromThreatActor, type CampaignSummary } from "../api/campaigns"; import { getThreatActors, type ThreatActorSummary } from "../api/threat-actors"; import { useAuth } from "../context/AuthContext"; const statusColors: Record = { draft: "bg-gray-800/50 text-gray-400 border-gray-600/30", active: "bg-cyan-900/50 text-cyan-400 border-cyan-500/30", completed: "bg-green-900/50 text-green-400 border-green-500/30", archived: "bg-gray-800/50 text-gray-500 border-gray-700/30", }; const typeColors: Record = { custom: "bg-gray-800/50 text-gray-400 border-gray-600/30", apt_emulation: "bg-red-900/50 text-red-400 border-red-500/30", kill_chain: "bg-orange-900/50 text-orange-400 border-orange-500/30", compliance: "bg-blue-900/50 text-blue-400 border-blue-500/30", }; const typeLabels: Record = { custom: "Custom", apt_emulation: "APT Emulation", kill_chain: "Kill Chain", compliance: "Compliance", }; export default function CampaignsPage() { const navigate = useNavigate(); const queryClient = useQueryClient(); const { user } = useAuth(); const [filters, setFilters] = useState({ type: "", status: "", search: "", }); const [showCreateForm, setShowCreateForm] = useState(false); const [showActorSelector, setShowActorSelector] = useState(false); const [actorSearch, setActorSearch] = useState(""); const [newCampaign, setNewCampaign] = useState({ name: "", description: "", type: "custom", target_platform: "", }); const canCreate = user?.role === "admin" || user?.role === "red_lead" || user?.role === "blue_lead"; const { data, isLoading, error } = useQuery({ queryKey: ["campaigns", filters], queryFn: () => listCampaigns({ type: filters.type || undefined, status: filters.status || undefined, search: filters.search || undefined, }), }); const createMutation = useMutation({ mutationFn: () => createCampaign(newCampaign), onSuccess: (campaign) => { queryClient.invalidateQueries({ queryKey: ["campaigns"] }); setShowCreateForm(false); setNewCampaign({ name: "", description: "", type: "custom", target_platform: "" }); navigate(`/campaigns/${campaign.id}`); }, }); // Threat actor selector data const { data: actorsData, isLoading: isLoadingActors } = useQuery({ queryKey: ["threat-actors-for-campaign", actorSearch], queryFn: () => getThreatActors({ search: actorSearch || undefined, limit: 50 }), enabled: showActorSelector, }); const generateMutation = useMutation({ mutationFn: (actorId: string) => generateCampaignFromThreatActor(actorId), onSuccess: (campaign) => { queryClient.invalidateQueries({ queryKey: ["campaigns"] }); setShowActorSelector(false); navigate(`/campaigns/${campaign.id}`); }, }); const formatDate = (dateStr: string | null) => { if (!dateStr) return ""; return new Date(dateStr).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", }); }; return (
{/* Header */}

Campaigns

Manage attack chain campaigns and APT emulations

{canCreate && ( <> )}
{/* Filters */}
setFilters((f) => ({ ...f, search: e.target.value }))} placeholder="Search campaigns..." className="w-full rounded-lg border border-gray-700 bg-gray-800 pl-10 pr-3 py-2 text-sm text-gray-200 placeholder-gray-500 focus:border-cyan-500 focus:outline-none" />
{/* Create Form Modal */} {showCreateForm && (

Create Campaign

setNewCampaign((c) => ({ ...c, name: e.target.value }))} className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-200 placeholder-gray-500 focus:border-cyan-500 focus:outline-none" placeholder="Campaign name..." />