603 lines
21 KiB
Python
603 lines
21 KiB
Python
# 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
|
|
|
|
from modules.shared.i18nRegistry import t
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# System metadata
|
|
FEATURE_CODE = "system"
|
|
FEATURE_LABEL = "System"
|
|
FEATURE_ICON = "mdi-cog"
|
|
|
|
# =============================================================================
|
|
# Navigation Structure (Single Source of Truth)
|
|
# =============================================================================
|
|
#
|
|
# Block Order (gemäss Navigation-API-Konzept):
|
|
# - System: 10
|
|
# - <dynamic/features>: 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 = [
|
|
# ─── Meine Sicht (with top-level item + subgroups) ───
|
|
{
|
|
"id": "system",
|
|
"title": t("Meine Sicht"),
|
|
"order": 10,
|
|
"items": [
|
|
{
|
|
"id": "home",
|
|
"objectKey": "ui.system.home",
|
|
"label": t("Übersicht"),
|
|
"icon": "FaHome",
|
|
"path": "/",
|
|
"order": 10,
|
|
"public": True,
|
|
},
|
|
{
|
|
"id": "integrations",
|
|
"objectKey": "ui.system.integrations",
|
|
"label": t("Integrationen"),
|
|
"icon": "FaProjectDiagram",
|
|
"path": "/integrations",
|
|
"order": 15,
|
|
"public": True,
|
|
},
|
|
],
|
|
"subgroups": [
|
|
# ── Basisdaten ──
|
|
{
|
|
"id": "system-basedata",
|
|
"title": t("Basisdaten"),
|
|
"order": 20,
|
|
"items": [
|
|
{
|
|
"id": "connections",
|
|
"objectKey": "ui.system.connections",
|
|
"label": t("Verbindungen"),
|
|
"icon": "FaLink",
|
|
"path": "/basedata/connections",
|
|
"order": 10,
|
|
},
|
|
{
|
|
"id": "files",
|
|
"objectKey": "ui.system.files",
|
|
"label": t("Dateien"),
|
|
"icon": "FaRegFileAlt",
|
|
"path": "/basedata/files",
|
|
"order": 20,
|
|
},
|
|
{
|
|
"id": "prompts",
|
|
"objectKey": "ui.system.prompts",
|
|
"label": t("Prompts"),
|
|
"icon": "FaLightbulb",
|
|
"path": "/basedata/prompts",
|
|
"order": 30,
|
|
},
|
|
],
|
|
},
|
|
# ── Nutzung ──
|
|
{
|
|
"id": "system-usage",
|
|
"title": t("Nutzung"),
|
|
"order": 30,
|
|
"items": [
|
|
{
|
|
"id": "billing-admin",
|
|
"objectKey": "ui.system.billingAdmin",
|
|
"label": t("Abrechnung"),
|
|
"icon": "FaMoneyBillAlt",
|
|
"path": "/billing/admin",
|
|
"order": 10,
|
|
},
|
|
{
|
|
"id": "statistics",
|
|
"objectKey": "ui.system.statistics",
|
|
"label": t("Statistiken"),
|
|
"icon": "FaChartBar",
|
|
"path": "/billing/transactions",
|
|
"order": 20,
|
|
},
|
|
{
|
|
"id": "automations",
|
|
"objectKey": "ui.system.automations",
|
|
"label": t("Automations"),
|
|
"icon": "FaRobot",
|
|
"path": "/automations",
|
|
"order": 30,
|
|
},
|
|
{
|
|
"id": "store",
|
|
"objectKey": "ui.system.store",
|
|
"label": t("Store"),
|
|
"icon": "FaStore",
|
|
"path": "/store",
|
|
"order": 40,
|
|
"public": True,
|
|
},
|
|
{
|
|
"id": "settings",
|
|
"objectKey": "ui.system.settings",
|
|
"label": t("Einstellungen"),
|
|
"icon": "FaCog",
|
|
"path": "/settings",
|
|
"order": 50,
|
|
"public": True,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
# ─── Administration (with subgroups) ───
|
|
{
|
|
"id": "admin",
|
|
"title": t("Administration"),
|
|
"order": 200,
|
|
"subgroups": [
|
|
# ── Wizards ──
|
|
{
|
|
"id": "admin-wizards",
|
|
"title": t("Wizards"),
|
|
"order": 10,
|
|
"items": [
|
|
{
|
|
"id": "admin-mandate-wizard",
|
|
"objectKey": "ui.admin.mandateWizard",
|
|
"label": t("Mandanten-Wizard"),
|
|
"icon": "FaMagic",
|
|
"path": "/admin/mandate-wizard",
|
|
"order": 10,
|
|
"adminOnly": True,
|
|
},
|
|
{
|
|
"id": "admin-invitation-wizard",
|
|
"objectKey": "ui.admin.invitationWizard",
|
|
"label": t("Einladungs-Wizard"),
|
|
"icon": "FaEnvelopeOpenText",
|
|
"path": "/admin/invitation-wizard",
|
|
"order": 20,
|
|
"adminOnly": True,
|
|
},
|
|
],
|
|
},
|
|
# ── Users ──
|
|
{
|
|
"id": "admin-users-group",
|
|
"title": t("Benutzer"),
|
|
"order": 20,
|
|
"items": [
|
|
{
|
|
"id": "admin-users",
|
|
"objectKey": "ui.admin.users",
|
|
"label": t("Benutzer"),
|
|
"icon": "FaUsers",
|
|
"path": "/admin/users",
|
|
"order": 10,
|
|
"adminOnly": True,
|
|
},
|
|
{
|
|
"id": "admin-invitations",
|
|
"objectKey": "ui.admin.invitations",
|
|
"label": t("Benutzer-Einladungen"),
|
|
"icon": "FaEnvelopeOpenText",
|
|
"path": "/admin/invitations",
|
|
"order": 20,
|
|
"adminOnly": True,
|
|
},
|
|
{
|
|
"id": "admin-user-access-overview",
|
|
"objectKey": "ui.admin.userAccessOverview",
|
|
"label": t("Benutzer-Zugriffsübersicht"),
|
|
"icon": "FaClipboardList",
|
|
"path": "/admin/user-access-overview",
|
|
"order": 30,
|
|
"adminOnly": True,
|
|
},
|
|
{
|
|
"id": "admin-subscriptions",
|
|
"objectKey": "ui.admin.subscriptions",
|
|
"label": t("Abonnements"),
|
|
"icon": "FaFileContract",
|
|
"path": "/admin/subscriptions",
|
|
"order": 40,
|
|
"adminOnly": True,
|
|
},
|
|
],
|
|
},
|
|
# ── System ──
|
|
{
|
|
"id": "admin-system-group",
|
|
"title": t("System"),
|
|
"order": 30,
|
|
"items": [
|
|
{
|
|
"id": "admin-roles",
|
|
"objectKey": "ui.admin.roles",
|
|
"label": t("Rollen"),
|
|
"icon": "FaUserTag",
|
|
"path": "/admin/mandate-roles",
|
|
"order": 10,
|
|
"adminOnly": True,
|
|
},
|
|
{
|
|
"id": "admin-mandate-role-permissions",
|
|
"objectKey": "ui.admin.mandateRolePermissions",
|
|
"label": t("Rollen-Berechtigungen"),
|
|
"icon": "FaKey",
|
|
"path": "/admin/mandate-role-permissions",
|
|
"order": 20,
|
|
"adminOnly": True,
|
|
},
|
|
{
|
|
"id": "admin-mandates",
|
|
"objectKey": "ui.admin.mandates",
|
|
"label": t("Mandanten"),
|
|
"icon": "FaBuilding",
|
|
"path": "/admin/mandates",
|
|
"order": 30,
|
|
"adminOnly": True,
|
|
},
|
|
{
|
|
"id": "admin-user-mandates",
|
|
"objectKey": "ui.admin.userMandates",
|
|
"label": t("Mandanten-Mitglieder"),
|
|
"icon": "FaUserFriends",
|
|
"path": "/admin/user-mandates",
|
|
"order": 40,
|
|
"adminOnly": True,
|
|
},
|
|
{
|
|
"id": "admin-access",
|
|
"objectKey": "ui.admin.access",
|
|
"label": t("Zugriffsverwaltung"),
|
|
"icon": "FaBuilding",
|
|
"path": "/admin/access",
|
|
"order": 50,
|
|
"adminOnly": True,
|
|
},
|
|
{
|
|
"id": "admin-feature-instances",
|
|
"objectKey": "ui.admin.featureInstances",
|
|
"label": t("Feature-Instanzen"),
|
|
"icon": "FaCubes",
|
|
"path": "/admin/feature-instances",
|
|
"order": 60,
|
|
"adminOnly": True,
|
|
},
|
|
{
|
|
"id": "admin-feature-roles",
|
|
"objectKey": "ui.admin.featureRoles",
|
|
"label": t("Features Rollen-Vorlagen"),
|
|
"icon": "FaShieldAlt",
|
|
"path": "/admin/feature-roles",
|
|
"order": 70,
|
|
"adminOnly": True,
|
|
"sysAdminOnly": True,
|
|
},
|
|
{
|
|
"id": "admin-logs",
|
|
"objectKey": "ui.admin.logs",
|
|
"label": t("Logs"),
|
|
"icon": "FaFileAlt",
|
|
"path": "/admin/logs",
|
|
"order": 90,
|
|
"adminOnly": True,
|
|
"sysAdminOnly": True,
|
|
},
|
|
{
|
|
"id": "admin-languages",
|
|
"objectKey": "ui.admin.languages",
|
|
"label": t("UI-Sprachen"),
|
|
"icon": "FaGlobe",
|
|
"path": "/admin/languages",
|
|
"order": 95,
|
|
"adminOnly": True,
|
|
"sysAdminOnly": True,
|
|
},
|
|
{
|
|
"id": "admin-demo-config",
|
|
"objectKey": "ui.admin.demoConfig",
|
|
"label": t("Demo Config"),
|
|
"icon": "FaCubes",
|
|
"path": "/admin/demo-config",
|
|
"order": 100,
|
|
"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.feature.* → Mandanten-/Feature-spezifische Daten (dynamisch)
|
|
# =============================================================================
|
|
|
|
DATA_OBJECTS = [
|
|
# UAM (User Access Management) - mandantenübergreifend
|
|
{
|
|
"objectKey": "data.uam.UserInDB",
|
|
"label": "Benutzer",
|
|
"meta": {"table": "UserInDB", "namespace": "uam"}
|
|
},
|
|
{
|
|
"objectKey": "data.uam.AuthEvent",
|
|
"label": "Auth-Ereignis",
|
|
"meta": {"table": "AuthEvent", "namespace": "uam"}
|
|
},
|
|
{
|
|
"objectKey": "data.uam.UserConnection",
|
|
"label": "Verbindung",
|
|
"meta": {"table": "UserConnection", "namespace": "uam"}
|
|
},
|
|
{
|
|
"objectKey": "data.uam.Mandate",
|
|
"label": "Mandant",
|
|
"meta": {"table": "Mandate", "namespace": "uam"}
|
|
},
|
|
{
|
|
"objectKey": "data.uam.UserMandate",
|
|
"label": "Benutzer-Mandant",
|
|
"meta": {"table": "UserMandate", "namespace": "uam"}
|
|
},
|
|
{
|
|
"objectKey": "data.uam.Invitation",
|
|
"label": "Einladung",
|
|
"meta": {"table": "Invitation", "namespace": "uam"}
|
|
},
|
|
{
|
|
"objectKey": "data.uam.Role",
|
|
"label": "Rolle",
|
|
"meta": {"table": "Role", "namespace": "uam"}
|
|
},
|
|
{
|
|
"objectKey": "data.uam.AccessRule",
|
|
"label": "Zugriffsregel",
|
|
"meta": {"table": "AccessRule", "namespace": "uam"}
|
|
},
|
|
{
|
|
"objectKey": "data.uam.FeatureInstance",
|
|
"label": "Feature-Instanz",
|
|
"meta": {"table": "FeatureInstance", "namespace": "uam"}
|
|
},
|
|
# Chat - benutzer-eigen, kein Mandantenkontext
|
|
{
|
|
"objectKey": "data.chat.Prompt",
|
|
"label": "Prompt",
|
|
"meta": {"table": "Prompt", "namespace": "chat", "groupDisabled": True}
|
|
},
|
|
{
|
|
"objectKey": "data.chat.ChatWorkflow",
|
|
"label": "Chat-Workflow",
|
|
"meta": {"table": "ChatWorkflow", "namespace": "chat", "groupDisabled": True}
|
|
},
|
|
# Files - benutzer-eigen
|
|
{
|
|
"objectKey": "data.files.FileItem",
|
|
"label": "Datei",
|
|
"meta": {"table": "FileItem", "namespace": "files", "groupDisabled": True}
|
|
},
|
|
]
|
|
|
|
# =============================================================================
|
|
# System RESOURCE Objects
|
|
# =============================================================================
|
|
|
|
RESOURCE_OBJECTS = [
|
|
{
|
|
"objectKey": "resource.store.teamsbot",
|
|
"label": "Store: Teams Bot",
|
|
"meta": {"category": "store", "featureCode": "teamsbot"}
|
|
},
|
|
{
|
|
"objectKey": "resource.store.workspace",
|
|
"label": "Store: AI Workspace",
|
|
"meta": {"category": "store", "featureCode": "workspace"}
|
|
},
|
|
{
|
|
"objectKey": "resource.store.commcoach",
|
|
"label": "Store: CommCoach",
|
|
"meta": {"category": "store", "featureCode": "commcoach"}
|
|
},
|
|
{
|
|
"objectKey": "resource.system.api.auth",
|
|
"label": "Authentifizierungs-API",
|
|
"meta": {"endpoint": "/api/auth/*"}
|
|
},
|
|
{
|
|
"objectKey": "resource.system.api.users",
|
|
"label": "Benutzer-API",
|
|
"meta": {"endpoint": "/api/users/*"}
|
|
},
|
|
{
|
|
"objectKey": "resource.system.api.mandates",
|
|
"label": "Mandanten-API",
|
|
"meta": {"endpoint": "/api/mandates/*"}
|
|
},
|
|
{
|
|
"objectKey": "resource.system.api.rbac",
|
|
"label": "RBAC-API",
|
|
"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": "Anthropic (Claude)",
|
|
"openai": "OpenAI (GPT)",
|
|
"mistral": "Mistral (Le Chat)",
|
|
"perplexity": "Perplexity",
|
|
"tavily": "Tavily (Websuche)",
|
|
"privatellm": "Private LLM",
|
|
"internal": "Intern",
|
|
}
|
|
|
|
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, 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
|