feat: Phase 8 - Frontend main views (T-026 to T-031)

Implement all main frontend views for the MITRE ATT&CK coverage platform:

- T-026: Dashboard with coverage summary cards and tactic breakdown table

- T-027: Interactive ATT&CK matrix with filtering by status, tactic, platform

- T-028: Technique detail page with tests, intel items, and review actions

- T-029: Test creation form with technique selector and validation

- T-030: Test detail page with drag and drop evidence upload and download

- T-031: System admin panel with MITRE sync and intel scan controls

New components: CoverageSummaryCard, TacticCoverageChart, AttackMatrix, TechniqueCell, TestForm, EvidenceUpload, EvidenceList

New API modules: metrics.ts, techniques.ts, tests.ts, evidence.ts, system.ts

All views use TanStack Query for data fetching with proper loading and error states. Role-based UI controls for admin/lead actions.
This commit is contained in:
2026-02-06 16:21:14 +01:00
parent 591b5df250
commit cb447f3803
22 changed files with 3092 additions and 27 deletions

View File

@@ -1,15 +1,134 @@
import { Shield } from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import {
Shield,
CheckCircle,
AlertTriangle,
XCircle,
Clock,
HelpCircle,
Percent,
Loader2,
AlertCircle,
} from "lucide-react";
import { getCoverageSummary, getCoverageByTactic } from "../api/metrics";
import CoverageSummaryCard from "../components/CoverageSummaryCard";
import TacticCoverageChart from "../components/TacticCoverageChart";
export default function DashboardPage() {
return (
<div className="space-y-4">
<h1 className="text-2xl font-bold text-white">Dashboard</h1>
<div className="flex items-center gap-3 rounded-xl border border-gray-800 bg-gray-900 p-6">
<Shield className="h-8 w-8 text-cyan-400" />
<p className="text-gray-300">
Coverage metrics will appear here.
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,
});
if (summaryLoading || tacticsLoading) {
return (
<div className="flex h-64 items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-cyan-400" />
</div>
);
}
if (summaryError || tacticsError) {
return (
<div className="flex h-64 flex-col items-center justify-center gap-2">
<AlertCircle className="h-10 w-10 text-red-400" />
<p className="text-red-400">Failed to load metrics</p>
<p className="text-sm text-gray-500">
{summaryError?.message || tacticsError?.message || "Unknown error"}
</p>
</div>
);
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-white">Dashboard</h1>
<p className="mt-1 text-sm text-gray-400">
MITRE ATT&CK coverage overview
</p>
</div>
{summary && (
<div className="flex items-center gap-2 rounded-lg border border-cyan-500/30 bg-cyan-500/10 px-4 py-2">
<Percent className="h-5 w-5 text-cyan-400" />
<span className="text-lg font-bold text-cyan-400">
{summary.coverage_percentage.toFixed(1)}%
</span>
<span className="text-sm text-gray-400">Coverage</span>
</div>
)}
</div>
{/* Summary Cards */}
{summary && (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6">
<CoverageSummaryCard
title="Total Techniques"
value={summary.total_techniques}
icon={<Shield className="h-6 w-6 text-cyan-400" />}
colorClass="text-cyan-400"
bgClass="bg-gray-900"
/>
<CoverageSummaryCard
title="Validated"
value={summary.validated}
total={summary.total_techniques}
icon={<CheckCircle className="h-6 w-6 text-green-400" />}
colorClass="text-green-400"
bgClass="bg-green-950/20"
/>
<CoverageSummaryCard
title="Partial"
value={summary.partial}
total={summary.total_techniques}
icon={<AlertTriangle className="h-6 w-6 text-yellow-400" />}
colorClass="text-yellow-400"
bgClass="bg-yellow-950/20"
/>
<CoverageSummaryCard
title="In Progress"
value={summary.in_progress}
total={summary.total_techniques}
icon={<Clock className="h-6 w-6 text-blue-400" />}
colorClass="text-blue-400"
bgClass="bg-blue-950/20"
/>
<CoverageSummaryCard
title="Not Covered"
value={summary.not_covered}
total={summary.total_techniques}
icon={<XCircle className="h-6 w-6 text-red-400" />}
colorClass="text-red-400"
bgClass="bg-red-950/20"
/>
<CoverageSummaryCard
title="Not Evaluated"
value={summary.not_evaluated}
total={summary.total_techniques}
icon={<HelpCircle className="h-6 w-6 text-gray-400" />}
colorClass="text-gray-400"
bgClass="bg-gray-900"
/>
</div>
)}
{/* Tactic Coverage Table */}
{tactics && <TacticCoverageChart data={tactics} />}
</div>
);
}