Files
mimic/frontend/src/App.tsx
Knacky 90fc5bab6c feat(frontend): sprint 5 — templates CRUD pages + nav + picker modal + dropdown
- types.ts: SimulationTemplate, SimulationTemplateCreateInput, SimulationTemplatePatchInput,
  extend SimulationCreateInput with template_id
- api/templates.ts: listTemplates, getTemplate, createTemplate, updateTemplate, deleteTemplate
- hooks/useTemplates.ts: useTemplates, useTemplate, useCreateTemplate, useUpdateTemplate,
  useDeleteTemplate (TanStack Query, invalidates ["templates"])
- TemplatesListPage: /admin/templates — table (name, MITRE count, created by, updated),
  New/Edit/Delete actions, loading/error/empty states
- TemplateFormPage: /admin/templates/new + /admin/templates/:id/edit — controlled form
  with inline MITRE field (picker + matrix modal), ConfirmDialog for delete
- TemplatePickerModal: reusable modal listing templates with empty state (AC-27.6)
- SimulationList: replace "New simulation" link with split-button dropdown
  (Blank → /simulations/new | From template… → TemplatePickerModal + POST template_id)
- Layout: "Templates" nav link (admin | redteam, before "Users")
- App.tsx: /admin/templates routes gated roles=["admin","redteam"]
- 26 new Vitest tests (118 total, 92 original preserved)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 06:36:10 +02:00

65 lines
2.7 KiB
TypeScript

import { Navigate, Route, Routes } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { ProtectedRoute } from '@/components/ProtectedRoute';
import { ToastViewport } from '@/components/Toast';
import { LoginPage } from '@/pages/LoginPage';
import { EngagementsListPage } from '@/pages/EngagementsListPage';
import { EngagementFormPage } from '@/pages/EngagementFormPage';
import { EngagementDetailPage } from '@/pages/EngagementDetailPage';
import { UsersAdminPage } from '@/pages/UsersAdminPage';
import { SimulationFormPage } from '@/pages/SimulationFormPage';
import { TemplatesListPage } from '@/pages/TemplatesListPage';
import { TemplateFormPage } from '@/pages/TemplateFormPage';
/**
* Router. Auth + role gates handled by <ProtectedRoute />.
* Default `/` redirects to /engagements (guarded — kicks to /login if no token).
*/
export function App(): JSX.Element {
return (
<>
<Routes>
<Route path="/login" element={<LoginPage />} />
{/* All authenticated routes share the Layout chrome. */}
<Route element={<ProtectedRoute />}>
<Route element={<Layout />}>
<Route path="/" element={<Navigate to="/engagements" replace />} />
<Route path="/engagements" element={<EngagementsListPage />} />
<Route path="/engagements/:id" element={<EngagementDetailPage />} />
{/* redteam + admin write actions */}
<Route element={<ProtectedRoute roles={['admin', 'redteam']} />}>
<Route path="/engagements/new" element={<EngagementFormPage />} />
<Route path="/engagements/:id/edit" element={<EngagementFormPage />} />
<Route path="/engagements/:eid/simulations/new" element={<SimulationFormPage />} />
</Route>
{/* simulation edit — all authenticated roles, RBAC handled inside the page */}
<Route
path="/engagements/:eid/simulations/:sid/edit"
element={<SimulationFormPage />}
/>
{/* admin-only routes */}
<Route element={<ProtectedRoute roles={['admin']} />}>
<Route path="/admin/users" element={<UsersAdminPage />} />
</Route>
{/* admin + redteam routes */}
<Route element={<ProtectedRoute roles={['admin', 'redteam']} />}>
<Route path="/admin/templates" element={<TemplatesListPage />} />
<Route path="/admin/templates/new" element={<TemplateFormPage />} />
<Route path="/admin/templates/:id/edit" element={<TemplateFormPage />} />
</Route>
</Route>
</Route>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
<ToastViewport />
</>
);
}