From 5d78faf7ffd0d8f2cc8cdfca0524f912220ce830 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Wed, 14 May 2025 23:37:33 +0200
Subject: [PATCH] access extracted from interface and all routes in separate
modules
---
app.py | 75 +----
modules/gatewayAccess.py | 111 +++++++
modules/gatewayInterface.py | 75 +----
modules/lucydomAccess.py | 131 +++++++++
modules/lucydomInterface.py | 118 +-------
notes/azuresetup.txt | 104 -------
notes/changelog.txt | 8 +-
notes/doc_frontend.md | 46 ---
notes/doc_statemachine_backend.md | 366 -----------------------
notes/doc_statemachine_frontend.md | 453 -----------------------------
notes/produce_diagrams.md | 48 +++
notes/readme.md | 1 +
notes/start.sh | 105 -------
routes/routeGeneral.py | 81 ++++++
14 files changed, 392 insertions(+), 1330 deletions(-)
create mode 100644 modules/gatewayAccess.py
create mode 100644 modules/lucydomAccess.py
delete mode 100644 notes/azuresetup.txt
delete mode 100644 notes/doc_frontend.md
delete mode 100644 notes/doc_statemachine_backend.md
delete mode 100644 notes/doc_statemachine_frontend.md
create mode 100644 notes/produce_diagrams.md
delete mode 100644 notes/start.sh
create mode 100644 routes/routeGeneral.py
diff --git a/app.py b/app.py
index a9a4c40c..3fb810f2 100644
--- a/app.py
+++ b/app.py
@@ -102,7 +102,7 @@ app = FastAPI(
# CORS configuration using environment variables
app.add_middleware(
CORSMiddleware,
- allow_origins= get_allowed_origins(), # ["http://localhost:8080","http://localhost:8081"], #get_allowed_origins(),
+ allow_origins= get_allowed_origins(),
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],
@@ -118,80 +118,16 @@ os.makedirs(staticFolder, exist_ok=True)
# Mount static files with proper configuration
app.mount("/static", StaticFiles(directory=str(staticFolder), html=True), name="static")
-# Add favicon route
-@app.get("/favicon.ico")
-async def favicon():
- return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon")
-
-# General Elements
-@app.get("/", tags=["General"])
-async def root():
- """API status endpoint"""
- return {"status": "online", "message": "Data Platform API is active"}
-
-@app.get("/api/test", tags=["General"])
-async def getTest():
- return f"Status: OK. Alowed origins: {APP_CONFIG.get('APP_ALLOWED_ORIGINS')}"
-
-@app.options("/{fullPath:path}", tags=["General"])
-async def optionsRoute(fullPath: str):
- return Response(status_code=200)
-
-@app.get("/api/environment", tags=["General"])
-async def get_environment():
- """Get environment configuration for frontend"""
- return {
- "apiBaseUrl": APP_CONFIG.get("APP_API_URL", ""),
- "environment": APP_CONFIG.get("APP_ENV", "development"),
- "instanceLabel": APP_CONFIG.get("APP_ENV_LABEL", "Development"),
- # Add other environment variables the frontend might need
- }
-
-# Token endpoint for login
-@app.post("/api/token", response_model=gatewayModel.Token, tags=["General"])
-async def loginForAccessToken(formData: OAuth2PasswordRequestForm = Depends()):
- # Initialize Gateway interface without context
- gateway = getGatewayInterface()
-
- # Authenticate user
- user = gateway.authenticateUser(formData.username, formData.password)
-
- if not user:
- raise HTTPException(
- status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Invalid username or password",
- headers={"WWW-Authenticate": "Bearer"},
- )
-
- # Create token with tenant ID
- accessTokenExpires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
- accessToken = createAccessToken(
- data={
- "sub": user["username"],
- "mandateId": user["mandateId"]
- },
- expiresDelta=accessTokenExpires
- )
-
- return {"accessToken": accessToken, "tokenType": "bearer"}
-
-# Get user info
-@app.get("/api/user/me", response_model=Dict[str, Any], tags=["General"])
-async def readUserMe(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
- return currentUser
-
# Include all routers
+from routes.routeGeneral import router as generalRouter
+app.include_router(generalRouter)
+
from routes.routeAttributes import router as attributesRouter
app.include_router(attributesRouter)
-gateway = getGatewayInterface()
-
from routes.routeMandates import router as mandateRouter
app.include_router(mandateRouter)
-gateway = getGatewayInterface()
-
-
from routes.routeUsers import router as userRouter
app.include_router(userRouter)
@@ -206,6 +142,3 @@ app.include_router(workflowRouter)
from routes.routeMsft import router as msftRouter
app.include_router(msftRouter)
-
-#if __name__ == "__main__":
-# uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
\ No newline at end of file
diff --git a/modules/gatewayAccess.py b/modules/gatewayAccess.py
new file mode 100644
index 00000000..bf93d58f
--- /dev/null
+++ b/modules/gatewayAccess.py
@@ -0,0 +1,111 @@
+"""
+Access control functions for the Gateway system.
+Manages user access and permissions.
+"""
+
+from typing import Dict, Any, List, Optional
+
+def _uam(currentUser: Dict[str, Any], table: str, recordset: List[Dict[str, Any]], mandateId: int, userId: int, db) -> List[Dict[str, Any]]:
+ """
+ Unified user access management function that filters data based on user privileges
+ and adds access control attributes.
+
+ Args:
+ currentUser: Current user information dictionary
+ table: Name of the table
+ recordset: Recordset to filter based on access rules
+ mandateId: Current mandate ID
+ userId: Current user ID
+ db: Database connector instance
+
+ Returns:
+ Filtered recordset with access control attributes
+ """
+ userPrivilege = currentUser.get("privilege", "user")
+ filtered_records = []
+
+ # Apply filtering based on privilege
+ if userPrivilege == "sysadmin":
+ filtered_records = recordset # System admins see all records
+ elif userPrivilege == "admin":
+ # Admins see records in their mandate
+ filtered_records = [r for r in recordset if r.get("mandateId") == mandateId]
+ else: # Regular users
+ # Users only see records they own within their mandate
+ filtered_records = [r for r in recordset
+ if r.get("mandateId") == mandateId and r.get("userId") == userId]
+
+ # Add access control attributes to each record
+ for record in filtered_records:
+ record_id = record.get("id")
+
+ # Set access control flags based on user permissions
+ if table == "mandates":
+ record["_hideView"] = False # Everyone can view
+ record["_hideEdit"] = not _canModify(currentUser, "mandates", record_id, mandateId, userId, db)
+ record["_hideDelete"] = not _canModify(currentUser, "mandates", record_id, mandateId, userId, db)
+ elif table == "users":
+ record["_hideView"] = False # Everyone can view
+ record["_hideEdit"] = not _canModify(currentUser, "users", record_id, mandateId, userId, db)
+ record["_hideDelete"] = not _canModify(currentUser, "users", record_id, mandateId, userId, db)
+ else:
+ # Default access control for other tables
+ record["_hideView"] = False
+ record["_hideEdit"] = not _canModify(currentUser, table, record_id, mandateId, userId, db)
+ record["_hideDelete"] = not _canModify(currentUser, table, record_id, mandateId, userId, db)
+
+ return filtered_records
+
+def _canModify(currentUser: Dict[str, Any], table: str, recordId: Optional[int] = None, mandateId: int = None, userId: int = None, db = None) -> bool:
+ """
+ Checks if the current user can modify (create/update/delete) records in a table.
+
+ Args:
+ currentUser: Current user information dictionary
+ table: Name of the table
+ recordId: Optional record ID for specific record check
+ mandateId: Current mandate ID
+ userId: Current user ID
+ db: Database connector instance
+
+ Returns:
+ Boolean indicating permission
+ """
+ userPrivilege = currentUser.get("privilege", "user")
+
+ # System admins can modify anything
+ if userPrivilege == "sysadmin":
+ return True
+
+ # Check specific record permissions
+ if recordId is not None:
+ # Get the record to check ownership
+ records = db.getRecordset(table, recordFilter={"id": recordId})
+ if not records:
+ return False
+
+ record = records[0]
+
+ # Admins can modify anything in their mandate
+ if userPrivilege == "admin" and record.get("mandateId") == mandateId:
+ # Exception: Can't modify Root mandate unless you are a sysadmin
+ if table == "mandates" and recordId == 1 and userPrivilege != "sysadmin":
+ return False
+ return True
+
+ # Users can only modify their own records
+ if (record.get("mandateId") == mandateId and
+ record.get("userId") == userId):
+ return True
+
+ return False
+ else:
+ # For general table modify permission (e.g., create)
+ # Admins can create anything in their mandate
+ if userPrivilege == "admin":
+ return True
+
+ # Regular users can create most entities
+ if table == "mandates":
+ return False # Regular users can't create mandates
+ return True
\ No newline at end of file
diff --git a/modules/gatewayInterface.py b/modules/gatewayInterface.py
index 8008e1f9..92e0c91f 100644
--- a/modules/gatewayInterface.py
+++ b/modules/gatewayInterface.py
@@ -11,6 +11,7 @@ from passlib.context import CryptContext
from connectors.connectorDbJson import DatabaseConnector
from modules.configuration import APP_CONFIG
+from modules.gatewayAccess import _uam, _canModify
logger = logging.getLogger(__name__)
@@ -133,40 +134,7 @@ class GatewayInterface:
Returns:
Filtered recordset with access control attributes
"""
- userPrivilege = self.currentUser.get("privilege", "user")
- filtered_records = []
-
- # Apply filtering based on privilege
- if userPrivilege == "sysadmin":
- filtered_records = recordset # System admins see all records
- elif userPrivilege == "admin":
- # Admins see records in their mandate
- filtered_records = [r for r in recordset if r.get("mandateId") == self.mandateId]
- else: # Regular users
- # Users only see records they own within their mandate
- filtered_records = [r for r in recordset
- if r.get("mandateId") == self.mandateId and r.get("userId") == self.userId]
-
- # Add access control attributes to each record
- for record in filtered_records:
- record_id = record.get("id")
-
- # Set access control flags based on user permissions
- if table == "mandates":
- record["_hideView"] = False # Everyone can view
- record["_hideEdit"] = not self._canModify("mandates", record_id)
- record["_hideDelete"] = not self._canModify("mandates", record_id)
- elif table == "users":
- record["_hideView"] = False # Everyone can view
- record["_hideEdit"] = not self._canModify("users", record_id)
- record["_hideDelete"] = not self._canModify("users", record_id)
- else:
- # Default access control for other tables
- record["_hideView"] = False
- record["_hideEdit"] = not self._canModify(table, record_id)
- record["_hideDelete"] = not self._canModify(table, record_id)
-
- return filtered_records
+ return _uam(self.currentUser, table, recordset, self.mandateId, self.userId, self.db)
def _canModify(self, table: str, recordId: Optional[int] = None) -> bool:
"""
@@ -179,44 +147,7 @@ class GatewayInterface:
Returns:
Boolean indicating permission
"""
- userPrivilege = self.currentUser.get("privilege", "user")
-
- # System admins can modify anything
- if userPrivilege == "sysadmin":
- return True
-
- # Check specific record permissions
- if recordId is not None:
- # Get the record to check ownership
- records = self.db.getRecordset(table, recordFilter={"id": recordId})
- if not records:
- return False
-
- record = records[0]
-
- # Admins can modify anything in their mandate
- if userPrivilege == "admin" and record.get("mandateId") == self.mandateId:
- # Exception: Can't modify Root mandate unless you are a sysadmin
- if table == "mandates" and recordId == 1 and userPrivilege != "sysadmin":
- return False
- return True
-
- # Users can only modify their own records
- if (record.get("mandateId") == self.mandateId and
- record.get("userId") == self.userId):
- return True
-
- return False
- else:
- # For general table modify permission (e.g., create)
- # Admins can create anything in their mandate
- if userPrivilege == "admin":
- return True
-
- # Regular users can create most entities
- if table == "mandates":
- return False # Regular users can't create mandates
- return True
+ return _canModify(self.currentUser, table, recordId, self.mandateId, self.userId, self.db)
def getInitialId(self, table: str) -> Optional[int]:
"""Returns the initial ID for a table."""
diff --git a/modules/lucydomAccess.py b/modules/lucydomAccess.py
new file mode 100644
index 00000000..2c018c52
--- /dev/null
+++ b/modules/lucydomAccess.py
@@ -0,0 +1,131 @@
+"""
+Access control module for LucyDOM interface.
+Handles user access management and permission checks.
+"""
+
+from typing import Dict, Any, List, Optional
+
+class LucyDOMAccess:
+ """
+ Access control class for LucyDOM interface.
+ Handles user access management and permission checks.
+ """
+
+ def __init__(self, currentUser: Dict[str, Any], mandateId: int, userId: int):
+ """Initialize with user context."""
+ self.currentUser = currentUser
+ self.mandateId = mandateId
+ self.userId = userId
+
+ def _uam(self, table: str, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
+ """
+ Unified user access management function that filters data based on user privileges
+ and adds access control attributes.
+
+ Args:
+ table: Name of the table
+ recordset: Recordset to filter based on access rules
+
+ Returns:
+ Filtered recordset with access control attributes
+ """
+ userPrivilege = self.currentUser.get("privilege", "user")
+ filtered_records = []
+
+ # Apply filtering based on privilege
+ if userPrivilege == "sysadmin":
+ filtered_records = recordset # System admins see all records
+ elif userPrivilege == "admin":
+ # Admins see records in their mandate
+ filtered_records = [r for r in recordset if r.get("mandateId") == self.mandateId]
+ else: # Regular users
+ # To see all prompts from mandate 0 and own
+ if table == "prompts":
+ filtered_records = [r for r in recordset if
+ (r.get("mandateId") == self.mandateId and r.get("userId") == self.userId)
+ or
+ (r.get("mandateId") == 0)
+ ]
+ else:
+ # Users see only their records
+ filtered_records = [r for r in recordset
+ if r.get("mandateId") == self.mandateId and r.get("userId") == self.userId]
+
+ # Add access control attributes to each record
+ for record in filtered_records:
+ record_id = record.get("id")
+
+ # Set access control flags based on user permissions
+ if table == "prompts":
+ record["_hideView"] = False # Everyone can view
+ record["_hideEdit"] = not self._canModify("prompts", record_id)
+ record["_hideDelete"] = not self._canModify("prompts", record_id)
+ elif table == "files":
+ record["_hideView"] = False # Everyone can view
+ record["_hideEdit"] = not self._canModify("files", record_id)
+ record["_hideDelete"] = not self._canModify("files", record_id)
+ record["_hideDownload"] = not self._canModify("files", record_id)
+ elif table == "workflows":
+ record["_hideView"] = False # Everyone can view
+ record["_hideEdit"] = not self._canModify("workflows", record_id)
+ record["_hideDelete"] = not self._canModify("workflows", record_id)
+ elif table == "workflowMessages":
+ record["_hideView"] = False # Everyone can view
+ record["_hideEdit"] = not self._canModify("workflows", record.get("workflowId"))
+ record["_hideDelete"] = not self._canModify("workflows", record.get("workflowId"))
+ elif table == "workflowLogs":
+ record["_hideView"] = False # Everyone can view
+ record["_hideEdit"] = not self._canModify("workflows", record.get("workflowId"))
+ record["_hideDelete"] = not self._canModify("workflows", record.get("workflowId"))
+ else:
+ # Default access control for other tables
+ record["_hideView"] = False
+ record["_hideEdit"] = not self._canModify(table, record_id)
+ record["_hideDelete"] = not self._canModify(table, record_id)
+
+ return filtered_records
+
+ def _canModify(self, table: str, recordId: Optional[int] = None) -> bool:
+ """
+ Checks if the current user can modify (create/update/delete) records in a table.
+
+ Args:
+ table: Name of the table
+ recordId: Optional record ID for specific record check
+
+ Returns:
+ Boolean indicating permission
+ """
+ userPrivilege = self.currentUser.get("privilege", "user")
+
+ # System admins can modify anything
+ if userPrivilege == "sysadmin":
+ return True
+
+ # For regular users and admins, check specific cases
+ if recordId is not None:
+ # Get the record to check ownership
+ records = self.db.getRecordset(table, recordFilter={"id": recordId})
+ if not records:
+ return False
+
+ record = records[0]
+
+ # Admins can modify anything in their mandate
+ if userPrivilege == "admin" and record.get("mandateId") == self.mandateId:
+ return True
+
+ # Regular users can only modify their own records
+ if (record.get("mandateId") == self.mandateId and
+ record.get("userId") == self.userId):
+ return True
+
+ return False
+ else:
+ # For general modification permission (e.g., create)
+ # Admins can create anything in their mandate
+ if userPrivilege == "admin":
+ return True
+
+ # Regular users can create in most tables
+ return True
\ No newline at end of file
diff --git a/modules/lucydomInterface.py b/modules/lucydomInterface.py
index 91d7769a..e63ef736 100644
--- a/modules/lucydomInterface.py
+++ b/modules/lucydomInterface.py
@@ -14,6 +14,7 @@ import hashlib
import json
from modules.mimeUtils import isTextMimeType, determineContentEncoding
+from modules.lucydomAccess import LucyDOMAccess
# DYNAMIC PART: Connectors to the Interface
from connectors.connectorDbJson import DatabaseConnector
@@ -74,6 +75,10 @@ class LucyDOMInterface:
# Load user information
self.currentUser = self._getCurrentUserInfo()
+ # Initialize access control
+ self.access = LucyDOMAccess(self.currentUser, self.mandateId, self.userId)
+ self.access.db = self.db # Share database connection
+
# Initialize standard database records if needed
self._initRecords()
@@ -161,117 +166,12 @@ class LucyDOMInterface:
logger.info(f"Prompt '{promptData.get('name', 'Standard')}' was created with ID {createdPrompt['id']}")
def _uam(self, table: str, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
- """
- Unified user access management function that filters data based on user privileges
- and adds access control attributes.
-
- Args:
- table: Name of the table
- recordset: Recordset to filter based on access rules
-
- Returns:
- Filtered recordset with access control attributes
- """
- userPrivilege = self.currentUser.get("privilege", "user")
- filtered_records = []
-
- # Apply filtering based on privilege
- if userPrivilege == "sysadmin":
- filtered_records = recordset # System admins see all records
- elif userPrivilege == "admin":
- # Admins see records in their mandate
- filtered_records = [r for r in recordset if r.get("mandateId") == self.mandateId]
- else: # Regular users
- # To see all prompts from mandate 0 and own
- if table == "prompts":
- filtered_records = [r for r in recordset if
- (r.get("mandateId") == self.mandateId and r.get("userId") == self.userId)
- or
- (r.get("mandateId") == 0)
- ]
- else:
- # Users see only their records
- filtered_records = [r for r in recordset
- if r.get("mandateId") == self.mandateId and r.get("userId") == self.userId]
-
- # Add access control attributes to each record
- for record in filtered_records:
- record_id = record.get("id")
-
- # Set access control flags based on user permissions
- if table == "prompts":
- record["_hideView"] = False # Everyone can view
- record["_hideEdit"] = not self._canModify("prompts", record_id)
- record["_hideDelete"] = not self._canModify("prompts", record_id)
- elif table == "files":
- record["_hideView"] = False # Everyone can view
- record["_hideEdit"] = not self._canModify("files", record_id)
- record["_hideDelete"] = not self._canModify("files", record_id)
- record["_hideDownload"] = not self._canModify("files", record_id)
- elif table == "workflows":
- record["_hideView"] = False # Everyone can view
- record["_hideEdit"] = not self._canModify("workflows", record_id)
- record["_hideDelete"] = not self._canModify("workflows", record_id)
- elif table == "workflowMessages":
- record["_hideView"] = False # Everyone can view
- record["_hideEdit"] = not self._canModify("workflows", record.get("workflowId"))
- record["_hideDelete"] = not self._canModify("workflows", record.get("workflowId"))
- elif table == "workflowLogs":
- record["_hideView"] = False # Everyone can view
- record["_hideEdit"] = not self._canModify("workflows", record.get("workflowId"))
- record["_hideDelete"] = not self._canModify("workflows", record.get("workflowId"))
- else:
- # Default access control for other tables
- record["_hideView"] = False
- record["_hideEdit"] = not self._canModify(table, record_id)
- record["_hideDelete"] = not self._canModify(table, record_id)
-
- return filtered_records
+ """Delegate to access control module."""
+ return self.access._uam(table, recordset)
def _canModify(self, table: str, recordId: Optional[int] = None) -> bool:
- """
- Checks if the current user can modify (create/update/delete) records in a table.
-
- Args:
- table: Name of the table
- recordId: Optional record ID for specific record check
-
- Returns:
- Boolean indicating permission
- """
- userPrivilege = self.currentUser.get("privilege", "user")
-
- # System admins can modify anything
- if userPrivilege == "sysadmin":
- return True
-
- # For regular users and admins, check specific cases
- if recordId is not None:
- # Get the record to check ownership
- records = self.db.getRecordset(table, recordFilter={"id": recordId})
- if not records:
- return False
-
- record = records[0]
-
- # Admins can modify anything in their mandate
- if userPrivilege == "admin" and record.get("mandateId") == self.mandateId:
- return True
-
- # Regular users can only modify their own records
- if (record.get("mandateId") == self.mandateId and
- record.get("userId") == self.userId):
- return True
-
- return False
- else:
- # For general modification permission (e.g., create)
- # Admins can create anything in their mandate
- if userPrivilege == "admin":
- return True
-
- # Regular users can create in most tables
- return True
+ """Delegate to access control module."""
+ return self.access._canModify(table, recordId)
# Language support method
diff --git a/notes/azuresetup.txt b/notes/azuresetup.txt
deleted file mode 100644
index 17570483..00000000
--- a/notes/azuresetup.txt
+++ /dev/null
@@ -1,104 +0,0 @@
-#!/bin/bash
-
-# Variables
-SUBSCRIPTION_ID="213596c9-34b2-4677-a712-45ed127cdae5"
-RESOURCE_GROUP="volucy-group"
-APP_NAME="poweron-gateway"
-DOMAIN_NAME="gateway.poweron-center.net"
-CERT_PASSWORD="TheSecurePass$(date +%s)" # Unique password with timestamp
-
-# Login to Azure (uncomment if not already logged in)
-# az login
-
-# Set subscription
-echo "Setting subscription..."
-az account set --subscription "$SUBSCRIPTION_ID"
-
-# Create directory for certificate files
-mkdir -p cert-files
-cd cert-files
-
-# Create OpenSSL config file with required extensions
-cat > openssl.cnf << EOF
-[ req ]
-default_bits = 2048
-distinguished_name = req_distinguished_name
-req_extensions = req_ext
-[ req_distinguished_name ]
-countryName = Country Name (2 letter code)
-stateOrProvinceName = State or Province Name (full name)
-localityName = Locality Name (eg, city)
-organizationName = Organization Name (eg, company)
-commonName = Common Name (e.g. server FQDN)
-[ req_ext ]
-subjectAltName = @alt_names
-extendedKeyUsage = serverAuth
-[alt_names]
-DNS.1 = ${DOMAIN_NAME}
-EOF
-
-# Generate private key
-openssl genrsa -out private.key 2048
-
-# Create CSR with config file
-openssl req -new -key private.key -out request.csr -config openssl.cnf -subj "/C=US/ST=State/L=City/O=Organization/CN=${DOMAIN_NAME}"
-
-# Generate self-signed certificate with extensions
-openssl x509 -req -days 365 -in request.csr -signkey private.key -out certificate.crt \
- -extfile openssl.cnf -extensions req_ext
-
-# Create PFX file
-openssl pkcs12 -export -out self-signed-cert.pfx -inkey private.key -in certificate.crt -passout pass:$CERT_PASSWORD
-
-cd ..
-
-# Upload certificate to App Service
-echo "Uploading certificate..."
-UPLOAD_RESULT=$(az webapp config ssl upload \
- --resource-group "$RESOURCE_GROUP" \
- --name "$APP_NAME" \
- --certificate-file "cert-files/self-signed-cert.pfx" \
- --certificate-password "$CERT_PASSWORD")
-
-# Extract thumbprint from upload result
-CERT_THUMBPRINT=$(echo $UPLOAD_RESULT | jq -r '.thumbprint')
-
-echo "Certificate uploaded successfully with thumbprint: $CERT_THUMBPRINT"
-
-# If the thumbprint is empty, try to find it another way
-if [ -z "$CERT_THUMBPRINT" ] || [ "$CERT_THUMBPRINT" == "null" ]; then
- echo "Thumbprint not found in upload result. Trying to list certificates..."
- CERT_LIST=$(az webapp config ssl list --resource-group "$RESOURCE_GROUP")
-
- # Look for the most recently uploaded certificate
- CERT_THUMBPRINT=$(echo $CERT_LIST | jq -r 'sort_by(.expirationDate) | reverse | .[0].thumbprint')
-
- if [ -z "$CERT_THUMBPRINT" ] || [ "$CERT_THUMBPRINT" == "null" ]; then
- echo "Error: Could not find certificate thumbprint."
- exit 1
- fi
-fi
-
-echo "Using certificate thumbprint: $CERT_THUMBPRINT"
-
-# Make sure the custom domain is added
-echo "Checking if custom domain exists..."
-DOMAIN_EXISTS=$(az webapp config hostname list --resource-group "$RESOURCE_GROUP" --webapp-name "$APP_NAME" | jq -r ".[] | select(.name==\"$DOMAIN_NAME\") | .name")
-
-if [ -z "$DOMAIN_EXISTS" ]; then
- echo "Adding custom domain..."
- az webapp config hostname add \
- --resource-group "$RESOURCE_GROUP" \
- --webapp-name "$APP_NAME" \
- --hostname "$DOMAIN_NAME"
-fi
-
-# Add IP-based SSL binding
-echo "Creating IP-based SSL binding..."
-az webapp config ssl bind \
- --resource-group "$RESOURCE_GROUP" \
- --name "$APP_NAME" \
- --certificate-thumbprint "$CERT_THUMBPRINT" \
- --ssl-type "IP"
-
-echo "SSL binding completed. Your domain should now be secured."
\ No newline at end of file
diff --git a/notes/changelog.txt b/notes/changelog.txt
index 75cb1e2d..25434eae 100644
--- a/notes/changelog.txt
+++ b/notes/changelog.txt
@@ -3,7 +3,7 @@
agentDocumentation delivers a ".docx" file, but the content is a ".md" text markup file
-access management to extract into separate modules "lucydomAccess.py" and "gatewayAccess.py". Here to move the functions from "*Interface.py", which define what access which role has.
+agentDocumentation to extract data per chapter
check data extraction tabelle im pdf
@@ -22,8 +22,6 @@ sharepoint connector with document search, content search, content extraction
PRIO2:
-sharepoint connector with document search, content search, content extraction
-
Split big files into content-parts
Integrate NDA Text as modal form - Data governance agreement by login with checkbox
@@ -36,11 +34,13 @@ Tools to transfer incl funds:
- Google SERPAPI (shelly)
- Anthropic Claude (valueon + shelly)
- Cursor Pro
+- Mermaid
+
+
----------------------- DONE
-
FRONTEND:
- login page and register page withoug fallback. they have mandatory to load their login.html or register.html pages to work (not html in the code).
diff --git a/notes/doc_frontend.md b/notes/doc_frontend.md
deleted file mode 100644
index cae1f927..00000000
--- a/notes/doc_frontend.md
+++ /dev/null
@@ -1,46 +0,0 @@
-Architectural Flow
-
-workflow.js - Entry point that:
-
-Creates the central state object
-Initializes API and UI components
-Connects components together
-Exposes required public functions
-
-
-workflow_state.js - State management that:
-
-Maintains a single source of truth for workflow data
-Implements an observer pattern for state changes
-Handles state transitions and business logic
-Validates state updates
-
-
-workflow_api.js - API communication that:
-
-Abstracts all API calls to backend services
-Manages polling for workflow status
-Processes API responses
-Tracks data transfer statistics
-Updates state with API results
-
-
-workflow_ui.js - UI layer that:
-
-Renders UI based on current state
-Sets up event listeners for user interactions
-Handles DOM manipulation
-Controls layout adjustments
-Triggers state changes via user actions
-
-
-
-Data Flow
-
-User initiates action in the UI
-UI controller handles the action and calls relevant API methods
-API communicates with backend and updates state
-State notifies observers about changes
-UI reacts to state changes and updates display
-Polling continues until completed state is reached
-Final UI update happens when workflow completes
diff --git a/notes/doc_statemachine_backend.md b/notes/doc_statemachine_backend.md
deleted file mode 100644
index 4556b8f3..00000000
--- a/notes/doc_statemachine_backend.md
+++ /dev/null
@@ -1,366 +0,0 @@
-# State Machine Documentation for Backend Chat Workflow
-
-## Overview
-
-The Chat Workflow system implements a state machine that processes user inputs through a sequence of well-defined steps. The system orchestrates interactions between users, project managers, and specialized agents to produce final outputs.
-
-## Core Objects
-
-### Workflow Object
-```json
-{
- "id": "uuid-string",
- "mandateId": int,
- "userId": int,
- "name": "Workflow name",
- "startedAt": "ISO-datetime",
- "messages": [], // References to messages
- "messageIds": [], // List of message IDs
- "logs": [], // Log entries
- "dataStats": {}, // Performance metrics
- "currentRound": int, // Increments with each interaction
- "status": "string", // running, completed, failed, stopped
- "lastActivity": "ISO-datetime"
-}
-```
-
-### Message Object
-```json
-{
- "id": "msg_uuid-string",
- "workflowId": "workflow-uuid",
- "role": "string", // user, assistant
- "agentName": "string", // Empty for user, agent name for assistant
- "content": "string", // The message text
- "documents": [], // List of document objects
- "timestamp": "ISO-datetime",
- "sequenceNo": int, // Position in conversation
- "status": "string" // first, step, last
-}
-```
-
-### Log Entry Object
-```json
-{
- "id": "log_uuid-string",
- "workflowId": "workflow-uuid",
- "message": "string",
- "progress": int, // Optional, 0-100
- "type": "string", // info, warning, error
- "timestamp": "ISO-datetime",
- "agentName": "string", // Name of the agent that generated the log
- "status": "string" // current workflow status (running, completed, failed, stopped)
-}
-```
-
-### Document Object
-```json
-{
- "id": "doc_uuid-string",
- "fileId": int,
- "name": "string", // Filename without extension
- "ext": "string", // File extension
- "data": "base64-encoded-string", // File contents
- "contents": [] // Extracted content items in text format
-}
-```
-
-### Content Item Object
-```json
-{
- "sequenceNr": int, // Sequence in the document
- "name": "string",
- "ext": "string",
- "contentType": "string", // mime type
- "data": "string|base64", // Original content
- "dataExtracted": "string", // Optional AI-processed content based on extraction requirement
- "metadata": {
- "isText": boolean,
- "base64Encoded": boolean,
- "aiProcessed": boolean,
- // Optional metadata specific to content type
- },
- "summary": "string" // AI-generated static summary of the content
-}
-```
-
-## State Machine Workflow
-
-### 1. Workflow Initialization
-- **Trigger**: User message received via `/api/workflows/start` OR `/api/workflows/start?id=string`
-- **Input**: `UserInputRequest` with `prompt` and optional `listFileId`
-- **Process**:
- - If `id` existing and workflow exists for `id`==`workflowId`: Load workflow, increment `currentRound`, set status "running"
- - Else: Create new workflow with "currentRound"=1, status "running"
-- **Logs**: "Workflow initialized" or "Running workflow", progress 0%
-- **API Responses**:
- - Success: 200 OK with workflow ID
- - Error: 400 Bad Request if input invalid, 404 Not Found if workflow ID not found
-
-### 2. Workflow Exception
-- **Trigger**:
- - User stopped workflow via API
- - An exception happened
-- **Process**:
- - If status=="stopped": Set workflow status to "stopped", add message with status "last", update lastActivity, stop execution immediately
- - If status=="failed": Set workflow status to "failed", add message with status "last", update lastActivity, stop execution immediately
- - Else: Continue normally
-- **Logs**: "Workflow failure reported", progress 100%
-- **API Responses**:
- - For stop request: 200 OK when workflow successfully stopped
- - For exceptions: 500 Internal Server Error with error details
-
-### 3. User Message Processing
-- **Process**:
- - Transform user input into message object with documents, message status "first"
- - Extract contents from files using `getDocumentContents()`
- - Generate static summaries for each content item
-- **State Changes**:
- - Add user message to `workflow.messages` array
- - Add message ID to `workflow.messageIds` array
- - Update `workflow.lastActivity`
-- **Logs**: "Workflow processing started", progress 0%
-
-### 4. Project Manager Analysis
-- **Process**:
- - Generate prompt for project manager AI
- - Project manager analyzes request and documents
- - Project manager generates work plan and response
-- **Outputs**:
- - `objFinalDocuments`: List of str "filename.ext" for expected final output documents
- - `objWorkplan`: List of agent tasks
- - `objUserResponse`: Text response to user
- - `userLanguage`: Detected language code (e.g. en)
-- **State Changes**:
- - Add assistant message with project manager response, status "step"
- - Set user language in mydom interface
-- **Logs**: "Analyzing request and planning work" (10%), "Planned outputs" (20%), "Work plan created" (25%)
-
-### 5. Agent Execution
-- **Process** (For each task in workplan):
- - Prepare input documents for agent
- - Execute agent with standardized task object
- - Save produced documents
- - Create assistant message with agent response, status "step"
-- **Agent Task Object**:
-```json
-{
- "taskId": "uuid-string",
- "workflowId": "workflow-uuid",
- "prompt": "string",
- "inputDocuments": [], // list of documents including original document data and all content items data with original (attribute "data") and based on prompt (attribute "dataExtracted")
- "outputSpecifications": [
- {
- "label": "filename.ext",
- "description": "string"
- }
- ],
- "context": {
- "workflowRound": int,
- "agentType": "string",
- "timestamp": "ISO-datetime",
- "language": "language-code"
- }
-}
-```
-- **Agent Result Object**:
-```json
-{
- "feedback": "string", // Text describing what the agent did
- "documents": [
- {
- "label": "filename.ext",
- "content": "string|binary" // Document contents
- }
- ]
-}
-```
-- **State Changes**: Add assistant message for each agent with agentName set, status "step"
-- **Logs**: "Running task X/Y: agentName" with progress updates from 30% to 90%
-
-### 6. Final Response Generation
-- **Process**:
- - Create final message reviewing promised and delivered documents
- - Add documents to workflow
-- **State Changes**: Add final assistant message from projectManager, status "last"
-- **Logs**: "Creating final response" (90%)
-
-### 7. Workflow Completion
-- **Process**:
- - Finalize workflow and update status
-- **State Changes**:
- - Set workflow status to "completed"
- - Update `workflow.lastActivity`
-- **Logs**: "Workflow completed successfully" with progress 100%
-- **API Responses**:
- - A message with status "last" is included in the response
- - Status endpoint will return "completed"
-
-### 8. Workflow Stopped
-- **Trigger**: `/api/workflows/{workflowId}/stop` endpoint called
-- **Process**:
- - Immediately interrupt workflow execution
- - Save current state and mark as stopped
-- **State Changes**:
- - Set workflow status to "stopped"
- - Update lastActivity timestamp
-- **Logs**: "Workflow stopped by user" with progress 100%
-- **API Responses**:
- - 200 OK with confirmation message
-
-### 9. Workflow Failed
-- **Trigger**: Exception during workflow execution
-- **Process**:
- - Log error details
- - Set workflow status to "failed"
-- **State Changes**:
- - Set workflow status to "failed"
- - Update lastActivity timestamp
-- **Logs**: Detailed error message with progress 100%
-- **API Responses**:
- - Status endpoint will return "failed" with error context
-
-### 10. Workflow Resumption
-- **Trigger**: `/api/workflows/start?id={workflowId}` endpoint called with existing workflow ID
-- **Process**:
- - Load existing workflow
- - Increment currentRound counter
- - Start processing from user message
-- **State Changes**:
- - Set status to "running"
- - Increment currentRound
- - Add new user message
-- **Logs**: "Resuming workflow, round {currentRound}" with progress 0%
-- **API Responses**:
- - Same as workflow initialization
-
-### 11. Workflow Reset/Deletion
-- **Trigger**: `/api/workflows/{workflowId}` DELETE endpoint called
-- **Process**:
- - Remove all workflow data from storage
-- **State Changes**:
- - Workflow no longer exists in the system
-- **Logs**: Log in system log that workflow was deleted
-- **API Responses**:
- - 200 OK if successful
- - 404 Not Found if workflow didn't exist
-
-## API Endpoints and Polling Support
-
-### Main Workflow Endpoints
-- `POST /api/workflows/start?id=string`: Submit user input to start a new workflow, optional with workflow id to continue existing workflow
-- `POST /api/workflows/{workflowId}/stop`: Stop a running workflow: Immediately to set workflow status to "stopped"
-- `DELETE /api/workflows/{workflowId}`: Delete a workflow
-- `GET /api/workflows/{workflowId}/status`: Get workflow status (running, completed, failed, stopped)
-- `GET /api/workflows/{workflowId}/logs?id=string`: Get workflow logs, optional with log id to get only logs produced after and including log with log id
-- `GET /api/workflows/{workflowId}/messages?id=string`: Get workflow messages, optional with message id to get only messages produced after and including message with log id
-
-### Document Management
-- `DELETE /api/workflows/{workflowId}/messages/{messageId}`: Delete a message
-- `DELETE /api/workflows/{workflowId}/messages/{messageId}/files/{fileId}`: Remove file from message
-
-### Backend Support for Frontend Polling
-
-The backend implements efficient support for frontend polling mechanisms:
-
-1. **Selective Data Transfer**:
- - Both `/logs` and `/messages` endpoints accept an optional `id` parameter
- - When provided, only records with IDs equal to or newer than the specified ID are returned
- - This minimizes data transfer and improves performance
-
-2. **Log Storage**:
- - Each log entry includes timestamp, progress indicators, and status
- - Frontend can accurately track workflow progress and update UI accordingly
- - Logs are stored in chronological order with monotonically increasing IDs
-
-3. **Message Handling**:
- - Messages include a status field ("first", "step", "last")
- - The "last" status indicates completion of the current workflow round
- - Frontend uses this to determine when to enable user input
-
-4. **Status Endpoint**:
- - Lightweight endpoint that returns only the current workflow status
- - Used by frontend to detect state changes without transferring all data
- - Also includes lastActivity timestamp to detect stalled workflows
-
-5. **Caching Layer**:
- - Backend implements caching for frequent polling requests
- - Reduces database load and improves response times
- - Cache invalidation occurs when workflow status changes
-
-6. **Batch Processing**:
- - Large log or message sets are paginated automatically
- - Frontend receives data in manageable chunks
- - Prevents memory issues with long-running workflows
-
-## Document Object Structure Clarification
-The Document Object contains both raw data and processed contents:
-- `data`: Contains the base64-encoded binary representation of the entire original file
-- `contents`: Contains an array of structured Content Item objects extracted from the original file
-
-The relationship works as follows:
-1. When a file is uploaded, its binary data is stored in the `data` field
-2. The original file's complete data is always preserved in the document's `data` field
-3. The file is then processed by content extractors based on file type (PDF, image, text, etc.)
-4. Each logically separate piece of content is added to the `contents` array
-5. For text files, there might be just one content item
-6. For PDFs, there might be multiple content items (one per page or per embedded image)
-7. For complex documents, content items might represent different sections or formats
-8. Each content item contains its own `data` field with the specific extracted content; for agents convenience it contains the additional field `dataExtracted` with extracted data based on agents task prompt
-
-This dual structure allows agents to:
-- Access the complete original file when needed
-- Work with pre-processed, extracted content for efficiency
-- Process specific sections of a document without loading the entire file
-
-## State Transitions
-
-```
-[null] → [running] // New workflow created
-[running] → [completed] // Workflow completes successfully
-[running] → [stopped] // User manually stops workflow
-[running] → [failed] // Error occurs during workflow
-[completed] → [running] // User continues workflow with new input (new round)
-[stopped] → [running] // User continues after manual stop (new round)
-[failed] → [running] // User retries workflow despite error (new round)
-[any] → [null] // Workflow deleted
-```
-
-## Exception Handling
-
-### Rules
-- Workflow status changes to "failed" on exceptions, all message and workflow generation exceptions to handle to ensure data consistency in the database
-- Errors are logged in workflow logs with type "error"
-- Produced project manager analysis output, inputs to agents, output from agents, workflow items, message items are all logged for debugging in the logger with type "debug"
-- HTTP exceptions are returned to the client with appropriate status codes
-- Failed agent tasks are recorded but don't stop the workflow
-
-### Workflow Stop Conditions
-- User explicitly cancels the workflow via the stop endpoint
-
-Action to take:
-- workflow to set to "stopped" status
-
-### Workflow Failure Conditions
-- Unhandled exceptions in the main workflow execution path
-- Project manager analysis fails to generate a valid workplan
-- More than 50% of the agent tasks in the workplan fail to complete
-- Timeout exceeded (workflow runs longer than the configured maximum duration)
-- System resource limits exceeded (memory, CPU, etc.)
-
-Action to take, when a workflow fails:
-- The last log entry will contain details about the failure reason
-- workflow to set to "failed" status
-
-### Workflow Exception Checkpoints
-At the following points in the code the Workflow Execution routine is called:
-- Before adding or updating a message to the workflow
-- Before doing an API call
-
-## Special Notes
-
-1. **Document Processing**: Files uploaded by users are processed with content extraction to make them accessible to agents.
-2. **AI Language Support**: The system detects and adapts to the user's language.
-3. **Round Counting**: Each interaction increments the `currentRound` counter.
-4. **Agent Registry**: Agents are loaded dynamically and registered in the AgentRegistry.
-5. **Standardized Task Processing**: All agents implement the same task processing interface.
diff --git a/notes/doc_statemachine_frontend.md b/notes/doc_statemachine_frontend.md
deleted file mode 100644
index 0fa1f374..00000000
--- a/notes/doc_statemachine_frontend.md
+++ /dev/null
@@ -1,453 +0,0 @@
-# State Machine Documentation for Frontend Chat Workflow
-
-## Overview
-
-The Chat Workflow frontend implements a state machine that manages user interactions through a well-defined sequence of states. This system coordinates the user interface components, handles file attachments, and manages communication with the backend to provide a seamless multi-agent chat experience.
-
-## Core Objects
-
-### Frontend Workflow State Object
-```json
-{
- "status": "string", // null or "running", "completed", "failed", "stopped"
- "workflowId": "string", // null or Unique workflow identifier
- "logs": [], // Log entries
- "chatMessages": [], // Chat messages
- "lastPolledLogId": "string", // ID of last polled log
- "lastPolledMessageId": "string", // ID of last polled message
- "dataStats": {
- "bytesSent": int, // Data sent to backend
- "bytesReceived": int, // Data received from backend
- "tokensUsed": float // Used tokens required for billing
- }
-}
-```
-
-### User Input State Object
-```json
-{
- "promptText": "string", // Current user input text
- "additionalFiles": [], // List of file IDs to attach
- "domElements": {} // References to UI DOM elements
-}
-```
-
-### Log Entry Object
-```json
-{
- "id": "log_timestamp",
- "message": "string",
- "progress": int, // Optional, 0-100
- "type": "string", // info, warning, error
- "timestamp": "ISO-datetime",
- "agentName": "string", // Name of the agent that generated the log
- "waiting": boolean, // Whether this log shows a waiting indicator
- "highlighted": boolean // Whether this log should be visually highlighted
-}
-```
-
-### Chat Message Object
-```json
-{
- "id": "msg_type_timestamp",
- "role": "string", // user, assistant
- "agentName": "string",
- "content": "string",
- "documents": [], // List of document objects
- "timestamp": "ISO-datetime",
- "status": "string" // first, step, last ("workflowComplete" can be asked as `status`=="last")
-}
-```
-
-### File Object
-```json
-{
- "id": "file_uuid-string",
- "name": "filename.ext",
- "size": int,
- "fileId": int,
- "contentType": "string", // mime type
-}
-```
-
-## State Machine Workflow
-
-### 1. Initial State
-- **State**: `null` (No workflow active)
-- **UI Elements**:
- - Empty chat container with placeholder
- - Prompt input field enabled
- - "Start" button enabled
- - "Stop" button hidden
- - File upload area enabled
- - Empty log panel
-- **User Actions**:
- - Enter prompt text
- - Add files via upload or drag & drop
- - Select pre-defined prompts from dropdown
- - Click Start button
-- **API Interactions**: None in this state
-
-### 2. Prompt Preparation
-- **State**: `null` (transitioning to `running`)
-- **Triggers**:
- - User typing in prompt field (updates `userInputState.promptText`)
- - File uploads/drag & drop (adds to `userInputState.additionalFiles`)
- - Prompt selection from dropdown (sets `userInputState.promptText`)
-- **Process**:
- - Files are uploaded to backend via `uploadAndAddFile()`
- - Files appear in attachment list with name, size, remove option
- - Prompt visualization is updated with file count and names
-- **State Changes**:
- - `userInputState.additionalFiles` array updated
- - `userInputState.promptText` updated
-- **UI Updates**:
- - File attachment list rendered
- - Prompt preview area shows attached files
-- **API Interactions**:
- - `POST /api/files/upload`: For each file being attached, an API call is made to upload the file and get a file ID
- - Response contains file metadata which is stored in the frontend state
-
-### 3. Workflow Initialization
-- **State**: `running`
-- **Trigger**: User clicks "Start" button or presses Enter
-- **Process**:
- - Validate user input (ensures non-empty prompt)
- - Set loading state (disable start button, show spinner)
- - Submit prompt and files
- - Receive workflow ID from backend
-- **State Changes**:
- - `status` → `running`
- - `workflowId` set to returned ID
- - `userInputState.additionalFiles` reset to empty array
- - `userInputState.promptText` reset to empty string
-- **UI Updates**:
- - Chat messages container shown
- - User message appears in chat
- - Log entry added: "Workflow started"
- - System chat message added: "Multi-Agent Chat has been started"
- - Start button becomes Send button, deactivated and with animation
- - Stop button becomes visible
- - Input field disabled temporarily
-- **API Interactions**:
- - `POST /api/workflows/start`: Submit user prompt and list of file IDs
- - Request payload: `{ prompt: string, fileIds: array }`
- - Response contains `workflowId` which is stored for future API calls
- - For continuing an existing workflow: `POST /api/workflows/start?id={workflowId}`
-
-### 4. Polling for Updates
-- **State**: `running`
-- **Process**:
- - `pollWorkflowStatus()` initiated with workflow ID
- - `pollWorkflowLogs()` and `pollWorkflowMessages()` called periodically
- - Last log and message IDs tracked to request only new items
- - New logs and messages added to state
-- **State Changes**:
- - `logs` array updated with new log entries
- - `chatMessages` array updated with new messages
- - `lastPolledLogId` and `lastPolledMessageId` updated
- - `dataStats` updated with sent/received bytes and tokens
-- **UI Updates**:
- - Updated and new logs rendered in log panel
- - Updated and new chat messages rendered in chat area
- - Waiting animation shown on latest log entry
- - Data statistics counters updated
-- **API Interactions**:
- - `GET /api/workflows/{workflowId}/status`: Polls workflow status (every 2000ms)
- - `GET /api/workflows/{workflowId}/logs?id={lastPolledLogId}`: Gets only logs newer than the last polled log
- - `GET /api/workflows/{workflowId}/messages?id={lastPolledMessageId}`: Gets only messages newer than the last polled message
- - Polling continues until workflow status changes from "running" or a message with status "last" is received
-
-### 5. Workflow Running
-- **State**: `running`
-- **Process**:
- - Backend processes user input through agent workflow
- - Frontend continuously polls for updates
- - Log entries show progress of agents
- - Chat messages display agent responses
-- **UI Elements**:
- - Stop button visible and enabled
- - Input field disabled
- - Log panel shows processing steps with progress indicators
- - Chat displays multi-agent conversation
-- **User Actions**:
- - Click Stop button to interrupt workflow
- - View file attachments in messages
- - Preview or download files
-- **API Interactions**:
- - Continued polling via endpoints described in State 4
- - `POST /api/workflows/{workflowId}/stop`: If user clicks Stop button
-
-### 6. Workflow Completion
-- **State**: `completed`
-- **Trigger**: Backend returns workflow status "completed" or message with status "last"
-- **Process**:
- - Final message displayed, input enabled for new prompt
- - Polling stops
-- **State Changes**:
- - `status` → `completed`
-- **UI Updates**:
- - Stop button hidden
- - Start button enabled
- - Input field enabled and focused
- - Final log shows "Workflow completed"
-- **API Interactions**:
- - No further API calls until user inputs new prompt
-
-### 7. Workflow Failure
-- **State**: `failed`
-- **Trigger**: Backend returns workflow status "failed"
-- **Process**:
- - Error message displayed, option to retry
- - Polling stops
-- **State Changes**:
- - `status` → `failed`
-- **UI Updates**:
- - Error indicators in UI
- - Retry option enabled
- - Input field enabled
-- **API Interactions**:
- - No further API calls until user initiates retry
-
-### 8. Workflow Stopped
-- **State**: `stopped`
-- **Trigger**: User clicks Stop button or backend returns "stopped"
-- **Process**:
- - Workflow processing interrupted
- - Polling stops
-- **State Changes**:
- - `status` → `stopped`
-- **UI Updates**:
- - Resume option enabled
- - Start button enabled
- - Input field enabled
-- **API Interactions**:
- - No further API calls until user continues or resets
-
-### 9. User Input Requested
-- **State**: Varies based on workflow state
-- **Trigger**: Received message with status "last" or workflow status not "running"
-- **Process**:
- - Special log entry added: "Waiting for user input to continue"
- - Input field enabled for user response
-- **State Changes**:
- - `status` → The status from the workflow
-- **UI Updates**:
- - Stop Polling
- - Waiting animation stopped
- - Input field enabled and focused
- - Send button enabled
- - Send button shows "Start" for new workflow or "Send" for continuation
- - Input field may show specific placeholder
- - Stop button hidden
-- **API Interactions**:
- - No API calls until user provides input
-
-### 10. Continuation Preparation
-- **State**: `completed`, `failed`, or `stopped` (transitioning to `running`)
-- **Trigger**: User enters new input after previous workflow cycle
-- **Process**:
- - Prepare continuation with existing workflow ID
- - Similar to Prompt Preparation but preserves context
-- **State Changes**:
- - `userInputState.promptText` updated
- - `userInputState.additionalFiles` updated if new files added
-- **UI Updates**:
- - File attachment list updated if changed
- - Send button ready for continuation
-- **API Interactions**:
- - Same as Prompt Preparation if new files are added
-
-### 11. Workflow Resumption
-- **State**: `running`
-- **Trigger**: User continues workflow after stop/failure/completion
-- **Process**:
- - Submit new input with existing workflow ID
-- **State Changes**:
- - `status` → `running`
- - New user message added
-- **UI Updates**:
- - Similar to Workflow Initialization but preserves history
- - Polling resumes
-- **API Interactions**:
- - `POST /api/workflows/start?id={workflowId}`: Sends continuation prompt with existing workflow ID
- - Polling endpoints resume as in State 4
-
-### 12. Workflow Reset
-- **State**: `null`
-- **Trigger**: User clicks Reset button
-- **Process**:
- - All state data cleared
- - UI reset to initial state
-- **State Changes**:
- - `status` → `null`
- - `workflowId` → `null`
- - `logs` → `[]`
- - `chatMessages` → `[]`
-- **UI Updates**:
- - Empty chat state shown
- - Log panel cleared
- - Input field reset
- - Chat area cleared
- - File attachments removed
-- **API Interactions**:
- - Optional `DELETE /api/workflows/{workflowId}` to clean up server resources
-
-## API Interaction Details
-
-### Polling Implementation
-The frontend implements a sophisticated polling mechanism that efficiently retrieves only the new data:
-
-1. **Initialization**:
- - When a workflow starts, the frontend stores the workflow ID
- - Initial polling begins immediately after receiving workflow ID
-
-2. **Selective Data Retrieval**:
- - Frontend tracks `lastPolledLogId` and `lastPolledMessageId`
- - Each polling request includes the last ID to receive only newer items
- - This significantly reduces bandwidth and processing requirements
-
-3. **Polling Schedule**:
- - Status polling: Every 2000ms while in `running` state
- - Logs polling: Every 1000ms while in `running` state
- - Messages polling: Every 1000ms while in `running` state
- - All polling stops when workflow status changes from `running`
-
-4. **Retry Mechanism**:
- - Network failures trigger exponential backoff
- - Starting at 1000ms, doubling up to 16000ms max
- - After 5 consecutive failures, displays connection error
-
-5. **Polling Suspension**:
- - Polling automatically pauses when browser tab is inactive
- - Resumes when tab becomes active again
- - Can be manually suspended with `suspendPolling()` during UI transitions
-
-### API Endpoints Used
-
-| Frontend Function | Backend Endpoint | Parameters | Description |
-|-------------------|------------------|------------|-------------|
-| `startWorkflow()` | `POST /api/workflows/start` | `{ prompt: string, fileIds: [] }` | Starts new workflow |
-| `continueWorkflow()` | `POST /api/workflows/start?id={workflowId}` | `{ prompt: string, fileIds: [] }` | Continues existing workflow |
-| `pollWorkflowStatus()` | `GET /api/workflows/{workflowId}/status` | None | Gets current workflow status |
-| `pollWorkflowLogs()` | `GET /api/workflows/{workflowId}/logs?id={lastLogId}` | Optional log ID | Gets logs newer than specified ID |
-| `pollWorkflowMessages()` | `GET /api/workflows/{workflowId}/messages?id={lastMessageId}` | Optional message ID | Gets messages newer than specified ID |
-| `stopWorkflow()` | `POST /api/workflows/{workflowId}/stop` | None | Interrupts running workflow |
-| `resetWorkflow()` | `DELETE /api/workflows/{workflowId}` | None | Cleans up workflow resources |
-| `uploadFile()` | `POST /api/files/upload` | FormData with file | Uploads file and returns file ID |
-| `deleteMessage()` | `DELETE /api/workflows/{workflowId}/messages/{messageId}` | None | Removes message from workflow |
-| `removeFileFromMessage()` | `DELETE /api/workflows/{workflowId}/messages/{messageId}/files/{fileId}` | None | Removes file from message |
-
-## Special Features
-
-### File Handling
-1. **File Upload**:
- - Direct upload via button or drag & drop
- - FileId added to `userInputState.additionalFiles`
- - UI renders file in attachment list
- - Each file can be removed individually
-
-2. **File Preview**:
- - Files in messages can be previewed
- - Preview shows content based on file type (text, image, PDF)
- - Files can be downloaded or copied to clipboard
- - Various file formats supported with appropriate visualizations
-
-### Chat Message Rendering
-1. **Message Types**:
- - User messages (grey background)
- - Agent messages (white background)
- - Moderator questions (specially formatted)
-
-2. **Message Features**:
- - Collapsible for long content
- - Symbol to delete message
- - File attachments with preview options
- - Markdown-like formatting support
- - Agent name and timestamp display
-
-### Log Panel Features
-1. **Log Types**:
- - Info (blue)
- - Warning (orange)
- - Error (red)
-
-2. **Log Features**:
- - Log items are read selectively from API: Only the last log entry and newer ones
- - Progress indicators for agent tasks; before a next log message is rendered, the last log message progress is set to 100%
- - Waiting animation for ongoing processes
- - Agent-specific highlighting
- - Collapsible detailed information, meaning the first n (e.g., 40) characters are by default displayed with "..." at the end. By clicking on "..." the additional part can be expanded/collapsed
- - Timestamp display
-
-### Waiting States
-1. **Animation**:
- - Dots animation in log entries
- - Spinner in Send button during loading
- - Progress indicators in agent logs
-
-2. **State Management**:
- - `waiting` flag in log objects
- - `waitingDotsInterval` controls animation
- - `setLoadingState()` manages UI element states
-
-## State Transitions
-
-```
-[null] → [running] // Initial prompt submission
-[running] → [running] // Ongoing polling updates
-[running] → [completed] // Workflow completes successfully
-[running] → [stopped] // User manually stops workflow
-[running] → [failed] // Error occurs during workflow
-[completed] → [running] // User continues workflow with new input
-[stopped] → [running] // User continues after manual stop
-[failed] → [running] // User retries workflow despite error
-[any] → [null] // User resets workflow
-```
-
-## Error Handling
-
-### Client-side Errors
-1. **Network Errors**:
- - Retry mechanism in polling functions
- - Exponential backoff for repeated failures
- - Informative error logs for debugging
-
-2. **UI Errors**:
- - Validation before state changes
- - Fallback DOM element selection
- - Error boundaries for component isolation
-
-### Backend Communication Errors
-1. **API Failures**:
- - Error logging in console
- - User-friendly error messages in UI
- - Error state with option to retry
- - Toast notifications for transient errors
-
-2. **Data Inconsistencies**:
- - ID matching with fallback to partial matching
- - Multiple file reference methods
- - Content extraction fallbacks
-
-## Implementation Notes
-
-1. **Module Structure**:
- - `workflow.js`: Main initialization and coordination
- - `workflowState.js`: State management
- - `workflowApi.js`: Backend communication
- - `workflowUi.js`: UI rendering
- - `workflowUtils.js`: Helper functions
-
-2. **Key Functions**:
- - `workflowInit()`: Entry point for initialization
- - `workflowStatusUpdate()`: Core state transition function
- - `workflowStatusPoll()`: Main update loop
- - `workflowUserInput()`: Handles user input submission
- - `renderLogs()` and `renderMessages()`: UI update functions
-
-3. **Performance Optimizations**:
- - Selective DOM updates
- - Scroll position preservation
- - Data size estimation for statistics
- - Conditional re-rendering
diff --git a/notes/produce_diagrams.md b/notes/produce_diagrams.md
new file mode 100644
index 00000000..f4f02abc
--- /dev/null
+++ b/notes/produce_diagrams.md
@@ -0,0 +1,48 @@
+MERMAID DIAGRAM:
+
+can you make chart "wiki/diagramm_komponenten.mermaid". produce an component diagram, based on current code in poweron/*
+if document existsadd missing components, remove obsolete components.
+
+in box texts to use
instead of \n
+
+for all subgraphs to to add path on a separate line to find the module in the code.
+
+read all code modules caerfully to identify all components and their relations.
+
+connectors without texts, only lines.
+
+to add connector between frontend and backend (apiCalls.js -> app.py)
+
+to connect app.py (Main application module) with the route*.py
+
+to put all items of frontend into subgraph "Frontend"
+to put all items of gateway into subgraph "Gateway"
+
+
+
+
+
+
+to put following boxes to a dedicated subgraph within their existing subgraph:
+- workflowManager.py, workflowAgentsRegistry.py, documentProcessor.py, --> "Workflow"
+- mimeUtils.py, defAttributes.py, configuration.py, autho.py --> "Shared"
+- agent*.py --> "Agents"
+- workflow*.js --> "Workflow"
+- all *.js in js/modules/ not starting with workflow* --> "Administration"
+- formGeneric.js not to put to subgraph "Shared", but to a separated subgraph "Shared
+
+to connect the main.js (main app in the frontend) to nativation.js, globalState.js, login.js, register.js, msftCall.js, config.js
+
+to connect navigation.js to moduleLoader.js
+
+to connect moduleLoader.js to workflow.js, and all *.js in js/modules/ not starting with workflow*
+
+to connect all *.js in js/modules/ not starting with workflow* --> formGeneric.js
+
+to connect fomrGeneric.js --> apiCalls.js
+
+
+to use underscores (e.g. Backend_Python, Workflow_Modules, etc.) for all subgraph titles.
+
+if adding legend, then to give same colors like references to legend
+
diff --git a/notes/readme.md b/notes/readme.md
index 44186a3d..677e78ed 100644
--- a/notes/readme.md
+++ b/notes/readme.md
@@ -90,6 +90,7 @@ Für größere Installationen die JSON-basierte Datenbank ersetzen durch:
### git permanent login with vs code
git remote set-url origin https://valueon@github.com/valueonag/gateway
git remote set-url origin https://valueon@github.com/valueonag/frontend_agents
+git remote set-url origin https://valueon@github.com/valueonag/wiki
## Lizenz
diff --git a/notes/start.sh b/notes/start.sh
deleted file mode 100644
index c8486531..00000000
--- a/notes/start.sh
+++ /dev/null
@@ -1,105 +0,0 @@
-#!/bin/bash
-
-# Erkennen des Betriebssystems
-case "$(uname -s)" in
- CYGWIN*|MINGW*|MSYS*|Windows*)
- OS="Windows"
- VENV_PATH="gwserver/venv/Scripts"
- ACTIVATE_CMD="$VENV_PATH/activate"
- ;;
- *)
- OS="Unix"
- VENV_PATH="gwserver/venv/bin"
- ACTIVATE_CMD="$VENV_PATH/activate"
- ;;
-esac
-
-echo "Erkanntes Betriebssystem: $OS"
-
-# Prüfen, ob Python installiert ist
-PYTHON_CMD=""
-if command -v python3 &>/dev/null; then
- PYTHON_CMD="python3"
-elif command -v python &>/dev/null; then
- PYTHON_CMD="python"
-else
- echo "Python ist nicht installiert. Bitte installieren Sie Python 3.8 oder höher."
- exit 1
-fi
-
-# Virtuelle Umgebung erstellen, falls sie nicht existiert
-if [ ! -d "gwserver/venv" ]; then
- echo "Erstelle virtuelle Python-Umgebung..."
- cd gwserver
- $PYTHON_CMD -m venv venv
- cd ..
-fi
-
-# Virtuelle Umgebung aktivieren
-echo "Aktiviere virtuelle Umgebung..."
-if [ "$OS" = "Windows" ]; then
- source "$ACTIVATE_CMD" 2>/dev/null || . "$ACTIVATE_CMD" 2>/dev/null || echo "Warnung: Aktivierung der virtuellen Umgebung fehlgeschlagen. Fahre fort..."
-else
- source "$ACTIVATE_CMD" 2>/dev/null || . "$ACTIVATE_CMD" 2>/dev/null || echo "Warnung: Aktivierung der virtuellen Umgebung fehlgeschlagen. Fahre fort..."
-fi
-
-# Abhängigkeiten installieren
-echo "Installiere Abhängigkeiten..."
-pip install -r requirements.txt
-
-# gwserver als Hintergrundprozess starten
-echo "Starte gwserver-Server..."
-
-# START CORE
-cd gwserver
-if [ "$OS" = "Windows" ]; then
- # Windows: starte in einem separaten Prozess
- start uvicorn app:app --reload --host 0.0.0.0 --port 8000
- # Keine PID-Erfassung nötig unter Windows mit 'start'
- GWSERVER_PID=""
-else
- # Unix: starte im Hintergrund und erfasse PID
- uvicorn app:app --reload --host 0.0.0.0 --port 8000 &
- GWSERVER_PID=$!
-fi
-# END CORE
-
-cd ..
-echo "gwserver API läuft auf: http://localhost:8000"
-echo "API-Dokumentation: http://localhost:8000/docs"
-echo "Drücke STRG+C, um Server zu beenden"
-
-# Funktion zum Beenden der Server bei STRG+C
-cleanup() {
- echo -e "\nBeende Server..."
- if [ "$OS" = "Windows" ]; then
- # Windows: Taskkill für uvicorn
- taskkill //F //IM uvicorn.exe 2>/dev/null || echo "Konnte uvicorn nicht beenden"
- else
- # Unix: Verwende die erfasste PID
- if [ -n "$GWSERVER_PID" ]; then
- kill $GWSERVER_PID 2>/dev/null || echo "Konnte Prozess $GWSERVER_PID nicht beenden"
- fi
- fi
- echo "Server wurden beendet"
- exit 0
-}
-
-# Signal-Handler für STRG+C
-trap cleanup SIGINT
-
-# Warten auf Benutzeraktion
-if [ "$OS" = "Windows" ]; then
- # Unter Windows: Warte auf Eingabe
- read -p "Drücke Enter zum Beenden..." dummy
- cleanup
-else
- # Unter Unix: Warte auf Prozess
- if [ -n "$GWSERVER_PID" ]; then
- wait $GWSERVER_PID
- else
- # Fallback: einfach warten
- read -p "Drücke Enter zum Beenden..." dummy
- cleanup
- fi
-fi
\ No newline at end of file
diff --git a/routes/routeGeneral.py b/routes/routeGeneral.py
new file mode 100644
index 00000000..0b5d3d33
--- /dev/null
+++ b/routes/routeGeneral.py
@@ -0,0 +1,81 @@
+from fastapi import APIRouter, HTTPException, Depends, Body, status, Response
+from fastapi.responses import FileResponse
+from fastapi.security import OAuth2PasswordRequestForm
+from typing import Dict, Any
+from datetime import timedelta
+import pathlib
+import os
+
+from modules.configuration import APP_CONFIG
+from modules.auth import (
+ createAccessToken,
+ getCurrentActiveUser,
+ getUserContext,
+ ACCESS_TOKEN_EXPIRE_MINUTES
+)
+import modules.gatewayModel as gatewayModel
+from modules.gatewayInterface import getGatewayInterface
+
+router = APIRouter()
+
+# Static folder for favicon
+baseDir = pathlib.Path(__file__).parent.parent
+staticFolder = baseDir / "static"
+
+@router.get("/favicon.ico")
+async def favicon():
+ return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon")
+
+@router.get("/", tags=["General"])
+async def root():
+ """API status endpoint"""
+ return {"status": "online", "message": "Data Platform API is active"}
+
+@router.get("/api/test", tags=["General"])
+async def getTest():
+ return f"Status: OK. Alowed origins: {APP_CONFIG.get('APP_ALLOWED_ORIGINS')}"
+
+@router.options("/{fullPath:path}", tags=["General"])
+async def optionsRoute(fullPath: str):
+ return Response(status_code=200)
+
+@router.get("/api/environment", tags=["General"])
+async def get_environment():
+ """Get environment configuration for frontend"""
+ return {
+ "apiBaseUrl": APP_CONFIG.get("APP_API_URL", ""),
+ "environment": APP_CONFIG.get("APP_ENV", "development"),
+ "instanceLabel": APP_CONFIG.get("APP_ENV_LABEL", "Development"),
+ # Add other environment variables the frontend might need
+ }
+
+@router.post("/api/token", response_model=gatewayModel.Token, tags=["General"])
+async def loginForAccessToken(formData: OAuth2PasswordRequestForm = Depends()):
+ # Initialize Gateway interface without context
+ gateway = getGatewayInterface()
+
+ # Authenticate user
+ user = gateway.authenticateUser(formData.username, formData.password)
+
+ if not user:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Invalid username or password",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+
+ # Create token with tenant ID
+ accessTokenExpires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+ accessToken = createAccessToken(
+ data={
+ "sub": user["username"],
+ "mandateId": user["mandateId"]
+ },
+ expiresDelta=accessTokenExpires
+ )
+
+ return {"accessToken": accessToken, "tokenType": "bearer"}
+
+@router.get("/api/user/me", response_model=Dict[str, Any], tags=["General"])
+async def readUserMe(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
+ return currentUser
\ No newline at end of file