24 KiB
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
orderNummer 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
{
"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.
{
"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.
{
"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
{
"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
- Gateway lädt Default-Order aus Konfiguration
- Gateway lädt User-Settings (falls vorhanden)
- Für jedes Element: User-Order überschreibt Default
- 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-name>
page.admin.<page-name>
Beispiele:
page.system.homepage.system.settingspage.system.promptspage.admin.userspage.admin.mandates
Feature-Seiten
page.feature.<feature-code>.<view-name>
Beispiele:
page.feature.trustee.dashboardpage.feature.trustee.positionspage.feature.trustee.instance-rolespage.feature.realestate.projects
Feature-Codes (für Gruppierung)
feature.<feature-code>
Beispiele:
feature.trusteefeature.realestatefeature.chatworkflow
ObjectKey-Konvention (RBAC)
Die objectKey werden für Access Rules verwendet und sind vollqualifiziert:
UI Objekte
ui.system.<name> → System-Seiten
ui.admin.<name> → Admin-Seiten
ui.feature.<code>.<view> → Feature-Views
DATA Objekte
data.system.<table> → System-Tabellen
data.feature.<code>.<table> → Feature-Tabellen
RESOURCE Objekte
resource.system.<action> → System-Aktionen
resource.feature.<code>.<action> → Feature-Aktionen
UI Mapping & Fehlerbehandlung
UI Code Registry
Das UI definiert ein Mapping von Codes zu Komponenten:
// frontend_nyla/src/config/pageRegistry.ts
export const PAGE_REGISTRY: Record<string, PageDefinition> = {
// 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<string, FeatureDefinition> = {
'feature.trustee': {
icon: FaBriefcase,
color: '#e74c3c',
},
'feature.realestate': {
icon: FaBuilding,
color: '#3498db',
},
};
Fehlerbehandlung
Wenn ein uiComponent Code nicht gemappt werden kann:
function renderNavItem(item: NavItem) {
const pageDef = PAGE_REGISTRY[item.uiComponent];
if (!pageDef) {
// Fehler sichtbar machen!
return (
<NavError>
⚠️ Unbekannter Code: {item.uiComponent}
<small>ObjectKey: {item.objectKey}</small>
</NavError>
);
}
return (
<NavLink to={item.uiPath} icon={pageDef.icon}>
{item.uiLabel}
</NavLink>
);
}
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:
- Default-Order aus Konfiguration
- User-Override aus Settings (falls vorhanden)
// 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
- Neuer Endpoint
/api/navigationerstellen - Static Blocks aus
NAVIGATION_SECTIONSgenerieren - Dynamic Block aus Feature-Instanzen generieren
- Permission-Filter anwenden (nur sichtbare Elemente)
- uiComponent Codes gemäß Konvention setzen
- Default-Order für alle Elemente setzen
Phase 2: Gateway - Access Rules
- TEMPLATE_ROLES auf vollqualifizierte
objectKeyumstellen - Permission-Check auf
objectKeyumstellen - Migration bestehender Access Rules (falls nötig)
Phase 3: Gateway - User Settings
- User Settings Modell erweitern mit
navOrder - API Endpoint
PUT /api/user/settings/navOrdererstellen - Merge-Logik implementieren (User-Override → Default)
- Sortierung vor Response durchführen
Phase 4: Frontend
- PAGE_REGISTRY erstellen mit uiComponent → Component Mapping
- FEATURE_REGISTRY erstellen mit Feature-Code → Style Mapping
- useNavigation Hook auf neuen Endpoint umstellen
- MandateNavigation refactoren für neue Datenstruktur
- Fehlerhandling für unbekannte uiComponents implementieren
Phase 5: Frontend - Drag & Drop (Optional)
- Drag & Drop UI für Nav-Tree Sortierung
- API Call bei Order-Änderung
- Optimistic Update für schnelle UX
Phase 6: Cleanup
- FEATURE_REGISTRY (alte Version in mandate.ts) entfernen
- Hardcodierte Navigation im Frontend entfernen
- Tests für Synchronisation Gateway ↔ UI
Beispiel: Vollständige Response
{
"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
- Single Source of Truth: Gateway definiert, was sichtbar ist
- Keine Permission-Logik im UI: Alles was kommt, wird gerendert
- Flexibles UI: Verschiedene UIs können dieselben Daten anders darstellen
- Fehlertoleranz: Synchronisationsprobleme werden sofort sichtbar
- Saubere Trennung: Daten (Gateway) vs. Darstellung (UI)
- Testbarkeit: API-Response kann einfach getestet werden
- Dokumentation: Codes und ObjectKeys sind selbstdokumentierend
- Personalisierung: User können Navigation individuell sortieren
- Konsistente Sortierung: Gateway sortiert zentral, UI respektiert die Reihenfolge
Offene Fragen (Beantwortet)
- Soll der
dynamicBlock vor oder nach denstaticBlocks kommen? → Diese initiale Reihenfolge: System,<dynamic>, workflows, basisdaten, migrate to features, administration - Braucht es eine Versionierung der API für Breaking Changes? → Nein
- Wie werden Feature-spezifische Sub-Navigationen behandelt (z.B. Tabs innerhalb einer View)? → Tabs innerhalb einer View werden als separate Views behandelt
- Soll der Default-Order-Abstand 10 sein (erlaubt Einfügen zwischen Elementen)? → Ja
- 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
# 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:
iconentfernen (UI mappt selbst)- Item-Level
orderhinzufügen - Felder umbenennen für API Response
2. routeSystem.py - Navigation Endpoint
# 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
# 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
# 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
// 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: <FaBriefcase /> };
Problem: Navigation wird im Frontend zusammengebaut, nicht vom Backend.
2. mandate.ts - FEATURE_REGISTRY
// 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:
- Neuen Endpoint
GET /api/navigationerstellen - Static Blocks aus
NAVIGATION_SECTIONSgenerieren - Dynamic Block aus
_getMyFeatureInstances-Logik generieren - Permission-Filter auf beide anwenden
iconaus Response entfernen- Felder umbenennen:
label→uiLabel,path→uiPath, neuuiComponent orderauf 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:
TEMPLATE_ROLES.itemauf vollqualifizierte ObjectKeys umstellen- Migration: Bestehende AccessRules in DB aktualisieren
- Permission-Check in
_checkUiPermissionanpassen
Phase 3: Gateway - User Settings
| Datei | Änderungstyp | Aufwand | Breaking |
|---|---|---|---|
datamodelUam.py |
Erweitern | Gering | Nein |
routeSystem.py |
Endpoint | Gering | Nein |
Konkrete Aufgaben:
UserSettingsTabelle oder JSON-Feld in UserPUT /api/user/settings/navOrderEndpoint- 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:
PAGE_REGISTRYerstellen mit uiComponent → Component MappingFEATURE_REGISTRYmitfeature.*CodesuseNavigationfür neuen Endpoint anpassenMandateNavigationfür Blocks-Struktur refactoren- 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.
-- 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
- Schritt 1:
PAGE_REGISTRYim Frontend erstellen (unabhängig) - Schritt 2: Neuen
/api/navigationEndpoint mit Feature-Flag - Schritt 3: Frontend auf neuen Endpoint umstellen
- Schritt 4: AccessRules-Migration
- Schritt 5: User-Settings für Order
- Schritt 6: Alte Endpoints/Code entfernen