# Copyright (c) 2025 Patrick Motsch # All rights reserved. """ System Module - Main Module. Registers system-level RBAC objects (UI, DATA, RESOURCE) that are not part of any feature. These are global system pages and resources available to all users with appropriate roles. Also defines the navigation structure for the frontend. """ import logging from typing import Dict, List, Any, Optional logger = logging.getLogger(__name__) # System metadata FEATURE_CODE = "system" FEATURE_LABEL = {"en": "System", "de": "System", "fr": "Système"} FEATURE_ICON = "mdi-cog" # ============================================================================= # Navigation Structure (Single Source of Truth) # ============================================================================= # # Block Order (gemäss Navigation-API-Konzept): # - System: 10 # - : 15 (wird in routeSystem.py eingefügt) # - Basisdaten: 30 # - Administration: 200 # # NOTE: Workflows and Migrate sections removed - now handled as features # # Item Order: Default-Abstand 10 pro Item # uiComponent: Abgeleitet von objectKey (ui.system.home -> page.system.home) # icon: Wird intern gehalten aber NICHT in der API Response zurückgegeben NAVIGATION_SECTIONS = [ { "id": "system", "title": {"en": "SYSTEM", "de": "SYSTEM", "fr": "SYSTÈME"}, "order": 10, "items": [ { "id": "home", "objectKey": "ui.system.home", "label": {"en": "Home", "de": "Übersicht", "fr": "Accueil"}, "icon": "FaHome", "path": "/", "order": 10, "public": True, }, { "id": "store", "objectKey": "ui.system.store", "label": {"en": "Store", "de": "Store", "fr": "Store"}, "icon": "FaStore", "path": "/store", "order": 15, "public": True, }, { "id": "settings", "objectKey": "ui.system.settings", "label": {"en": "Settings", "de": "Einstellungen", "fr": "Paramètres"}, "icon": "FaCog", "path": "/settings", "order": 20, "public": True, }, ], }, { "id": "basedata", "title": {"en": "BASE DATA", "de": "BASISDATEN", "fr": "DONNÉES DE BASE"}, "order": 30, "items": [ { "id": "prompts", "objectKey": "ui.system.prompts", "label": {"en": "Prompts", "de": "Prompts", "fr": "Prompts"}, "icon": "FaLightbulb", "path": "/basedata/prompts", "order": 10, }, { "id": "files", "objectKey": "ui.system.files", "label": {"en": "Files", "de": "Dateien", "fr": "Fichiers"}, "icon": "FaRegFileAlt", "path": "/basedata/files", "order": 20, }, { "id": "connections", "objectKey": "ui.system.connections", "label": {"en": "Connections", "de": "Verbindungen", "fr": "Connexions"}, "icon": "FaLink", "path": "/basedata/connections", "order": 30, }, ], }, { "id": "billing", "title": {"en": "BILLING", "de": "BILLING", "fr": "FACTURATION"}, "order": 35, "items": [ { "id": "billing-transactions", "objectKey": "ui.billing.transactions", "label": {"en": "Billing", "de": "Billing", "fr": "Facturation"}, "icon": "FaWallet", "path": "/billing/transactions", "order": 10, }, ], }, # ─── Administration (with subgroups) ─── # Access control is at item level, NOT section level. # Groups auto-hide if 0 visible pages for the user. { "id": "admin", "title": {"en": "ADMINISTRATION", "de": "ADMINISTRATION", "fr": "ADMINISTRATION"}, "order": 200, "subgroups": [ # ── Wizards ── { "id": "admin-wizards", "title": {"en": "Wizards", "de": "Wizards", "fr": "Assistants"}, "order": 10, "items": [ { "id": "admin-mandate-wizard", "objectKey": "ui.admin.mandateWizard", "label": {"en": "Mandate Wizard", "de": "Mandanten-Wizard", "fr": "Assistant mandat"}, "icon": "FaMagic", "path": "/admin/mandate-wizard", "order": 10, "adminOnly": True, }, { "id": "admin-invitation-wizard", "objectKey": "ui.admin.invitationWizard", "label": {"en": "Invitation Wizard", "de": "Einladungs-Wizard", "fr": "Assistant d'invitation"}, "icon": "FaEnvelopeOpenText", "path": "/admin/invitation-wizard", "order": 20, "adminOnly": True, }, ], }, # ── Users ── { "id": "admin-users-group", "title": {"en": "Users", "de": "Benutzer", "fr": "Utilisateurs"}, "order": 20, "items": [ { "id": "admin-users", "objectKey": "ui.admin.users", "label": {"en": "Users", "de": "Benutzer", "fr": "Utilisateurs"}, "icon": "FaUsers", "path": "/admin/users", "order": 10, "adminOnly": True, }, { "id": "admin-invitations", "objectKey": "ui.admin.invitations", "label": {"en": "User Invitations", "de": "Benutzer-Einladungen", "fr": "Invitations utilisateurs"}, "icon": "FaEnvelopeOpenText", "path": "/admin/invitations", "order": 20, "adminOnly": True, }, { "id": "admin-user-access-overview", "objectKey": "ui.admin.userAccessOverview", "label": {"en": "User Access Overview", "de": "Benutzer-Zugriffsübersicht", "fr": "Aperçu des accès utilisateur"}, "icon": "FaClipboardList", "path": "/admin/user-access-overview", "order": 30, "adminOnly": True, }, { "id": "admin-billing", "objectKey": "ui.admin.billing", "label": {"en": "Billing Administration", "de": "Billing-Verwaltung", "fr": "Administration de facturation"}, "icon": "FaMoneyBillAlt", "path": "/admin/billing", "order": 40, "adminOnly": True, "sysAdminOnly": True, }, ], }, # ── System ── { "id": "admin-system-group", "title": {"en": "System", "de": "System", "fr": "Système"}, "order": 30, "items": [ { "id": "admin-roles", "objectKey": "ui.admin.roles", "label": {"en": "Roles", "de": "Rollen", "fr": "Rôles"}, "icon": "FaUserTag", "path": "/admin/mandate-roles", "order": 10, "adminOnly": True, }, { "id": "admin-mandate-role-permissions", "objectKey": "ui.admin.mandateRolePermissions", "label": {"en": "Role Permissions", "de": "Rollen-Berechtigungen", "fr": "Permissions des rôles"}, "icon": "FaKey", "path": "/admin/mandate-role-permissions", "order": 20, "adminOnly": True, }, { "id": "admin-mandates", "objectKey": "ui.admin.mandates", "label": {"en": "Mandates", "de": "Mandanten", "fr": "Mandats"}, "icon": "FaBuilding", "path": "/admin/mandates", "order": 30, "adminOnly": True, }, { "id": "admin-user-mandates", "objectKey": "ui.admin.userMandates", "label": {"en": "Mandate Members", "de": "Mandanten-Mitglieder", "fr": "Membres du mandat"}, "icon": "FaUserFriends", "path": "/admin/user-mandates", "order": 40, "adminOnly": True, }, { "id": "admin-access", "objectKey": "ui.admin.access", "label": {"en": "Access Management", "de": "Zugriffsverwaltung", "fr": "Gestion des accès"}, "icon": "FaBuilding", "path": "/admin/access", "order": 50, "adminOnly": True, }, { "id": "admin-feature-instances", "objectKey": "ui.admin.featureInstances", "label": {"en": "Feature Instances", "de": "Feature-Instanzen", "fr": "Instances de features"}, "icon": "FaCubes", "path": "/admin/feature-instances", "order": 60, "adminOnly": True, }, { "id": "admin-feature-roles", "objectKey": "ui.admin.featureRoles", "label": {"en": "Feature Role Templates", "de": "Features Rollen-Vorlagen", "fr": "Modèles de rôles features"}, "icon": "FaShieldAlt", "path": "/admin/feature-roles", "order": 70, "adminOnly": True, "sysAdminOnly": True, }, { "id": "admin-automation-events", "objectKey": "ui.admin.automationEvents", "label": {"en": "Automation Events", "de": "Automation Events", "fr": "Événements d'automatisation"}, "icon": "FaClock", "path": "/admin/automation-events", "order": 80, "adminOnly": True, "sysAdminOnly": True, }, { "id": "admin-logs", "objectKey": "ui.admin.logs", "label": {"en": "Logs", "de": "Logs", "fr": "Logs"}, "icon": "FaFileAlt", "path": "/admin/logs", "order": 90, "adminOnly": True, "sysAdminOnly": True, }, ], }, ], }, ] def _objectKeyToUiComponent(objectKey: str) -> str: """ Convert objectKey to uiComponent. Example: ui.system.home -> page.system.home ui.admin.users -> page.admin.users ui.feature.trustee.dashboard -> page.feature.trustee.dashboard """ if objectKey.startswith("ui."): return "page." + objectKey[3:] return objectKey def _buildUiObjectsFromNavigation() -> List[Dict[str, Any]]: """Build UI_OBJECTS list from NAVIGATION_SECTIONS for RBAC registration.""" uiObjects = [] for section in NAVIGATION_SECTIONS: # Process direct items for item in section.get("items", []): uiObjects.append({ "objectKey": item["objectKey"], "label": item["label"], "meta": { "area": section["id"], "public": item.get("public", False), "adminOnly": item.get("adminOnly", False), "deprecated": item.get("deprecated", False), "path": item["path"], "icon": item["icon"], } }) # Process subgroups (nested items within section) for subgroup in section.get("subgroups", []): for item in subgroup.get("items", []): uiObjects.append({ "objectKey": item["objectKey"], "label": item["label"], "meta": { "area": section["id"], "subgroup": subgroup["id"], "public": item.get("public", False), "adminOnly": item.get("adminOnly", False), "deprecated": item.get("deprecated", False), "path": item["path"], "icon": item["icon"], } }) return uiObjects # Generate UI_OBJECTS from navigation structure UI_OBJECTS = _buildUiObjectsFromNavigation() # ============================================================================= # System DATA Objects # ============================================================================= # Namespace structure: # - data.uam.* → User Access Management (mandantenübergreifend) # - data.chat.* → Chat/AI-Daten (benutzer-eigen, kein Mandantenkontext) # - data.files.* → Dateien (benutzer-eigen) # - data.automation.* → Automation (benutzer-eigen) # - data.feature.* → Mandanten-/Feature-spezifische Daten (dynamisch) # ============================================================================= DATA_OBJECTS = [ # UAM (User Access Management) - mandantenübergreifend { "objectKey": "data.uam.UserInDB", "label": {"en": "User", "de": "Benutzer", "fr": "Utilisateur"}, "meta": {"table": "UserInDB", "namespace": "uam"} }, { "objectKey": "data.uam.AuthEvent", "label": {"en": "Auth Event", "de": "Auth-Ereignis", "fr": "Événement d'auth"}, "meta": {"table": "AuthEvent", "namespace": "uam"} }, { "objectKey": "data.uam.UserConnection", "label": {"en": "Connection", "de": "Verbindung", "fr": "Connexion"}, "meta": {"table": "UserConnection", "namespace": "uam"} }, { "objectKey": "data.uam.Mandate", "label": {"en": "Mandate", "de": "Mandant", "fr": "Mandat"}, "meta": {"table": "Mandate", "namespace": "uam"} }, { "objectKey": "data.uam.UserMandate", "label": {"en": "User Mandate", "de": "Benutzer-Mandant", "fr": "Mandat utilisateur"}, "meta": {"table": "UserMandate", "namespace": "uam"} }, { "objectKey": "data.uam.Invitation", "label": {"en": "Invitation", "de": "Einladung", "fr": "Invitation"}, "meta": {"table": "Invitation", "namespace": "uam"} }, { "objectKey": "data.uam.Role", "label": {"en": "Role", "de": "Rolle", "fr": "Rôle"}, "meta": {"table": "Role", "namespace": "uam"} }, { "objectKey": "data.uam.AccessRule", "label": {"en": "Access Rule", "de": "Zugriffsregel", "fr": "Règle d'accès"}, "meta": {"table": "AccessRule", "namespace": "uam"} }, { "objectKey": "data.uam.FeatureInstance", "label": {"en": "Feature Instance", "de": "Feature-Instanz", "fr": "Instance de feature"}, "meta": {"table": "FeatureInstance", "namespace": "uam"} }, # Chat - benutzer-eigen, kein Mandantenkontext { "objectKey": "data.chat.Prompt", "label": {"en": "Prompt", "de": "Prompt", "fr": "Prompt"}, "meta": {"table": "Prompt", "namespace": "chat", "groupDisabled": True} }, { "objectKey": "data.chat.ChatWorkflow", "label": {"en": "Chat Workflow", "de": "Chat-Workflow", "fr": "Workflow de chat"}, "meta": {"table": "ChatWorkflow", "namespace": "chat", "groupDisabled": True} }, # Files - benutzer-eigen { "objectKey": "data.files.FileItem", "label": {"en": "File", "de": "Datei", "fr": "Fichier"}, "meta": {"table": "FileItem", "namespace": "files", "groupDisabled": True} }, # Automation - benutzer-eigen { "objectKey": "data.automation.AutomationDefinition", "label": {"en": "Automation", "de": "Automatisierung", "fr": "Automatisation"}, "meta": {"table": "AutomationDefinition", "namespace": "automation", "groupDisabled": True} }, ] # ============================================================================= # System RESOURCE Objects # ============================================================================= RESOURCE_OBJECTS = [ { "objectKey": "resource.store.automation", "label": {"en": "Store: Automation", "de": "Store: Automation", "fr": "Store: Automatisation"}, "meta": {"category": "store", "featureCode": "automation"} }, { "objectKey": "resource.store.chatplayground", "label": {"en": "Store: Chat Playground", "de": "Store: Chat Playground", "fr": "Store: Chat Playground"}, "meta": {"category": "store", "featureCode": "chatplayground"} }, { "objectKey": "resource.store.codeeditor", "label": {"en": "Store: Code Editor", "de": "Store: Code Editor", "fr": "Store: Code Editor"}, "meta": {"category": "store", "featureCode": "codeeditor"} }, { "objectKey": "resource.store.teamsbot", "label": {"en": "Store: Teams Bot", "de": "Store: Teams Bot", "fr": "Store: Teams Bot"}, "meta": {"category": "store", "featureCode": "teamsbot"} }, { "objectKey": "resource.store.workspace", "label": {"en": "Store: AI Workspace", "de": "Store: AI Workspace", "fr": "Store: AI Workspace"}, "meta": {"category": "store", "featureCode": "workspace"} }, { "objectKey": "resource.system.api.auth", "label": {"en": "Authentication API", "de": "Authentifizierungs-API", "fr": "API d'authentification"}, "meta": {"endpoint": "/api/auth/*"} }, { "objectKey": "resource.system.api.users", "label": {"en": "Users API", "de": "Benutzer-API", "fr": "API des utilisateurs"}, "meta": {"endpoint": "/api/users/*"} }, { "objectKey": "resource.system.api.mandates", "label": {"en": "Mandates API", "de": "Mandanten-API", "fr": "API des mandats"}, "meta": {"endpoint": "/api/mandates/*"} }, { "objectKey": "resource.system.api.rbac", "label": {"en": "RBAC API", "de": "RBAC-API", "fr": "API RBAC"}, "meta": {"endpoint": "/api/rbac/*"} }, ] def _discoverAicoreProviderObjects() -> List[Dict[str, Any]]: """ Dynamically discover AICore provider resources for the RBAC catalog. Providers are discovered from the model registry at startup. """ providerLabels = { "anthropic": {"en": "Anthropic (Claude)", "de": "Anthropic (Claude)", "fr": "Anthropic (Claude)"}, "openai": {"en": "OpenAI (GPT)", "de": "OpenAI (GPT)", "fr": "OpenAI (GPT)"}, "mistral": {"en": "Mistral (Le Chat)", "de": "Mistral (Le Chat)", "fr": "Mistral (Le Chat)"}, "perplexity": {"en": "Perplexity", "de": "Perplexity", "fr": "Perplexity"}, "tavily": {"en": "Tavily (Web Search)", "de": "Tavily (Websuche)", "fr": "Tavily (Recherche Web)"}, "privatellm": {"en": "Private LLM", "de": "Private LLM", "fr": "LLM Privé"}, "internal": {"en": "Internal", "de": "Intern", "fr": "Interne"}, } try: from modules.aicore.aicoreModelRegistry import modelRegistry connectors = modelRegistry.discoverConnectors() providers = [c.getConnectorType() for c in connectors] objects = [] for provider in providers: label = providerLabels.get(provider, {"en": provider, "de": provider, "fr": provider}) objects.append({ "objectKey": f"resource.aicore.{provider}", "label": label, "meta": {"provider": provider, "category": "aicore"} }) if objects: logger.info(f"Discovered {len(objects)} AICore provider catalog objects: {providers}") return objects except Exception as e: logger.warning(f"Failed to discover AICore providers for catalog: {e}") return [] def registerFeature(catalogService) -> bool: """ Register system RBAC objects in the catalog. Args: catalogService: The RBAC catalog service instance Returns: True if registration was successful """ try: # Register UI objects for uiObj in UI_OBJECTS: catalogService.registerUiObject( featureCode=FEATURE_CODE, objectKey=uiObj["objectKey"], label=uiObj["label"], meta=uiObj.get("meta") ) # Register DATA objects for dataObj in DATA_OBJECTS: catalogService.registerDataObject( featureCode=FEATURE_CODE, objectKey=dataObj["objectKey"], label=dataObj["label"], meta=dataObj.get("meta") ) # Register RESOURCE objects for resObj in RESOURCE_OBJECTS: catalogService.registerResourceObject( featureCode=FEATURE_CODE, objectKey=resObj["objectKey"], label=resObj["label"], meta=resObj.get("meta") ) # Register dynamically discovered AICore provider resources aicoreObjects = _discoverAicoreProviderObjects() for aicoreObj in aicoreObjects: catalogService.registerResourceObject( featureCode=FEATURE_CODE, objectKey=aicoreObj["objectKey"], label=aicoreObj["label"], meta=aicoreObj.get("meta") ) # Register feature definition catalogService.registerFeatureDefinition( featureCode=FEATURE_CODE, label=FEATURE_LABEL, icon=FEATURE_ICON ) logger.info(f"Registered system RBAC objects: {len(UI_OBJECTS)} UI, {len(DATA_OBJECTS)} DATA, {len(RESOURCE_OBJECTS)} RESOURCE") return True except Exception as e: logger.error(f"Failed to register system RBAC objects: {e}") return False