feat(dashboard): enrôlement lit l'adresse serveur depuis system_config.json

Câble l'éditeur adresses/ports du dashboard (services.streaming) vers le
RPA_SERVER_URL généré pour chaque agent Léa. Priorité config > env > défaut ;
host loopback/vide = non configuré (fallback env → pas de régression).
Permet de changer l'IP serveur (labo .45 → clinique .178) depuis l'UI sans
toucher l'env ni le code. +3 tests TDD.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-06-22 12:07:27 +02:00
parent cf81ce4c7b
commit 1d6efdb1b7
2 changed files with 72 additions and 1 deletions

View File

@@ -0,0 +1,44 @@
"""TDD — résolution de l'URL serveur d'enrôlement depuis system_config.json.
Câble l'éditeur adresses/ports du dashboard (`services.streaming`) vers le
`RPA_SERVER_URL` généré pour chaque agent Léa.
Priorité : config (`system_config.json`) > variable d'env > défaut.
Un host loopback/vide dans la config = « non configuré » → fallback env, pour
ne PAS régresser le déploiement actuel où l'URL vient de l'environnement.
"""
import os
# L'import du dashboard est fail-closed sur l'auth → escape dev/test documenté.
os.environ.setdefault("DASHBOARD_AUTH_DISABLED", "true")
from web_dashboard import app as dash # noqa: E402
def test_resolve_url_from_config_streaming_host(monkeypatch):
"""La config (host streaming édité dans l'UI) prime, même si l'env existe."""
monkeypatch.setattr(
dash, "load_system_config",
lambda: {"services": {"streaming": {"host": "192.168.1.178", "port": 5005}}},
)
monkeypatch.setenv("RPA_SERVER_URL", "http://192.168.1.45:5005/api/v1")
assert dash._resolve_public_server_url() == "http://192.168.1.178:5005/api/v1"
def test_resolve_url_loopback_in_config_falls_back_to_env(monkeypatch):
"""host=localhost dans la config = non configuré → on garde l'env (pas de régression)."""
monkeypatch.setattr(
dash, "load_system_config",
lambda: {"services": {"streaming": {"host": "localhost", "port": 5005}}},
)
monkeypatch.delenv("RPA_PUBLIC_URL", raising=False)
monkeypatch.setenv("RPA_SERVER_URL", "http://192.168.1.45:5005/api/v1")
assert dash._resolve_public_server_url() == "http://192.168.1.45:5005/api/v1"
def test_resolve_url_no_config_uses_env(monkeypatch):
"""Aucune section streaming → fallback env, normalisé en /api/v1."""
monkeypatch.setattr(dash, "load_system_config", lambda: {"services": {}})
monkeypatch.delenv("RPA_PUBLIC_URL", raising=False)
monkeypatch.setenv("RPA_SERVER_URL", "http://10.0.0.5:5005/api/v1")
assert dash._resolve_public_server_url() == "http://10.0.0.5:5005/api/v1"

View File

@@ -2248,6 +2248,33 @@ def _extract_host(url: str) -> str:
_RPA_PUBLIC_HOST = _extract_host(_RPA_PUBLIC_URL)
def _resolve_public_server_url() -> str:
"""URL publique du serveur Léa pour l'enrôlement (config.txt des agents).
Priorité :
1. system_config.json → services.streaming.{host,port} (édité dans le dashboard)
2. variables d'env RPA_PUBLIC_URL / RPA_SERVER_URL
3. défaut historique
Un host vide ou loopback dans la config = « non configuré » : on retombe sur
l'env pour ne pas régresser un déploiement qui pilote l'URL par l'environnement.
Toujours normalisée pour se terminer par /api/v1.
"""
_NON_ROUTABLE = {"", "localhost", "127.0.0.1", "0.0.0.0", "configure_me"}
try:
streaming = (load_system_config().get("services") or {}).get("streaming") or {}
host = str(streaming.get("host") or "").strip()
if host.lower() not in _NON_ROUTABLE:
port = streaming.get("port") or 5005
return _normalize_server_url(f"http://{host}:{port}")
except Exception as exc: # config illisible → ne jamais casser l'enrôlement
api_logger.warning(f"Résolution URL via system_config échouée: {exc}")
env_url = os.getenv("RPA_PUBLIC_URL") or os.getenv("RPA_SERVER_URL")
if env_url:
return _normalize_server_url(env_url)
return _normalize_server_url("https://lea.labs.laurinebazin.design")
def _fetch_fleet_agent(machine_id: str):
"""Récupère un agent depuis le serveur streaming (5005).
@@ -2284,7 +2311,7 @@ def _build_custom_config(machine_id: str, user_name: str, token: str) -> str:
Le host est extrait proprement via urlparse (sans schema/port/path).
"""
now = datetime.now().strftime("%Y-%m-%d %H:%M")
server_url = _normalize_server_url(_RPA_PUBLIC_URL)
server_url = _resolve_public_server_url()
return f"""\
# ============================================================