feat(templates): UI fuer Berechtigungs-Vorlagen — Modul 6/16 des UI-Backlogs

Backend existierte vollstaendig (list mit entity_type-Filter, create, update,
delete, apply — templates:read/write, seit Audit-Fix im Katalog), Frontend
hatte 0% Abdeckung.

- api/permissionTemplates.ts: TanStack-Hooks (usePermissionTemplates,
  create/update/delete/apply mit Cache-Invalidierung, TemplateLevel-Typ)
- pages/PermissionTemplates.tsx: Template-Karten (Name, Level-Badge,
  Entity-Type, Auto-Share-Zusammenfassung), Create/Edit-Dialog mit
  Level-Select und JSON-Textarea inkl. Array-Validierung mit Fehlertext,
  Apply-Dialog (Entity-Type vorbelegt, Entity-ID), Ergebnis-Banner mit
  Anzahl erstellter Berechtigungen, Delete mit Confirm — alle Aktionen
  hinter templates:write gegated
- Platzierung: Settings-Subpage /settings/permission-templates (statisch,
  Core-Route) + Settings-Nav-Item
- i18n permissionTemplates.* de/en

Verifikation: Vitest 10/10 (Rendering, Level-Badges, Create mit validem +
invalidem JSON, Edit prefilled, Apply mit Ergebnis, Delete-Confirm,
Permission-Gating, Empty/Error) · tsc exit 0 · production build exit 0.
This commit is contained in:
Agent Zero
2026-09-13 10:27:10 +02:00
parent c99d2f19ef
commit 33b4b52206
7 changed files with 880 additions and 0 deletions
+2
View File
@@ -47,6 +47,7 @@ const ApprovalsPage = React.lazy(() => import('@/pages/Approvals').then(m => ({
const DelegationsPage = React.lazy(() => import('@/pages/Delegations').then(m => ({ default: m.DelegationsPage })));
const ApiTokensPage = React.lazy(() => import('@/pages/ApiTokens').then(m => ({ default: m.ApiTokensPage })));
const TenantsPage = React.lazy(() => import('@/pages/Tenants').then(m => ({ default: m.TenantsPage })));
const PermissionTemplatesPage = React.lazy(() => import('@/pages/PermissionTemplates').then(m => ({ default: m.PermissionTemplatesPage })));
const SettingsWebhooksPage = React.lazy(() => import('@/pages/SettingsWebhooks').then(m => ({ default: m.SettingsWebhooksPage })));
const SettingsBackupPage = React.lazy(() => import('@/pages/SettingsBackup').then(m => ({ default: m.SettingsBackupPage })));
const WorkspaceManagerPage = React.lazy(() => import('@/pages/SettingsWorkspaces').then(m => ({ default: m.WorkspaceManagerPage })));
@@ -208,6 +209,7 @@ const router = createBrowserRouter([
{ path: 'backup', element: withSuspense(<SettingsBackupPage />) },
{ path: 'api-tokens', element: withSuspense(<ApiTokensPage />) },
{ path: 'tenants', element: withSuspense(<TenantsPage />) },
{ path: 'permission-templates', element: withSuspense(<PermissionTemplatesPage />) },
{ path: 'rechte', element: <PermissionRoute permission="settings:read">{withSuspense(<SettingsRechtePage />)}</PermissionRoute> },
// Phase Q2: plugin settings sub-pages render with bare sub-segments
{ path: '*', element: <ErrorBoundary>{<PluginRouteRenderer variant="settings" />}</ErrorBoundary> },