fix: D3FEND expandable cards, System page cleanup, and multi-source improvements

- Make D3FEND defense cards clickable with expandable details and external link
- Fix D3FEND URLs to use PascalCase technique names matching the ontology
- Remove duplicate Import Atomic Red Team from System page (use Data Sources)
- Add bulk Activate All / Deactivate All buttons with confirmation modal
- Fix template admin list to show both active and inactive templates
- Add PATCH /test-templates/bulk-activate backend endpoint
- Auto-seed data sources on container startup via entrypoint.sh
- Fix SigmaHQ, CALDERA, GTFOBins import issues
- Register D3FEND sync handler in data sources router
- Add CIS Controls v8 compliance framework import
- Expand Test Catalog source filters (CALDERA, LOLBAS, GTFOBins)
- Campaign Generate from Threat Actor now opens actor selector modal
- Add coverage snapshot creation button to Comparison page
- Update README with accurate data source and feature documentation
This commit is contained in:
2026-02-10 13:22:23 +01:00
parent 8032b67fab
commit c2e9c687f4
19 changed files with 778 additions and 197 deletions
+83 -82
View File
@@ -13,7 +13,6 @@ import {
Shield,
Search,
FlaskConical,
Download,
Plus,
ToggleLeft,
ToggleRight,
@@ -28,12 +27,11 @@ import {
type IntelScanResponse,
} from "../api/system";
import {
importAtomicTests,
getTemplateStats,
getAllTemplates,
createTemplate,
toggleTemplateActive,
type ImportAtomicResponse,
bulkActivateTemplates,
type TemplateStats,
type CreateTemplatePayload,
} from "../api/test-templates";
@@ -43,8 +41,8 @@ export default function SystemPage() {
const queryClient = useQueryClient();
const [syncResult, setSyncResult] = useState<SyncMitreResponse | null>(null);
const [intelResult, setIntelResult] = useState<IntelScanResponse | null>(null);
const [importResult, setImportResult] = useState<ImportAtomicResponse | null>(null);
const [showCreateForm, setShowCreateForm] = useState(false);
const [bulkConfirm, setBulkConfirm] = useState<"activate" | "deactivate" | null>(null);
// ── Existing queries ─────────────────────────────────────────────
const {
@@ -71,7 +69,7 @@ export default function SystemPage() {
isLoading: templatesLoading,
} = useQuery({
queryKey: ["templates-admin"],
queryFn: () => getAllTemplates({ limit: 100 }),
queryFn: () => getAllTemplates({ limit: 200 }),
});
// ── Mutations ────────────────────────────────────────────────────
@@ -92,12 +90,12 @@ export default function SystemPage() {
},
});
const importAtomicMutation = useMutation({
mutationFn: importAtomicTests,
onSuccess: (data) => {
setImportResult(data);
queryClient.invalidateQueries({ queryKey: ["template-stats"] });
const bulkActivateMutation = useMutation({
mutationFn: (activate: boolean) => bulkActivateTemplates(activate),
onSuccess: () => {
setBulkConfirm(null);
queryClient.invalidateQueries({ queryKey: ["templates-admin"] });
queryClient.invalidateQueries({ queryKey: ["template-stats"] });
queryClient.invalidateQueries({ queryKey: ["test-templates"] });
},
});
@@ -281,70 +279,8 @@ export default function SystemPage() {
TEMPLATE ADMINISTRATION (T-124)
──────────────────────────────────────────────────────────────── */}
{/* Import Atomic Red Team + Stats */}
<div className="grid gap-6 lg:grid-cols-2">
{/* Import Atomic Red Team */}
<div className="rounded-xl border border-gray-800 bg-gray-900 p-6">
<div className="flex items-start gap-4">
<div className="rounded-lg bg-red-500/10 p-3">
<Download className="h-6 w-6 text-red-400" />
</div>
<div className="flex-1">
<h2 className="text-lg font-semibold text-white">Import Atomic Red Team</h2>
<p className="mt-1 text-sm text-gray-400">
Import test templates from the Atomic Red Team repository by Red Canary, mapped to MITRE ATT&CK techniques.
</p>
{importResult && (
<div className="mt-4 rounded-lg border border-green-500/30 bg-green-900/20 p-3">
<div className="flex items-center gap-2">
<CheckCircle className="h-4 w-4 text-green-400" />
<span className="text-sm font-medium text-green-400">Import Complete</span>
</div>
<div className="mt-2 grid grid-cols-3 gap-2 text-sm">
<div>
<span className="text-gray-400">Imported:</span>
<span className="ml-1 font-medium text-white">{importResult.imported}</span>
</div>
<div>
<span className="text-gray-400">Skipped:</span>
<span className="ml-1 font-medium text-white">{importResult.skipped}</span>
</div>
<div>
<span className="text-gray-400">Parsed:</span>
<span className="ml-1 font-medium text-white">{importResult.total_parsed}</span>
</div>
</div>
</div>
)}
{importAtomicMutation.isError && (
<div className="mt-4 rounded-lg border border-red-500/30 bg-red-900/20 p-3">
<div className="flex items-center gap-2">
<XCircle className="h-4 w-4 text-red-400" />
<span className="text-sm text-red-400">
Import failed: {(importAtomicMutation.error as Error)?.message}
</span>
</div>
</div>
)}
<button
onClick={() => importAtomicMutation.mutate()}
disabled={importAtomicMutation.isPending}
className="mt-4 flex items-center gap-2 rounded-lg bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-500 disabled:opacity-50 transition-colors"
>
{importAtomicMutation.isPending ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Download className="h-4 w-4" />
)}
{importAtomicMutation.isPending ? "Importing..." : "Import Now"}
</button>
</div>
</div>
</div>
{/* Template Catalog Stats */}
<div className="grid gap-6 lg:grid-cols-1">
{/* Template Catalog Stats */}
<div className="rounded-xl border border-gray-800 bg-gray-900 p-6">
<div className="flex items-start gap-4">
@@ -433,20 +369,85 @@ export default function SystemPage() {
/>
)}
{/* Bulk Activate Confirmation Modal */}
{bulkConfirm && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
<div className="w-full max-w-md rounded-xl border border-gray-700 bg-gray-900 p-6 shadow-xl">
<h3 className="text-lg font-semibold text-white">
{bulkConfirm === "activate" ? "Activate All Templates" : "Deactivate All Templates"}
</h3>
<p className="mt-2 text-sm text-gray-400">
{bulkConfirm === "activate"
? "This will activate ALL templates in the catalog, including previously deactivated ones. All templates will become available for test creation."
: "This will deactivate ALL templates in the catalog. No templates will be available for test creation until reactivated."}
</p>
<p className="mt-2 text-sm font-medium text-yellow-400">
This action affects all {templateStats?.total || 0} templates.
</p>
<div className="mt-4 flex items-center justify-end gap-3">
<button
onClick={() => setBulkConfirm(null)}
className="rounded-lg border border-gray-700 bg-gray-800 px-4 py-2 text-sm font-medium text-gray-300 hover:border-gray-600 hover:text-white transition-colors"
>
Cancel
</button>
<button
onClick={() => bulkActivateMutation.mutate(bulkConfirm === "activate")}
disabled={bulkActivateMutation.isPending}
className={`flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-white transition-colors disabled:opacity-50 ${
bulkConfirm === "activate"
? "bg-green-600 hover:bg-green-500"
: "bg-red-600 hover:bg-red-500"
}`}
>
{bulkActivateMutation.isPending ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : bulkConfirm === "activate" ? (
<ToggleRight className="h-4 w-4" />
) : (
<ToggleLeft className="h-4 w-4" />
)}
{bulkActivateMutation.isPending
? "Processing..."
: bulkConfirm === "activate"
? "Activate All"
: "Deactivate All"}
</button>
</div>
</div>
</div>
)}
{/* Templates Management Table */}
<div className="rounded-xl border border-gray-800 bg-gray-900 p-6">
<div className="mb-4 flex items-center justify-between">
<div className="mb-4 flex flex-wrap items-center justify-between gap-2">
<h2 className="text-lg font-semibold text-white flex items-center gap-2">
<FlaskConical className="h-5 w-5 text-cyan-400" />
Manage Templates
</h2>
<button
onClick={() => setShowCreateForm(!showCreateForm)}
className="flex items-center gap-1.5 rounded-lg bg-cyan-600 px-3 py-2 text-sm font-medium text-white hover:bg-cyan-500 transition-colors"
>
<Plus className="h-4 w-4" />
Create Custom Template
</button>
<div className="flex items-center gap-2">
<button
onClick={() => setBulkConfirm("activate")}
className="flex items-center gap-1.5 rounded-lg border border-green-500/30 bg-green-900/20 px-3 py-2 text-sm font-medium text-green-400 hover:bg-green-900/40 transition-colors"
>
<ToggleRight className="h-4 w-4" />
Activate All
</button>
<button
onClick={() => setBulkConfirm("deactivate")}
className="flex items-center gap-1.5 rounded-lg border border-red-500/30 bg-red-900/20 px-3 py-2 text-sm font-medium text-red-400 hover:bg-red-900/40 transition-colors"
>
<ToggleLeft className="h-4 w-4" />
Deactivate All
</button>
<button
onClick={() => setShowCreateForm(!showCreateForm)}
className="flex items-center gap-1.5 rounded-lg bg-cyan-600 px-3 py-2 text-sm font-medium text-white hover:bg-cyan-500 transition-colors"
>
<Plus className="h-4 w-4" />
Create Custom
</button>
</div>
</div>
{templatesLoading ? (