Files
Metamorph/frontend/src/App.tsx
Knacky 2781ce4117 feat(m5): admin SPA pages for the template catalogue
- AdminTestsPage with filters (q, tactic, opsec, tag), modal-based CRUD,
  markdown textareas for procedure/result/detection, embedded MitreTagPicker
  for tagging.
- AdminScenariosPage with @dnd-kit/sortable drag-and-drop on the ordered
  test list, two-step save (PATCH metadata + PUT tests), catalogue picker
  excluding soft-deleted items.
- lib/templates.ts typed client + queryKey factory.
- MarkdownField helper (textarea with markdown hint label).
- Layout adds Tests + Scenarios admin nav links; App.tsx routes both
  behind RequireAdmin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 19:57:41 +02:00

113 lines
3.5 KiB
TypeScript

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { RequireAdmin } from '@/components/RequireAdmin';
import { RequireAuth } from '@/components/RequireAuth';
import { AdminGroupsPage } from '@/pages/AdminGroupsPage';
import { AdminInvitationsPage } from '@/pages/AdminInvitationsPage';
import { AdminScenariosPage } from '@/pages/AdminScenariosPage';
import { AdminTestsPage } from '@/pages/AdminTestsPage';
import { AdminUsersPage } from '@/pages/AdminUsersPage';
import { HomePage } from '@/pages/HomePage';
import { MitrePage } from '@/pages/MitrePage';
import { LoginPage } from '@/pages/LoginPage';
import { ProfilePage } from '@/pages/ProfilePage';
import { RegisterPage } from '@/pages/RegisterPage';
import { SetupPage } from '@/pages/SetupPage';
import { AuthContext, useProvideAuth } from '@/lib/auth';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
refetchOnWindowFocus: false,
staleTime: 30_000,
},
},
});
function AuthProvider({ children }: { children: React.ReactNode }) {
const auth = useProvideAuth();
return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
}
function App() {
return (
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<AuthProvider>
<Routes>
<Route element={<Layout />}>
<Route path="/login" element={<LoginPage />} />
<Route path="/setup" element={<SetupPage />} />
<Route path="/register" element={<RegisterPage />} />
{/* Home page stays public — it's an ops dashboard, not sensitive. */}
<Route path="/" element={<HomePage />} />
<Route
path="/profile"
element={
<RequireAuth>
<ProfilePage />
</RequireAuth>
}
/>
<Route
path="/mitre"
element={
<RequireAuth>
<MitrePage />
</RequireAuth>
}
/>
<Route
path="/admin/users"
element={
<RequireAdmin>
<AdminUsersPage />
</RequireAdmin>
}
/>
<Route
path="/admin/groups"
element={
<RequireAdmin>
<AdminGroupsPage />
</RequireAdmin>
}
/>
<Route
path="/admin/invitations"
element={
<RequireAdmin>
<AdminInvitationsPage />
</RequireAdmin>
}
/>
<Route
path="/admin/tests"
element={
<RequireAdmin>
<AdminTestsPage />
</RequireAdmin>
}
/>
<Route
path="/admin/scenarios"
element={
<RequireAdmin>
<AdminScenariosPage />
</RequireAdmin>
}
/>
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
</Routes>
</AuthProvider>
</BrowserRouter>
</QueryClientProvider>
);
}
export default App;