diff --git a/common/utils/nginx_utils.py b/common/utils/nginx_utils.py index 1a5771b..b9e2e4d 100644 --- a/common/utils/nginx_utils.py +++ b/common/utils/nginx_utils.py @@ -1,27 +1,61 @@ -from flask import request, current_app, url_for +from flask import request, url_for from urllib.parse import urlsplit, urlunsplit +import re + +VISIBLE_PREFIXES = ('/admin', '/api', '/chat-client') + +def _derive_visible_prefix(): + # 1) Edge-provided header (beste en meest expliciete bron) + xfp = request.headers.get('X-Forwarded-Prefix') + if xfp and any(xfp.startswith(p) for p in VISIBLE_PREFIXES): + return xfp.rstrip('/') + + # 2) Referer fallback: haal het top-level segment uit de Referer path + ref = request.headers.get('Referer') or '' + try: + ref_path = urlsplit(ref).path or '' + m = re.match(r'^/(admin|api|chat-client)(?:\b|/)', ref_path) + if m: + return f"/{m.group(1)}" + except Exception: + pass + + # 3) Geen prefix bekend + return '' def prefixed_url_for(endpoint, **values): - prefix = request.headers.get('X-Forwarded-Prefix', '') - scheme = request.headers.get('X-Forwarded-Proto', request.scheme) - host = request.headers.get('Host', request.host) - + """ + Gedrag: + - Default (_external=False, for_redirect=False): retourneer relatief pad (zonder leading '/') + voor templates/JS. De dynamische zorgt voor correcte resolutie onder het zichtbare prefix. + - _external=True: bouw absolute URL (schema/host). Als X-Forwarded-Prefix aanwezig is, + prefixeer de path daarmee (handig voor e-mails/deeplinks). + - for_redirect=True: geef root-absoluut pad inclusief zichtbaar top-prefix, geschikt + voor HTTP Location headers. Backwards compat: _as_location=True wordt behandeld als for_redirect. + """ external = values.pop('_external', False) - generated_url = url_for(endpoint, **values) + # Backwards compatibility met oudere paramnaam + if values.pop('_as_location', False): + values['for_redirect'] = True + for_redirect = values.pop('for_redirect', False) + + generated_url = url_for(endpoint, **values) # bv. "/user/tenant_overview" + path, query, fragment = urlsplit(generated_url)[2:5] if external: - path, query, fragment = urlsplit(generated_url)[2:5] - # Check if the prefix is already present in the path - if prefix and not path.startswith(prefix): - new_path = prefix + path - else: - new_path = path + scheme = request.headers.get('X-Forwarded-Proto', request.scheme) + host = request.headers.get('Host', request.host) + xfp = request.headers.get('X-Forwarded-Prefix', '') or '' + new_path = (xfp.rstrip('/') + path) if (xfp and not path.startswith(xfp)) else path return urlunsplit((scheme, host, new_path, query, fragment)) - else: - # Check if the prefix is already present in the generated URL - if prefix and not generated_url.startswith(prefix): - return prefix + generated_url - else: - return generated_url + if for_redirect: + visible_prefix = _derive_visible_prefix() + if visible_prefix and not path.startswith(visible_prefix): + return f"{visible_prefix}{path}" + # root-absoluut pad, zonder prefix als onbekend + return path + + # Default: relatief pad + return path[1:] if path.startswith('/') else path \ No newline at end of file diff --git a/config/config.py b/config/config.py index df3b03a..549b50b 100644 --- a/config/config.py +++ b/config/config.py @@ -353,9 +353,9 @@ class StagingConfig(Config): EXPLAIN_TEMPLATE_LOADING = False # Define the nginx prefix used for the specific apps - EVEAI_APP_LOCATION_PREFIX = '/admin' - EVEAI_CHAT_LOCATION_PREFIX = '/chat' - CHAT_CLIENT_PREFIX = 'chat-client/chat/' + EVEAI_APP_LOCATION_PREFIX = '' + EVEAI_CHAT_LOCATION_PREFIX = '' + CHAT_CLIENT_PREFIX = '' # Define the static path STATIC_URL = 'https://evie-staging-static.askeveai.com' @@ -386,8 +386,8 @@ class ProdConfig(Config): WTF_CSRF_SSL_STRICT = True # Set to True if using HTTPS # Define the nginx prefix used for the specific apps - EVEAI_APP_LOCATION_PREFIX = '/admin' - EVEAI_CHAT_LOCATION_PREFIX = '/chat' + EVEAI_APP_LOCATION_PREFIX = '' + EVEAI_CHAT_LOCATION_PREFIX = '' # Define the static path STATIC_URL = 'https://evie-staging-static.askeveai.com' diff --git a/eveai_app/__init__.py b/eveai_app/__init__.py index 999c2e4..3177a0a 100644 --- a/eveai_app/__init__.py +++ b/eveai_app/__init__.py @@ -119,20 +119,14 @@ def create_app(config_file=None): from flask_login import current_user import datetime - # app.logger.debug(f"Before request - URL: {request.url}") - # app.logger.debug(f"Before request - Session permanent: {session.permanent}") + app.logger.debug(f"Before request - URL: {request.url}") + app.logger.debug(f"Before request - Session permanent: {session.permanent}") # Log session expiry tijd als deze bestaat if current_user.is_authenticated: # Controleer of sessie permanent is (nodig voor PERMANENT_SESSION_LIFETIME) if not session.permanent: session.permanent = True - # app.logger.debug("Session marked as permanent (enables 60min timeout)") - - # Log wanneer sessie zou verlopen - # if '_permanent' in session: - # expires_at = datetime.datetime.now() + app.permanent_session_lifetime - # app.logger.debug(f"Session will expire at: {expires_at} (60 min from now)") @app.route('/debug/session') def debug_session(): @@ -185,21 +179,20 @@ def register_extensions(app): def register_blueprints(app): - prefix = app.config.get('EVEAI_APP_LOCATION_PREFIX', '/admin') from .views.user_views import user_bp - app.register_blueprint(user_bp, url_prefix=prefix) + app.register_blueprint(user_bp) from .views.basic_views import basic_bp - app.register_blueprint(basic_bp, url_prefix=prefix) + app.register_blueprint(basic_bp) from .views.document_views import document_bp - app.register_blueprint(document_bp, url_prefix=prefix) + app.register_blueprint(document_bp) from .views.security_views import security_bp - app.register_blueprint(security_bp, url_prefix=prefix) + app.register_blueprint(security_bp) from .views.interaction_views import interaction_bp - app.register_blueprint(interaction_bp, url_prefix=prefix) + app.register_blueprint(interaction_bp) from .views.entitlements_views import entitlements_bp - app.register_blueprint(entitlements_bp, url_prefix=prefix) + app.register_blueprint(entitlements_bp) from .views.partner_views import partner_bp - app.register_blueprint(partner_bp, url_prefix=prefix) + app.register_blueprint(partner_bp) from .views.healthz_views import healthz_bp, init_healtz app.register_blueprint(healthz_bp) init_healtz(app) diff --git a/eveai_app/templates/document/library_operations.html b/eveai_app/templates/document/library_operations.html index 97152f7..cc2b5fe 100644 --- a/eveai_app/templates/document/library_operations.html +++ b/eveai_app/templates/document/library_operations.html @@ -8,7 +8,7 @@ {% block content %}
-
+

Create Default RAG Library

This function will create a default library setup for RAG purposes. More specifically, it will create:

@@ -35,7 +35,6 @@ Please use it with caution!

-

diff --git a/eveai_app/templates/entitlements/view_usages.html b/eveai_app/templates/entitlements/view_usages.html index 5003615..001029d 100644 --- a/eveai_app/templates/entitlements/view_usages.html +++ b/eveai_app/templates/entitlements/view_usages.html @@ -7,7 +7,7 @@ {% block content_description %}View License Usage{% endblock %} {% block content %} -
+ {{ render_selectable_table(headers=["Usage ID", "Start Date", "End Date", "Storage (MiB)", "Embedding (MiB)", "Interaction (tokens)"], rows=rows, selectable=False, id="usagesTable") }} diff --git a/eveai_app/templates/head.html b/eveai_app/templates/head.html index baf02de..54c35cf 100644 --- a/eveai_app/templates/head.html +++ b/eveai_app/templates/head.html @@ -14,5 +14,6 @@ + diff --git a/eveai_app/templates/interaction/component_list.html b/eveai_app/templates/interaction/component_list.html index 49816ad..c875470 100644 --- a/eveai_app/templates/interaction/component_list.html +++ b/eveai_app/templates/interaction/component_list.html @@ -9,7 +9,7 @@ {% block content %}
- + {{ render_selectable_table(headers=["ID", "Name", "Type"], rows=rows, selectable=True, id=table_id) }}
diff --git a/eveai_app/templates/interaction/edit_specialist.html b/eveai_app/templates/interaction/edit_specialist.html index 6a9f79f..3ea1708 100644 --- a/eveai_app/templates/interaction/edit_specialist.html +++ b/eveai_app/templates/interaction/edit_specialist.html @@ -11,7 +11,7 @@
- + {{ form.hidden_tag() }} {% set disabled_fields = ['type', 'type_version'] %} {% set exclude_fields = [] %} @@ -348,7 +348,7 @@ document.addEventListener('DOMContentLoaded', function() { editorTabLink.classList.contains('component-task') ? 'task' : 'tool'; // Submit the data - fetch(`/admin/interaction/${componentType}/${componentId}/save`, { + fetch(`interaction/${componentType}/${componentId}/save`, { method: 'POST', body: formData, headers: { diff --git a/eveai_app/templates/interaction/session_interactions.html b/eveai_app/templates/interaction/session_interactions.html index 9c5a3c0..b6bb5ac 100644 --- a/eveai_app/templates/interaction/session_interactions.html +++ b/eveai_app/templates/interaction/session_interactions.html @@ -9,7 +9,7 @@ {% block content %}
- + {{ render_selectable_table(headers=["ID", "Question At", "Detailed Question At", "Answer At", "Processing Error"], rows=rows, selectable=False, id="interactionsTable") }} {#
#} {#
#} diff --git a/eveai_app/templates/list_view.html b/eveai_app/templates/list_view.html index ce715c7..3f4901c 100644 --- a/eveai_app/templates/list_view.html +++ b/eveai_app/templates/list_view.html @@ -6,7 +6,7 @@ {% block content %}
- + {% include 'eveai_list_view.html' %}
diff --git a/eveai_app/templates/navbar.html b/eveai_app/templates/navbar.html index d8ed4e4..03cc0f2 100644 --- a/eveai_app/templates/navbar.html +++ b/eveai_app/templates/navbar.html @@ -53,7 +53,7 @@ EveAI - + {% if 'tenant' in session %} TENANT: {{ session['tenant'].get('name', 'None') }} {% endif %} @@ -69,24 +69,24 @@ diff --git a/eveai_app/templates/partner/trigger_actions.html b/eveai_app/templates/partner/trigger_actions.html index 486e7b0..ef5ee34 100644 --- a/eveai_app/templates/partner/trigger_actions.html +++ b/eveai_app/templates/partner/trigger_actions.html @@ -6,7 +6,7 @@ {% block content %} -
+
diff --git a/eveai_app/templates/security/forgot_password.html b/eveai_app/templates/security/forgot_password.html index 2f3295e..41448d8 100644 --- a/eveai_app/templates/security/forgot_password.html +++ b/eveai_app/templates/security/forgot_password.html @@ -7,7 +7,7 @@ {% block content %} {% include "security/_messages.html" %} - + {{ forgot_password_form.hidden_tag() }}

diff --git a/eveai_app/templates/security/login_user.html b/eveai_app/templates/security/login_user.html index b3e70e4..3abd350 100644 --- a/eveai_app/templates/security/login_user.html +++ b/eveai_app/templates/security/login_user.html @@ -4,7 +4,7 @@ {% block content_title %}Sign In{% endblock %} {% block content_description %}Enter your email and password to Sign In{% endblock %} {% block content %} - + {{ login_user_form.hidden_tag() }}

{{ login_user_form.email.label }}
diff --git a/eveai_app/templates/user/edit_tenant.html b/eveai_app/templates/user/edit_tenant.html index cd66e17..be0eae5 100644 --- a/eveai_app/templates/user/edit_tenant.html +++ b/eveai_app/templates/user/edit_tenant.html @@ -28,7 +28,7 @@ const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; // Send timezone to the server via a POST request - fetch('/set_user_timezone', { + fetch('set_user_timezone', { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/eveai_app/templates/user/tenant.html b/eveai_app/templates/user/tenant.html index 451a116..b575b06 100644 --- a/eveai_app/templates/user/tenant.html +++ b/eveai_app/templates/user/tenant.html @@ -32,7 +32,7 @@ const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; // Send timezone to the server via a POST request - fetch('/set_user_timezone', { + fetch('set_user_timezone', { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/eveai_app/templates/user/view_tenant_domains.html b/eveai_app/templates/user/view_tenant_domains.html index 6f05614..610d89f 100644 --- a/eveai_app/templates/user/view_tenant_domains.html +++ b/eveai_app/templates/user/view_tenant_domains.html @@ -7,7 +7,7 @@ {% block content_description %}Select the domain you'd like to action upon{% endblock %} {% block content %} - + {{ render_selectable_table(headers=["ID", "Domain Name", "Valid To"], rows=rows, selectable=True, id="tenantDomainsTable") }}

diff --git a/eveai_app/views/basic_views.py b/eveai_app/views/basic_views.py index 544e523..fd7b1dc 100644 --- a/eveai_app/views/basic_views.py +++ b/eveai_app/views/basic_views.py @@ -70,7 +70,7 @@ def session_defaults(): session.pop('catalog_name', None) flash('Session defaults updated successfully', 'success') - return redirect(prefixed_url_for('basic_bp.index')) + return redirect(prefixed_url_for('basic_bp.index', for_redirect=True)) return render_template('basic/session_defaults.html', form=form) @@ -80,7 +80,7 @@ def session_defaults(): except Exception as e: current_app.logger.error(f"Error in session_defaults: {str(e)}") flash('Error accessing catalog data. Please ensure your session is valid.', 'danger') - return redirect(prefixed_url_for('security_bp.login')) + return redirect(prefixed_url_for('security_bp.login', for_redirect=True)) @basic_bp.route('/set_user_timezone', methods=['POST']) @@ -129,7 +129,7 @@ def view_content(content_type): if not content_data: flash(f'Content van type {content_type} werd niet gevonden.', 'danger') - return redirect(prefixed_url_for('basic_bp.index')) + return redirect(prefixed_url_for('basic_bp.index', for_redirect=True)) # Titels en beschrijvingen per contenttype titles = { @@ -156,10 +156,10 @@ def view_content(content_type): except Exception as e: current_app.logger.error(f"Error displaying content {content_type}: {str(e)}") flash(f'Error displaying content: {str(e)}', 'danger') - return redirect(prefixed_url_for('basic_bp.index')) + return redirect(prefixed_url_for('basic_bp.index', for_redirect=True)) @basic_bp.route('/release_notes', methods=['GET']) @roles_accepted('Super User', 'Partner Admin', 'Tenant Admin') def release_notes(): """Doorverwijzen naar de nieuwe content view voor changelog""" - return redirect(prefixed_url_for('basic_bp.view_content', content_type='changelog')) + return redirect(prefixed_url_for('basic_bp.view_content', content_type='changelog', for_redirect=True)) diff --git a/eveai_app/views/document_views.py b/eveai_app/views/document_views.py index 56ced27..ba1e08b 100644 --- a/eveai_app/views/document_views.py +++ b/eveai_app/views/document_views.py @@ -74,7 +74,7 @@ def catalog(): flash('Catalog successfully added!', 'success') current_app.logger.info(f'Catalog {new_catalog.name} successfully added for tenant {tenant_id}!') # Enable step 2 of creation of catalog - add configuration of the catalog (dependent on type) - return redirect(prefixed_url_for('document_bp.catalog', catalog_id=new_catalog.id)) + return redirect(prefixed_url_for('document_bp.catalog', catalog_id=new_catalog.id, for_redirect=True)) except SQLAlchemyError as e: db.session.rollback() flash(f'Failed to add catalog. Error: {e}', 'danger') @@ -97,7 +97,7 @@ def catalogs(): def handle_catalog_selection(): action = request.form['action'] if action == 'create_catalog': - return redirect(prefixed_url_for('document_bp.catalog')) + return redirect(prefixed_url_for('document_bp.catalog', for_redirect=True)) catalog_identification = request.form.get('selected_row') catalog_id = ast.literal_eval(catalog_identification).get('value') catalog = Catalog.query.get_or_404(catalog_id) @@ -108,9 +108,9 @@ def handle_catalog_selection(): session['catalog_name'] = catalog.name session['catalog'] = catalog.to_dict() elif action == 'edit_catalog': - return redirect(prefixed_url_for('document_bp.edit_catalog', catalog_id=catalog_id)) + return redirect(prefixed_url_for('document_bp.edit_catalog', catalog_id=catalog_id, for_redirect=True)) - return redirect(prefixed_url_for('document_bp.catalogs')) + return redirect(prefixed_url_for('document_bp.catalogs', for_redirect=True)) @document_bp.route('/catalog/', methods=['GET', 'POST']) @@ -136,7 +136,7 @@ def edit_catalog(catalog_id): flash(f'Failed to update catalog. Error: {e}', 'danger') current_app.logger.error(f'Failed to update catalog {catalog_id} for tenant {tenant_id}. Error: {str(e)}') - return redirect(prefixed_url_for('document_bp.catalogs')) + return redirect(prefixed_url_for('document_bp.catalogs', for_redirect=True)) else: form_validation_failed(request, form) @@ -166,7 +166,7 @@ def processor(): flash('Processor successfully added!', 'success') current_app.logger.info(f'Processor {new_processor.name} successfully added for tenant {tenant_id}!') # Enable step 2 of creation of retriever - add configuration of the retriever (dependent on type) - return redirect(prefixed_url_for('document_bp.edit_processor', processor_id=new_processor.id)) + return redirect(prefixed_url_for('document_bp.edit_processor', processor_id=new_processor.id, for_redirect=True)) except SQLAlchemyError as e: db.session.rollback() flash(f'Failed to add processor. Error: {e}', 'danger') @@ -215,7 +215,7 @@ def edit_processor(processor_id): current_app.logger.error(f'Failed to update processor {processor_id}. Error: {str(e)}') return render_template('document/edit_processor.html', form=form, processor_id=processor_id) - return redirect(prefixed_url_for('document_bp.processors')) + return redirect(prefixed_url_for('document_bp.processors', for_redirect=True)) else: form_validation_failed(request, form) @@ -228,7 +228,7 @@ def processors(): catalog_id = session.get('catalog_id', None) if not catalog_id: flash('You need to set a Session Catalog before adding Documents or URLs', 'warning') - return redirect(prefixed_url_for('document_bp.catalogs')) + return redirect(prefixed_url_for('document_bp.catalogs', for_redirect=True)) # Get configuration and render the list view config = get_processors_list_view(catalog_id) @@ -240,14 +240,14 @@ def processors(): def handle_processor_selection(): action = request.form['action'] if action == 'create_processor': - return redirect(prefixed_url_for('document_bp.processor')) + return redirect(prefixed_url_for('document_bp.processor', for_redirect=True)) processor_identification = request.form.get('selected_row') processor_id = ast.literal_eval(processor_identification).get('value') if action == 'edit_processor': - return redirect(prefixed_url_for('document_bp.edit_processor', processor_id=processor_id)) + return redirect(prefixed_url_for('document_bp.edit_processor', processor_id=processor_id, for_redirect=True)) - return redirect(prefixed_url_for('document_bp.processors')) + return redirect(prefixed_url_for('document_bp.processors', for_redirect=True)) # Retriever Management ---------------------------------------------------------------------------- @@ -272,7 +272,7 @@ def retriever(): flash('Retriever successfully added!', 'success') current_app.logger.info(f'Catalog {new_retriever.name} successfully added for tenant {tenant_id}!') # Enable step 2 of creation of retriever - add configuration of the retriever (dependent on type) - return redirect(prefixed_url_for('document_bp.edit_retriever', retriever_id=new_retriever.id)) + return redirect(prefixed_url_for('document_bp.edit_retriever', retriever_id=new_retriever.id, for_redirect=True)) except SQLAlchemyError as e: db.session.rollback() flash(f'Failed to add retriever. Error: {e}', 'danger') @@ -315,7 +315,7 @@ def edit_retriever(retriever_id): current_app.logger.error(f'Failed to update retriever {retriever_id}. Error: {str(e)}') return render_template('document/edit_retriever.html', form=form, retriever_id=retriever_id) - return redirect(prefixed_url_for('document_bp.retrievers')) + return redirect(prefixed_url_for('document_bp.retrievers', for_redirect=True)) else: form_validation_failed(request, form) @@ -328,7 +328,7 @@ def retrievers(): catalog_id = session.get('catalog_id', None) if not catalog_id: flash('You need to set a Session Catalog before adding Documents or URLs', 'warning') - return redirect(prefixed_url_for('document_bp.catalogs')) + return redirect(prefixed_url_for('document_bp.catalogs', for_redirect=True)) # Get configuration and render the list view config = get_retrievers_list_view(catalog_id) @@ -340,14 +340,14 @@ def retrievers(): def handle_retriever_selection(): action = request.form['action'] if action == 'create_retriever': - return redirect(prefixed_url_for('document_bp.retriever')) + return redirect(prefixed_url_for('document_bp.retriever', for_redirect=True)) retriever_identification = request.form.get('selected_row') retriever_id = ast.literal_eval(retriever_identification).get('value') if action == 'edit_retriever': - return redirect(prefixed_url_for('document_bp.edit_retriever', retriever_id=retriever_id)) + return redirect(prefixed_url_for('document_bp.edit_retriever', retriever_id=retriever_id, for_redirect=True)) - return redirect(prefixed_url_for('document_bp.retrievers')) + return redirect(prefixed_url_for('document_bp.retrievers', for_redirect=True)) # Document Management ----------------------------------------------------------------------------- @@ -358,7 +358,7 @@ def add_document(): catalog_id = session.get('catalog_id', None) if catalog_id is None: flash('You need to set a Session Catalog before adding Documents or URLs', 'warning') - return redirect(prefixed_url_for('document_bp.catalogs')) + return redirect(prefixed_url_for('document_bp.catalogs', for_redirect=True)) catalog = Catalog.query.get_or_404(catalog_id) if catalog.configuration and len(catalog.configuration) > 0: @@ -392,7 +392,7 @@ def add_document(): flash(f'Processing on document {new_doc.name}, version {new_doc_vers.id} started. Task ID: {task_id}.', 'success') - return redirect(prefixed_url_for('document_bp.documents_processing')) + return redirect(prefixed_url_for('document_bp.documents_processing', for_redirect=True)) except EveAIException as e: flash(str(e), 'danger') @@ -411,7 +411,7 @@ def add_url(): catalog_id = session.get('catalog_id', None) if catalog_id is None: flash('You need to set a Session Catalog before adding Documents or URLs', 'warning') - return redirect(prefixed_url_for('document_bp.catalogs')) + return redirect(prefixed_url_for('document_bp.catalogs', for_redirect=True)) catalog = Catalog.query.get_or_404(catalog_id) if catalog.configuration and len(catalog.configuration) > 0: @@ -451,7 +451,7 @@ def add_url(): flash(f'Processing on document {new_doc.name}, version {new_doc_vers.id} started. Task ID: {task_id}.', 'success') - return redirect(prefixed_url_for('document_bp.documents_processing')) + return redirect(prefixed_url_for('document_bp.documents_processing', for_redirect=True)) except EveAIException as e: current_app.logger.error(f"Error adding document: {str(e)}") @@ -472,7 +472,7 @@ def documents(): catalog_id = session.get('catalog_id', None) if not catalog_id: flash('You need to set a Session Catalog before adding Documents or URLs', 'warning') - return redirect(prefixed_url_for('document_bp.catalogs')) + return redirect(prefixed_url_for('document_bp.catalogs', for_redirect=True)) config = get_documents_list_view(catalog_id) return render_list_view('list_view.html', **config) @@ -484,7 +484,7 @@ def documents_processing(): catalog_id = session.get('catalog_id', None) if not catalog_id: flash('You need to set a Session Catalog before adding Documents or URLs', 'warning') - return redirect(prefixed_url_for('document_bp.catalogs')) + return redirect(prefixed_url_for('document_bp.catalogs', for_redirect=True)) config = get_documents_processing_list_view(catalog_id) return render_list_view('list_view.html', **config) @@ -502,16 +502,16 @@ def handle_document_selection(): doc_id = ast.literal_eval(document_identification).get('value') except (ValueError, AttributeError): flash('Invalid document selection.', 'danger') - return redirect(prefixed_url_for('document_bp.documents')) + return redirect(prefixed_url_for('document_bp.documents', for_redirect=True)) action = request.form['action'] match action: case 'edit_document': - return redirect(prefixed_url_for('document_bp.edit_document', document_id=doc_id)) + return redirect(prefixed_url_for('document_bp.edit_document', document_id=doc_id, for_redirect=True)) case 'refresh': refresh_document_view(doc_id) - return redirect(prefixed_url_for('document_bp.documents', document_id=doc_id)) + return redirect(prefixed_url_for('document_bp.documents', document_id=doc_id, for_redirect=True)) case 're_process': document = Document.query.get_or_404(doc_id) doc_vers_id = document.latest_version.id @@ -520,14 +520,14 @@ def handle_document_selection(): document = Document.query.get_or_404(doc_id) doc_vers_id = document.latest_version.id return redirect(prefixed_url_for('document_bp.view_document_version_markdown', - document_version_id=doc_vers_id)) + document_version_id=doc_vers_id, for_redirect=True)) case 'edit_document_version': document = Document.query.get_or_404(doc_id) doc_vers_id = document.latest_version.id - return redirect(prefixed_url_for('document_bp.edit_document_version', document_version_id=doc_vers_id)) + return redirect(prefixed_url_for('document_bp.edit_document_version', document_version_id=doc_vers_id, for_redirect=True)) # Add more conditions for other actions - return redirect(prefixed_url_for('document_bp.documents')) + return redirect(prefixed_url_for('document_bp.documents', for_redirect=True)) @document_bp.route('/edit_document/', methods=['GET', 'POST']) @@ -562,7 +562,7 @@ def edit_document(document_id): ) if updated_doc: flash(f'Document {updated_doc.id} updated successfully', 'success') - return redirect(prefixed_url_for('document_bp.documents')) + return redirect(prefixed_url_for('document_bp.documents', for_redirect=True)) else: flash(f'Error updating document: {error}', 'danger') else: @@ -600,7 +600,7 @@ def edit_document_version(document_version_id): ) if updated_version: flash(f'Document Version {updated_version.id} updated successfully', 'success') - return redirect(prefixed_url_for('document_bp.documents', document_id=updated_version.doc_id)) + return redirect(prefixed_url_for('document_bp.documents', document_id=updated_version.doc_id, for_redirect=True)) else: flash(f'Error updating document version: {error}', 'danger') else: @@ -644,7 +644,7 @@ def view_document_version_markdown(document_version_id): except Exception as e: current_app.logger.error(f"Error retrieving markdown for document version {document_version_id}: {str(e)}") flash(f"Error retrieving processed document: {str(e)}", "danger") - return redirect(prefixed_url_for('document_bp.document_versions')) + return redirect(prefixed_url_for('document_bp.document_versions', for_redirect=True)) # Utilities --------------------------------------------------------------------------------------- @@ -667,7 +667,7 @@ def handle_library_selection(): case 'refresh_all_documents': refresh_all_documents() - return redirect(prefixed_url_for('document_bp.library_operations')) + return redirect(prefixed_url_for('document_bp.library_operations', for_redirect=True)) def create_default_rag_library(): @@ -675,7 +675,7 @@ def create_default_rag_library(): catalogs = Catalog.query.all() if catalogs: flash("Default RAG Library can only be created if no catalogs are defined!", 'danger') - return redirect(prefixed_url_for('document_bp.library_operations')) + return redirect(prefixed_url_for('document_bp.library_operations', for_redirect=True)) timestamp = dt.now(tz=tz.utc) try: @@ -749,7 +749,7 @@ def create_default_rag_library(): current_app.logger.error(f'Failed to create Default RAG Library' f'for tenant {session['tenant']['id']}. Error: {str(e)}') - return redirect(prefixed_url_for('document_bp.library_operations')) + return redirect(prefixed_url_for('document_bp.library_operations', for_redirect=True)) def refresh_all_documents(): @@ -763,7 +763,7 @@ def refresh_document_view(document_id): flash(f'Document refreshed. New version: {new_version.id}. Task ID: {result}', 'success') else: flash(f'Error refreshing document: {result}', 'danger') - return redirect(prefixed_url_for('document_bp.documents')) + return redirect(prefixed_url_for('document_bp.documents', for_redirect=True)) def re_embed_latest_versions(): @@ -785,7 +785,7 @@ def process_version(version_id): flash(f'Processing for document version {version_id} retriggered successfully...', 'success') - return redirect(prefixed_url_for('document_bp.documents')) + return redirect(prefixed_url_for('document_bp.documents', for_redirect=True)) def set_logging_information(obj, timestamp): diff --git a/eveai_app/views/entitlements_views.py b/eveai_app/views/entitlements_views.py index bbf9cc3..a4b0207 100644 --- a/eveai_app/views/entitlements_views.py +++ b/eveai_app/views/entitlements_views.py @@ -47,7 +47,7 @@ def license_tier(): current_app.logger.info(f"Successfully created license tier {new_license_tier.id}") flash(f"Successfully created tenant license tier {new_license_tier.id}", 'success') - return redirect(prefixed_url_for('entitlements_bp.license_tiers')) + return redirect(prefixed_url_for('entitlements_bp.license_tiers', for_redirect=True)) else: form_validation_failed(request, form) @@ -71,7 +71,7 @@ def license_tiers(): def handle_license_tier_selection(): action = request.form['action'] if action == 'create_license_tier': - return redirect(prefixed_url_for('entitlements_bp.license_tier')) + return redirect(prefixed_url_for('entitlements_bp.license_tier', for_redirect=True)) license_tier_identification = request.form['selected_row'] license_tier_id = ast.literal_eval(license_tier_identification).get('value') @@ -79,15 +79,15 @@ def handle_license_tier_selection(): match action: case 'edit_license_tier': return redirect(prefixed_url_for('entitlements_bp.edit_license_tier', - license_tier_id=license_tier_id)) + license_tier_id=license_tier_id, for_redirect=True)) case 'create_license_for_tenant': return redirect(prefixed_url_for('entitlements_bp.create_license', - license_tier_id=license_tier_id)) + license_tier_id=license_tier_id, for_redirect=True)) case 'associate_license_tier_to_partner': LicenseTierServices.associate_license_tier_with_partner(license_tier_id) # Add more conditions for other actions - return redirect(prefixed_url_for('entitlements_bp.license_tiers')) + return redirect(prefixed_url_for('entitlements_bp.license_tiers', for_redirect=True)) @entitlements_bp.route('/license_tier/', methods=['GET', 'POST']) @@ -113,7 +113,7 @@ def edit_license_tier(license_tier_id): flash('License Tier updated successfully.', 'success') return redirect( - prefixed_url_for('entitlements_bp.edit_license_tier', license_tier_id=license_tier_id)) + prefixed_url_for('entitlements_bp.edit_license_tier', license_tier_id=license_tier_id, for_redirect=True)) else: form_validation_failed(request, form) @@ -152,7 +152,7 @@ def create_license(license_tier_id): current_app.logger.error(f'Invalid currency {currency} for tenant {tenant_id} while creating license.') flash(f"Invalid currency {currency} for tenant {tenant_id} while creating license. " f"Check tenant's currency and try again.", 'danger') - return redirect(prefixed_url_for('user_bp.edit_tenant', tenant_id=tenant_id)) + return redirect(prefixed_url_for('user_bp.edit_tenant', tenant_id=tenant_id, for_redirect=True)) # General data form.currency.data = currency form.max_storage_mb.data = license_tier.max_storage_mb @@ -181,7 +181,7 @@ def create_license(license_tier_id): db.session.add(new_license) db.session.commit() flash('License created successfully', 'success') - return redirect(prefixed_url_for('entitlements_bp.edit_license', license_id=new_license.id)) + return redirect(prefixed_url_for('entitlements_bp.edit_license', license_id=new_license.id, for_redirect=True)) except Exception as e: db.session.rollback() flash(f'Error creating license: {str(e)}', 'error') @@ -223,7 +223,7 @@ def edit_license(license_id): flash('License updated successfully.', 'success') return redirect( - prefixed_url_for('entitlements_bp.edit_license', license_id=license_id)) + prefixed_url_for('entitlements_bp.edit_license', license_id=license_id, for_redirect=True)) else: form_validation_failed(request, form) @@ -253,11 +253,11 @@ def handle_license_selection(): match action: case 'edit_license': - return redirect(prefixed_url_for('entitlements_bp.edit_license', license_id=license_id)) + return redirect(prefixed_url_for('entitlements_bp.edit_license', license_id=license_id, for_redirect=True)) case 'view_periods': - return redirect(prefixed_url_for('entitlements_bp.license_periods', license_id=license_id)) + return redirect(prefixed_url_for('entitlements_bp.license_periods', license_id=license_id, for_redirect=True)) case _: - return redirect(prefixed_url_for('entitlements_bp.licenses')) + return redirect(prefixed_url_for('entitlements_bp.licenses', for_redirect=True)) @entitlements_bp.route('/license//periods') @@ -270,7 +270,7 @@ def license_periods(license_id): tenant_id = session.get('tenant').get('id') if license.tenant_id != tenant_id: flash('Access denied to this license', 'danger') - return redirect(prefixed_url_for('entitlements_bp.licenses')) + return redirect(prefixed_url_for('entitlements_bp.licenses', for_redirect=True)) config = get_license_periods_list_view(license_id) @@ -315,7 +315,7 @@ def edit_license_period(period_id): flash(f'Error updating status: {str(e)}', 'danger') current_app.logger.error(f"Error updating period {period_id}: {str(e)}") - return redirect(prefixed_url_for('entitlements_bp.license_periods', license_id=period.license_id)) + return redirect(prefixed_url_for('entitlements_bp.license_periods', license_id=period.license_id, for_redirect=True)) return render_template('entitlements/edit_license_period.html', form=form) @@ -328,7 +328,7 @@ def handle_license_period_selection(license_id): # For actions that don't require a selection if 'selected_row' not in request.form: - return redirect(prefixed_url_for('entitlements_bp.license_periods', license_id=license_id)) + return redirect(prefixed_url_for('entitlements_bp.license_periods', license_id=license_id, for_redirect=True)) period_identification = request.form['selected_row'] period_id = ast.literal_eval(period_identification).get('value') @@ -337,19 +337,19 @@ def handle_license_period_selection(license_id): case 'view_period_details': # TODO: Implement period details view if needed flash('Period details view not yet implemented', 'info') - return redirect(prefixed_url_for('entitlements_bp.license_periods', license_id=license_id)) + return redirect(prefixed_url_for('entitlements_bp.license_periods', license_id=license_id, for_redirect=True)) case 'edit_license_period': # Display a form to choose the new status - return redirect(prefixed_url_for('entitlements_bp.edit_license_period', period_id=period_id)) + return redirect(prefixed_url_for('entitlements_bp.edit_license_period', period_id=period_id, for_redirect=True)) case _: - return redirect(prefixed_url_for('entitlements_bp.license_periods', license_id=license_id)) + return redirect(prefixed_url_for('entitlements_bp.license_periods', license_id=license_id, for_redirect=True)) @entitlements_bp.route('/view_licenses') @roles_accepted('Super User', 'Partner Admin', 'Tenant Admin') def view_licenses_redirect(): # Redirect to the new licenses route - return redirect(prefixed_url_for('entitlements_bp.licenses')) + return redirect(prefixed_url_for('entitlements_bp.licenses', for_redirect=True)) @entitlements_bp.route('/active_usage') @@ -359,7 +359,7 @@ def active_license_usage(): tenant_id = session.get('tenant', {}).get('id') if not tenant_id: flash('No active or pending license period found for this tenant', 'warning') - return redirect(prefixed_url_for('user_bp.tenants')) + return redirect(prefixed_url_for('user_bp.tenants', for_redirect=True)) active_period = LicensePeriod.query \ .join(License) \ diff --git a/eveai_app/views/interaction_views.py b/eveai_app/views/interaction_views.py index f4ebcc7..de36786 100644 --- a/eveai_app/views/interaction_views.py +++ b/eveai_app/views/interaction_views.py @@ -77,12 +77,12 @@ def handle_chat_session_selection(): match action: case 'view_chat_session': - return redirect(prefixed_url_for('interaction_bp.view_chat_session', chat_session_id=cs_id)) + return redirect(prefixed_url_for('interaction_bp.view_chat_session', chat_session_id=cs_id, for_redirect=True)) case 'chat_session_interactions': - return redirect(prefixed_url_for('interaction_bp.session_interactions', chat_session_id=cs_id)) + return redirect(prefixed_url_for('interaction_bp.session_interactions', chat_session_id=cs_id, for_redirect=True)) # Add more conditions for other actions - return redirect(prefixed_url_for('interaction_bp.chat_sessions')) + return redirect(prefixed_url_for('interaction_bp.chat_sessions', for_redirect=True)) @interaction_bp.route('/view_chat_session/', methods=['GET']) @@ -184,7 +184,7 @@ def specialist(): # Initialize the newly create specialist SpecialistServices.initialize_specialist(new_specialist.id, new_specialist.type, new_specialist.type_version) - return redirect(prefixed_url_for('interaction_bp.edit_specialist', specialist_id=new_specialist.id)) + return redirect(prefixed_url_for('interaction_bp.edit_specialist', specialist_id=new_specialist.id, for_redirect=True)) except Exception as e: db.session.rollback() @@ -257,7 +257,7 @@ def edit_specialist(specialist_id): db.session.commit() flash('Specialist updated successfully!', 'success') current_app.logger.info(f'Specialist {specialist.id} updated successfully') - return redirect(prefixed_url_for('interaction_bp.specialists')) + return redirect(prefixed_url_for('interaction_bp.specialists', for_redirect=True)) except SQLAlchemyError as e: db.session.rollback() flash(f'Failed to update specialist. Error: {str(e)}', 'danger') @@ -297,17 +297,17 @@ def specialists(): def handle_specialist_selection(): action = request.form.get('action') if action == 'create_specialist': - return redirect(prefixed_url_for('interaction_bp.specialist')) + return redirect(prefixed_url_for('interaction_bp.specialist', for_redirect=True)) specialist_identification = request.form.get('selected_row') specialist_id = ast.literal_eval(specialist_identification).get('value') if action == "edit_specialist": - return redirect(prefixed_url_for('interaction_bp.edit_specialist', specialist_id=specialist_id)) + return redirect(prefixed_url_for('interaction_bp.edit_specialist', specialist_id=specialist_id, for_redirect=True)) elif action == "execute_specialist": - return redirect(prefixed_url_for('interaction_bp.execute_specialist', specialist_id=specialist_id)) + return redirect(prefixed_url_for('interaction_bp.execute_specialist', specialist_id=specialist_id, for_redirect=True)) - return redirect(prefixed_url_for('interaction_bp.specialists')) + return redirect(prefixed_url_for('interaction_bp.specialists', for_redirect=True)) # Routes for Agent management --------------------------------------------------------------------- @@ -442,9 +442,9 @@ def handle_agent_selection(): action = request.form.get('action') if action == "edit_agent": - return redirect(prefixed_url_for('interaction_bp.edit_agent', agent_id=agent_id)) + return redirect(prefixed_url_for('interaction_bp.edit_agent', agent_id=agent_id, for_redirect=True)) - return redirect(prefixed_url_for('interaction_bp.edit_specialist')) + return redirect(prefixed_url_for('interaction_bp.edit_specialist', for_redirect=True)) @interaction_bp.route('/handle_task_selection', methods=['POST']) @@ -455,9 +455,9 @@ def handle_task_selection(): action = request.form.get('action') if action == "edit_task": - return redirect(prefixed_url_for('interaction_bp.edit_task', task_id=task_id)) + return redirect(prefixed_url_for('interaction_bp.edit_task', task_id=task_id, for_redirect=True)) - return redirect(prefixed_url_for('interaction_bp.edit_specialist')) + return redirect(prefixed_url_for('interaction_bp.edit_specialist', for_redirect=True)) @interaction_bp.route('/handle_tool_selection', methods=['POST']) @@ -468,9 +468,9 @@ def handle_tool_selection(): action = request.form.get('action') if action == "edit_tool": - return redirect(prefixed_url_for('interaction_bp.edit_tool', tool_id=tool_id)) + return redirect(prefixed_url_for('interaction_bp.edit_tool', tool_id=tool_id, for_redirect=True)) - return redirect(prefixed_url_for('interaction_bp.edit_specialist')) + return redirect(prefixed_url_for('interaction_bp.edit_specialist', for_redirect=True)) # Specialist Execution ---------------------------------------------------------------------------- @@ -481,7 +481,7 @@ def execute_specialist(specialist_id): if specialist_config.get('chat', True): flash("Only specialists that don't require interactions can be executed this way!", 'error') - return redirect(prefixed_url_for('interaction_bp.specialists')) + return redirect(prefixed_url_for('interaction_bp.specialists', for_redirect=True)) form = ExecuteSpecialistForm(request.form, obj=specialist) if 'arguments' in specialist_config: @@ -498,7 +498,7 @@ def execute_specialist(specialist_id): session_id=session_id, user_timezone=session.get('tenant').get('timezone') ) - return redirect(prefixed_url_for('interaction_bp.session_interactions_by_session_id', session_id=session_id)) + return redirect(prefixed_url_for('interaction_bp.session_interactions_by_session_id', session_id=session_id, for_redirect=True)) return render_template('interaction/execute_specialist.html', form=form) @@ -564,7 +564,7 @@ def session_interactions_by_session_id(session_id): waiting='true')) # If we've already shown a waiting message and still don't have a session, go back to the specialists page - return redirect(prefixed_url_for('interaction_bp.specialists')) + return redirect(prefixed_url_for('interaction_bp.specialists', for_redirect=True)) @interaction_bp.route('/session_interactions/', methods=['GET']) @@ -619,7 +619,7 @@ def specialist_magic_link(): f'tenant {tenant_id}!') return redirect(prefixed_url_for('interaction_bp.edit_specialist_magic_link', - specialist_magic_link_id=new_specialist_magic_link.id)) + specialist_magic_link_id=new_specialist_magic_link.id, for_redirect=True)) except Exception as e: db.session.rollback() @@ -666,7 +666,7 @@ def edit_specialist_magic_link(specialist_magic_link_id): db.session.commit() flash('Specialist Magic Link updated successfully!', 'success') current_app.logger.info(f'Specialist Magic Link {specialist_ml.id} updated successfully') - return redirect(prefixed_url_for('interaction_bp.specialist_magic_links')) + return redirect(prefixed_url_for('interaction_bp.specialist_magic_links', for_redirect=True)) except SQLAlchemyError as e: db.session.rollback() flash(f'Failed to update specialist Magic Link. Error: {str(e)}', 'danger') @@ -745,19 +745,19 @@ def specialist_magic_links(): def handle_specialist_magic_link_selection(): action = request.form.get('action') if action == 'create_specialist_magic_link': - return redirect(prefixed_url_for('interaction_bp.specialist_magic_link')) + return redirect(prefixed_url_for('interaction_bp.specialist_magic_link', for_redirect=True)) specialist_ml_identification = request.form.get('selected_row') specialist_ml_id = ast.literal_eval(specialist_ml_identification).get('value') if action == "edit_specialist_magic_link": return redirect(prefixed_url_for('interaction_bp.edit_specialist_magic_link', - specialist_magic_link_id=specialist_ml_id)) + specialist_magic_link_id=specialist_ml_id, for_redirect=True)) if action == "view_specialist_magic_link_urls": return redirect(prefixed_url_for('interaction_bp.view_specialist_magic_link_urls', - specialist_magic_link_id=specialist_ml_id)) + specialist_magic_link_id=specialist_ml_id, for_redirect=True)) - return redirect(prefixed_url_for('interaction_bp.specialists')) + return redirect(prefixed_url_for('interaction_bp.specialists', for_redirect=True)) # Routes for Asset Management --------------------------------------------------------------------- @@ -788,13 +788,13 @@ def handle_data_capsule_selection(): match action: case 'view_data_capsule': # For now, we'll just redirect to view_data_capsule - return redirect(prefixed_url_for('interaction_bp.view_data_capsule', data_capsule_id=capsule_id)) + return redirect(prefixed_url_for('interaction_bp.view_data_capsule', data_capsule_id=capsule_id, for_redirect=True)) case 'view_chat_session': # Redirect to the chat session - return redirect(prefixed_url_for('interaction_bp.view_chat_session', chat_session_id=data_capsule.chat_session_id)) + return redirect(prefixed_url_for('interaction_bp.view_chat_session', chat_session_id=data_capsule.chat_session_id, for_redirect=True)) # Default redirect back to the data capsules list - return redirect(prefixed_url_for('interaction_bp.eveai_data_capsules')) + return redirect(prefixed_url_for('interaction_bp.eveai_data_capsules', for_redirect=True)) @interaction_bp.route('/handle_asset_selection', methods=['POST']) @@ -804,9 +804,9 @@ def handle_asset_selection(): asset_id = ast.literal_eval(asset_identification).get('value') if action == 'edit_asset': - return redirect(prefixed_url_for('interaction_bp.edit_asset', asset_id=asset_id)) + return redirect(prefixed_url_for('interaction_bp.edit_asset', asset_id=asset_id, for_redirect=True)) - return redirect(prefixed_url_for('interaction_bp.assets')) + return redirect(prefixed_url_for('interaction_bp.assets', for_redirect=True)) @interaction_bp.route('/view_data_capsule/', methods=['GET']) @@ -830,14 +830,14 @@ def edit_asset(asset_id): if not tenant_id: flash('Geen tenant geselecteerd', 'error') - return redirect(url_for('interaction_bp.assets')) + return redirect(url_for('interaction_bp.assets', for_redirect=True)) # Controleer of het bestandstype wordt ondersteund if asset.file_type != 'json': flash( f'Bestandstype "{asset.file_type}" wordt momenteel niet ondersteund voor bewerking. Alleen JSON-bestanden kunnen worden bewerkt.', 'warning') - return redirect(url_for('interaction_bp.assets')) + return redirect(url_for('interaction_bp.assets', for_redirect=True)) if request.method == 'GET': try: @@ -861,11 +861,11 @@ def edit_asset(asset_id): except json.JSONDecodeError: flash('Fout bij het laden van het JSON-bestand: ongeldig JSON-formaat', 'error') - return redirect(prefixed_url_for('interaction_bp.assets')) + return redirect(prefixed_url_for('interaction_bp.assets', for_redirect=True)) except Exception as e: current_app.logger.error(f"Error loading asset {asset_id}: {str(e)}") flash(f'Fout bij het laden van het asset: {str(e)}', 'error') - return redirect(prefixed_url_for('interaction_bp.assets')) + return redirect(prefixed_url_for('interaction_bp.assets', for_redirect=True)) elif request.method == 'POST': try: @@ -877,14 +877,14 @@ def edit_asset(asset_id): if not json_data: current_app.logger.error(f"No JSON data received for asset {asset_id}") flash('Geen JSON data ontvangen', 'error') - return redirect(prefixed_url_for('interaction_bp.edit_asset', asset_id=asset_id)) + return redirect(prefixed_url_for('interaction_bp.edit_asset', asset_id=asset_id, for_redirect=True)) # Valideer JSON formaat try: parsed_json = json.loads(json_data) except json.JSONDecodeError as e: flash(f'Ongeldig JSON-formaat: {str(e)}', 'error') - return redirect(prefixed_url_for('interaction_bp.edit_asset', asset_id=asset_id)) + return redirect(prefixed_url_for('interaction_bp.edit_asset', asset_id=asset_id, for_redirect=True)) # Upload de bijgewerkte JSON naar MinIO bucket_name, object_name, file_size = minio_client.upload_asset_file( @@ -904,10 +904,10 @@ def edit_asset(asset_id): db.session.commit() flash('Asset succesvol bijgewerkt', 'success') - return redirect(prefixed_url_for('interaction_bp.assets')) + return redirect(prefixed_url_for('interaction_bp.assets', for_redirect=True)) except Exception as e: db.session.rollback() current_app.logger.error(f"Error saving asset {asset_id}: {str(e)}") flash(f'Fout bij het opslaan van het asset: {str(e)}', 'error') - return redirect(prefixed_url_for('interaction_bp.edit_asset', asset_id=asset_id)) + return redirect(prefixed_url_for('interaction_bp.edit_asset', asset_id=asset_id, for_redirect=True)) diff --git a/eveai_app/views/partner_views.py b/eveai_app/views/partner_views.py index a49e322..d558ce0 100644 --- a/eveai_app/views/partner_views.py +++ b/eveai_app/views/partner_views.py @@ -44,7 +44,7 @@ def handle_trigger_action(): current_app.logger.error(f"Failed to trigger usage update task: {str(e)}") flash(f'Failed to trigger usage update: {str(e)}', 'danger') - return redirect(prefixed_url_for('partner_bp.trigger_actions')) + return redirect(prefixed_url_for('partner_bp.trigger_actions', for_redirect=True)) # Partner Management ------------------------------------------------------------------------------ @@ -67,7 +67,7 @@ def edit_partner(partner_id): refresh_session_partner(partner.id) return redirect( prefixed_url_for('partner_bp.edit_partner', - partner_id=partner.id)) # Assuming there's a user profile view to redirect to + partner_id=partner.id, for_redirect=True)) # Assuming there's a user profile view to redirect to else: form_validation_failed(request, form) @@ -88,11 +88,11 @@ def handle_partner_selection(): if action == 'create_partner': try: partner_id = register_partner_from_tenant(session['tenant']['id']) - return redirect(prefixed_url_for('partner_bp.edit_partner', partner_id=partner_id, )) + return redirect(prefixed_url_for('partner_bp.edit_partner', partner_id=partner_id, for_redirect=True )) except EveAIException as e: current_app.logger.error(f'Error registering partner for tenant {session['tenant']['id']}: {str(e)}') flash('Error Registering Partner for Selected Tenant', 'danger') - return redirect(prefixed_url_for('partner_bp.partners')) + return redirect(prefixed_url_for('partner_bp.partners', for_redirect=True)) partner_identification = request.form.get('selected_row') partner_id = ast.literal_eval(partner_identification).get('value') partner = Partner.query.get_or_404(partner_id) @@ -101,9 +101,9 @@ def handle_partner_selection(): current_app.logger.info(f"Setting session partner: {partner.id}") session['partner'] = partner.to_dict() elif action == 'edit_partner': - return redirect(prefixed_url_for('partner_bp.edit_partner', partner_id=partner_id)) + return redirect(prefixed_url_for('partner_bp.edit_partner', partner_id=partner_id, for_redirect=True)) - return redirect(prefixed_url_for('partner_bp.partners')) + return redirect(prefixed_url_for('partner_bp.partners', for_redirect=True)) # Partner Servide Management ---------------------------------------------------------------------- @@ -116,7 +116,7 @@ def partner_service(): partner = session.get('partner', None) if not partner: flash('No partner has been selected. Set partner before adding services.', 'warning') - return redirect(prefixed_url_for('partner_bp.partners')) + return redirect(prefixed_url_for('partner_bp.partners', for_redirect=True)) partner_id = partner['id'] new_partner_service = PartnerService() form.populate_obj(new_partner_service) @@ -130,7 +130,7 @@ def partner_service(): current_app.logger.info(f"Partner Service {new_partner_service.name} added successfully for {partner_id}") # Step 2 of the creation process (depending on type) return redirect(prefixed_url_for('partner_bp.partner_service', - partner_service_id=new_partner_service.id)) + partner_service_id=new_partner_service.id, for_redirect=True)) except SQLAlchemyError as e: db.session.rollback() flash(f'Failed to add Partner Service: {str(e)}', 'danger') @@ -172,7 +172,7 @@ def edit_partner_service(partner_service_id): return render_template('partner/edit_partner_service.html', form=form, partner_service_id=partner_service_id) - return redirect(prefixed_url_for('partner_bp.partner_services')) + return redirect(prefixed_url_for('partner_bp.partner_services', for_redirect=True)) else: form_validation_failed(request, form) @@ -186,7 +186,7 @@ def partner_services(): partner = session.get('partner', None) if not partner: flash('No partner has been selected. Set partner before adding services.', 'warning') - return redirect(prefixed_url_for('partner_bp.partners')) + return redirect(prefixed_url_for('partner_bp.partners', for_redirect=True)) partner_id = session['partner']['id'] config = get_partner_services_list_view(partner_id) @@ -198,20 +198,20 @@ def partner_services(): def handle_partner_service_selection(): action = request.form['action'] if action == 'create_partner_service': - return redirect(prefixed_url_for('partner_bp.partner_service')) + return redirect(prefixed_url_for('partner_bp.partner_service', for_redirect=True)) partner_service_identification = request.form.get('selected_row') partner_service_id = ast.literal_eval(partner_service_identification).get('value') if action == 'edit_partner_service': return redirect(prefixed_url_for('partner_bp.edit_partner_service', - partner_service_id=partner_service_id)) + partner_service_id=partner_service_id, for_redirect=True)) elif action == 'add_partner_service_for_tenant': add_partner_service_for_tenant(partner_service_id) return redirect(prefixed_url_for('partner_bp.edit_partner_service', - partner_service_id=partner_service_id)) + partner_service_id=partner_service_id, for_redirect=True)) - return redirect(prefixed_url_for('partner_bp.partner_services')) + return redirect(prefixed_url_for('partner_bp.partner_services', for_redirect=True)) # Utility Functions @@ -248,7 +248,7 @@ def add_partner_service_for_tenant(partner_service_id): tenant = session.get('tenant', None) if not tenant: flash('No tenant has been selected. Set tenant before adding services.', 'warning') - return redirect(prefixed_url_for('user_bp.tenants')) + return redirect(prefixed_url_for('user_bp.tenants', for_redirect=True)) tenant_id = tenant['id'] @@ -257,7 +257,7 @@ def add_partner_service_for_tenant(partner_service_id): partner_service = PartnerService.query.get(partner_service_id) if not partner_service: flash(f'Partner service with ID {partner_service_id} not found.', 'danger') - return redirect(prefixed_url_for('partner_bp.partner_services')) + return redirect(prefixed_url_for('partner_bp.partner_services', for_redirect=True)) # Check if the association already exists existing = PartnerTenant.query.filter_by( @@ -267,7 +267,7 @@ def add_partner_service_for_tenant(partner_service_id): if existing: flash(f'This tenant already has access to this partner service.', 'warning') - return redirect(prefixed_url_for('partner_bp.partner_services')) + return redirect(prefixed_url_for('partner_bp.partner_services', for_redirect=True)) # Create new association new_partner_tenant = PartnerTenant( @@ -289,13 +289,13 @@ def add_partner_service_for_tenant(partner_service_id): partner_name = partner_tenant.name if partner_tenant else 'Unknown Partner' flash(f'Successfully added {partner_service.type} service from {partner_name} to this tenant.', 'success') - return redirect(prefixed_url_for('partner_bp.partner_services')) + return redirect(prefixed_url_for('partner_bp.partner_services', for_redirect=True)) except SQLAlchemyError as e: db.session.rollback() current_app.logger.error(f"Database error adding partner service: {str(e)}") flash(f'Error adding partner service: {str(e)}', 'danger') - return redirect(prefixed_url_for('partner_bp.partner_services')) + return redirect(prefixed_url_for('partner_bp.partner_services', for_redirect=True)) def refresh_session_partner(partner_id): diff --git a/eveai_app/views/security_views.py b/eveai_app/views/security_views.py index 66f2739..9d2264c 100644 --- a/eveai_app/views/security_views.py +++ b/eveai_app/views/security_views.py @@ -34,7 +34,7 @@ def log_after_request(response): @security_bp.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: - return redirect(prefixed_url_for('basic_bp.index')) + return redirect(prefixed_url_for('basic_bp.index', for_redirect=True)) form = LoginForm() @@ -55,9 +55,9 @@ def login(): current_app.logger.info(f'Login successful! Current User is {current_user.email}') db.session.commit() if current_user.has_roles('Super User'): - return redirect(prefixed_url_for('user_bp.tenants')) + return redirect(prefixed_url_for('user_bp.tenants', for_redirect=True)) else: - return redirect(prefixed_url_for('user_bp.tenant_overview')) + return redirect(prefixed_url_for('user_bp.tenant_overview', for_redirect=True)) else: flash('Invalid username or password', 'danger') current_app.logger.error(f'Invalid username or password for given email: {user.email}') @@ -68,7 +68,7 @@ def login(): except CSRFError: current_app.logger.warning('CSRF token mismatch during login attempt') flash('Your session has expired. Please try logging in again.', 'danger') - return redirect(prefixed_url_for('security_bp.login')) + return redirect(prefixed_url_for('security_bp.login', for_redirect=True)) if request.method == 'GET': csrf_token = generate_csrf() @@ -80,7 +80,7 @@ def login(): @login_required def logout(): logout_user() - return redirect(prefixed_url_for('basic_bp.index')) + return redirect(prefixed_url_for('basic_bp.index', for_redirect=True)) @security_bp.route('/confirm_email/', methods=['GET', 'POST']) @@ -89,12 +89,12 @@ def confirm_email(token): email = confirm_token(token) except Exception as e: flash('The confirmation link is invalid or has expired.', 'danger') - return redirect(prefixed_url_for('basic_bp.confirm_email_fail')) + return redirect(prefixed_url_for('basic_bp.confirm_email_fail', for_redirect=True)) user = User.query.filter_by(email=email).first_or_404() if user.active: flash('Account already confirmed. Please login.', 'success') - return redirect(prefixed_url_for('security_bp.login')) + return redirect(prefixed_url_for('security_bp.login', for_redirect=True)) else: user.active = True user.updated_at = dt.now(tz.utc) @@ -105,10 +105,10 @@ def confirm_email(token): db.session.commit() except SQLAlchemyError as e: db.session.rollback() - return redirect(prefixed_url_for('basic_bp.confirm_email_fail')) + return redirect(prefixed_url_for('basic_bp.confirm_email_fail', for_redirect=True)) send_reset_email(user) - return redirect(prefixed_url_for('basic_bp.confirm_email_ok')) + return redirect(prefixed_url_for('basic_bp.confirm_email_ok', for_redirect=True)) @security_bp.route('/forgot_password', methods=['GET', 'POST']) @@ -119,7 +119,7 @@ def forgot_password(): if user: send_reset_email(user) flash('An email with instructions to reset your password has been sent.', 'info') - return redirect(prefixed_url_for('security_bp.login')) + return redirect(prefixed_url_for('security_bp.login', for_redirect=True)) return render_template('security/forgot_password.html', form=form) @@ -130,7 +130,7 @@ def reset_password(token): except Exception as e: flash('The reset link is invalid or has expired.', 'danger') current_app.logger.error(f'Invalid reset link detected: {token} - error: {e}') - return redirect(prefixed_url_for('security_bp.reset_password_request')) + return redirect(prefixed_url_for('security_bp.reset_password_request', for_redirect=True)) user = User.query.filter_by(email=email).first_or_404() form = ResetPasswordForm() @@ -139,7 +139,7 @@ def reset_password(token): user.updated_at = dt.now(tz.utc) db.session.commit() flash('Your password has been updated.', 'success') - return redirect(prefixed_url_for('security_bp.login')) + return redirect(prefixed_url_for('security_bp.login', for_redirect=True)) return render_template('security/reset_password.html', reset_password_form=form) diff --git a/eveai_app/views/user_views.py b/eveai_app/views/user_views.py index 0486ab4..64407bb 100644 --- a/eveai_app/views/user_views.py +++ b/eveai_app/views/user_views.py @@ -48,7 +48,7 @@ def tenant(): if not UserServices.can_user_create_tenant(): current_app.logger.error(f'User {current_user.email} cannot create tenant') flash(f"You don't have the appropriate permissions to create a tenant", 'danger') - return redirect(prefixed_url_for('user_bp.tenants')) + return redirect(prefixed_url_for('user_bp.tenants', for_redirect=True)) form = TenantForm() if request.method == 'GET': code = f"TENANT-{str(uuid.uuid4())}" @@ -107,7 +107,7 @@ def tenant(): current_app.logger.info(f"Creating MinIO bucket for tenant {new_tenant.id}") minio_client.create_tenant_bucket(new_tenant.id) - return redirect(prefixed_url_for('user_bp.tenants')) + return redirect(prefixed_url_for('user_bp.tenants', for_redirect=True)) else: form_validation_failed(request, form) @@ -155,14 +155,14 @@ def tenants(): def handle_tenant_selection(): action = request.form['action'] if action == 'create_tenant': - return redirect(prefixed_url_for('user_bp.tenant')) + return redirect(prefixed_url_for('user_bp.tenant', for_redirect=True)) tenant_identification = request.form['selected_row'] tenant_id = ast.literal_eval(tenant_identification).get('value') if not UserServices.can_user_edit_tenant(tenant_id): current_app.logger.info(f"User not authenticated to edit tenant {tenant_id}.") flash(f"You are not authenticated to manage tenant {tenant_id}", 'danger') - return redirect(prefixed_url_for('user_bp.tenants')) + return redirect(prefixed_url_for('user_bp.tenants', for_redirect=True)) the_tenant = Tenant.query.get(tenant_id) # set tenant information in the session @@ -173,12 +173,12 @@ def handle_tenant_selection(): match action: case 'edit_tenant': - return redirect(prefixed_url_for('user_bp.edit_tenant', tenant_id=tenant_id)) + return redirect(prefixed_url_for('user_bp.edit_tenant', tenant_id=tenant_id, for_redirect=True)) case 'select_tenant': - return redirect(prefixed_url_for('user_bp.tenant_overview')) + return redirect(prefixed_url_for('user_bp.tenant_overview', for_redirect=True)) # Add more conditions for other actions - return redirect(prefixed_url_for('tenants')) + return redirect(prefixed_url_for('tenants', for_redirect=True)) @user_bp.route('/tenant_overview', methods=['GET']) @@ -244,7 +244,7 @@ def user(): flash('User added successfully, but failed to send confirmation email. ' 'Please contact the administrator.', 'warning') - return redirect(prefixed_url_for('user_bp.view_users')) + return redirect(prefixed_url_for('user_bp.view_users', for_redirect=True)) except Exception as e: current_app.logger.error(f'Failed to add user with name {new_user.user_name}. Error: {str(e)}') db.session.rollback() @@ -286,13 +286,13 @@ def edit_user(user_id): flash('Trying to assign unauthorized roles', 'danger') current_app.logger.error(f"Trying to assign unauthorized roles by user {user_id}," f"tenant {session['tenant']['id']}") - return redirect(prefixed_url_for('user_bp.edit_user', user_id=user_id)) + return redirect(prefixed_url_for('user_bp.edit_user', user_id=user_id, for_redirect=True)) db.session.commit() flash('User updated successfully.', 'success') return redirect( prefixed_url_for('user_bp.edit_user', - user_id=user.id)) # Assuming there's a user profile view to redirect to + user_id=user.id, for_redirect=True)) # Assuming there's a user profile view to redirect to else: form_validation_failed(request, form) @@ -314,14 +314,14 @@ def view_users(): def handle_user_action(): action = request.form['action'] if action == 'create_user': - return redirect(prefixed_url_for('user_bp.user')) + return redirect(prefixed_url_for('user_bp.user', for_redirect=True)) user_identification = request.form['selected_row'] user_id = ast.literal_eval(user_identification).get('value') user = User.query.get_or_404(user_id) if action == 'edit_user': - return redirect(prefixed_url_for('user_bp.edit_user', user_id=user_id)) + return redirect(prefixed_url_for('user_bp.edit_user', user_id=user_id, for_redirect=True)) elif action == 'resend_confirmation_email': send_confirmation_email(user) flash(f'Confirmation email sent to {user.email}.', 'success') @@ -332,7 +332,7 @@ def handle_user_action(): reset_uniquifier(user) flash(f'Uniquifier reset for {user.user_name}.', 'success') - return redirect(prefixed_url_for('user_bp.view_users')) + return redirect(prefixed_url_for('user_bp.view_users', for_redirect=True)) # Tenant Domain Management (Probably obsolete )------------------------------------------------------------------------ @@ -349,15 +349,15 @@ def tenant_domains(): def handle_tenant_domain_action(): action = request.form['action'] if action == 'create_tenant_domain': - return redirect(prefixed_url_for('user_bp.tenant_domain')) + return redirect(prefixed_url_for('user_bp.tenant_domain', for_redirect=True)) tenant_domain_identification = request.form['selected_row'] tenant_domain_id = ast.literal_eval(tenant_domain_identification).get('value') if action == 'edit_tenant_domain': - return redirect(prefixed_url_for('user_bp.edit_tenant_domain', tenant_domain_id=tenant_domain_id)) + return redirect(prefixed_url_for('user_bp.edit_tenant_domain', tenant_domain_id=tenant_domain_id, for_redirect=True)) # Add more conditions for other actions - return redirect(prefixed_url_for('tenant_domains')) + return redirect(prefixed_url_for('tenant_domains', for_redirect=True)) @user_bp.route('/tenant_domain', methods=['GET', 'POST']) @@ -410,7 +410,7 @@ def edit_tenant_domain(tenant_domain_id): f'Error: {str(e)}') return redirect( prefixed_url_for('user_bp.tenant_domains', - tenant_id=session['tenant']['id'])) # Assuming there's a user profile view to redirect to + tenant_id=session['tenant']['id'], for_redirect=True)) # Assuming there's a user profile view to redirect to else: form_validation_failed(request, form) @@ -461,7 +461,7 @@ def tenant_project(): current_app.logger.info(f'Tenant Project {new_tenant_project.name} added for tenant ' f'{session['tenant']['id']}.') - return redirect(prefixed_url_for('user_bp.tenant_projects')) + return redirect(prefixed_url_for('user_bp.tenant_projects', for_redirect=True)) except SQLAlchemyError as e: db.session.rollback() flash(f'Failed to create Tenant Project. Error: {str(e)}', 'danger') @@ -484,13 +484,13 @@ def tenant_projects(): def handle_tenant_project_selection(): action = request.form.get('action') if action == 'create_tenant_project': - return redirect(prefixed_url_for('user_bp.tenant_project')) + return redirect(prefixed_url_for('user_bp.tenant_project', for_redirect=True)) tenant_project_identification = request.form.get('selected_row') tenant_project_id = ast.literal_eval(tenant_project_identification).get('value') tenant_project = TenantProject.query.get_or_404(tenant_project_id) if action == 'edit_tenant_project': - return redirect(prefixed_url_for('user_bp.edit_tenant_project', tenant_project_id=tenant_project_id)) + return redirect(prefixed_url_for('user_bp.edit_tenant_project', tenant_project_id=tenant_project_id, for_redirect=True)) elif action == 'invalidate_tenant_project': tenant_project.active = False try: @@ -505,9 +505,9 @@ def handle_tenant_project_selection(): current_app.logger.error(f"Failed to invalidate Tenant Project for tenant {session['tenant']['id']}. " f"Error: {str(e)}") elif action == 'delete_tenant_project': - return redirect(prefixed_url_for('user_bp.delete_tenant_project', tenant_project_id=tenant_project_id)) + return redirect(prefixed_url_for('user_bp.delete_tenant_project', tenant_project_id=tenant_project_id, for_redirect=True)) - return redirect(prefixed_url_for('user_bp.tenant_projects')) + return redirect(prefixed_url_for('user_bp.tenant_projects', for_redirect=True)) @user_bp.route('/tenant_project/', methods=['GET', 'POST']) @@ -527,7 +527,7 @@ def edit_tenant_project(tenant_project_id): db.session.commit() flash('Tenant Project updated successfully.', 'success') current_app.logger.info(f'Tenant Project {tenant_project.name} updated for tenant {tenant_id}.') - return redirect(prefixed_url_for('user_bp.tenant_projects')) + return redirect(prefixed_url_for('user_bp.tenant_projects', for_redirect=True)) except SQLAlchemyError as e: db.session.rollback() flash(f'Failed to update Tenant Project. Error: {str(e)}', 'danger') @@ -545,7 +545,7 @@ def delete_tenant_project(tenant_project_id): # Ensure project belongs to current tenant if tenant_project.tenant_id != tenant_id: flash('You do not have permission to delete this project.', 'danger') - return redirect(prefixed_url_for('user_bp.tenant_projects')) + return redirect(prefixed_url_for('user_bp.tenant_projects', for_redirect=True)) if request.method == 'GET': return render_template('user/confirm_delete_tenant_project.html', @@ -562,7 +562,7 @@ def delete_tenant_project(tenant_project_id): flash(f'Failed to delete Tenant Project. Error: {str(e)}', 'danger') current_app.logger.error(f'Failed to delete Tenant Project {tenant_project_id}. Error: {str(e)}') - return redirect(prefixed_url_for('user_bp.tenant_projects')) + return redirect(prefixed_url_for('user_bp.tenant_projects', for_redirect=True)) # Tenant Make Management -------------------------------------------------------------------------- @@ -589,7 +589,7 @@ def tenant_make(): current_app.logger.info(f'Tenant Make {new_tenant_make.name}, id {new_tenant_make.id} successfully added ' f'for tenant {tenant_id}!') # Enable step 2 of creation of retriever - add configuration of the retriever (dependent on type) - return redirect(prefixed_url_for('user_bp.edit_tenant_make', tenant_make_id=new_tenant_make.id)) + return redirect(prefixed_url_for('user_bp.edit_tenant_make', tenant_make_id=new_tenant_make.id, for_redirect=True)) except SQLAlchemyError as e: db.session.rollback() flash(f'Failed to add Tenant Make. Error: {e}', 'danger') @@ -647,7 +647,7 @@ def edit_tenant_make(tenant_make_id): current_app.logger.error(f'Failed to update tenant make {tenant_make_id}. Error: {str(e)}') return render_template('user/edit_tenant_make.html', form=form, tenant_make_id=tenant_make_id) - return redirect(prefixed_url_for('user_bp.tenant_makes')) + return redirect(prefixed_url_for('user_bp.tenant_makes', for_redirect=True)) else: form_validation_failed(request, form) @@ -659,12 +659,12 @@ def edit_tenant_make(tenant_make_id): def handle_tenant_make_selection(): action = request.form['action'] if action == 'create_tenant_make': - return redirect(prefixed_url_for('user_bp.tenant_make')) + return redirect(prefixed_url_for('user_bp.tenant_make', for_redirect=True)) tenant_make_identification = request.form.get('selected_row') tenant_make_id = ast.literal_eval(tenant_make_identification).get('value') if action == 'edit_tenant_make': - return redirect(prefixed_url_for('user_bp.edit_tenant_make', tenant_make_id=tenant_make_id)) + return redirect(prefixed_url_for('user_bp.edit_tenant_make', tenant_make_id=tenant_make_id, for_redirect=True)) elif action == 'set_as_default': # Set this make as the default for the tenant tenant_id = session['tenant']['id'] @@ -682,7 +682,7 @@ def handle_tenant_make_selection(): current_app.logger.error(f'Failed to update default tenant make. Error: {str(e)}') # Altijd teruggaan naar de tenant_makes pagina - return redirect(prefixed_url_for('user_bp.tenant_makes')) + return redirect(prefixed_url_for('user_bp.tenant_makes', for_redirect=True)) @user_bp.route('/tenant_partner_services', methods=['GET', 'POST']) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 4155747..7910c32 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -57,9 +57,6 @@ http { index index.html index.htm; } - location /reset { - rewrite ^/reset(.*)$ /admin/reset$1 permanent; - } location /static/ { alias /etc/nginx/static/; @@ -100,17 +97,10 @@ http { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Prefix /chat-client; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_buffering off; - - # Add CORS headers - add_header 'Access-Control-Allow-Origin' '*' always; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; - add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization' always; - add_header 'Access-Control-Allow-Credentials' 'true' always; } location /admin/ { @@ -122,7 +112,6 @@ http { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Prefix /admin; # Required for Flask views (used in nginx_utils proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; @@ -140,60 +129,14 @@ http { proxy_send_timeout 60s; proxy_read_timeout 60s; send_timeout 60s; - - # Subfilter to hide admin prefix from app - sub_filter_once off; - sub_filter_types text/html text/css application/javascript; - - # General HTML sub_filters - sub_filter 'href="/static/' 'href="/static/'; - sub_filter 'src="/static/' 'src="/static/'; - sub_filter 'url("/static/' 'url("/static/'; - sub_filter 'href="/' 'href="/admin/'; # Rewrites for other content - sub_filter 'src="/' 'src="/admin/'; - sub_filter 'action="/' 'action="/admin/'; - sub_filter 'url("/' 'url("/admin/'; - - # Sub_filters for JavaScript URLs - sub_filter 'url: "/' 'url: "/admin/'; - sub_filter 'url: \"/' 'url: "/admin/'; - sub_filter 'url("/' 'url("/admin/'; - sub_filter 'url(\\"/' 'url("/admin/'; - - # Sub_filters for AJAX requests - sub_filter 'url: \'/user/' 'url: \'/admin/user/'; - sub_filter 'url: "/user/' 'url: "/admin/user/'; - sub_filter 'url: "/api/' 'url: "/admin/api/'; - sub_filter 'url: \'/api/' 'url: \'/admin/api/'; - } location /api/ { - # Handle preflight requests - if ($request_method = 'OPTIONS') { - add_header 'Access-Control-Allow-Origin' $http_origin always; - add_header 'Access-Control-Allow-Credentials' 'true' always; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; - add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always; - add_header 'Access-Control-Max-Age' 1728000; - add_header 'Content-Type' 'text/plain charset=UTF-8'; - add_header 'Content-Length' 0; - return 204; - } - # Mirror the Origin header if it's allowed by the application - # The application will handle the actual origin validation - add_header 'Access-Control-Allow-Origin' $http_origin always; - add_header 'Access-Control-Allow-Credentials' 'true' always; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; - add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always; - add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; - proxy_pass http://eveai_api:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Prefix /api; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; diff --git a/scaleway/manifests/base/networking/README-ROUTING.md b/scaleway/manifests/base/networking/README-ROUTING.md new file mode 100644 index 0000000..96f228c --- /dev/null +++ b/scaleway/manifests/base/networking/README-ROUTING.md @@ -0,0 +1,23 @@ +Routing alignment notes (staging/prod) + +Summary +- Root (/) issues a 301 redirect to /admin/ via server-snippet on the apps ingress. +- Prefixes /admin, /api, /chat-client are stripped at the edge and forwarded to their backends on /. The applications do not need to be prefix-aware. +- /verify remains available (Prefix) without any rewrite in a separate Ingress. +- No CORS annotations at ingress. Static assets are served by Bunny CDN; API CORS is not handled here. +- /flower is intentionally NOT exposed on k8s. + +Files +- ingress-https.yaml: NGINX Ingress (apps) with regex paths and rewrite-target to strip prefixes; includes server-snippet to 301 redirect root to /admin/. +- ingress-verify.yaml: Separate Ingress for /verify without regex/rewrite. + +Paths behavior +- / -> 301 /admin/ +- /admin/<...> -> eveai-app-service:80, backend receives /<...> +- /api/<...> -> eveai-api-service:80, backend receives /<...> +- /chat-client/<...> -> eveai-chat-client-service:80, backend receives /<...> +- /verify[/**] -> verify-service:80, path preserved. + +Notes +- The rewrite-target is global per Ingress. To avoid affecting /verify, we split it into its own Ingress. Keep this structure when adding/removing services. +- If you need temporary legacy redirects (e.g., /client -> /chat-client), add an additional Ingress with nginx.ingress.kubernetes.io/permanent-redirect and regex matching, or handle it at the app/CDN layer. diff --git a/scaleway/manifests/base/networking/ingress-https.yaml b/scaleway/manifests/base/networking/ingress-https.yaml index d9642b4..f5c0eef 100644 --- a/scaleway/manifests/base/networking/ingress-https.yaml +++ b/scaleway/manifests/base/networking/ingress-https.yaml @@ -14,6 +14,12 @@ metadata: nginx.ingress.kubernetes.io/proxy-body-size: "10m" nginx.ingress.kubernetes.io/proxy-read-timeout: "300" cert-manager.io/cluster-issuer: letsencrypt-staging + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/rewrite-target: "/$2" + nginx.ingress.kubernetes.io/server-snippet: | + location = / { + return 301 /admin/; + } spec: ingressClassName: nginx tls: @@ -24,54 +30,28 @@ spec: - host: evie-staging.askeveai.com http: paths: - # Verification service paths - - path: /verify - pathType: Prefix - backend: - service: - name: verify-service - port: - number: 80 - - # Application services - - path: /admin - pathType: Prefix + # Application services (strip prefix) + - path: /admin(/|$)(.*) + pathType: ImplementationSpecific backend: service: name: eveai-app-service port: number: 80 - - path: /api - pathType: Prefix + - path: /api(/|$)(.*) + pathType: ImplementationSpecific backend: service: name: eveai-api-service port: number: 80 - - path: /client - pathType: Prefix + - path: /chat-client(/|$)(.*) + pathType: ImplementationSpecific backend: service: name: eveai-chat-client-service port: number: 80 - - # Monitoring (when deployed) - # - path: /monitoring - # pathType: Prefix - # backend: - # service: - # name: monitoring-grafana - # port: - # number: 80 - - # Default: root path to verification service - - path: / - pathType: Prefix - backend: - service: - name: verify-service - port: - number: 80 \ No newline at end of file + \ No newline at end of file diff --git a/scaleway/manifests/base/networking/ingress-verify.yaml b/scaleway/manifests/base/networking/ingress-verify.yaml new file mode 100644 index 0000000..62bd60a --- /dev/null +++ b/scaleway/manifests/base/networking/ingress-verify.yaml @@ -0,0 +1,32 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: eveai-staging-ingress-verify + namespace: eveai-staging + labels: + app: eveai + environment: staging + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: "10m" + nginx.ingress.kubernetes.io/proxy-read-timeout: "300" + cert-manager.io/cluster-issuer: letsencrypt-staging +spec: + ingressClassName: nginx + tls: + - hosts: + - evie-staging.askeveai.com + secretName: evie-staging-tls + rules: + - host: evie-staging.askeveai.com + http: + paths: + - path: /verify + pathType: Prefix + backend: + service: + name: verify-service + port: + number: 80