diff --git a/concepts/Navigation-API-Konzept.md b/concepts/Navigation-API-Konzept.md new file mode 100644 index 0000000..6d6275f --- /dev/null +++ b/concepts/Navigation-API-Konzept.md @@ -0,0 +1,863 @@ +# Navigation API Konzept + +## Übersicht + +Dieses Dokument beschreibt das Konzept für die Navigation-API, welche dem UI alle notwendigen Informationen für das Rendering der Navigation liefert. Die API ist die **Single Source of Truth** für die Navigationsstruktur. + +## Grundprinzipien + +### 1. Backend liefert, UI rendert +- Das **Gateway** bestimmt, welche Navigationselemente ein User sehen darf +- Das **UI** rendert nur das, was es vom Gateway erhält +- Keine Permission-Logik im UI für Navigation + +### 2. Trennung von Daten und Darstellung +- Die API liefert **keine Style-Elemente** (Icons, CSS-Klassen, etc.) +- Die API liefert **UI-Komponenten-Codes**, die das UI auf seine Komponenten mappt +- Verschiedene UIs können dieselben Daten unterschiedlich darstellen + +### 3. Nur sichtbare Elemente +- Elemente ohne Zugriffsberechtigung werden **nicht** in den Baum aufgenommen +- Kein `hasAccess: false` - wenn kein Zugriff, dann nicht im Response + +### 4. Fehlertoleranz +- Wenn ein Code im UI nicht gemappt werden kann → Fehlertext im Nav-Tree +- Synchronisationsprobleme zwischen Gateway und UI werden sofort sichtbar + +### 5. Personalisierbare Sortierung +- Jedes Element hat eine `order` Nummer für die Sortierung +- User können die Reihenfolge anpassen durch User-Settings +- Gateway liefert bereits sortiert (Default + User-Override) + +--- + +## API Response Struktur + +### Endpoint + +``` +GET /api/navigation +``` + +### Response Format + +```json +{ + "language": "de", + "blocks": [ + { + "type": "static", + "id": "system", + "title": "SYSTEM", + "order": 10, + "items": [...] + }, + { + "type": "static", + "id": "workflows", + "title": "WORKFLOWS", + "order": 20, + "items": [...] + }, + { + "type": "dynamic", + "id": "features", + "title": "FEATURES", + "order": 100, + "mandates": [...] + } + ] +} +``` + +--- + +## Block-Typen + +### Static Block + +Für System-Seiten, die nicht an Feature-Instanzen gebunden sind. + +```json +{ + "type": "static", + "id": "system", + "title": "SYSTEM", + "order": 10, + "items": [ + { + "uiComponent": "page.system.home", + "uiLabel": "Übersicht", + "uiPath": "/", + "order": 10, + "objectKey": "ui.system.home" + }, + { + "uiComponent": "page.system.settings", + "uiLabel": "Einstellungen", + "uiPath": "/settings", + "order": 20, + "objectKey": "ui.system.settings" + } + ] +} +``` + +**Felder pro Item:** +| Feld | Beschreibung | +|------|--------------| +| `uiComponent` | Eindeutiger Code für UI-Mapping (z.B. `page.system.home`) | +| `uiLabel` | Anzeigetext (bereits in der gewählten Sprache) | +| `uiPath` | URL-Pfad für Navigation | +| `order` | Sortierreihenfolge (Default oder User-Override) | +| `objectKey` | Vollqualifizierter RBAC-Objektname (für Debugging/Referenz) | + +### Dynamic Block + +Für Feature-Instanzen, gruppiert nach Mandanten. + +```json +{ + "type": "dynamic", + "id": "features", + "title": "FEATURES", + "order": 100, + "mandates": [ + { + "id": "5ce04434-c4ce-4269-9861-19ff7ebc2a1d", + "uiLabel": "SOHA Treuhand AG", + "order": 10, + "features": [ + { + "uiComponent": "feature.trustee", + "uiLabel": "Treuhand", + "order": 10, + "instances": [ + { + "id": "2fc48f66-ad1b-4581-aa87-6aaa1e7c16e0", + "uiLabel": "ValueOn AG 2026", + "order": 10, + "views": [ + { + "uiComponent": "page.feature.trustee.dashboard", + "uiLabel": "Übersicht", + "uiPath": "/mandates/5ce04434.../trustee/2fc48f66.../dashboard", + "order": 10, + "objectKey": "ui.feature.trustee.dashboard" + }, + { + "uiComponent": "page.feature.trustee.positions", + "uiLabel": "Positionen", + "uiPath": "/mandates/5ce04434.../trustee/2fc48f66.../positions", + "order": 20, + "objectKey": "ui.feature.trustee.positions" + } + ] + } + ] + } + ] + } + ] +} +``` + +**Hinweis:** Der `dynamic` Block ist nur vorhanden, wenn der User mindestens eine Feature-Instanz hat. + +**Sortierung auf allen Ebenen:** +- Mandanten: `mandates[].order` +- Features: `features[].order` +- Instanzen: `instances[].order` +- Views: `views[].order` + +--- + +## User-Personalisierung (Order Override) + +### Konzept + +User können die Reihenfolge der Navigation anpassen. Die Einstellungen werden in den User-Settings gespeichert. + +### User Settings Struktur + +```json +{ + "userId": "user-123", + "settings": { + "navOrder": { + "page.system.home": 10, + "page.system.prompts": 5, + "page.admin.users": 100, + "feature.trustee": 20, + "feature.realestate": 10, + "mandate.5ce04434-...": 5, + "instance.2fc48f66-...": 1 + } + } +} +``` + +### Merge-Logik im Gateway + +``` +Effektive Order = User-Override ?? Default-Order ?? 50 +``` + +1. Gateway lädt Default-Order aus Konfiguration +2. Gateway lädt User-Settings (falls vorhanden) +3. Für jedes Element: User-Order überschreibt Default +4. Sortierung erfolgt im Gateway, UI erhält bereits sortiert + +### Sortier-Priorität + +| Ebene | Key für navOrder | Beispiel | +|-------|------------------|----------| +| Block | Block-ID | `"system"`, `"workflows"`, `"features"` | +| Static Item | uiComponent | `"page.system.home"` | +| Mandate | `mandate.{id}` | `"mandate.5ce04434-..."` | +| Feature | uiComponent | `"feature.trustee"` | +| Instance | `instance.{id}` | `"instance.2fc48f66-..."` | +| View | uiComponent | `"page.feature.trustee.dashboard"` | + +### API für Order-Update + +``` +PUT /api/user/settings/navOrder +Body: { "page.system.prompts": 5, "feature.trustee": 20 } +``` + +--- + +## Code-Konvention + +### Statische Seiten +``` +page.system. +page.admin. +``` + +Beispiele: +- `page.system.home` +- `page.system.settings` +- `page.system.prompts` +- `page.admin.users` +- `page.admin.mandates` + +### Feature-Seiten +``` +page.feature.. +``` + +Beispiele: +- `page.feature.trustee.dashboard` +- `page.feature.trustee.positions` +- `page.feature.trustee.instance-roles` +- `page.feature.realestate.projects` + +### Feature-Codes (für Gruppierung) +``` +feature. +``` + +Beispiele: +- `feature.trustee` +- `feature.realestate` +- `feature.chatworkflow` + +--- + +## ObjectKey-Konvention (RBAC) + +Die `objectKey` werden für Access Rules verwendet und sind vollqualifiziert: + +### UI Objekte +``` +ui.system. → System-Seiten +ui.admin. → Admin-Seiten +ui.feature.. → Feature-Views +``` + +### DATA Objekte +``` +data.system. → System-Tabellen +data.feature..
→ Feature-Tabellen +``` + +### RESOURCE Objekte +``` +resource.system. → System-Aktionen +resource.feature.. → Feature-Aktionen +``` + +--- + +## UI Mapping & Fehlerbehandlung + +### UI Code Registry + +Das UI definiert ein Mapping von Codes zu Komponenten: + +```typescript +// frontend_nyla/src/config/pageRegistry.ts + +export const PAGE_REGISTRY: Record = { + // Static pages + 'page.system.home': { + component: HomePage, + icon: FaHome, + layout: 'default', + }, + 'page.system.settings': { + component: SettingsPage, + icon: FaCog, + layout: 'default', + }, + 'page.admin.users': { + component: AdminUsersPage, + icon: FaUsers, + layout: 'admin', + }, + + // Feature pages + 'page.feature.trustee.dashboard': { + component: TrusteeDashboardView, + icon: FaChartBar, + layout: 'feature', + }, + 'page.feature.trustee.positions': { + component: TrusteePositionsView, + icon: FaListAlt, + layout: 'feature', + }, + // ... +}; + +export const FEATURE_REGISTRY: Record = { + 'feature.trustee': { + icon: FaBriefcase, + color: '#e74c3c', + }, + 'feature.realestate': { + icon: FaBuilding, + color: '#3498db', + }, +}; +``` + +### Fehlerbehandlung + +Wenn ein `uiComponent` Code nicht gemappt werden kann: + +```typescript +function renderNavItem(item: NavItem) { + const pageDef = PAGE_REGISTRY[item.uiComponent]; + + if (!pageDef) { + // Fehler sichtbar machen! + return ( + + ⚠️ Unbekannter Code: {item.uiComponent} + ObjectKey: {item.objectKey} + + ); + } + + return ( + + {item.uiLabel} + + ); +} +``` + +**Wichtig:** Fehler werden im Nav-Tree angezeigt, nicht versteckt! So werden Synchronisationsprobleme zwischen Gateway und UI sofort sichtbar. + +### Sortierung im UI + +Das UI sortiert die bereits vom Gateway sortierten Elemente NICHT neu. Die Sortierung erfolgt ausschliesslich im Gateway basierend auf: + +1. Default-Order aus Konfiguration +2. User-Override aus Settings (falls vorhanden) + +```typescript +// UI rendert in der Reihenfolge wie empfangen +function renderBlock(block: Block) { + // items sind bereits sortiert + return block.items.map(item => renderNavItem(item)); +} +``` + +--- + +## Implementierungsschritte + +### Phase 1: Gateway - Navigation Endpoint + +1. **Neuer Endpoint** `/api/navigation` erstellen +2. **Static Blocks** aus `NAVIGATION_SECTIONS` generieren +3. **Dynamic Block** aus Feature-Instanzen generieren +4. **Permission-Filter** anwenden (nur sichtbare Elemente) +5. **uiComponent Codes** gemäß Konvention setzen +6. **Default-Order** für alle Elemente setzen + +### Phase 2: Gateway - Access Rules + +1. **TEMPLATE_ROLES** auf vollqualifizierte `objectKey` umstellen +2. **Permission-Check** auf `objectKey` umstellen +3. **Migration** bestehender Access Rules (falls nötig) + +### Phase 3: Gateway - User Settings + +1. **User Settings Modell** erweitern mit `navOrder` +2. **API Endpoint** `PUT /api/user/settings/navOrder` erstellen +3. **Merge-Logik** implementieren (User-Override → Default) +4. **Sortierung** vor Response durchführen + +### Phase 4: Frontend + +1. **PAGE_REGISTRY** erstellen mit uiComponent → Component Mapping +2. **FEATURE_REGISTRY** erstellen mit Feature-Code → Style Mapping +3. **useNavigation Hook** auf neuen Endpoint umstellen +4. **MandateNavigation** refactoren für neue Datenstruktur +5. **Fehlerhandling** für unbekannte uiComponents implementieren + +### Phase 5: Frontend - Drag & Drop (Optional) + +1. **Drag & Drop UI** für Nav-Tree Sortierung +2. **API Call** bei Order-Änderung +3. **Optimistic Update** für schnelle UX + +### Phase 6: Cleanup + +1. **FEATURE_REGISTRY** (alte Version in mandate.ts) entfernen +2. **Hardcodierte Navigation** im Frontend entfernen +3. **Tests** für Synchronisation Gateway ↔ UI + +--- + +## Beispiel: Vollständige Response + +```json +{ + "language": "de", + "blocks": [ + { + "type": "static", + "id": "system", + "title": "SYSTEM", + "order": 10, + "items": [ + { + "uiComponent": "page.system.home", + "uiLabel": "Übersicht", + "uiPath": "/", + "order": 10, + "objectKey": "ui.system.home" + }, + { + "uiComponent": "page.system.settings", + "uiLabel": "Einstellungen", + "uiPath": "/settings", + "order": 20, + "objectKey": "ui.system.settings" + } + ] + }, + { + "type": "static", + "id": "workflows", + "title": "WORKFLOWS", + "order": 20, + "items": [ + { + "uiComponent": "page.system.playground", + "uiLabel": "Chat Playground", + "uiPath": "/workflows/playground", + "order": 10, + "objectKey": "ui.system.playground" + }, + { + "uiComponent": "page.system.chats", + "uiLabel": "Chats", + "uiPath": "/workflows/list", + "order": 20, + "objectKey": "ui.system.chats" + } + ] + }, + { + "type": "static", + "id": "basedata", + "title": "BASISDATEN", + "order": 30, + "items": [ + { + "uiComponent": "page.system.prompts", + "uiLabel": "Prompts", + "uiPath": "/basedata/prompts", + "order": 10, + "objectKey": "ui.system.prompts" + }, + { + "uiComponent": "page.system.files", + "uiLabel": "Dateien", + "uiPath": "/basedata/files", + "order": 20, + "objectKey": "ui.system.files" + } + ] + }, + { + "type": "static", + "id": "admin", + "title": "ADMINISTRATION", + "order": 200, + "items": [ + { + "uiComponent": "page.admin.users", + "uiLabel": "Benutzer", + "uiPath": "/admin/users", + "order": 10, + "objectKey": "ui.admin.users" + }, + { + "uiComponent": "page.admin.mandates", + "uiLabel": "Mandanten", + "uiPath": "/admin/mandates", + "order": 20, + "objectKey": "ui.admin.mandates" + } + ] + }, + { + "type": "dynamic", + "id": "features", + "title": "MEINE FEATURES", + "order": 100, + "mandates": [ + { + "id": "5ce04434-c4ce-4269-9861-19ff7ebc2a1d", + "uiLabel": "SOHA Treuhand AG", + "order": 10, + "features": [ + { + "uiComponent": "feature.trustee", + "uiLabel": "Treuhand", + "order": 10, + "instances": [ + { + "id": "2fc48f66-ad1b-4581-aa87-6aaa1e7c16e0", + "uiLabel": "ValueOn AG 2026", + "order": 10, + "views": [ + { + "uiComponent": "page.feature.trustee.dashboard", + "uiLabel": "Übersicht", + "uiPath": "/mandates/5ce04434-c4ce-4269-9861-19ff7ebc2a1d/trustee/2fc48f66-ad1b-4581-aa87-6aaa1e7c16e0/dashboard", + "order": 10, + "objectKey": "ui.feature.trustee.dashboard" + }, + { + "uiComponent": "page.feature.trustee.positions", + "uiLabel": "Positionen", + "uiPath": "/mandates/5ce04434-c4ce-4269-9861-19ff7ebc2a1d/trustee/2fc48f66-ad1b-4581-aa87-6aaa1e7c16e0/positions", + "order": 20, + "objectKey": "ui.feature.trustee.positions" + }, + { + "uiComponent": "page.feature.trustee.documents", + "uiLabel": "Dokumente", + "uiPath": "/mandates/5ce04434-c4ce-4269-9861-19ff7ebc2a1d/trustee/2fc48f66-ad1b-4581-aa87-6aaa1e7c16e0/documents", + "order": 30, + "objectKey": "ui.feature.trustee.documents" + } + ] + } + ] + } + ] + }, + { + "id": "abc123-mandate-2", + "uiLabel": "Partner AG", + "order": 20, + "features": [ + { + "uiComponent": "feature.realestate", + "uiLabel": "Immobilien", + "order": 10, + "instances": [ + { + "id": "def456-instance", + "uiLabel": "Objektstudien ZH", + "order": 10, + "views": [ + { + "uiComponent": "page.feature.realestate.dashboard", + "uiLabel": "Übersicht", + "uiPath": "/mandates/abc123-mandate-2/realestate/def456-instance/dashboard", + "order": 10, + "objectKey": "ui.feature.realestate.dashboard" + }, + { + "uiComponent": "page.feature.realestate.projects", + "uiLabel": "Projekte", + "uiPath": "/mandates/abc123-mandate-2/realestate/def456-instance/projects", + "order": 20, + "objectKey": "ui.feature.realestate.projects" + } + ] + } + ] + } + ] + } + ] + } + ] +} +``` + +--- + +## Vorteile dieses Ansatzes + +1. **Single Source of Truth**: Gateway definiert, was sichtbar ist +2. **Keine Permission-Logik im UI**: Alles was kommt, wird gerendert +3. **Flexibles UI**: Verschiedene UIs können dieselben Daten anders darstellen +4. **Fehlertoleranz**: Synchronisationsprobleme werden sofort sichtbar +5. **Saubere Trennung**: Daten (Gateway) vs. Darstellung (UI) +6. **Testbarkeit**: API-Response kann einfach getestet werden +7. **Dokumentation**: Codes und ObjectKeys sind selbstdokumentierend +8. **Personalisierung**: User können Navigation individuell sortieren +9. **Konsistente Sortierung**: Gateway sortiert zentral, UI respektiert die Reihenfolge + +--- + +## Offene Fragen (Beantwortet) + +1. Soll der `dynamic` Block vor oder nach den `static` Blocks kommen? → **Diese initiale Reihenfolge: System, ``, workflows, basisdaten, migrate to features, administration** +2. Braucht es eine Versionierung der API für Breaking Changes? → **Nein** +3. Wie werden Feature-spezifische Sub-Navigationen behandelt (z.B. Tabs innerhalb einer View)? → **Tabs innerhalb einer View werden als separate Views behandelt** +4. Soll der Default-Order-Abstand 10 sein (erlaubt Einfügen zwischen Elementen)? → **Ja** +5. Soll die Order-Personalisierung auch für Admins pro Mandate/Feature-Instanz möglich sein (System-Default vs. User-Override)? → **Ja** + +--- + +## Code-Analyse: IST-Zustand + +### Gateway (Backend) + +#### 1. `mainSystem.py` - Statische Navigation + +```python +# IST: Items haben id, label, path, icon, objectKey +# NEU: Brauchen uiComponent, uiLabel, uiPath, order (ohne icon) +NAVIGATION_SECTIONS = [ + { + "id": "system", + "order": 10, # ✓ Vorhanden + "items": [ + { + "id": "home", + "objectKey": "ui.system.home", # ✓ Korrekt + "label": {"en": "Home", "de": "Übersicht"}, + "icon": "FaHome", # ✗ Muss entfernt werden + "path": "/", + }, + ] + } +] +``` + +**Änderungen:** +- `icon` entfernen (UI mappt selbst) +- Item-Level `order` hinzufügen +- Felder umbenennen für API Response + +#### 2. `routeSystem.py` - Navigation Endpoint + +```python +# IST: GET /api/system/navigation +# Gibt zurück: { "sections": [...], "language": "de" } + +# NEU: GET /api/navigation +# Soll zurückgeben: { "blocks": [...], "language": "de" } +``` + +**Änderungen:** +- Neuer kombinierter Endpoint +- Static Blocks + Dynamic Block zusammenführen +- Response-Struktur gemäss Konzept + +#### 3. `mainTrustee.py` - Feature Views + +```python +# IST: TEMPLATE_ROLES verwendet Kurznamen +TEMPLATE_ROLES = [ + { + "roleLabel": "trustee-client", + "accessRules": [ + {"context": "UI", "item": "dashboard", "view": True}, # ✗ Kurzname! + {"context": "UI", "item": "positions", "view": True}, # ✗ Kurzname! + ] + } +] + +# ABER: UI_OBJECTS verwendet vollqualifizierte Namen +UI_OBJECTS = [ + {"objectKey": "ui.feature.trustee.dashboard", ...}, # ✓ Vollständig +] +``` + +**Inkonsistenz gefunden!** `item` in AccessRules muss `objectKey` entsprechen. + +#### 4. `routeAdminFeatures.py` - Permission Logic + +```python +# IST: _deriveViewPermissions ist TRUSTEE-spezifisch! +def _deriveViewPermissions(permissions): + # Hard-coded: TrusteePosition → positions view + # Hard-coded: TrusteeDocument → documents view +``` + +**Problem:** Nicht generisch, funktioniert nur für Trustee. + +### Frontend (UI) + +#### 1. `MandateNavigation.tsx` + +```typescript +// IST: Zwei separate Datenquellen +const { sections } = useNavigation(); // Static von API +const mandates = useMandates(); // Dynamic von API + +// IST: FEATURE_ICONS definiert im Frontend +const FEATURE_ICONS = { trustee: }; +``` + +**Problem:** Navigation wird im Frontend zusammengebaut, nicht vom Backend. + +#### 2. `mandate.ts` - FEATURE_REGISTRY + +```typescript +// IST: Doppelte Definition von Views! +export const FEATURE_REGISTRY = { + trustee: { + views: [ + { code: 'dashboard', label: {...}, path: 'dashboard' }, // ✗ Kurzname + { code: 'positions', label: {...}, path: 'positions' }, // ✗ Kurzname + ] + } +}; +``` + +**Duplizierung:** Views sind in `mainTrustee.py` UND `mandate.ts` definiert! + +--- + +## Impact-Analyse + +### Phase 1: Gateway - Navigation Endpoint + +| Datei | Änderungstyp | Aufwand | Breaking | +|-------|--------------|---------|----------| +| `routeSystem.py` | Neu/Refactor | Hoch | Ja | +| `mainSystem.py` | Update | Mittel | Nein | +| `routeAdminFeatures.py` | Integrieren | Hoch | Nein | + +**Konkrete Aufgaben:** +1. Neuen Endpoint `GET /api/navigation` erstellen +2. Static Blocks aus `NAVIGATION_SECTIONS` generieren +3. Dynamic Block aus `_getMyFeatureInstances`-Logik generieren +4. Permission-Filter auf beide anwenden +5. `icon` aus Response entfernen +6. Felder umbenennen: `label→uiLabel`, `path→uiPath`, neu `uiComponent` +7. `order` auf Item-Level hinzufügen + +### Phase 2: Gateway - Access Rules Migration + +| Datei | Änderungstyp | Aufwand | Breaking | +|-------|--------------|---------|----------| +| `mainTrustee.py` | Update | Mittel | Ja (DB) | +| `mainRealEstate.py` | Update | Mittel | Ja (DB) | +| Migration Script | Neu | Mittel | - | + +**Konkrete Aufgaben:** +1. `TEMPLATE_ROLES.item` auf vollqualifizierte ObjectKeys umstellen +2. Migration: Bestehende AccessRules in DB aktualisieren +3. Permission-Check in `_checkUiPermission` anpassen + +### Phase 3: Gateway - User Settings + +| Datei | Änderungstyp | Aufwand | Breaking | +|-------|--------------|---------|----------| +| `datamodelUam.py` | Erweitern | Gering | Nein | +| `routeSystem.py` | Endpoint | Gering | Nein | + +**Konkrete Aufgaben:** +1. `UserSettings` Tabelle oder JSON-Feld in User +2. `PUT /api/user/settings/navOrder` Endpoint +3. Merge-Logik in Navigation-Endpoint + +### Phase 4: Frontend + +| Datei | Änderungstyp | Aufwand | Breaking | +|-------|--------------|---------|----------| +| `pageRegistry.ts` | Neu | Hoch | - | +| `useNavigation.ts` | Refactor | Mittel | Ja | +| `MandateNavigation.tsx` | Refactor | Hoch | Ja | +| `mandate.ts` | Cleanup | Gering | Ja | + +**Konkrete Aufgaben:** +1. `PAGE_REGISTRY` erstellen mit uiComponent → Component Mapping +2. `FEATURE_REGISTRY` mit `feature.*` Codes +3. `useNavigation` für neuen Endpoint anpassen +4. `MandateNavigation` für Blocks-Struktur refactoren +5. Error-Handling für unbekannte uiComponents + +--- + +## Kritische Punkte + +### 1. Breaking Changes +Die Umstellung erfordert **simultane Änderungen** in Gateway UND Frontend. Empfehlung: +- Feature-Flag für neuen Endpoint +- Alte Endpoints temporär behalten +- Frontend schrittweise migrieren + +### 2. Datenbank-Migration +Bestehende AccessRules haben `item="dashboard"`, neu soll `item="ui.feature.trustee.dashboard"` sein. +```sql +-- Beispiel-Migration +UPDATE access_rules +SET item = 'ui.feature.trustee.' || item +WHERE role_id IN (SELECT id FROM roles WHERE feature_code = 'trustee') + AND context = 'UI' + AND item IS NOT NULL; +``` + +### 3. Performance +Navigation mit vielen Instanzen kann langsam werden. Empfehlungen: +- Response cachen pro User +- Cache invalidieren bei Permission-Änderungen +- Lazy-Loading für sehr grosse Deployments prüfen + +### 4. Feature-spezifische View-Ableitung +`_deriveViewPermissions` ist Trustee-spezifisch. Optionen: +- **A)** Jedes Feature definiert seine Ableitungslogik +- **B)** Keine Ableitung, nur explizite UI-Rules +- **C)** Generische Mapping-Konfiguration pro Feature + +**Empfehlung:** Option B - Explizite UI-Rules sind klarer und testbarer. + +--- + +## Empfohlene Reihenfolge + +1. **Schritt 1:** `PAGE_REGISTRY` im Frontend erstellen (unabhängig) +2. **Schritt 2:** Neuen `/api/navigation` Endpoint mit Feature-Flag +3. **Schritt 3:** Frontend auf neuen Endpoint umstellen +4. **Schritt 4:** AccessRules-Migration +5. **Schritt 5:** User-Settings für Order +6. **Schritt 6:** Alte Endpoints/Code entfernen diff --git a/implementation/RBAC-AccessRules-Refactoring.md b/implementation/RBAC-AccessRules-Refactoring.md new file mode 100644 index 0000000..55a5f4c --- /dev/null +++ b/implementation/RBAC-AccessRules-Refactoring.md @@ -0,0 +1,981 @@ +# RBAC Access Rules Editor - Refactoring Vorschlag + +## Übersicht + +Dieses Dokument beschreibt die geplante Überarbeitung der RBAC Access Rules UI-Komponenten basierend auf drei Hauptanforderungen: + +1. **System-Rollen bearbeitbar machen** - AccessRules für System-Rollen editierbar +2. **Checkbox-basiertes UI** - Kompakteres Layout mit Checkboxen statt Dropdowns +3. **Dot-Notation & Objekt-Katalog** - Verfügbare Objekte als Dropdown auswählbar + +--- + +## 1. System-Rollen mit bearbeitbaren AccessRules + +### Ist-Zustand + +Aktuell in `AdminMandateRolePermissionsPage.tsx`: +```typescript + +``` + +Die `readOnly`-Prop wird basierend auf `isSystemRole` gesetzt, was dazu führt, dass AccessRules für System-Rollen weder angezeigt noch bearbeitet werden können. + +### Soll-Zustand + +- **Rollen selbst** (roleLabel, description) bleiben geschützt für System-Rollen +- **AccessRules** sind für alle Rollen bearbeitbar (inkl. System-Rollen) +- Zugriffskontrolle erfolgt über RBAC selbst (`rbac.rules.manage` Permission) + +### Implementierung + +#### Option A: Separate Prop für Rollen-Protection + +```typescript +// AdminMandateRolePermissionsPage.tsx + +``` + +#### Option B: Immer bearbeitbar (empfohlen) + +Entferne die `readOnly`-Logic basierend auf `isSystemRole`: + +```typescript +// AdminMandateRolePermissionsPage.tsx + +``` + +Die Zugriffskontrolle wird durch Backend-RBAC sichergestellt (nur SysAdmin kann AccessRules ändern). + +### Betroffene Dateien + +| Datei | Änderung | +|-------|----------| +| `frontend_nyla/src/pages/admin/AdminMandateRolePermissionsPage.tsx` | `readOnly={false}` setzen | + +--- + +## 2. Checkbox-basiertes Kompakt-Layout + +### Ist-Zustand + +Aktuell werden AccessLevels (n/m/g/a) über Dropdown-Selects ausgewählt: + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ 📊 TrusteeContract [🗑] │ +├──────────────────────────────────────────────────────────────────┤ +│ VIEW READ CREATE UPDATE DELETE │ +│ [✓] [Gruppe▼] [Eigene▼] [Eigene▼] [Keine▼] │ +└──────────────────────────────────────────────────────────────────┘ +``` + +**Probleme:** +- Dropdowns benötigen Klicks zum Öffnen +- Nicht auf einen Blick erkennbar welche Berechtigungen gesetzt sind +- Mehrere Regeln brauchen viel vertikalen Platz + +### Soll-Zustand + +Kompaktes Checkbox-Grid in einer Zeile pro Objekt: + +``` +┌────────────────────────────────────────────────────────────────────────────┐ +│ OBJEKT │ VIEW │ EIGENE │ GRUPPE │ ALLE │ +│ │ │ C R U D │ C R U D │C R U D│ +├────────────────────────────────────────────────────────────────────────────┤ +│ data.TrusteeContract │ [✓] │ [✓] [✓] [✓] [ ]│[ ] [✓] [ ] [ ]│[ ][ ][ ][ ]│ +│ data.TrusteePosition │ [✓] │ [✓] [✓] [ ] [ ]│[✓] [✓] [✓] [ ]│[ ][ ][ ][ ]│ +│ ui.feature.trustee.* │ [✓] │ - - - - │ - - - - │ - - - - │ +└────────────────────────────────────────────────────────────────────────────┘ +``` + +**Vorteile:** +- Alle Berechtigungen auf einen Blick sichtbar +- Schnelle Toggle-Aktionen mit einzelnem Klick +- Kompakter - mehr Regeln pro Bildschirm +- Intuitive Matrix-Darstellung (bekannt von Unix-Permissions) + +### Neue Komponenten-Struktur + +``` +AccessRulesEditor/ +├── AccessRulesEditor.tsx # Haupt-Container +├── AccessRulesTable.tsx # Tabellen-basierte Darstellung (NEU) +├── AccessRuleRow.tsx # Eine Zeile = ein AccessRule (NEU) +├── AccessLevelCheckboxGroup.tsx # Checkbox-Gruppe für m/g/a (NEU) +└── AccessRules.module.css # Styles +``` + +### Neue Komponente: `AccessRulesTable.tsx` + +```typescript +interface AccessRulesTableProps { + rules: AccessRule[]; + context: RuleContext; + readOnly?: boolean; + onUpdate: (ruleId: string, updates: Partial) => void; + onDelete: (ruleId: string) => void; +} + +const AccessRulesTable: React.FC = ({ + rules, + context, + readOnly, + onUpdate, + onDelete, +}) => { + const isDataContext = context === 'DATA'; + + return ( +
+ + + + + {isDataContext && ( + <> + + + + + )} + + + {isDataContext && ( + + + + + + + + + )} + + + {rules.map(rule => ( + + ))} + +
Objekt (Dot-Notation)ViewEigene (m)Gruppe (g)Alle (a)
CRUDCRUDCRUD
+ ); +}; +``` + +### Neue Komponente: `AccessRuleRow.tsx` + +```typescript +interface AccessRuleRowProps { + rule: AccessRule; + isDataContext: boolean; + readOnly?: boolean; + onUpdate: (ruleId: string, updates: Partial) => void; + onDelete: (ruleId: string) => void; +} + +const AccessRuleRow: React.FC = ({ + rule, + isDataContext, + readOnly, + onUpdate, + onDelete, +}) => { + // Hilfsfunktion: Prüft ob Level mindestens X erreicht + const hasLevel = (level: AccessLevel | null, minLevel: 'm' | 'g' | 'a'): boolean => { + if (!level || level === 'n') return false; + const hierarchy = ['n', 'm', 'g', 'a']; + return hierarchy.indexOf(level) >= hierarchy.indexOf(minLevel); + }; + + // Hilfsfunktion: Setzt Level basierend auf Checkbox-Änderung + const toggleLevel = ( + field: 'read' | 'create' | 'update' | 'delete', + targetLevel: 'm' | 'g' | 'a', + checked: boolean + ) => { + const currentLevel = rule[field] || 'n'; + let newLevel: AccessLevel; + + if (checked) { + // Aktiviere mindestens dieses Level + newLevel = targetLevel; + } else { + // Deaktiviere dieses Level, setze auf nächst-niedrigeres + const hierarchy = ['n', 'm', 'g', 'a']; + const targetIndex = hierarchy.indexOf(targetLevel); + newLevel = hierarchy[targetIndex - 1] as AccessLevel || 'n'; + } + + onUpdate(rule.id, { [field]: newLevel }); + }; + + return ( + + {/* Objekt-Name in Dot-Notation */} + + {rule.item || '(global)'} + + + {/* View Checkbox */} + + onUpdate(rule.id, { view: e.target.checked })} + disabled={readOnly} + /> + + + {/* CRUD Checkboxen für DATA-Kontext */} + {isDataContext && ( + <> + {/* Eigene (m) */} + {['create', 'read', 'update', 'delete'].map(op => ( + + toggleLevel(op as any, 'm', e.target.checked)} + disabled={readOnly} + /> + + ))} + + {/* Gruppe (g) */} + {['create', 'read', 'update', 'delete'].map(op => ( + + toggleLevel(op as any, 'g', e.target.checked)} + disabled={readOnly} + /> + + ))} + + {/* Alle (a) */} + {['create', 'read', 'update', 'delete'].map(op => ( + + toggleLevel(op as any, 'a', e.target.checked)} + disabled={readOnly} + /> + + ))} + + )} + + {/* Löschen-Button */} + + {!readOnly && ( + + )} + + + ); +}; +``` + +### CSS für Tabellen-Layout + +```css +/* AccessRules.module.css - Erweiterungen */ + +.accessRulesTable { + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; +} + +.accessRulesTable th, +.accessRulesTable td { + padding: 0.5rem 0.375rem; + border-bottom: 1px solid var(--border-color); + text-align: center; +} + +.accessRulesTable th { + background: var(--bg-secondary); + font-weight: 600; + font-size: 0.75rem; + text-transform: uppercase; + color: var(--text-secondary); +} + +.colObject { + text-align: left !important; + min-width: 200px; +} + +.colView { + width: 50px; +} + +.colGroup { + border-left: 2px solid var(--border-color); +} + +.subHeader th { + font-size: 0.6875rem; + padding: 0.25rem; + background: var(--bg-tertiary); +} + +.objectCell { + text-align: left !important; +} + +.objectCell code { + font-family: 'Monaco', 'Menlo', monospace; + font-size: 0.8125rem; + background: var(--bg-tertiary); + padding: 0.125rem 0.375rem; + border-radius: 3px; +} + +.checkboxCell { + width: 32px; +} + +.checkboxCell input[type="checkbox"] { + width: 16px; + height: 16px; + cursor: pointer; + accent-color: var(--primary-color); +} + +.actionsCell { + width: 40px; +} + +.deleteButton { + padding: 0.25rem; + background: transparent; + border: none; + color: var(--text-tertiary); + cursor: pointer; + border-radius: 4px; +} + +.deleteButton:hover { + background: #fed7d7; + color: #c53030; +} +``` + +### Betroffene Dateien + +| Datei | Änderung | +|-------|----------| +| `frontend_nyla/src/components/AccessRules/AccessRulesTable.tsx` | NEU erstellen | +| `frontend_nyla/src/components/AccessRules/AccessRuleRow.tsx` | NEU erstellen | +| `frontend_nyla/src/components/AccessRules/AccessRulesEditor.tsx` | Integriere `AccessRulesTable` | +| `frontend_nyla/src/components/AccessRules/AccessRules.module.css` | Tabellen-Styles hinzufügen | +| `frontend_nyla/src/components/AccessRules/index.ts` | Exports aktualisieren | + +--- + +## 3. Dot-Notation & RBAC-Objekt-Katalog + +### Ist-Zustand + +Aktuell wird das `item`-Feld als Freitext eingegeben: + +```typescript +// AddRuleForm.tsx (aktuell) + setItem(e.target.value)} + placeholder="z.B. TrusteeContract oder TrusteeContract.salary" +/> +``` + +**Probleme:** +- Benutzer muss gültige Objekt-Namen kennen +- Keine Validierung gegen registrierte Objekte +- Inkonsistente Schreibweisen möglich +- Keine Übersicht der verfügbaren Objekte + +### Backend: RBAC Catalog Service + +Der `RbacCatalogService` (`gateway/modules/security/rbacCatalog.py`) registriert bereits alle verfügbaren Objekte: + +```python +class RbacCatalogService: + def __init__(self): + self._uiObjects: Dict[str, Dict[str, Any]] = {} + self._resourceObjects: Dict[str, Dict[str, Any]] = {} + self._dataObjects: Dict[str, Dict[str, Any]] = {} # ← NEU + + def getUiObjects(self, featureCode: Optional[str] = None) -> List[Dict[str, Any]]: + # Gibt UI-Objekte zurück (z.B. "ui.feature.trustee.dashboard") + + def getResourceObjects(self, featureCode: Optional[str] = None) -> List[Dict[str, Any]]: + # Gibt Resource-Objekte zurück (z.B. "resource.feature.trustee.documents.create") +``` + +**Fehlt:** DATA-Objekte werden nicht im Katalog registriert! + +### Soll-Zustand + +1. **Backend:** Neuer API-Endpoint zum Abrufen verfügbarer RBAC-Objekte +2. **Backend:** DATA-Objekte (Tabellen/Entitäten) im Katalog registrieren +3. **Frontend:** Dropdown zur Auswahl aus verfügbaren Objekten +4. **Frontend:** Konsistente Dot-Notation für alle Objekte + +### Backend-Implementierung + +#### 1. DATA-Objekte im Katalog registrieren + +Erweitere `rbacCatalog.py`: + +```python +# gateway/modules/security/rbacCatalog.py + +class RbacCatalogService: + def __init__(self): + self._uiObjects: Dict[str, Dict[str, Any]] = {} + self._resourceObjects: Dict[str, Dict[str, Any]] = {} + self._dataObjects: Dict[str, Dict[str, Any]] = {} # NEU + + def registerDataObject( + self, + featureCode: str, + objectKey: str, + label: Dict[str, str], + meta: Optional[Dict[str, Any]] = None + ) -> bool: + """Register a DATA object (table/entity) for a feature.""" + try: + self._dataObjects[objectKey] = { + "objectKey": objectKey, + "featureCode": featureCode, + "label": label, + "meta": meta or {}, + "type": "DATA" + } + return True + except Exception as e: + logger.error(f"Failed to register DATA object {objectKey}: {e}") + return False + + def getDataObjects(self, featureCode: Optional[str] = None) -> List[Dict[str, Any]]: + """Get all DATA objects, optionally filtered by feature.""" + if featureCode: + return [obj for obj in self._dataObjects.values() + if obj["featureCode"] == featureCode] + return list(self._dataObjects.values()) + + def getAllCatalogObjects(self, featureCode: Optional[str] = None) -> Dict[str, List[Dict[str, Any]]]: + """Get all catalog objects grouped by type.""" + return { + "DATA": self.getDataObjects(featureCode), + "UI": self.getUiObjects(featureCode), + "RESOURCE": self.getResourceObjects(featureCode) + } +``` + +#### 2. DATA-Objekte in Feature registrieren + +Erweitere `mainTrustee.py`: + +```python +# gateway/modules/features/trustee/mainTrustee.py + +# DATA Objects for RBAC catalog (Tabellen/Entitäten) +DATA_OBJECTS = [ + { + "objectKey": "data.feature.trustee.TrusteeContract", + "label": {"en": "Contract", "de": "Vertrag", "fr": "Contrat"}, + "meta": {"table": "TrusteeContract", "fields": ["id", "name", "salary", ...]} + }, + { + "objectKey": "data.feature.trustee.TrusteePosition", + "label": {"en": "Position", "de": "Position", "fr": "Position"}, + "meta": {"table": "TrusteePosition", "fields": ["id", "label", ...]} + }, + { + "objectKey": "data.feature.trustee.TrusteeDocument", + "label": {"en": "Document", "de": "Dokument", "fr": "Document"}, + "meta": {"table": "TrusteeDocument", "fields": ["id", "filename", ...]} + }, +] + +def registerFeature(catalogService) -> bool: + # ... bestehende UI/Resource Registrierung ... + + # NEU: DATA-Objekte registrieren + for dataObj in DATA_OBJECTS: + catalogService.registerDataObject( + featureCode=FEATURE_CODE, + objectKey=dataObj["objectKey"], + label=dataObj["label"], + meta=dataObj.get("meta") + ) +``` + +#### 3. Neuer API-Endpoint für Katalog-Objekte + +Erweitere `routeAdminRbacRules.py`: + +```python +# gateway/modules/routes/routeAdminRbacRules.py + +from modules.security.rbacCatalog import getCatalogService + +@router.get("/catalog/objects", response_model=Dict[str, Any]) +@limiter.limit("60/minute") +async def get_catalog_objects( + request: Request, + context: Optional[str] = Query(None, description="Filter by context (DATA, UI, RESOURCE)"), + featureCode: Optional[str] = Query(None, description="Filter by feature code"), + currentUser: User = Depends(requireSysAdmin) +) -> Dict[str, Any]: + """ + Get available RBAC catalog objects. + Returns all registered DATA, UI and RESOURCE objects that can be used in AccessRules. + + Query Parameters: + - context: Optional filter by context type (DATA, UI, RESOURCE) + - featureCode: Optional filter by feature (e.g., "trustee") + + Returns: + - Dictionary with objects grouped by context type, each with: + - objectKey: Dot-notation identifier (e.g., "data.feature.trustee.TrusteeContract") + - label: Multilingual label + - featureCode: Owning feature + - meta: Additional metadata + + Examples: + - GET /api/rbac/catalog/objects → all objects + - GET /api/rbac/catalog/objects?context=DATA → only DATA objects + - GET /api/rbac/catalog/objects?featureCode=trustee → only trustee objects + """ + try: + catalog = getCatalogService() + + if context: + # Einzelner Context + try: + accessContext = AccessRuleContext(context.upper()) + except ValueError: + raise HTTPException( + status_code=400, + detail=f"Invalid context '{context}'. Must be one of: DATA, UI, RESOURCE" + ) + + if accessContext == AccessRuleContext.DATA: + objects = catalog.getDataObjects(featureCode) + elif accessContext == AccessRuleContext.UI: + objects = catalog.getUiObjects(featureCode) + else: + objects = catalog.getResourceObjects(featureCode) + + return {context.upper(): objects} + else: + # Alle Contexts + return catalog.getAllCatalogObjects(featureCode) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Error getting catalog objects: {str(e)}") + raise HTTPException( + status_code=500, + detail=f"Failed to get catalog objects: {str(e)}" + ) +``` + +### Frontend-Implementierung + +#### 1. Hook für Katalog-Objekte + +Erstelle `useCatalogObjects.ts`: + +```typescript +// frontend_nyla/src/hooks/useCatalogObjects.ts + +import { useState, useCallback } from 'react'; +import api from '../api'; +import { RuleContext } from './useAccessRules'; + +export interface CatalogObject { + objectKey: string; + featureCode: string; + label: { [lang: string]: string }; + meta?: Record; + type: RuleContext; +} + +interface CatalogObjects { + DATA: CatalogObject[]; + UI: CatalogObject[]; + RESOURCE: CatalogObject[]; +} + +export function useCatalogObjects() { + const [objects, setObjects] = useState({ DATA: [], UI: [], RESOURCE: [] }); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const fetchObjects = useCallback(async ( + context?: RuleContext, + featureCode?: string + ): Promise => { + setLoading(true); + setError(null); + + try { + const params = new URLSearchParams(); + if (context) params.append('context', context); + if (featureCode) params.append('featureCode', featureCode); + + const url = `/api/rbac/catalog/objects${params.toString() ? `?${params}` : ''}`; + const response = await api.get(url); + + const data = response.data as CatalogObjects; + setObjects(data); + return data; + } catch (err: any) { + const errorMsg = err.response?.data?.detail || err.message || 'Fehler beim Laden'; + setError(errorMsg); + return { DATA: [], UI: [], RESOURCE: [] }; + } finally { + setLoading(false); + } + }, []); + + const getObjectsByContext = useCallback((context: RuleContext): CatalogObject[] => { + return objects[context] || []; + }, [objects]); + + return { + objects, + loading, + error, + fetchObjects, + getObjectsByContext, + }; +} +``` + +#### 2. Objekt-Auswahl Dropdown + +Aktualisiere `AddRuleForm`: + +```typescript +// AccessRulesEditor.tsx - AddRuleForm Komponente + +interface AddRuleFormProps { + context: RuleContext; + availableObjects: CatalogObject[]; // NEU + onAdd: (rule: AccessRuleCreate) => void; + onCancel: () => void; +} + +const AddRuleForm: React.FC = ({ + context, + availableObjects, // NEU + onAdd, + onCancel +}) => { + const [item, setItem] = useState(''); + const [useCustom, setUseCustom] = useState(false); // NEU: Toggle für Freitext + // ... rest state + + // Gruppiere Objekte nach Feature + const groupedObjects = useMemo(() => { + const grouped: Record = {}; + availableObjects.forEach(obj => { + if (!grouped[obj.featureCode]) { + grouped[obj.featureCode] = []; + } + grouped[obj.featureCode].push(obj); + }); + return grouped; + }, [availableObjects]); + + // Aktuelle Sprache für Labels + const lang = useLanguage(); // oder 'de' als Default + + return ( +
+
+ + + {useCustom ? ( + // Freitext-Eingabe (wie bisher) + setItem(e.target.value)} + placeholder={getPlaceholder()} + className={styles.formInput} + /> + ) : ( + // Dropdown mit verfügbaren Objekten + + )} + + + Leer lassen für globale Regel. Längster Match gewinnt. + +
+ + {/* ... rest of form */} +
+ ); +}; +``` + +#### 3. Katalog-Objekte im Editor laden + +```typescript +// AccessRulesEditor.tsx + +export const AccessRulesEditor: React.FC = ({ + roleId, + roleName, + readOnly = false, + apiBasePath = '/api/rbac', + mandateId, +}) => { + // ... bestehende hooks + + // NEU: Katalog-Objekte laden + const { objects: catalogObjects, fetchObjects } = useCatalogObjects(); + + useEffect(() => { + fetchObjects(); // Alle Objekte laden beim Mount + }, [fetchObjects]); + + // Objekte für aktuellen Tab filtern + const currentContextObjects = useMemo(() => { + return catalogObjects[activeTab] || []; + }, [catalogObjects, activeTab]); + + return ( +
+ {/* ... header, tabs */} + +
+ {activeTab !== 'JSON' && ( + + )} + {/* ... JSON tab */} +
+ + {/* ... action bar */} +
+ ); +}; +``` + +### Dot-Notation Schema + +Konsistentes Namensschema für alle RBAC-Objekte: + +``` +...[.] + +Beispiele: +├── DATA +│ ├── data.feature.trustee.TrusteeContract +│ ├── data.feature.trustee.TrusteeContract.salary (Feld-Level) +│ ├── data.feature.trustee.TrusteePosition +│ ├── data.system.UserInDB (System-Entität) +│ └── data.system.Mandate +│ +├── UI +│ ├── ui.feature.trustee.dashboard +│ ├── ui.feature.trustee.positions +│ ├── ui.admin.users (Admin-Bereich) +│ ├── ui.playground.voice.settings (Playground) +│ └── ui.nav.trustee (Navigation) +│ +└── RESOURCE + ├── resource.feature.trustee.documents.create + ├── resource.feature.trustee.instance-roles.manage + ├── resource.ai.model.anthropic (AI-Resources) + └── resource.connector.sharepoint (Connectors) +``` + +### Betroffene Dateien + +| Datei | Änderung | +|-------|----------| +| **Backend** || +| `gateway/modules/security/rbacCatalog.py` | `registerDataObject()`, `getDataObjects()`, `getAllCatalogObjects()` | +| `gateway/modules/routes/routeAdminRbacRules.py` | Neuer Endpoint `GET /api/rbac/catalog/objects` | +| `gateway/modules/features/trustee/mainTrustee.py` | `DATA_OBJECTS` Liste, Registrierung erweitern | +| `gateway/modules/features/*/main*.py` | DATA_OBJECTS in allen Features | +| **Frontend** || +| `frontend_nyla/src/hooks/useCatalogObjects.ts` | NEU erstellen | +| `frontend_nyla/src/hooks/index.ts` | Export hinzufügen | +| `frontend_nyla/src/components/AccessRules/AccessRulesEditor.tsx` | Katalog-Objekte integrieren | + +--- + +## Zusammenfassung der Änderungen + +### Backend + +| Priorität | Datei | Beschreibung | +|-----------|-------|--------------| +| 1 | `rbacCatalog.py` | DATA-Objekte Registrierung | +| 1 | `routeAdminRbacRules.py` | `/catalog/objects` Endpoint | +| 2 | `mainTrustee.py` | DATA_OBJECTS definieren | +| 2 | Alle Feature `main*.py` | DATA_OBJECTS in allen Features | + +### Frontend + +| Priorität | Datei | Beschreibung | +|-----------|-------|--------------| +| 1 | `AdminMandateRolePermissionsPage.tsx` | `readOnly={false}` für System-Rollen | +| 1 | `AccessRulesTable.tsx` | Neue Tabellen-Komponente | +| 1 | `AccessRuleRow.tsx` | Zeilen-Komponente mit Checkboxen | +| 2 | `useCatalogObjects.ts` | Hook für Katalog-Objekte | +| 2 | `AccessRulesEditor.tsx` | Integration Katalog + Tabelle | +| 2 | `AccessRules.module.css` | Styles für Tabelle | + +### Migrations-Strategie + +1. **Phase 1:** Backend-Erweiterungen (Katalog + Endpoint) +2. **Phase 2:** Frontend Checkbox-UI (ersetzt Dropdown) +3. **Phase 3:** Katalog-Integration im Frontend +4. **Phase 4:** System-Rollen bearbeitbar machen + +--- + +## Mockups + +### Kompakte Tabellen-Ansicht (Phase 2) + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ 📊 DATEN-REGELN [+ Neue Regel] │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ │ │ EIGENE │ GRUPPE │ ALLE │ +│ OBJEKT │VIEW │ C R U D │ C R U D │C R U D│ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ data.feature.trustee.* │ ✓ │ ✓ ✓ ✓ □ │ □ ✓ □ □ │□ □ □ □│ 🗑 +│ data.feature.trustee.docs │ ✓ │ ✓ ✓ ✓ ✓ │ ✓ ✓ ✓ ✓ │✓ ✓ ✓ ✓│ 🗑 +└─────────────────────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ 🖥 UI-REGELN [+ Neue Regel] │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ OBJEKT │ VIEW │ │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ ui.feature.trustee.dashboard │ ✓ │ 🗑 │ +│ ui.feature.trustee.positions │ ✓ │ 🗑 │ +│ ui.feature.trustee.documents │ ✓ │ 🗑 │ +│ ui.admin.mandate-roles │ □ │ 🗑 │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + +### Objekt-Auswahl Dropdown (Phase 3) + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Neue Regel hinzufügen │ +├─────────────────────────────────────────────────────────────────┤ +│ Objekt auswählen: [Freie Eingabe →] │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ -- Global (alle Objekte) -- ▼│ │ +│ ├─────────────────────────────────────────────────────────────┤ │ +│ │ ▸ TRUSTEE │ │ +│ │ data.feature.trustee.TrusteeContract - Vertrag │ │ +│ │ data.feature.trustee.TrusteePosition - Position │ │ +│ │ data.feature.trustee.TrusteeDocument - Dokument │ │ +│ │ ▸ SYSTEM │ │ +│ │ data.system.UserInDB - Benutzer │ │ +│ │ data.system.Mandate - Mandant │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ │ +│ [✓] Sichtbar (View) │ +│ │ +│ [Abbrechen] [Hinzufügen] │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Offene Fragen + +1. **Feature-Filter:** Soll der Katalog nach aktiven Features des Mandanten gefiltert werden? Ja, der Katalog soll nach aktiven Features des Mandanten gefiltert werden. +2. **Wildcard-Patterns:** Unterstützung für `data.feature.trustee.*` (alle Objekte eines Features)? Ja, die Unterstützung für `data.feature.trustee.*` (alle Objekte eines Features) soll implementiert werden. +3. **Feld-Level Permissions:** Sollen einzelne Felder (z.B. `TrusteeContract.salary`) unterstützt werden? Ja, die Unterstützung für einzelne Felder (z.B. `TrusteeContract.salary`) soll implementiert werden. +4. **Vererbung:** Sollen Berechtigungen von übergeordneten Objekten vererbt werden? Ja, die Vererbung von Berechtigungen von übergeordneten Objekten soll implementiert werden. + +--- + +*Erstellt: 2026-01-24* +*Status: Entwurf zur Diskussion*