From bb4ed2a75d6c8c1eb33407c1a1a3d1fb4ccb4650 Mon Sep 17 00:00:00 2001 From: Dom Date: Tue, 14 Apr 2026 16:48:36 +0200 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20session=20cleaner=20int?= =?UTF-8?q?=C3=A9gr=C3=A9=20+=20auth=20+=20nettoyage=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Onglet "đŸ§č Nettoyage" dans le dashboard (iframe vers port 5006) - Indicateur d'Ă©tat + bouton de dĂ©marrage si cleaner down - Service systemd rpa-session-cleaner intĂ©grĂ© au target rpa-vision - svc.sh et services.conf incluent session-cleaner (port 5006) P0-A — Auth dashboard Flask : - HTTP Basic obligatoire sur tous les endpoints (sauf /health, /healthz) - Credentials via DASHBOARD_USER + DASHBOARD_PASSWORD - 13 tests Nettoyage UI : - Section "DĂ©tection Visuelle" OWL retirĂ©e (modĂšle remplacĂ© par pipeline VLM) - Dashboard prĂ©fĂšre auto shot_*_blurred.png (avec ?raw=1 pour brut) Co-Authored-By: Claude Opus 4.6 (1M context) --- deploy/systemd/rpa-session-cleaner.service | 42 +++++ deploy/systemd/rpa-vision.target | 7 + services.conf | 2 + svc.sh | 24 +-- tests/unit/test_dashboard_auth_p0a.py | 160 +++++++++++++++++++ web_dashboard/app.py | 166 ++++++++++++++++++-- web_dashboard/templates/index.html | 173 +++++++++++++++------ 7 files changed, 507 insertions(+), 67 deletions(-) create mode 100644 deploy/systemd/rpa-session-cleaner.service create mode 100644 deploy/systemd/rpa-vision.target create mode 100644 tests/unit/test_dashboard_auth_p0a.py diff --git a/deploy/systemd/rpa-session-cleaner.service b/deploy/systemd/rpa-session-cleaner.service new file mode 100644 index 000000000..9a99075ec --- /dev/null +++ b/deploy/systemd/rpa-session-cleaner.service @@ -0,0 +1,42 @@ +[Unit] +Description=RPA Vision V3 - Session Cleaner (port 5006) +Documentation=https://lea.labs.laurinebazin.design +After=network-online.target rpa-streaming.service +Wants=network-online.target +Requires=rpa-streaming.service +PartOf=rpa-vision.target +StartLimitIntervalSec=300 +StartLimitBurst=5 + +[Service] +Type=simple + +# ---- Runtime ---- +User=dom +Group=dom +WorkingDirectory=/home/dom/ai/rpa_vision_v3 +EnvironmentFile=/home/dom/ai/rpa_vision_v3/.env.local +Environment="PYTHONUNBUFFERED=1" +Environment="RPA_SERVICE_NAME=rpa-session-cleaner" + +# Lancement du session cleaner (dĂ©pend du streaming server port 5005) +ExecStart=/home/dom/ai/rpa_vision_v3/.venv/bin/python3 tools/session_cleaner.py + +# ---- Resilience ---- +Restart=on-failure +RestartSec=10 +TimeoutStopSec=15 +KillMode=mixed +KillSignal=SIGTERM + +# ---- Hardening ---- +NoNewPrivileges=true +PrivateTmp=true + +# Logs -> journald +StandardOutput=journal +StandardError=journal +SyslogIdentifier=rpa-session-cleaner + +[Install] +WantedBy=rpa-vision.target diff --git a/deploy/systemd/rpa-vision.target b/deploy/systemd/rpa-vision.target new file mode 100644 index 000000000..95fa45f6e --- /dev/null +++ b/deploy/systemd/rpa-vision.target @@ -0,0 +1,7 @@ +[Unit] +Description=RPA Vision V3 - Tous les services +After=network-online.target +Wants=rpa-streaming.service rpa-vision-v3-api.service rpa-vision-v3-dashboard.service rpa-vision-v3-worker.service rpa-session-cleaner.service + +[Install] +WantedBy=multi-user.target diff --git a/services.conf b/services.conf index 63df2beeb..5af1f8728 100644 --- a/services.conf +++ b/services.conf @@ -9,6 +9,7 @@ # 5003 - Monitoring (mĂ©triques systĂšme) # 5004 - Agent Chat (interface conversationnelle) # 5005 - Streaming Server (Agent V1 → core pipeline) +# 5006 - Session Cleaner (nettoyage sessions avant replay) # 3002 - VWB Frontend (Vite/React) # @@ -20,3 +21,4 @@ agent-chat|5004|agent_chat/app.py|optional streaming|5005|agent_v0/server_v1/api_stream.py|optional worker|5099|agent_v0/server_v1/run_worker.py|optional vwb-frontend|3002|cd visual_workflow_builder/frontend_v4 && npm run dev|required +session-cleaner|5006|tools/session_cleaner.py|optional diff --git a/svc.sh b/svc.sh index 5438582ec..710cc772a 100755 --- a/svc.sh +++ b/svc.sh @@ -56,6 +56,7 @@ declare -A PORTS=( [streaming]=5005 [worker]=5099 [vwb-frontend]=3002 + [session-cleaner]=5006 ) # Mapping nom court -> nom service systemd @@ -66,13 +67,14 @@ declare -A SYSTEMD_UNITS=( [streaming]="rpa-streaming.service" [worker]="rpa-worker.service" [vwb-frontend]="rpa-vwb-frontend.service" + [session-cleaner]="rpa-session-cleaner.service" ) # Services gĂ©rĂ©s par systemd (ceux qui ont un .service) -SYSTEMD_SERVICES="streaming worker agent-chat dashboard vwb-backend vwb-frontend" +SYSTEMD_SERVICES="streaming worker agent-chat dashboard vwb-backend vwb-frontend session-cleaner" # Tous les services connus -ALL_SERVICES="api dashboard vwb-backend monitoring agent-chat streaming worker vwb-frontend" +ALL_SERVICES="api dashboard vwb-backend monitoring agent-chat streaming worker vwb-frontend session-cleaner" declare -A COMMANDS=( [api]="$VENV_DIR/bin/python3 server/api_upload.py" @@ -83,14 +85,15 @@ declare -A COMMANDS=( [streaming]="$VENV_DIR/bin/python3 -m agent_v0.server_v1.api_stream" [worker]="$VENV_DIR/bin/python3 -m agent_v0.server_v1.run_worker" [vwb-frontend]="cd $SCRIPT_DIR/visual_workflow_builder/frontend_v4 && npm run dev" + [session-cleaner]="$VENV_DIR/bin/python3 tools/session_cleaner.py" ) # Groupes de services declare -A SVC_GROUPS=( [vwb]="vwb-backend vwb-frontend" [all]="api dashboard vwb-backend vwb-frontend" - [full]="api dashboard vwb-backend vwb-frontend monitoring agent-chat streaming worker" - [boot]="streaming worker agent-chat dashboard vwb-backend vwb-frontend" + [full]="api dashboard vwb-backend vwb-frontend monitoring agent-chat streaming worker session-cleaner" + [boot]="streaming worker agent-chat dashboard vwb-backend vwb-frontend session-cleaner" ) # ============================================================================= @@ -353,7 +356,7 @@ do_install() { # VĂ©rifier que les fichiers existent local missing=false - for unit in rpa-streaming.service rpa-worker.service rpa-agent-chat.service rpa-dashboard.service rpa-vwb-backend.service rpa-vwb-frontend.service rpa-vision.target; do + for unit in rpa-streaming.service rpa-worker.service rpa-agent-chat.service rpa-dashboard.service rpa-vwb-backend.service rpa-vwb-frontend.service rpa-session-cleaner.service rpa-vision.target; do if [ -f "$SYSTEMD_DIR/$unit" ]; then echo -e " ${GREEN}OK${NC} $unit" else @@ -397,7 +400,7 @@ do_enable() { echo -e "${CYAN}${BOLD}Activation du demarrage automatique au boot...${NC}" systemctl --user daemon-reload systemctl --user enable rpa-vision.target - for unit in rpa-streaming.service rpa-worker.service rpa-agent-chat.service rpa-dashboard.service rpa-vwb-backend.service rpa-vwb-frontend.service; do + for unit in rpa-streaming.service rpa-worker.service rpa-agent-chat.service rpa-dashboard.service rpa-vwb-backend.service rpa-vwb-frontend.service rpa-session-cleaner.service; do systemctl --user enable "$unit" 2>/dev/null echo -e " ${GREEN}OK${NC} $unit" done @@ -408,7 +411,7 @@ do_enable() { do_disable() { echo -e "${YELLOW}${BOLD}Desactivation du demarrage automatique...${NC}" systemctl --user disable rpa-vision.target 2>/dev/null || true - for unit in rpa-streaming.service rpa-worker.service rpa-agent-chat.service rpa-dashboard.service rpa-vwb-backend.service rpa-vwb-frontend.service; do + for unit in rpa-streaming.service rpa-worker.service rpa-agent-chat.service rpa-dashboard.service rpa-vwb-backend.service rpa-vwb-frontend.service rpa-session-cleaner.service; do systemctl --user disable "$unit" 2>/dev/null || true echo -e " ${GREEN}OK${NC} $unit" done @@ -438,11 +441,12 @@ show_help() { echo " dashboard Web Dashboard (port 5001)" echo " vwb-backend VWB Backend Flask (port 5002)" echo " vwb-frontend VWB Frontend Vite (port 3002)" + echo " session-cleaner Session Cleaner (port 5006)" echo " api API Server (port 8000) [legacy uniquement]" echo " monitoring Monitoring (port 5003) [legacy uniquement]" echo "" echo -e "${BOLD}Groupes:${NC}" - echo " boot Services systemd (streaming, worker, chat, dashboard, vwb)" + echo " boot Services systemd (streaming, worker, chat, dashboard, vwb, session-cleaner)" echo " vwb VWB backend + frontend" echo " all Core (api, dashboard, vwb)" echo " full Tous les services" @@ -451,8 +455,8 @@ show_help() { echo " --legacy Forcer le mode legacy (PID files au lieu de systemd)" echo "" echo -e "${BOLD}Exemples:${NC}" - echo " $0 start boot # Demarrer les 5 services systemd" - echo " $0 stop boot # Arreter les 5 services systemd" + echo " $0 start boot # Demarrer les services systemd" + echo " $0 stop boot # Arreter les services systemd" echo " $0 restart streaming # Redemarrer le streaming server" echo " $0 logs streaming -f # Suivre les logs du streaming" echo " $0 status # Voir l'etat de tout" diff --git a/tests/unit/test_dashboard_auth_p0a.py b/tests/unit/test_dashboard_auth_p0a.py new file mode 100644 index 000000000..468c39c76 --- /dev/null +++ b/tests/unit/test_dashboard_auth_p0a.py @@ -0,0 +1,160 @@ +""" +Tests du Fix P0-A : authentification HTTP Basic sur le dashboard Flask (port 5001). + +Avant ce fix, 71 endpoints Ă©taient exposĂ©s sans auth. Le middleware +`_dashboard_basic_auth_middleware` ajoute un challenge 401 sur toutes les +routes HTTP sauf les healthchecks publics. + +ContrĂŽles : +- Sans Authorization header → 401 avec WWW-Authenticate +- Avec mauvais credentials → 401 +- Avec bons credentials → passage normal (200) +- /health, /healthz, /api/health restent publics (monitoring externe) +- Mode TESTING sans DASHBOARD_AUTH_ENABLED → bypass (compat tests existants) +- DASHBOARD_AUTH_DISABLED=true → bypass global (dev local) +""" +from __future__ import annotations + +import base64 +import importlib +import sys +from pathlib import Path + +import pytest + +# Ajouter le rĂ©pertoire racine au path +sys.path.insert(0, str(Path(__file__).parent.parent.parent)) + + +@pytest.fixture +def auth_enabled_client(monkeypatch): + """Client Flask avec l'auth activĂ©e (TESTING + DASHBOARD_AUTH_ENABLED). + + On recharge le module pour forcer la relecture des variables d'env. + """ + monkeypatch.setenv("DASHBOARD_USER", "lea") + monkeypatch.setenv("DASHBOARD_PASSWORD", "secret-test-pwd") + monkeypatch.delenv("DASHBOARD_AUTH_DISABLED", raising=False) + + # Recharger le module pour que les constantes soient relues + if "web_dashboard.app" in sys.modules: + importlib.reload(sys.modules["web_dashboard.app"]) + from web_dashboard.app import app + + app.config["TESTING"] = True + app.config["DASHBOARD_AUTH_ENABLED"] = True + with app.test_client() as c: + yield c + + +@pytest.fixture +def auth_disabled_client(monkeypatch): + """Client Flask avec l'auth dĂ©sactivĂ©e (bypass global).""" + monkeypatch.setenv("DASHBOARD_AUTH_DISABLED", "true") + if "web_dashboard.app" in sys.modules: + importlib.reload(sys.modules["web_dashboard.app"]) + from web_dashboard.app import app + + app.config["TESTING"] = True + with app.test_client() as c: + yield c + + +def _basic_auth_header(user: str, password: str) -> str: + token = base64.b64encode(f"{user}:{password}".encode()).decode() + return f"Basic {token}" + + +class TestDashboardAuthP0A: + """Fix P0-A : auth HTTP Basic obligatoire sur le dashboard.""" + + def test_no_auth_header_returns_401(self, auth_enabled_client): + """Sans header Authorization → 401 + challenge WWW-Authenticate.""" + resp = auth_enabled_client.get("/api/system/status") + assert resp.status_code == 401 + assert "WWW-Authenticate" in resp.headers + assert "Basic" in resp.headers["WWW-Authenticate"] + + def test_wrong_password_returns_401(self, auth_enabled_client): + """Mauvais mot de passe → 401.""" + resp = auth_enabled_client.get( + "/api/system/status", + headers={"Authorization": _basic_auth_header("lea", "wrong")}, + ) + assert resp.status_code == 401 + + def test_wrong_user_returns_401(self, auth_enabled_client): + """Mauvais utilisateur → 401.""" + resp = auth_enabled_client.get( + "/api/system/status", + headers={"Authorization": _basic_auth_header("intruder", "secret-test-pwd")}, + ) + assert resp.status_code == 401 + + def test_malformed_header_returns_401(self, auth_enabled_client): + """Header mal formĂ© (pas de Basic) → 401.""" + resp = auth_enabled_client.get( + "/api/system/status", + headers={"Authorization": "Bearer tototoken"}, + ) + assert resp.status_code == 401 + + def test_valid_credentials_pass(self, auth_enabled_client): + """Bons credentials → 200.""" + resp = auth_enabled_client.get( + "/api/system/status", + headers={"Authorization": _basic_auth_header("lea", "secret-test-pwd")}, + ) + assert resp.status_code == 200 + + def test_healthz_public(self, auth_enabled_client): + """/healthz reste public (systemd healthcheck).""" + resp = auth_enabled_client.get("/healthz") + assert resp.status_code == 200 + + def test_health_public(self, auth_enabled_client): + """/health reste public (monitoring externe).""" + resp = auth_enabled_client.get("/health") + assert resp.status_code == 200 + + def test_api_health_public(self, auth_enabled_client): + """/api/health reste public (NPM reverse proxy).""" + resp = auth_enabled_client.get("/api/health") + assert resp.status_code == 200 + + def test_auth_disabled_bypass(self, auth_disabled_client): + """DASHBOARD_AUTH_DISABLED=true → pas d'auth requise.""" + resp = auth_disabled_client.get("/api/system/status") + assert resp.status_code == 200 + + def test_config_endpoint_requires_auth(self, auth_enabled_client): + """L'endpoint sensible /api/config exige l'auth.""" + resp = auth_enabled_client.get("/api/config") + assert resp.status_code == 401 + + def test_services_endpoint_requires_auth(self, auth_enabled_client): + """L'endpoint sensible /api/services exige l'auth.""" + resp = auth_enabled_client.get("/api/services") + assert resp.status_code == 401 + + def test_services_start_all_requires_auth(self, auth_enabled_client): + """Un endpoint POST destructeur exige l'auth.""" + resp = auth_enabled_client.post("/api/services/start-all") + assert resp.status_code == 401 + + def test_index_page_requires_auth(self, auth_enabled_client): + """MĂȘme la page HTML d'accueil exige l'auth (pas de leak cĂŽtĂ© public).""" + resp = auth_enabled_client.get("/") + assert resp.status_code == 401 + + +@pytest.fixture(autouse=True) +def _restore_module(monkeypatch): + """Recharge web_dashboard.app aprĂšs chaque test pour que les autres + tests (TestDashboardRoutes sans auth explicite) continuent de passer.""" + yield + monkeypatch.delenv("DASHBOARD_AUTH_DISABLED", raising=False) + monkeypatch.delenv("DASHBOARD_USER", raising=False) + monkeypatch.delenv("DASHBOARD_PASSWORD", raising=False) + if "web_dashboard.app" in sys.modules: + importlib.reload(sys.modules["web_dashboard.app"]) diff --git a/web_dashboard/app.py b/web_dashboard/app.py index 008c0c9d1..9746f5e57 100644 --- a/web_dashboard/app.py +++ b/web_dashboard/app.py @@ -7,8 +7,16 @@ FonctionnalitĂ©s: - Gestion des workflows - Visualisation des sessions et screenshots - Graphiques de performance + +SĂ©curitĂ© (Fix P0-A) : +- HTTP Basic Auth sur tous les endpoints (middleware before_request). +- Credentials via DASHBOARD_USER / DASHBOARD_PASSWORD. +- Exceptions : /health, /healthz, /api/health (monitoring externe). +- DĂ©sactivation auth en dev local : DASHBOARD_AUTH_DISABLED=true """ +import base64 +import hmac import os import sys import json @@ -41,9 +49,125 @@ app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'dev-key-change-in-production socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading') +# ============================================================================= +# Fix P0-A : HTTP Basic Auth sur le dashboard (port 5001) +# ============================================================================= +# Avant ce fix, 71 endpoints Ă©taient exposĂ©s sans authentification. +# On ajoute un middleware Flask (before_request) qui exige un header +# Authorization: Basic . Les credentials sont pris dans l'environnement. +# +# Chemins publics (pas de challenge) : healthcheck uniquement — ils servent au +# monitoring externe (Prometheus, systemd, k8s, NPM reverse proxy). +# +# Pour dĂ©sactiver l'auth (dev local, tests) : DASHBOARD_AUTH_DISABLED=true. +# Les tests unitaires dĂ©finissent cette variable via un flag Flask config. + +_DASHBOARD_USER = os.getenv("DASHBOARD_USER", "lea").strip() +_DASHBOARD_PASSWORD = os.getenv("DASHBOARD_PASSWORD", "").strip() +_DASHBOARD_AUTH_DISABLED = os.getenv("DASHBOARD_AUTH_DISABLED", "").lower() in ( + "1", "true", "yes", +) + +# Si pas de password dĂ©fini en env ET auth pas explicitement dĂ©sactivĂ©e → +# on utilise un mot de passe par dĂ©faut "safe" (long, random-ish) ET on log +# un WARNING trĂšs visible au dĂ©marrage pour forcer Dom Ă  le configurer +# avant un dĂ©ploiement prod. On ne veut surtout pas gĂ©nĂ©rer un mot de passe +# alĂ©atoire Ă  chaque boot (mĂȘme problĂšme que l'API token auto-gĂ©nĂ©rĂ©). +if not _DASHBOARD_PASSWORD and not _DASHBOARD_AUTH_DISABLED: + _DASHBOARD_PASSWORD = "changeme-dashboard-Medecin2026!" + api_logger.warning( + "[SÉCURITÉ] DASHBOARD_PASSWORD non dĂ©fini en env — utilisation d'un " + "mot de passe par dĂ©faut temporaire. DÉFINIR DASHBOARD_PASSWORD " + "AVANT TOUT DÉPLOIEMENT (identifiant : DASHBOARD_USER)." + ) + +# Paths publics (pas d'auth, pour healthchecks externes) +_PUBLIC_DASHBOARD_PATHS = { + "/health", + "/healthz", + "/api/health", +} + + +def _dashboard_auth_ok(header_value: str) -> bool: + """Valide le header Authorization Basic. Comparaison constant-time.""" + if not header_value or not header_value.lower().startswith("basic "): + return False + try: + decoded = base64.b64decode(header_value[6:].strip()).decode("utf-8") + except (ValueError, UnicodeDecodeError): + return False + if ":" not in decoded: + return False + user, _, password = decoded.partition(":") + # Comparaison constant-time pour Ă©viter les timing attacks. + user_ok = hmac.compare_digest(user, _DASHBOARD_USER) + pwd_ok = hmac.compare_digest(password, _DASHBOARD_PASSWORD) + return user_ok and pwd_ok + + +@app.before_request +def _dashboard_basic_auth_middleware(): + """Middleware d'auth HTTP Basic sur tous les endpoints HTTP du dashboard. + + - Bypass complet si DASHBOARD_AUTH_DISABLED=true (dev/tests). + - Bypass complet si app.config['TESTING'] (pytest) et qu'aucun credential + n'est passĂ© : les tests existants du dashboard doivent continuer de + passer sans retoucher chaque fixture. + - Paths dans _PUBLIC_DASHBOARD_PATHS : toujours publics (healthchecks). + - Sinon : header Authorization: Basic obligatoire. + + Note WebSocket : Flask-SocketIO utilise son propre canal pour le handshake. + Le before_request ci-dessus s'applique Ă  la requĂȘte HTTP de l'upgrade + (compatible mode threading). Les sockets post-handshake ne passent pas par + Flask, c'est acceptable pour un MVP (le client doit avoir passĂ© l'auth HTTP). + """ + # Dev / tests : bypass total + if _DASHBOARD_AUTH_DISABLED: + return None + if app.config.get("TESTING") and not app.config.get("DASHBOARD_AUTH_ENABLED"): + return None + + path = request.path or "/" + if path in _PUBLIC_DASHBOARD_PATHS: + return None + + header_value = request.headers.get("Authorization", "") + if _dashboard_auth_ok(header_value): + return None + + # Pas authentifiĂ© — challenge 401 avec WWW-Authenticate + return Response( + '{"error": "authentication required"}', + status=401, + mimetype="application/json", + headers={"WWW-Authenticate": 'Basic realm="RPA Vision V3 Dashboard"'}, + ) + + @app.get('/healthz') def healthz(): - """Healthcheck minimal (systemd/k8s).""" + """Healthcheck minimal (systemd/k8s). Public — pas d'auth.""" + return jsonify({ + 'status': 'ok', + 'service': 'rpa-vision-v3-dashboard', + 'timestamp': datetime.now().isoformat(), + }) + + +@app.get('/api/health') +def api_health(): + """Healthcheck JSON public — pas d'auth (monitoring externe).""" + return jsonify({ + 'status': 'ok', + 'service': 'rpa-vision-v3-dashboard', + 'timestamp': datetime.now().isoformat(), + }) + + +@app.get('/health') +def health(): + """Healthcheck public — pas d'auth (NPM reverse proxy).""" return jsonify({ 'status': 'ok', 'service': 'rpa-vision-v3-dashboard', @@ -532,21 +656,29 @@ def get_session(session_id): @app.route('/api/agent/sessions//screenshot/') def get_screenshot(session_id, filename): - """RĂ©cupĂšre un screenshot.""" + """RĂ©cupĂšre un screenshot. + + Par dĂ©faut, si une version floutĂ©e (PII) `_blurred.png` existe Ă  cĂŽtĂ© + du fichier demandĂ©, elle est servie Ă  la place (affichage conforme RGPD). + Pour obtenir la version brute, passer `?raw=1` (rĂ©servĂ© aux endpoints + d'entraĂźnement/grounding, Ă  protĂ©ger par auth). + """ try: + want_raw = request.args.get('raw', '0') in ('1', 'true', 'yes') + # Chercher le screenshot dans tous les rĂ©pertoires screenshot_path = None - + for dir_path in SESSIONS_PATH.iterdir(): if not dir_path.is_dir(): continue - + # Chercher dans diffĂ©rents emplacements possibles possible_paths = [ dir_path / "screenshots" / filename, dir_path / "shots" / filename, ] - + # Chercher aussi dans les sous-rĂ©pertoires for subdir in dir_path.iterdir(): if subdir.is_dir(): @@ -554,18 +686,26 @@ def get_screenshot(session_id, filename): subdir / "screenshots" / filename, subdir / "shots" / filename, ]) - + for path in possible_paths: if path.exists(): screenshot_path = path break - + if screenshot_path: break - + if not screenshot_path: return jsonify({'error': 'Screenshot non trouvĂ©'}), 404 - + + # PrĂ©fĂ©rer la version floutĂ©e si dispo et si l'appelant ne demande pas le brut + if not want_raw and "_blurred" not in screenshot_path.stem: + blurred_candidate = screenshot_path.with_name( + f"{screenshot_path.stem}_blurred{screenshot_path.suffix}" + ) + if blurred_candidate.is_file(): + screenshot_path = blurred_candidate + return send_file(screenshot_path, mimetype='image/png') except Exception as e: return jsonify({'error': str(e)}), 500 @@ -1630,6 +1770,14 @@ SERVICES_CONFIG = { "url": "http://localhost:5005", "icon": "📡" }, + "session_cleaner": { + "name": "Session Cleaner", + "description": "Nettoyage de sessions avant replay (dĂ©pend du Streaming Server)", + "port": 5006, + "start_cmd": "cd {base} && {base}/.venv/bin/python3 tools/session_cleaner.py", + "url": "http://localhost:5006", + "icon": "đŸ§č" + }, "web_dashboard": { "name": "Dashboard (ce service)", "description": "Panneau de contrĂŽle RPA Vision V3", diff --git a/web_dashboard/templates/index.html b/web_dashboard/templates/index.html index 721592ed4..0674d78dc 100644 --- a/web_dashboard/templates/index.html +++ b/web_dashboard/templates/index.html @@ -69,6 +69,7 @@
🔧 Corrections
🧠 Apprentissage
🔧 Configuration
+
đŸ§č Nettoyage
@@ -783,6 +784,14 @@
+
+ +
+ + + +
+
@@ -834,37 +843,6 @@
- -
-

đŸ‘ïž Detection Visuelle

-
-
- - -
-
- - - 0.3 -
-
- - - 0.3 -
-
- -
-
-
-

đŸ’Ÿ Base de Donnees

@@ -896,9 +874,7 @@
- -

🔒 Securite

@@ -963,6 +939,46 @@
+ + +
+
+
+
+

đŸ§č Nettoyage de sessions avant replay

+

Visualisez les sessions, supprimez les clics parasites et regénérez un replay propre (Session Cleaner, port 5006)

+
+
+ + ↗ Ouvrir dans un onglet +
+
+
+ + + + + +
+ +
+