6021f0801c
Backend — POST /tests/import-rt (red_lead + admin): Accepts engagement JSON with name/date/description/operator and a list of techniques each with mitre_id, result, attack_success, platform, notes. Creates one Test per technique directly in 'validated' state (red + blue validation = approved) bypassing the normal workflow. Recalculates technique.status_global for all affected techniques. Returns created/skipped summary. Frontend — /tests/import-rt (new dedicated page): - Format reference panel (collapsible) with field descriptions - Download template JSON button (generates a filled example) - Paste JSON textarea + file upload (.json) - Live validation + preview table showing what will be imported - Import button with spinner - Success / warning / error result display Accessible to admin and red_lead only. Added to sidebar under Tests > Import RT Results.
159 lines
8.6 KiB
TypeScript
159 lines
8.6 KiB
TypeScript
import React, { Suspense } from "react";
|
|
import { Routes, Route, Navigate } from "react-router-dom";
|
|
import LoadingSpinner from "./components/LoadingSpinner";
|
|
import Layout from "./components/Layout";
|
|
import ProtectedRoute from "./components/ProtectedRoute";
|
|
|
|
/* ── Eagerly loaded (core pages) ──────────────────────────────────── */
|
|
import LoginPage from "./pages/LoginPage";
|
|
import DashboardPage from "./pages/DashboardPage";
|
|
|
|
/* ── Lazy loaded (V1-V3 pages) ────────────────────────────────────── */
|
|
const TechniquesPage = React.lazy(() => import("./pages/TechniquesPage"));
|
|
const MatrixPage = React.lazy(() => import("./pages/MatrixPage"));
|
|
const ExecutiveDashboardPage = React.lazy(() => import("./pages/ExecutiveDashboardPage"));
|
|
const CompliancePage = React.lazy(() => import("./pages/CompliancePage"));
|
|
const TechniqueDetailPage = React.lazy(() => import("./pages/TechniqueDetailPage"));
|
|
const TestsPage = React.lazy(() => import("./pages/TestsPage"));
|
|
const TestCreatePage = React.lazy(() => import("./pages/TestCreatePage"));
|
|
const TestDetailPage = React.lazy(() => import("./pages/TestDetailPage"));
|
|
const TestCatalogPage = React.lazy(() => import("./pages/TestCatalogPage"));
|
|
const ValidatedTestsPage = React.lazy(() => import("./pages/ValidatedTestsPage"));
|
|
const ReviewQueuePage = React.lazy(() => import("./pages/ReviewQueuePage"));
|
|
const ImportRTPage = React.lazy(() => import("./pages/ImportRTPage"));
|
|
const ReportsPage = React.lazy(() => import("./pages/ReportsPage"));
|
|
const SystemPage = React.lazy(() => import("./pages/SystemPage"));
|
|
const UsersPage = React.lazy(() => import("./pages/UsersPage"));
|
|
const AuditLogPage = React.lazy(() => import("./pages/AuditLogPage"));
|
|
const DataSourcesPage = React.lazy(() => import("./pages/DataSourcesPage"));
|
|
const ThreatActorsPage = React.lazy(() => import("./pages/ThreatActorsPage"));
|
|
const ThreatActorDetailPage = React.lazy(() => import("./pages/ThreatActorDetailPage"));
|
|
const CampaignsPage = React.lazy(() => import("./pages/CampaignsPage"));
|
|
const CampaignDetailPage = React.lazy(() => import("./pages/CampaignDetailPage"));
|
|
const ComparisonPage = React.lazy(() => import("./pages/ComparisonPage"));
|
|
const SettingsPage = React.lazy(() => import("./pages/SettingsPage"));
|
|
|
|
export default function App() {
|
|
return (
|
|
<Routes>
|
|
{/* Public */}
|
|
<Route path="/login" element={<LoginPage />} />
|
|
|
|
{/* Protected — wrapped in shared Layout */}
|
|
<Route
|
|
element={
|
|
<ProtectedRoute>
|
|
<Layout />
|
|
</ProtectedRoute>
|
|
}
|
|
>
|
|
{/* ── Core ─────────────────────────────────────────────── */}
|
|
<Route path="/dashboard" element={<DashboardPage />} />
|
|
|
|
<Route path="/techniques" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><TechniquesPage /></Suspense>} />
|
|
<Route path="/techniques/:mitreId" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><TechniqueDetailPage /></Suspense>} />
|
|
|
|
<Route path="/matrix" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><MatrixPage /></Suspense>} />
|
|
<Route
|
|
path="/techniques/review-queue"
|
|
element={
|
|
<ProtectedRoute roles={["admin", "red_lead", "blue_lead"]}>
|
|
<Suspense fallback={<LoadingSpinner text="Loading…" />}><ReviewQueuePage /></Suspense>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
|
|
{/* ── Executive Dashboard (leads + admin + viewer) ──────── */}
|
|
<Route
|
|
path="/executive-dashboard"
|
|
element={
|
|
<ProtectedRoute roles={["admin", "red_lead", "blue_lead", "viewer"]}>
|
|
<Suspense fallback={<LoadingSpinner text="Loading…" />}><ExecutiveDashboardPage /></Suspense>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
|
|
{/* ── Tests ────────────────────────────────────────────── */}
|
|
<Route path="/tests" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><TestsPage /></Suspense>} />
|
|
<Route path="/tests/new" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><TestCreatePage /></Suspense>} />
|
|
<Route path="/tests/validated" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><ValidatedTestsPage /></Suspense>} />
|
|
<Route
|
|
path="/tests/import-rt"
|
|
element={
|
|
<ProtectedRoute roles={["admin", "red_lead"]}>
|
|
<Suspense fallback={<LoadingSpinner text="Loading…" />}><ImportRTPage /></Suspense>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route path="/tests/:testId" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><TestDetailPage /></Suspense>} />
|
|
<Route path="/test-catalog" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><TestCatalogPage /></Suspense>} />
|
|
<Route path="/test-catalog/:templateId/use" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><TestCatalogPage /></Suspense>} />
|
|
|
|
{/* ── Campaigns ────────────────────────────────────────── */}
|
|
<Route path="/campaigns" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><CampaignsPage /></Suspense>} />
|
|
<Route path="/campaigns/:campaignId" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><CampaignDetailPage /></Suspense>} />
|
|
|
|
{/* ── Threat Actors ────────────────────────────────────── */}
|
|
<Route path="/threat-actors" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><ThreatActorsPage /></Suspense>} />
|
|
<Route path="/threat-actors/:actorId" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><ThreatActorDetailPage /></Suspense>} />
|
|
|
|
{/* ── Compliance ───────────────────────────────────────── */}
|
|
<Route path="/compliance" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><CompliancePage /></Suspense>} />
|
|
|
|
{/* ── Comparison (leads + admin + viewer) ──────────────── */}
|
|
<Route
|
|
path="/comparison"
|
|
element={
|
|
<ProtectedRoute roles={["admin", "red_lead", "blue_lead", "viewer"]}>
|
|
<Suspense fallback={<LoadingSpinner text="Loading…" />}><ComparisonPage /></Suspense>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
|
|
{/* ── Reports ──────────────────────────────────────────── */}
|
|
<Route path="/reports" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><ReportsPage /></Suspense>} />
|
|
|
|
{/* ── Settings (all authenticated users) ───────────────── */}
|
|
<Route path="/settings" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><SettingsPage /></Suspense>} />
|
|
|
|
{/* ── System (admin only) ──────────────────────────────── */}
|
|
<Route
|
|
path="/system"
|
|
element={
|
|
<ProtectedRoute roles={["admin"]}>
|
|
<Suspense fallback={<LoadingSpinner text="Loading…" />}><SystemPage /></Suspense>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/users"
|
|
element={
|
|
<ProtectedRoute roles={["admin"]}>
|
|
<Suspense fallback={<LoadingSpinner text="Loading…" />}><UsersPage /></Suspense>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/audit"
|
|
element={
|
|
<ProtectedRoute roles={["admin"]}>
|
|
<Suspense fallback={<LoadingSpinner text="Loading…" />}><AuditLogPage /></Suspense>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/data-sources"
|
|
element={
|
|
<ProtectedRoute roles={["admin"]}>
|
|
<Suspense fallback={<LoadingSpinner text="Loading…" />}><DataSourcesPage /></Suspense>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
</Route>
|
|
|
|
{/* Catch-all → dashboard */}
|
|
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
|
</Routes>
|
|
);
|
|
}
|