import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Loader2, AlertCircle, Download, FileText, Plus } from "lucide-react"; import { getComplianceFrameworks, getFrameworkStatus, downloadComplianceCSV, importNistMappings, importCisMappings, type ComplianceFrameworkSummary, } from "../api/compliance"; import { useAuth } from "../context/AuthContext"; import ComplianceGauge from "../components/compliance/ComplianceGauge"; import ControlsTable from "../components/compliance/ControlsTable"; export default function CompliancePage() { const [selectedFrameworkId, setSelectedFrameworkId] = useState(null); const queryClient = useQueryClient(); const { user } = useAuth(); const isAdmin = user?.role === "admin"; // Fetch available frameworks const { data: frameworks, isLoading: loadingFrameworks, } = useQuery({ queryKey: ["compliance-frameworks"], queryFn: getComplianceFrameworks, }); // Auto-select first framework const activeFrameworkId = selectedFrameworkId || frameworks?.[0]?.id || null; // Fetch framework status const { data: frameworkStatus, isLoading: loadingStatus, } = useQuery({ queryKey: ["compliance-status", activeFrameworkId], queryFn: () => getFrameworkStatus(activeFrameworkId!), enabled: !!activeFrameworkId, }); const isLoading = loadingFrameworks || loadingStatus; const summary = frameworkStatus?.summary; const controls = frameworkStatus?.controls || []; const handleExportCSV = async () => { if (activeFrameworkId) { await downloadComplianceCSV(activeFrameworkId); } }; const handleExportJSON = async () => { if (!frameworkStatus) return; const json = JSON.stringify(frameworkStatus, null, 2); const blob = new Blob([json], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `compliance_${frameworkStatus.framework.name.replace(/\s+/g, "_")}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; const importNist = useMutation({ mutationFn: importNistMappings, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["compliance-frameworks"] }); queryClient.invalidateQueries({ queryKey: ["compliance-status"] }); }, }); const importCis = useMutation({ mutationFn: importCisMappings, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["compliance-frameworks"] }); queryClient.invalidateQueries({ queryKey: ["compliance-status"] }); }, }); const isImporting = importNist.isPending || importCis.isPending; if (isLoading && !frameworkStatus) { return (
); } return (
{/* Header */}

Compliance

Map ATT&CK coverage to compliance framework controls

{/* Framework selector */} {/* Export buttons */}
{/* Summary cards */} {summary && (
{/* Gauge */}

Overall Compliance

{/* Covered */}

Covered

{summary.covered}

0 ? (summary.covered / summary.total_controls) * 100 : 0}%` }} />
{/* Partial */}

Partial

{summary.partially_covered}

0 ? (summary.partially_covered / summary.total_controls) * 100 : 0}%` }} />
{/* Not Covered */}

Not Covered

{summary.not_covered}

0 ? (summary.not_covered / summary.total_controls) * 100 : 0}%` }} />
{/* Not Evaluated */}

Not Evaluated

{summary.not_evaluated}

0 ? (summary.not_evaluated / summary.total_controls) * 100 : 0}%` }} />
)} {/* Import buttons for admin */} {isAdmin && (
Import frameworks: {(importNist.isSuccess || importCis.isSuccess) && ( Import complete )} {(importNist.isError || importCis.isError) && ( Import failed )}
)} {/* Controls table */} {controls.length > 0 ? ( ) : ( !isLoading && (

No compliance data available. Use the import buttons above to load a compliance framework.

) )}
); }