From 0e22c5fc3ca8e546825da9a8d0646b0a5ad30782 Mon Sep 17 00:00:00 2001 From: Christian Chwala Date: Thu, 12 Feb 2026 16:57:25 +0100 Subject: [PATCH 1/2] Fix Grafana embedding in real-time website - Configure Grafana to serve from /grafana/ subpath with proper root URL - Update Flask proxy to forward requests to Grafana subpath endpoint - Add cookie settings for iframe embedding (SameSite=none, disable login form) --- docker-compose.yml | 5 ++++ webserver/main.py | 48 ++++++++++++++++++++++++++++++- webserver/templates/realtime.html | 2 +- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4c0fc48..bab31c8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -113,6 +113,11 @@ services: - GF_AUTH_ANONYMOUS_ENABLED=true - GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer - GF_SECURITY_ALLOW_EMBEDDING=true + - GF_SERVER_ROOT_URL=http://localhost:5000/grafana/ + - GF_SERVER_SERVE_FROM_SUB_PATH=true + - GF_AUTH_DISABLE_LOGIN_FORM=true + - GF_SECURITY_COOKIE_SAMESITE=none + - GF_SECURITY_COOKIE_SECURE=false volumes: - grafana_data:/var/lib/grafana - ./grafana/provisioning:/etc/grafana/provisioning diff --git a/webserver/main.py b/webserver/main.py index 661a2ae..b530ce7 100644 --- a/webserver/main.py +++ b/webserver/main.py @@ -6,7 +6,7 @@ import folium import altair as alt import requests -from flask import Flask, render_template, request, jsonify +from flask import Flask, render_template, request, jsonify, Response, redirect from datetime import datetime, timedelta from pathlib import Path import uuid @@ -351,6 +351,52 @@ def realtime(): ) +@app.route("/grafana") +def grafana_root_redirect(): + """Redirect /grafana to /grafana/ for proper subpath routing.""" + return redirect("/grafana/", code=302) + + +@app.route( + "/grafana/", + defaults={"path": ""}, + methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"], +) +@app.route( + "/grafana/", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] +) +def grafana_proxy(path): + """Proxy all requests to Grafana container.""" + grafana_url = f"http://grafana:3000/grafana/{path}" + method = request.method + headers = {key: value for key, value in request.headers if key.lower() != "host"} + data = request.get_data() + params = request.args + + resp = requests.request( + method, + grafana_url, + headers=headers, + params=params, + data=data, + cookies=request.cookies, + allow_redirects=False, + ) + + excluded_headers = [ + "content-encoding", + "content-length", + "transfer-encoding", + "connection", + ] + response_headers = [ + (name, value) + for name, value in resp.headers.items() + if name.lower() not in excluded_headers + ] + return Response(resp.content, resp.status_code, response_headers) + + @app.route("/grafana-dashboard") def grafana_dashboard(): """Proxy to Grafana dashboard solo panel""" diff --git a/webserver/templates/realtime.html b/webserver/templates/realtime.html index dcccbd7..99bac05 100644 --- a/webserver/templates/realtime.html +++ b/webserver/templates/realtime.html @@ -85,7 +85,7 @@
From f5e55ffff01916754f0fbcb1661bdc08bdc477ce Mon Sep 17 00:00:00 2001 From: Christian Chwala Date: Thu, 12 Feb 2026 17:00:50 +0100 Subject: [PATCH 2/2] cleanup: Remove Grafana dashboard proxy route from main application (steming from approach that did not work) --- webserver/main.py | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/webserver/main.py b/webserver/main.py index b530ce7..d4f9969 100644 --- a/webserver/main.py +++ b/webserver/main.py @@ -397,37 +397,6 @@ def grafana_proxy(path): return Response(resp.content, resp.status_code, response_headers) -@app.route("/grafana-dashboard") -def grafana_dashboard(): - """Proxy to Grafana dashboard solo panel""" - try: - # Proxy a request to the full Grafana dashboard (not d-solo) so the timepicker is available. - # Forward query params from the incoming request to Grafana. - base = "http://grafana:3000/d/cml-realtime/cml-real-time-data" - params = request.args.to_dict(flat=True) - # Ensure a theme is provided (default to light) - params.setdefault("theme", "light") - params.setdefault("orgId", "1") - - # Build Grafana URL - from urllib.parse import urlencode - - grafana_url = base + "?" + urlencode(params) - - resp = requests.get(grafana_url, timeout=10) - resp.raise_for_status() - - content = resp.text - - # Return response with header allowing embedding - response = app.response_class(content, mimetype="text/html") - response.headers["X-Frame-Options"] = "ALLOWALL" - return response - except Exception as e: - print(f"Error proxying Grafana dashboard: {e}") - return f"
Error loading Grafana dashboard: {e}
" - - @app.route("/api/cml-metadata") def api_cml_metadata(): """API endpoint for fetching CML metadata"""