feat(ui): une saisie réussie se voit, et le trafic est vérifié derrière
Un code accepté ne se manifestait que par une ligne de journal, écrite pendant que la fenêtre de saisie se refermait : rien à l'écran ne distinguait une validation d'un abandon, et l'échéance obtenue — ce que l'utilisateur venait précisément de demander — restait invisible. Une confirmation l'annonce désormais, avec sa date de fin et le temps restant. Le serveur peut par ailleurs enregistrer l'autorisation sans que le trafic passe pour autant : la règle de pare-feu peut ne pas s'appliquer, et le réseau reste alors muet malgré un code accepté. Le client le constate maintenant en fond, en observant ce qui passe là où le sondage se contente de demander au serveur ce qu'il a enregistré. `remote_network_reachable` devient publique pour cela — l'interface l'appelle, et un nom privé traversant la frontière du module aurait menti sur sa portée. Corrige au passage un refus qui, au lieu de s'afficher, interrompait le traitement de la réponse. Contre un serveur antérieur à la v0.3.2, qui exige encore l'identifiant que ce poste ne transmet plus, `POST /api/session` répond 422 ; le `detail` d'une erreur de validation FastAPI est une liste, non une phrase, et elle atterrissait telle quelle dans un champ que l'interface passe ensuite à Qt. Les deux gestionnaires `HTTPError` extrayaient ce champ par les mêmes quatre lignes : elles deviennent `_error_detail`, qui ne retient qu'une chaîne et rattrape en plus le corps JSON sans objet, sur lequel `.get` levait une `AttributeError` non capturée. Le 422 dit désormais quoi faire — mettre le serveur à jour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+54
-1
@@ -88,6 +88,10 @@ class MainWindow(QMainWindow):
|
||||
# déclenché sans invite est déjà parti.
|
||||
self._probe_worker: ValueWorker | None = None
|
||||
self._probe_prompt = False
|
||||
# Vérification, après un code accepté, que le trafic atteint bien le
|
||||
# réseau distant. Distincte du sondage : celui-ci demande au serveur
|
||||
# ce qu'il a enregistré, celle-là observe ce qui passe réellement.
|
||||
self._reach_worker: ValueWorker | None = None
|
||||
# Date du dernier sondage abouti, et nombre de sondages consécutifs
|
||||
# ayant trouvé fermé un accès qu'on croyait ouvert (cf.
|
||||
# `_access_stale` et `_on_probe_reply`). `_access_message` évite de
|
||||
@@ -1369,6 +1373,7 @@ class MainWindow(QMainWindow):
|
||||
self._access_denials = 0
|
||||
self._probe_retried = False
|
||||
self._add_log(message, "success")
|
||||
self._announce_access_granted()
|
||||
else:
|
||||
# Abandon : l'état d'accès n'est pas touché. Il l'était, et à tort
|
||||
# — un sondage abouti pendant que la fenêtre était ouverte (les
|
||||
@@ -1383,6 +1388,53 @@ class MainWindow(QMainWindow):
|
||||
"accès distant fermé", "warning")
|
||||
self._refresh_status()
|
||||
|
||||
def _announce_access_granted(self):
|
||||
"""Confirme la validation, en disant jusqu'à quand l'accès tient.
|
||||
|
||||
Le message du serveur partait jusqu'ici dans le seul journal, pendant
|
||||
que la fenêtre se refermait : rien ne distinguait à l'écran un code
|
||||
accepté d'un code ignoré, et la durée obtenue restait invisible alors
|
||||
que c'est précisément ce que l'utilisateur vient de demander.
|
||||
"""
|
||||
if self._access_until is not None:
|
||||
fin = self._access_until.astimezone()
|
||||
texte = (f"Code accepté.\n\nAccès au réseau distant ouvert "
|
||||
f"jusqu'au {fin:%d/%m/%Y à %H:%M} "
|
||||
f"(soit {self._remaining_text()}).")
|
||||
else:
|
||||
texte = ("Code accepté.\n\nAccès au réseau distant ouvert. Le "
|
||||
"serveur n'a pas annoncé d'échéance.")
|
||||
QMessageBox.information(self, "Accès ouvert", texte)
|
||||
# Le serveur peut avoir enregistré l'autorisation sans que le trafic
|
||||
# passe pour autant — règle de pare-feu non appliquée, par exemple.
|
||||
# Le constater vaut mieux que laisser l'utilisateur découvrir seul un
|
||||
# réseau resté muet après un code pourtant accepté.
|
||||
self._verify_access_effective()
|
||||
|
||||
def _verify_access_effective(self):
|
||||
"""Vérifie en fond que le trafic atteint réellement le réseau distant."""
|
||||
if self._reach_worker is not None:
|
||||
return
|
||||
self._reach_worker = ValueWorker(vpn_session.remote_network_reachable,
|
||||
self._cfg, parent=self)
|
||||
self._reach_worker.done.connect(self._on_reach_reply)
|
||||
self._reach_worker.finished.connect(self._reach_worker.deleteLater)
|
||||
self._reach_worker.start()
|
||||
|
||||
def _on_reach_reply(self, joignable: bool):
|
||||
self._reach_worker = None
|
||||
cible = (self._cfg.wg.get("split_dns_server") or "").strip()
|
||||
if not cible:
|
||||
return # rien à sonder, donc rien à conclure
|
||||
if joignable:
|
||||
self._add_log(f"Trafic vers le réseau distant confirmé ({cible})",
|
||||
"success")
|
||||
return
|
||||
self._add_log(
|
||||
f"Code accepté, mais {cible} reste injoignable : le serveur a "
|
||||
"enregistré l'autorisation sans que le trafic passe. Prévenez "
|
||||
"l'administrateur.", "warning")
|
||||
|
||||
def _begin_task(self, label: str):
|
||||
self._connecting = True
|
||||
self._btn_connect.setEnabled(False)
|
||||
@@ -1505,7 +1557,7 @@ class MainWindow(QMainWindow):
|
||||
# Laisser les threads en cours se terminer avant de démonter le tunnel :
|
||||
# une connexion et une déconnexion simultanées laisseraient un état mixte.
|
||||
for worker in (self._worker, self._ping_worker, self._probe_worker,
|
||||
self._update_worker):
|
||||
self._reach_worker, self._update_worker):
|
||||
try:
|
||||
if worker is not None and worker.isRunning():
|
||||
worker.wait(15000)
|
||||
@@ -1520,6 +1572,7 @@ class MainWindow(QMainWindow):
|
||||
self._worker = None
|
||||
self._ping_worker = None
|
||||
self._probe_worker = None
|
||||
self._reach_worker = None
|
||||
self._update_worker = None
|
||||
|
||||
if self._session_id is not None:
|
||||
|
||||
Reference in New Issue
Block a user