From cea470053fe08472116e3dec1ec56145bf3addeb Mon Sep 17 00:00:00 2001 From: Kitos Date: Mon, 9 Feb 2026 11:14:44 +0100 Subject: [PATCH] feat(phase-14): redesign Test Detail page with Red/Blue tabs and dual validation (T-115, T-116, T-117, T-118) T-115: TestDetailHeader with progress bar, contextual action buttons, and dual validation indicators T-116: TeamTabs component with Red Team, Blue Team, Summary, and Timeline tabs T-117: Redesigned TestDetailPage integrating new components with react-query mutations, toast notifications, and role/state-based permissions T-118: ValidationModal for dual Red Lead / Blue Lead approval with required notes on rejection --- .../src/components/test-detail/TeamTabs.tsx | 582 ++++++++++++++++++ .../test-detail/TestDetailHeader.tsx | 329 ++++++++++ .../test-detail/ValidationModal.tsx | 196 ++++++ frontend/src/pages/TestDetailPage.tsx | 559 ++++++++++------- 4 files changed, 1434 insertions(+), 232 deletions(-) create mode 100644 frontend/src/components/test-detail/TeamTabs.tsx create mode 100644 frontend/src/components/test-detail/TestDetailHeader.tsx create mode 100644 frontend/src/components/test-detail/ValidationModal.tsx diff --git a/frontend/src/components/test-detail/TeamTabs.tsx b/frontend/src/components/test-detail/TeamTabs.tsx new file mode 100644 index 0000000..2589694 --- /dev/null +++ b/frontend/src/components/test-detail/TeamTabs.tsx @@ -0,0 +1,582 @@ +import { useState } from "react"; +import { + Shield, + ShieldCheck, + FileText, + Clock, + Upload, + Loader2, + CheckCircle, + XCircle, + AlertTriangle, + Trash2, +} from "lucide-react"; +import type { + Test, + TestResult, + TeamSide, + Evidence, + TestTimelineEntry, + User, +} from "../../types/models"; +import { RED_EDITABLE_STATES, BLUE_EDITABLE_STATES } from "../../types/models"; +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 ?? ""; + + const canEditRed = + RED_EDITABLE_STATES.includes(test.state) && + (role === "red_tech" || role === "admin"); + + const canEditBlue = + BLUE_EDITABLE_STATES.includes(test.state) && + (role === "blue_tech" || role === "admin"); + + // ── Red Team Tab ───────────────────────────────────────────────── + + const renderRedTab = () => ( +
+ {/* Procedure */} +
+ + {canEditRed ? ( +