import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import MarkdownText from "../MarkdownText"; import { Shield, ShieldCheck, FileText, Clock, Upload, Loader2, CheckCircle, XCircle, AlertTriangle, Trash2, ExternalLink, } from "lucide-react"; import type { Test, TestResult, TeamSide, Evidence, TestTimelineEntry, User, DefensiveTechnique, } from "../../types/models"; import { RED_EDITABLE_STATES, BLUE_EDITABLE_STATES } from "../../types/models"; import { getDefensesForTechnique } from "../../api/d3fend"; import DetectionRuleChecklist from "./DetectionRuleChecklist"; import EvidenceUpload from "../EvidenceUpload"; import EvidenceList from "../EvidenceList"; // ── Tab definitions ──────────────────────────────────────────────── type TabKey = "red" | "blue" | "summary" | "timeline"; const TABS: { key: TabKey; label: string; icon: React.ReactNode }[] = [ { key: "red", label: "Red Team", icon: }, { key: "blue", label: "Blue Team", icon: }, { key: "summary", label: "Summary", icon: }, { key: "timeline", label: "Timeline", icon: }, ]; const TAB_COLORS: Record = { red: "border-orange-500 text-orange-400", blue: "border-indigo-500 text-indigo-400", summary: "border-cyan-500 text-cyan-400", timeline: "border-gray-500 text-gray-400", }; // ── Detection result options ─────────────────────────────────────── const DETECTION_RESULTS: { value: TestResult; label: string; color: string }[] = [ { value: "detected", label: "Detected", color: "border-green-500 bg-green-500/10 text-green-400" }, { value: "not_detected", label: "Not Detected", color: "border-red-500 bg-red-500/10 text-red-400" }, { value: "partially_detected", label: "Partially Detected", color: "border-yellow-500 bg-yellow-500/10 text-yellow-400", }, ]; // ── Props ────────────────────────────────────────────────────────── interface TeamTabsProps { test: Test; user: User | null; timeline: TestTimelineEntry[]; isTimelineLoading: boolean; // Red Team field handlers onRedFieldChange: (field: string, value: string | boolean) => void; redDraft: { procedure_text: string; tool_used: string; attack_success: boolean; red_summary: string; }; // Blue Team field handlers onBlueFieldChange: (field: string, value: string) => void; blueDraft: { detection_result: TestResult | ""; blue_summary: string; }; // Evidence onUploadEvidence: (file: File, team: TeamSide) => Promise; isUploading: boolean; onDownloadEvidence: (evidenceId: string) => void; onDeleteEvidence?: (evidenceId: string) => void; isDeletingEvidence?: boolean; } // ── Component ────────────────────────────────────────────────────── export default function TeamTabs({ test, user, timeline, isTimelineLoading, onRedFieldChange, redDraft, onBlueFieldChange, blueDraft, onUploadEvidence, isUploading, onDownloadEvidence, onDeleteEvidence, isDeletingEvidence, }: TeamTabsProps) { const [activeTab, setActiveTab] = useState("red"); const role = user?.role ?? ""; // Fetch D3FEND defenses for the test's technique const { data: d3fendData } = useQuery({ queryKey: ["d3fend-defenses", test.technique_mitre_id], queryFn: () => getDefensesForTechnique(test.technique_mitre_id!), enabled: !!test.technique_mitre_id, }); // Leads and admins can edit during both draft and executing phases. // Operators (red_tech) may only edit once execution has started — // the timer must be running before they can document the attack. const canEditRed = (test.state === "red_executing" && (role === "red_tech" || role === "red_lead" || role === "admin")) || (test.state === "draft" && (role === "red_lead" || role === "admin")); // Blue operators may only edit after they explicitly pick up the test // (Start Evaluation pressed → blue_work_started_at is set). // Blue leads and admins can edit at any point during blue_evaluating. const canEditBlue = BLUE_EDITABLE_STATES.includes(test.state) && ((role === "blue_lead" || role === "admin") || (role === "blue_tech" && !!test.blue_work_started_at)); // Hint messages shown to operators when editing is locked const redLockedHint = test.state === "draft" && role === "red_tech" ? "Press Start Execution to begin editing — the timer must be running first." : null; const blueLockedHint = BLUE_EDITABLE_STATES.includes(test.state) && role === "blue_tech" && !test.blue_work_started_at ? "Press Start Evaluation to begin editing — pick up the test first." : null; // ── Red Team Tab ───────────────────────────────────────────────── const renderRedTab = () => (
{/* Locked hint for red_tech in draft state */} {redLockedHint && (
{redLockedHint}
)} {/* Procedure */}
{canEditRed ? (