fix(ui): la saisie du code ne dépend plus du champ Identifiant VPN (v0.8.4)
Le bouton et l'entrée de systray étaient conditionnés à un champ vide par défaut depuis son introduction en v0.8.0 : un poste tenu en quarantaine par le serveur n'affichait donc aucun moyen d'en sortir. La saisie ne dépend plus que de l'état du tunnel ; la fenêtre demande l'identifiant quand il manque et ne l'enregistre qu'une fois le serveur l'ayant accepté. L'état « Non géré par le serveur » disparaît au passage : le client l'affirmait à partir de sa seule configuration locale, sans rien vérifier. « Non vérifié » est la vérité tant qu'aucun code n'a été validé. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@ class AccessCodeDialog(QDialog):
|
||||
self._cfg = cfg
|
||||
self._worker: TaskWorker | None = None
|
||||
self._message = ""
|
||||
self._username = ""
|
||||
self.setWindowTitle("WGSecure — Code d'accès")
|
||||
self.setFixedWidth(360)
|
||||
self.setModal(True)
|
||||
@@ -83,6 +84,19 @@ class AccessCodeDialog(QDialog):
|
||||
sep.setStyleSheet(f"color: {theme.BORDER_SOFT};")
|
||||
layout.addWidget(sep)
|
||||
|
||||
# L'identifiant n'est demandé que s'il manque encore. Le renvoyer au
|
||||
# panneau Administrateur à cet instant précis — tunnel monté, accès
|
||||
# fermé, code en main — était un cul-de-sac : il est saisi ici, puis
|
||||
# enregistré dès que le serveur l'a reconnu.
|
||||
self._user_input: QLineEdit | None = None
|
||||
if not (self._cfg.wg.get("vpn_username") or "").strip():
|
||||
self._user_input = QLineEdit()
|
||||
self._user_input.setPlaceholderText("Identifiant VPN (ex. TestB)")
|
||||
self._user_input.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self._user_input.setStyleSheet(self._input_css(theme.BORDER_SOFT))
|
||||
self._user_input.returnPressed.connect(self._submit)
|
||||
layout.addWidget(self._user_input)
|
||||
|
||||
self._code_input = QLineEdit()
|
||||
self._code_input.setPlaceholderText("000 000")
|
||||
self._code_input.setMaxLength(7)
|
||||
@@ -170,10 +184,18 @@ class AccessCodeDialog(QDialog):
|
||||
self._fail("Entrez exactement 6 chiffres.")
|
||||
return
|
||||
|
||||
self._username = ""
|
||||
if self._user_input is not None:
|
||||
self._username = self._user_input.text().strip()
|
||||
if not self._username:
|
||||
self._error_label.setText("Indiquez votre identifiant VPN.")
|
||||
self._user_input.setStyleSheet(self._input_css(theme.FAIL_TEXT))
|
||||
return
|
||||
|
||||
self._error_label.setText("")
|
||||
self._set_busy(True)
|
||||
self._worker = TaskWorker(vpn_session.authenticate, self._cfg, raw,
|
||||
tag="auth", parent=self)
|
||||
self._username, tag="auth", parent=self)
|
||||
self._worker.done.connect(self._on_reply)
|
||||
self._worker.finished.connect(self._worker.deleteLater)
|
||||
self._worker.start()
|
||||
@@ -182,6 +204,11 @@ class AccessCodeDialog(QDialog):
|
||||
self._worker = None
|
||||
self._message = message
|
||||
if ok:
|
||||
# Enregistré seulement maintenant : un identifiant que le serveur
|
||||
# a refusé n'a rien à faire dans la configuration.
|
||||
if self._username:
|
||||
self._cfg.set("wg", "vpn_username", self._username)
|
||||
self._cfg.save()
|
||||
self.accept()
|
||||
return
|
||||
self._set_busy(False)
|
||||
|
||||
+51
-25
@@ -247,14 +247,10 @@ class MainWindow(QMainWindow):
|
||||
# rien ajouter aux installations sans authentification serveur.
|
||||
self._btn_access = QPushButton("🔐 Saisir le code d'accès")
|
||||
self._btn_access.setFixedHeight(32)
|
||||
self._btn_access.setStyleSheet(
|
||||
f"QPushButton {{ background: {theme.WARN_SOLID}; color: {theme.TEXT};"
|
||||
" font-size: 12px; font-weight: bold; border-radius: 6px; border: none; }"
|
||||
"QPushButton:hover { background: #d68910; }"
|
||||
)
|
||||
self._btn_access.clicked.connect(self.request_access_code)
|
||||
self._btn_access.setVisible(False)
|
||||
s.addWidget(self._btn_access)
|
||||
self._style_access_button(False)
|
||||
|
||||
# Repli/dépli du détail (transfert, config, bande passante, journal) :
|
||||
# rattaché à l'action qu'il détaille, pas à l'en-tête.
|
||||
@@ -370,6 +366,18 @@ class MainWindow(QMainWindow):
|
||||
)
|
||||
self._sync_window_height()
|
||||
|
||||
def _style_access_button(self, expected: bool):
|
||||
"""Met le bouton en avant seulement quand un code est réellement dû."""
|
||||
bg, hover = ((theme.WARN_SOLID, "#d68910") if expected
|
||||
else (theme.BG_RAISED, theme.ACCENT_HOVER))
|
||||
weight = "bold" if expected else "normal"
|
||||
self._btn_access.setStyleSheet(
|
||||
f"QPushButton {{ background: {bg}; color: {theme.TEXT};"
|
||||
f" font-size: 12px; font-weight: {weight}; border-radius: 6px;"
|
||||
" border: none; }"
|
||||
f"QPushButton:hover {{ background: {hover}; }}"
|
||||
)
|
||||
|
||||
def _sync_window_height(self):
|
||||
"""Réajuste la hauteur fixe de la fenêtre à son contenu visible.
|
||||
|
||||
@@ -591,12 +599,19 @@ class MainWindow(QMainWindow):
|
||||
tant que le serveur n'a pas levé la quarantaine. Sans cette ligne,
|
||||
un utilisateur ayant refusé la saisie du code verrait « Connecté »
|
||||
et un réseau inexplicablement muet.
|
||||
|
||||
Sans identifiant VPN, l'état est « non vérifié » et non « non géré » :
|
||||
le client n'en sait rien. Il l'affirmait, et se trompait — le serveur
|
||||
peut parfaitement tenir ce pair en quarantaine pendant que la
|
||||
configuration locale, elle, ne mentionne aucun serveur.
|
||||
"""
|
||||
if not (self._cfg.wg.get("vpn_username") or "").strip():
|
||||
return "Non géré par le serveur"
|
||||
if not self._is_connected:
|
||||
return "—"
|
||||
return "Ouvert ✓" if self._access_open else "Quarantaine (code requis)"
|
||||
if self._access_open:
|
||||
return "Ouvert ✓"
|
||||
if (self._cfg.wg.get("vpn_username") or "").strip():
|
||||
return "Quarantaine (code requis)"
|
||||
return "Non vérifié"
|
||||
|
||||
def _refresh_status(self):
|
||||
wg = self._cfg.wg
|
||||
@@ -638,6 +653,13 @@ class MainWindow(QMainWindow):
|
||||
if needed == self._btn_access.isHidden():
|
||||
self._btn_access.setVisible(needed)
|
||||
self._sync_window_height()
|
||||
if needed:
|
||||
# Un code est attendu de façon certaine quand l'identifiant VPN
|
||||
# est connu ; sinon le bouton reste disponible mais discret, pour
|
||||
# ne pas réclamer une action à une installation qui n'a pas de
|
||||
# serveur d'authentification.
|
||||
self._style_access_button(
|
||||
bool((self._cfg.wg.get("vpn_username") or "").strip()))
|
||||
|
||||
if connected:
|
||||
if self._connected_since is None:
|
||||
@@ -734,12 +756,17 @@ class MainWindow(QMainWindow):
|
||||
def access_code_needed(self) -> bool:
|
||||
"""Vrai quand une saisie de code aurait un sens ici et maintenant.
|
||||
|
||||
Trois conditions : un identifiant VPN configuré (sans quoi le serveur
|
||||
ne gère pas ce poste), un tunnel monté (l'API n'est joignable que par
|
||||
l'intérieur) et aucune autorisation en cours.
|
||||
Deux conditions seulement : un tunnel monté — l'API du serveur n'est
|
||||
joignable que par l'intérieur — et aucune autorisation en cours.
|
||||
|
||||
L'identifiant VPN n'en fait volontairement pas partie. Il en faisait
|
||||
partie, et c'était un piège : un poste bel et bien tenu en quarantaine
|
||||
par le serveur, mais dont ce champ n'avait jamais été renseigné,
|
||||
n'affichait aucun moyen de saisir son code — précisément dans la
|
||||
situation où il en avait besoin. La fenêtre de saisie demande
|
||||
l'identifiant quand il manque.
|
||||
"""
|
||||
return (bool((self._cfg.wg.get("vpn_username") or "").strip())
|
||||
and self._is_connected and not self._access_open)
|
||||
return self._is_connected and not self._access_open
|
||||
|
||||
def request_access_code(self):
|
||||
"""Ouvre la saisie du code, à la demande de l'utilisateur.
|
||||
@@ -755,21 +782,17 @@ class MainWindow(QMainWindow):
|
||||
"Le serveur d'authentification n'est joignable que depuis "
|
||||
"l'intérieur du tunnel.\nConnectez-vous d'abord.")
|
||||
return
|
||||
if not (self._cfg.wg.get("vpn_username") or "").strip():
|
||||
QMessageBox.information(
|
||||
self, "Aucun identifiant VPN",
|
||||
"Renseignez l'identifiant VPN connu du serveur dans le "
|
||||
"panneau Administrateur.")
|
||||
return
|
||||
self._authenticate_with_server(silent=False)
|
||||
self._open_access_dialog()
|
||||
|
||||
def _authenticate_with_server(self, silent: bool):
|
||||
"""Demande le code à 6 chiffres et le fait valider par le serveur.
|
||||
"""Invite spontanée à saisir le code, juste après une connexion.
|
||||
|
||||
Sans identifiant VPN configuré, l'étape est ignorée : le serveur
|
||||
peut très bien tourner en mode permissif, où un compte non enrôlé
|
||||
conserve son accès. Imposer la saisie dans ce cas bloquerait des
|
||||
installations qui fonctionnent parfaitement.
|
||||
Sans identifiant VPN configuré, l'invite est tue : le serveur peut
|
||||
très bien tourner en mode permissif, où un compte non enrôlé conserve
|
||||
son accès, et une installation sans serveur d'authentification n'a
|
||||
aucune raison de voir surgir cette fenêtre à chaque connexion. Cela
|
||||
ne vaut que pour l'invite : la saisie reste atteignable à la demande,
|
||||
par le bouton et par le systray, quelle que soit la configuration.
|
||||
"""
|
||||
if not (self._cfg.wg.get("vpn_username") or "").strip():
|
||||
return
|
||||
@@ -780,7 +803,10 @@ class MainWindow(QMainWindow):
|
||||
self._add_log("Code d'accès requis — bouton « Saisir le code "
|
||||
"d'accès »", "warning")
|
||||
return
|
||||
self._open_access_dialog()
|
||||
|
||||
def _open_access_dialog(self):
|
||||
"""Ouvre la fenêtre de saisie et journalise la réponse du serveur."""
|
||||
dlg = AccessCodeDialog(self._cfg, self)
|
||||
accepted = dlg.exec() == AccessCodeDialog.DialogCode.Accepted
|
||||
self._access_open = accepted
|
||||
|
||||
Reference in New Issue
Block a user