import { useQuery } from "@tanstack/react-query"; import { useNavigate } from "react-router-dom"; import { Shield, CheckCircle, AlertTriangle, XCircle, Clock, HelpCircle, Percent, Loader2, AlertCircle, Play, Eye, Users, TrendingUp, ArrowRight, } from "lucide-react"; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, } from "recharts"; import { getCoverageSummary, getCoverageByTactic, getTestPipeline, getTeamActivity, getValidationRate, getRecentTests, type TestPipelineCounts, type TeamActivityItem, type ValidationRateItem, type RecentTestItem, } from "../api/metrics"; import { getCoverageEvolution } from "../api/snapshots"; import CoverageSummaryCard from "../components/CoverageSummaryCard"; import TacticCoverageChart from "../components/TacticCoverageChart"; import type { TestState } from "../types/models"; /* ── Badge colours (reused from TestsPage) ─────────────────────────── */ const testStateBadgeColors: Record = { draft: "bg-gray-800/50 text-gray-400 border-gray-600/30", red_executing: "bg-orange-900/50 text-orange-400 border-orange-500/30", blue_evaluating: "bg-indigo-900/50 text-indigo-400 border-indigo-500/30", in_review: "bg-blue-900/50 text-blue-400 border-blue-500/30", validated: "bg-green-900/50 text-green-400 border-green-500/30", rejected: "bg-red-900/50 text-red-400 border-red-500/30", }; const testStateLabels: Record = { draft: "Draft", red_executing: "Red Executing", blue_evaluating: "Blue Evaluating", in_review: "In Review", validated: "Validated", rejected: "Rejected", }; /* ── Component ──────────────────────────────────────────────────────── */ export default function DashboardPage() { const navigate = useNavigate(); // Existing coverage queries const { data: summary, isLoading: summaryLoading, error: summaryError, } = useQuery({ queryKey: ["metrics", "summary"], queryFn: getCoverageSummary, }); const { data: tactics, isLoading: tacticsLoading, error: tacticsError, } = useQuery({ queryKey: ["metrics", "by-tactic"], queryFn: getCoverageByTactic, }); // V2 queries — retry:2 so transient failures don't leave widgets blank const { data: pipeline, isLoading: pipelineLoading, isError: pipelineError } = useQuery({ queryKey: ["metrics", "test-pipeline"], queryFn: getTestPipeline, retry: 2, }); const { data: teamActivity, isLoading: teamLoading, isError: teamError } = useQuery({ queryKey: ["metrics", "team-activity"], queryFn: getTeamActivity, retry: 2, }); const { data: validationRates, isLoading: validationLoading, isError: validationError } = useQuery({ queryKey: ["metrics", "validation-rate"], queryFn: getValidationRate, retry: 2, }); const { data: recentTests, isLoading: recentLoading, isError: recentError } = useQuery({ queryKey: ["metrics", "recent-tests"], queryFn: getRecentTests, retry: 2, }); const { data: coverageEvolution, isLoading: evolutionLoading } = useQuery({ queryKey: ["snapshots", "evolution", 6], queryFn: () => getCoverageEvolution(6), }); if (summaryLoading || tacticsLoading) { return (
); } if (summaryError || tacticsError) { return (

Failed to load metrics

{summaryError?.message || tacticsError?.message || "Unknown error"}

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

Dashboard

MITRE ATT&CK coverage overview

{summary && (
{summary.coverage_percentage.toFixed(1)}% Coverage
)}
{/* Summary Cards */} {summary && (
} colorClass="text-cyan-400" bgClass="bg-gray-900" /> } colorClass="text-green-400" bgClass="bg-green-950/20" /> } colorClass="text-yellow-400" bgClass="bg-yellow-950/20" /> } colorClass="text-blue-400" bgClass="bg-blue-950/20" /> } colorClass="text-red-400" bgClass="bg-red-950/20" /> } colorClass="text-gray-400" bgClass="bg-gray-900" />
)} {/* Coverage evolution (snapshots) */}

Coverage Evolution (6 months)

{evolutionLoading ? (
) : coverageEvolution && coverageEvolution.length > 0 ? ( { const d = new Date(val); return `${d.getMonth() + 1}/${d.getDate()}`; }} /> ) : (

No snapshots yet. Weekly snapshots populate this chart automatically.

)}
{/* ── V2 Section: Test Pipeline ────────────────────────────────── */}

Test Pipeline

{pipelineLoading ? (
) : pipelineError ? (

Could not load pipeline data.

) : pipeline ? ( ) : null}
{/* ── V2 Section: Team Activity + Validation Rate ──────────────── */}
{/* Team Activity */}

Team Activity

{teamLoading ? (
) : teamError ? (

Could not load team activity.

) : teamActivity ? (
{teamActivity.map((team: TeamActivityItem) => { const isRed = team.team.toLowerCase().includes("red"); const total = team.tests_completed + team.tests_pending; const pct = total > 0 ? (team.tests_completed / total) * 100 : 0; return (
{team.team}
{team.tests_completed} completed / {team.tests_pending} pending

{pct.toFixed(0)}% completion rate

); })}
) : null}
{/* Validation Rate */}

Validation Rate

{validationLoading ? (
) : validationError ? (

Could not load validation data.

) : validationRates ? (
{validationRates.map((rate: ValidationRateItem) => { const isRed = rate.role === "red_lead"; return (
{isRed ? "Red Lead" : "Blue Lead"}
{rate.total_reviewed} reviewed
{rate.approved} approved
{rate.rejected} rejected

{rate.approval_rate}% approval rate

); })}
) : null}
{/* ── V2 Section: Recent Tests ─────────────────────────────────── */}

Recent Tests

{recentLoading ? (
) : recentError ? (

Could not load recent tests — refresh the page.

) : recentTests && recentTests.length > 0 ? (
{recentTests.map((t: RecentTestItem) => ( navigate(`/tests/${t.id}`)} > ))}
Name Technique State Created
{t.name} {t.technique_mitre_id ? ( {t.technique_mitre_id} ) : ( - )} {testStateLabels[t.state] || t.state} {t.created_at ? new Date(t.created_at).toLocaleDateString("en-US", { month: "short", day: "numeric", }) : "-"}
) : (
No tests created yet.
)}
{/* Tactic Coverage Table (original V1) */} {tactics && }
); } /* ── Pipeline Funnel Sub-component ─────────────────────────────────── */ function PipelineFunnel({ pipeline }: { pipeline: TestPipelineCounts }) { const stages: { key: keyof TestPipelineCounts; label: string; color: string; icon: React.ReactNode }[] = [ { key: "draft", label: "Draft", color: "bg-gray-600", icon: }, { key: "red_executing", label: "Red Executing", color: "bg-orange-500", icon: }, { key: "blue_evaluating", label: "Blue Evaluating", color: "bg-indigo-500", icon: }, { key: "in_review", label: "In Review", color: "bg-blue-500", icon: }, { key: "validated", label: "Validated", color: "bg-green-500", icon: }, { key: "rejected", label: "Rejected", color: "bg-red-500", icon: }, ]; const maxCount = Math.max(...stages.map((s) => pipeline[s.key] as number), 1); return (
{stages.map((stage) => { const count = pipeline[stage.key] as number; const pct = (count / maxCount) * 100; return (
{stage.icon} {stage.label}
0 ? 8 : 0)}%` }} > {count > 0 && ( {count} )}
{count}
); })}
Total tests {pipeline.total}
); }