from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QTabWidget, QWidget, QLabel, QLineEdit, QPushButton, QSpinBox, QCheckBox, QGroupBox, QFormLayout, QTextEdit, QMessageBox, QFrame, ) from PyQt6.QtCore import Qt from PyQt6.QtGui import QFont from app.core.config import Config from app.core import wireguard as wg_core from app.core import mfa as mfa_core # CSS sombre commun à tous les onglets _TAB_CSS = """ QWidget { background-color: #1c2833; color: white; } QLabel { color: white; background: transparent; } QCheckBox { color: white; } QGroupBox { color: white; border: 1px solid rgba(255,255,255,0.2); border-radius: 5px; margin-top: 10px; font-weight: bold; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 4px; color: white; } QLineEdit { background: rgba(255,255,255,0.1); color: white; border: 1px solid rgba(255,255,255,0.25); border-radius: 4px; padding: 4px 6px; } QLineEdit:read-only { background: rgba(255,255,255,0.06); } QSpinBox { background: rgba(255,255,255,0.1); color: white; border: 1px solid rgba(255,255,255,0.25); border-radius: 4px; padding: 3px 6px; } QSpinBox::up-button, QSpinBox::down-button { background: rgba(255,255,255,0.15); } QTextEdit { background: rgba(255,255,255,0.07); color: #a8d8ea; border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; } QPushButton { background: #2471a3; color: white; border-radius: 5px; padding: 7px 12px; border: none; } QPushButton:hover { background: #1a5276; } QPushButton:checked { background: #154360; } """ class AdminWindow(QDialog): def __init__(self, config: Config, parent=None): super().__init__(parent) self._cfg = config self.setWindowTitle("WGSecure — Panneau Administrateur") self.setWindowFlags( Qt.WindowType.Dialog | Qt.WindowType.WindowTitleHint | Qt.WindowType.WindowCloseButtonHint ) self.setFixedSize(700, 560) self._build_ui() self._load_values() # ------------------------------------------------------------------ # # Helper : page sombre avec bandeau coloré # ------------------------------------------------------------------ # def _dark_page(self, banner_text: str, banner_color: str): """Retourne (page_widget, body_layout) : fond sombre + bandeau.""" w = QWidget() w.setStyleSheet(_TAB_CSS) outer = QVBoxLayout(w) outer.setContentsMargins(0, 0, 0, 0) outer.setSpacing(0) hdr = QLabel(f" {banner_text}") hdr.setFixedHeight(36) hdr.setStyleSheet( f"background: {banner_color}; color: white; font-size: 13px;" " font-weight: bold; padding-left: 8px;" ) outer.addWidget(hdr) body = QWidget() body_layout = QVBoxLayout(body) body_layout.setContentsMargins(14, 14, 14, 14) body_layout.setSpacing(10) outer.addWidget(body) return w, body_layout # ------------------------------------------------------------------ # # Construction principale # ------------------------------------------------------------------ # def _build_ui(self): layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) banner = QLabel(" WGSecure — Configuration Administrateur") banner.setFixedHeight(40) banner.setStyleSheet( "background: #17202a; color: white; font-size: 14px; font-weight: bold;" ) layout.addWidget(banner) tabs = QTabWidget() tabs.setStyleSheet(""" QTabWidget::pane { border: none; } QTabBar::tab { padding: 8px 16px; background: #1c2833; color: rgba(255,255,255,0.6); border: none; } QTabBar::tab:selected { background: #2e4057; color: white; font-weight: bold; } QTabBar::tab:hover { background: #253545; color: white; } """) tabs.addTab(self._tab_wireguard(), "WireGuard") tabs.addTab(self._tab_keys(), "Clés") tabs.addTab(self._tab_mfa(), "MFA") tabs.addTab(self._tab_test(), "Test connexion") tabs.addTab(self._tab_admin(), "Sécurité") tabs.addTab(self._tab_about(), "À propos") layout.addWidget(tabs) # Barre de boutons bar = QWidget() bar.setStyleSheet("background: #17202a;") btn_row = QHBoxLayout(bar) btn_row.setContentsMargins(12, 8, 12, 10) btn_cancel = QPushButton("Fermer sans sauvegarder") btn_cancel.setStyleSheet( "QPushButton { padding: 8px 20px; background: #2e4057; color: white;" " border-radius: 5px; border: none; }" "QPushButton:hover { background: #3d5166; }" ) btn_cancel.clicked.connect(self.reject) btn_save = QPushButton("Enregistrer et fermer") btn_save.setStyleSheet( "QPushButton { padding: 8px 20px; background: #1e8449; color: white;" " border-radius: 5px; font-weight: bold; border: none; }" "QPushButton:hover { background: #27ae60; }" ) btn_save.clicked.connect(self._save_and_close) btn_row.addStretch() btn_row.addWidget(btn_cancel) btn_row.addWidget(btn_save) layout.addWidget(bar) # ------------------------------------------------------------------ # # Onglet WireGuard # ------------------------------------------------------------------ # def _tab_wireguard(self) -> QWidget: w, lay = self._dark_page("Configuration WireGuard", "#154360") grp = QGroupBox("Serveur WireGuard") form = QFormLayout(grp) self._srv_endpoint = QLineEdit() self._srv_endpoint.setPlaceholderText("vpn.exemple.com ou 1.2.3.4") form.addRow("Adresse serveur :", self._srv_endpoint) self._srv_port = QSpinBox() self._srv_port.setRange(1, 65535) self._srv_port.setValue(51820) form.addRow("Port UDP :", self._srv_port) self._srv_pubkey = QLineEdit() self._srv_pubkey.setPlaceholderText("Clé publique du serveur (base64)") form.addRow("Clé publique serveur :", self._srv_pubkey) lay.addWidget(grp) grp2 = QGroupBox("Interface client") form2 = QFormLayout(grp2) self._iface_name = QLineEdit() self._iface_name.setPlaceholderText("wgs0") form2.addRow("Nom interface :", self._iface_name) self._client_addr = QLineEdit() self._client_addr.setPlaceholderText("10.8.0.2/24") form2.addRow("Adresse IP client :", self._client_addr) self._dns = QLineEdit() self._dns.setPlaceholderText("1.1.1.1") form2.addRow("DNS :", self._dns) self._allowed_ips = QLineEdit() self._allowed_ips.setPlaceholderText("0.0.0.0/0") form2.addRow("IPs autorisées :", self._allowed_ips) self._keepalive = QSpinBox() self._keepalive.setRange(0, 300) self._keepalive.setValue(25) form2.addRow("Keepalive (s) :", self._keepalive) lay.addWidget(grp2) lay.addStretch() return w # ------------------------------------------------------------------ # # Onglet Clés # ------------------------------------------------------------------ # def _tab_keys(self) -> QWidget: w, lay = self._dark_page("Gestion des clés Curve25519", "#1a3a52") grp = QGroupBox("Paire de clés client") form = QFormLayout(grp) self._priv_key = QLineEdit() self._priv_key.setEchoMode(QLineEdit.EchoMode.Password) self._priv_key.setPlaceholderText("(générer ou coller)") show_priv = QPushButton("Afficher") show_priv.setFixedWidth(80) show_priv.setCheckable(True) show_priv.toggled.connect( lambda checked: self._priv_key.setEchoMode( QLineEdit.EchoMode.Normal if checked else QLineEdit.EchoMode.Password ) ) priv_row = QHBoxLayout() priv_row.addWidget(self._priv_key) priv_row.addWidget(show_priv) form.addRow("Clé privée :", priv_row) self._pub_key = QLineEdit() self._pub_key.setReadOnly(True) self._pub_key.setPlaceholderText("(dérivée automatiquement)") form.addRow("Clé publique :", self._pub_key) btn_gen = QPushButton("Générer une nouvelle paire de clés") btn_gen.clicked.connect(self._generate_keys) form.addRow("", btn_gen) lay.addWidget(grp) grp2 = QGroupBox("Aperçu config WireGuard") v2 = QVBoxLayout(grp2) self._config_preview = QTextEdit() self._config_preview.setReadOnly(True) self._config_preview.setFont(QFont("Courier", 9)) self._config_preview.setFixedHeight(150) v2.addWidget(self._config_preview) btn_preview = QPushButton("Rafraîchir l'aperçu") btn_preview.clicked.connect(self._refresh_preview) v2.addWidget(btn_preview) lay.addWidget(grp2) lay.addStretch() return w # ------------------------------------------------------------------ # # Onglet MFA # ------------------------------------------------------------------ # def _tab_mfa(self) -> QWidget: w, lay = self._dark_page("Authentification Multi-Facteurs (MFA / TOTP)", "#0e6655") self._mfa_enabled_cb = QCheckBox("Activer la vérification MFA avant connexion") self._mfa_enabled_cb.setStyleSheet("color: white; font-weight: bold;") lay.addWidget(self._mfa_enabled_cb) sep = QFrame() sep.setFrameShape(QFrame.Shape.HLine) sep.setStyleSheet("color: rgba(255,255,255,0.15);") lay.addWidget(sep) grp = QGroupBox("Secret TOTP") form = QFormLayout(grp) self._mfa_secret = QLineEdit() self._mfa_secret.setPlaceholderText("(générer ou coller votre secret TOTP)") form.addRow("Secret :", self._mfa_secret) btn_gen_secret = QPushButton("Générer un nouveau secret") btn_gen_secret.clicked.connect(self._generate_mfa_secret) form.addRow("", btn_gen_secret) lay.addWidget(grp) grp2 = QGroupBox("QR Code — Scanner avec Google Authenticator / Aegis") v2 = QVBoxLayout(grp2) self._qr_label = QLabel("(générez un secret pour afficher le QR Code)") self._qr_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self._qr_label.setFixedHeight(180) v2.addWidget(self._qr_label) self._mfa_uri_label = QLabel("") self._mfa_uri_label.setWordWrap(True) self._mfa_uri_label.setStyleSheet("font-size: 9px; color: rgba(255,255,255,0.55);") v2.addWidget(self._mfa_uri_label) btn_show_qr = QPushButton("Afficher le QR Code") btn_show_qr.clicked.connect(self._show_qr) v2.addWidget(btn_show_qr) lay.addWidget(grp2) lay.addStretch() return w # ------------------------------------------------------------------ # # Onglet Test connexion # ------------------------------------------------------------------ # def _tab_test(self) -> QWidget: w, lay = self._dark_page("Test de connectivité réseau", "#1a4a7a") info = QLabel( "Teste la joignabilité du serveur WireGuard (port UDP) avant d'établir le tunnel." ) info.setWordWrap(True) info.setStyleSheet("color: white; font-size: 13px;") lay.addWidget(info) btn_test = QPushButton("Lancer le test de connexion") btn_test.setStyleSheet( "QPushButton { padding: 10px; background: #1e8449; color: white;" " border-radius: 5px; font-weight: bold; border: none; }" "QPushButton:hover { background: #27ae60; }" ) btn_test.clicked.connect(self._run_test) lay.addWidget(btn_test) self._test_result = QLabel("") self._test_result.setWordWrap(True) self._test_result.setAlignment(Qt.AlignmentFlag.AlignCenter) self._test_result.setStyleSheet( "color: white; padding: 12px; border-radius: 6px; font-size: 13px;" ) lay.addWidget(self._test_result) lay.addStretch() return w # ------------------------------------------------------------------ # # Onglet Sécurité # ------------------------------------------------------------------ # def _tab_admin(self) -> QWidget: w, lay = self._dark_page("Sécurité — Accès Administrateur", "#6e2e1c") note = QLabel( "Définissez un mot de passe pour protéger l'accès au panneau Administrateur.\n" "Laissez vide pour désactiver la protection." ) note.setWordWrap(True) note.setStyleSheet("color: rgba(255,255,255,0.8); font-size: 12px;") lay.addWidget(note) grp = QGroupBox("Mot de passe administrateur") form = QFormLayout(grp) self._admin_pw1 = QLineEdit() self._admin_pw1.setEchoMode(QLineEdit.EchoMode.Password) self._admin_pw1.setPlaceholderText("Nouveau mot de passe") form.addRow("Mot de passe :", self._admin_pw1) self._admin_pw2 = QLineEdit() self._admin_pw2.setEchoMode(QLineEdit.EchoMode.Password) self._admin_pw2.setPlaceholderText("Confirmer") form.addRow("Confirmation :", self._admin_pw2) btn_set_pw = QPushButton("Définir le mot de passe") btn_set_pw.setStyleSheet( "QPushButton { padding: 8px; background: #922b21; color: white;" " border-radius: 5px; border: none; }" "QPushButton:hover { background: #c0392b; }" ) btn_set_pw.clicked.connect(self._set_admin_password) form.addRow("", btn_set_pw) lay.addWidget(grp) lay.addStretch() return w # ------------------------------------------------------------------ # # Onglet À propos # ------------------------------------------------------------------ # def _tab_about(self) -> QWidget: w = QWidget() w.setStyleSheet(_TAB_CSS) w.setAutoFillBackground(True) layout = QVBoxLayout(w) layout.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.setSpacing(12) layout.setContentsMargins(30, 24, 30, 24) from app.ui.icons import icon_app logo_lbl = QLabel() logo_lbl.setPixmap(icon_app().pixmap(86, 86)) logo_lbl.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(logo_lbl) name_lbl = QLabel("WGSecure") name_lbl.setAlignment(Qt.AlignmentFlag.AlignCenter) f = QFont(); f.setPointSize(22); f.setBold(True) name_lbl.setFont(f) name_lbl.setStyleSheet("color: white;") layout.addWidget(name_lbl) version_lbl = QLabel("Version 0.1.0") version_lbl.setAlignment(Qt.AlignmentFlag.AlignCenter) version_lbl.setStyleSheet("color: rgba(255,255,255,0.7); font-size: 13px;") layout.addWidget(version_lbl) sep = QFrame(); sep.setFrameShape(QFrame.Shape.HLine) sep.setStyleSheet("color: rgba(255,255,255,0.15);") layout.addWidget(sep) desc_lbl = QLabel( "Interface graphique WireGuard avec surcouche MFA TOTP.\n" "Compatible Linux et Windows." ) desc_lbl.setAlignment(Qt.AlignmentFlag.AlignCenter) desc_lbl.setWordWrap(True) desc_lbl.setStyleSheet("color: white; font-size: 13px;") layout.addWidget(desc_lbl) sep2 = QFrame(); sep2.setFrameShape(QFrame.Shape.HLine) sep2.setStyleSheet("color: rgba(255,255,255,0.15);") layout.addWidget(sep2) author_lbl = QLabel("Développé par") author_lbl.setAlignment(Qt.AlignmentFlag.AlignCenter) author_lbl.setStyleSheet("color: rgba(255,255,255,0.7); font-size: 12px;") layout.addWidget(author_lbl) brand_lbl = QLabel("JT-Tools by Johnny") brand_lbl.setAlignment(Qt.AlignmentFlag.AlignCenter) fb = QFont(); fb.setPointSize(15); fb.setBold(True) brand_lbl.setFont(fb) brand_lbl.setStyleSheet("color: #5dade2;") layout.addWidget(brand_lbl) date_lbl = QLabel("Juin 2026") date_lbl.setAlignment(Qt.AlignmentFlag.AlignCenter) date_lbl.setStyleSheet("color: rgba(255,255,255,0.55); font-size: 12px;") layout.addWidget(date_lbl) layout.addStretch() return w # ------------------------------------------------------------------ # # Chargement / sauvegarde # ------------------------------------------------------------------ # def _load_values(self): wg = self._cfg.wg self._srv_endpoint.setText(wg.get("server_endpoint", "")) self._srv_port.setValue(int(wg.get("server_port", 51820))) self._srv_pubkey.setText(wg.get("server_public_key", "")) self._iface_name.setText(wg.get("interface_name", "wgs0")) self._client_addr.setText(wg.get("client_address", "10.8.0.2/24")) self._dns.setText(wg.get("dns", "1.1.1.1")) self._allowed_ips.setText(wg.get("allowed_ips", "0.0.0.0/0")) self._keepalive.setValue(int(wg.get("keepalive", 25))) self._priv_key.setText(wg.get("client_private_key", "")) self._pub_key.setText(wg.get("client_public_key", "")) self._mfa_enabled_cb.setChecked(self._cfg.mfa_enabled) self._mfa_secret.setText(self._cfg.mfa_secret) def _save_values(self): self._cfg.set("wg", "server_endpoint", self._srv_endpoint.text().strip()) self._cfg.set("wg", "server_port", self._srv_port.value()) self._cfg.set("wg", "server_public_key", self._srv_pubkey.text().strip()) self._cfg.set("wg", "interface_name", self._iface_name.text().strip() or "wgs0") self._cfg.set("wg", "client_address", self._client_addr.text().strip()) self._cfg.set("wg", "dns", self._dns.text().strip()) self._cfg.set("wg", "allowed_ips", self._allowed_ips.text().strip()) self._cfg.set("wg", "keepalive", self._keepalive.value()) self._cfg.set("wg", "client_private_key", self._priv_key.text().strip()) self._cfg.set("wg", "client_public_key", self._pub_key.text().strip()) self._cfg.mfa_enabled = self._mfa_enabled_cb.isChecked() self._cfg.mfa_secret = self._mfa_secret.text().strip() self._cfg.save() def _save_and_close(self): self._save_values() QMessageBox.information(self, "Sauvegardé", "Configuration enregistrée avec succès.") self.accept() # ------------------------------------------------------------------ # # Actions # ------------------------------------------------------------------ # def _generate_keys(self): priv, pub = wg_core.generate_keypair() self._priv_key.setText(priv) self._pub_key.setText(pub) self._refresh_preview() QMessageBox.information( self, "Clés générées", "Nouvelle paire de clés générée.\n\n" "⚠ Copiez la clé publique sur votre serveur WireGuard." ) def _refresh_preview(self): self._save_values() self._config_preview.setPlainText(wg_core.build_client_config(self._cfg)) def _generate_mfa_secret(self): secret = mfa_core.generate_secret() self._mfa_secret.setText(secret) self._show_qr() def _show_qr(self): secret = self._mfa_secret.text().strip() if not secret: QMessageBox.warning(self, "Erreur", "Aucun secret MFA renseigné.") return pixmap = mfa_core.generate_qr_pixmap(secret) self._qr_label.setPixmap( pixmap.scaled(160, 160, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) ) self._mfa_uri_label.setText(mfa_core.get_provisioning_uri(secret)) def _run_test(self): self._save_values() self._test_result.setText("Test en cours…") self._test_result.setStyleSheet( "color: rgba(255,255,255,0.6); padding: 12px; border-radius: 6px; font-size: 13px;" ) from PyQt6.QtWidgets import QApplication QApplication.processEvents() ok, msg = wg_core.test_connection(self._cfg) if ok: self._test_result.setText(f"✓ {msg}") self._test_result.setStyleSheet( "padding: 12px; border-radius: 6px; font-size: 13px; font-weight: bold;" "background: rgba(39,174,96,0.25); color: #a9dfbf;" ) else: self._test_result.setText(f"✗ {msg}") self._test_result.setStyleSheet( "padding: 12px; border-radius: 6px; font-size: 13px; font-weight: bold;" "background: rgba(231,76,60,0.25); color: #f1948a;" ) def _set_admin_password(self): p1 = self._admin_pw1.text() p2 = self._admin_pw2.text() if p1 != p2: QMessageBox.warning(self, "Erreur", "Les mots de passe ne correspondent pas.") return self._cfg.set_admin_password(p1) self._admin_pw1.clear() self._admin_pw2.clear() msg = "Mot de passe supprimé." if not p1 else "Mot de passe administrateur mis à jour." QMessageBox.information(self, "Succès", msg)