Add Remote DNS

This commit is contained in:
2026-09-04 08:55:35 +02:00
parent 910c04f845
commit 902c177330
9 changed files with 876 additions and 599 deletions
+254 -176
View File
@@ -2,8 +2,8 @@ from __future__ import annotations
from PyQt6.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QFrame, QMessageBox, QInputDialog,
QLineEdit, QListWidget, QListWidgetItem, QApplication,
QComboBox, QSizePolicy,
QLineEdit, QListWidget, QListWidgetItem,
QComboBox,
)
from PyQt6.QtCore import Qt, QTimer, QDateTime
from PyQt6.QtGui import QFont, QCloseEvent, QColor
@@ -20,24 +20,7 @@ from app.ui.admin_window import AdminWindow
from app.ui.bw_graph import BandwidthGraph
from app.ui.history_dialog import HistoryDialog
from app.ui import icons
_DARK = "#1c2833"
_DARK2 = "#17202a"
_CARD = "#212f3d"
_LOG_COLORS = {
"success": "#a9dfbf",
"error": "#f1948a",
"warning": "#f9e79f",
"info": "#aed6f1",
}
_PING_CSS = {
"good": "background:#1e8449; color:white;",
"medium": "background:#d4ac0d; color:black;",
"bad": "background:#922b21; color:white;",
"offline": "background:#2e4057; color:#5d6d7e;",
}
from app.ui import theme
class MainWindow(QMainWindow):
@@ -58,7 +41,7 @@ class MainWindow(QMainWindow):
self._warned_foreign: set[str] = set()
self.setWindowTitle("WGSecure")
self.setFixedSize(430, 680)
self.setFixedWidth(430)
self.setWindowFlags(
Qt.WindowType.Window
| Qt.WindowType.WindowTitleHint
@@ -67,6 +50,11 @@ class MainWindow(QMainWindow):
)
self.setWindowIcon(icons.icon_app())
self._build_ui()
# Repliée par défaut : au lancement, seuls statut et action de
# connexion sont utiles — le détail ne sert qu'à qui va le chercher.
self._details_expanded = False
self._details.setVisible(False)
self._sync_window_height()
# Timers
self._status_timer = QTimer(self)
@@ -98,198 +86,287 @@ class MainWindow(QMainWindow):
# ------------------------------------------------------------------ #
# Construction UI
#
# Ordre pensé pour l'usage réel : statut et action de connexion sont
# immédiats (pas besoin de traverser la config ou le journal pour les
# atteindre) ; viennent ensuite les valeurs dynamiques (transfert,
# handshake), puis la config statique — consultée rarement une fois
# réglée — et enfin bande passante / journal, les plus techniques.
# ------------------------------------------------------------------ #
def _build_ui(self):
central = QWidget()
central.setStyleSheet(f"background: {_DARK};")
central.setStyleSheet(f"background: {theme.BG};")
self.setCentralWidget(central)
root = QVBoxLayout(central)
root.setContentsMargins(0, 0, 0, 0)
root.setSpacing(0)
# ── En-tête ──────────────────────────────────────────────────
root.addWidget(self._build_header())
root.addWidget(self._build_status_action())
root.addWidget(self._build_details())
# ── Pied de page ─────────────────────────────────────────────────
root.addWidget(self._build_footer())
# ── Section repliable : tout ce qui n'est pas statut/action ──────────
def _build_details(self) -> QWidget:
self._details = QWidget()
d = QVBoxLayout(self._details)
d.setContentsMargins(0, 0, 0, 0)
d.setSpacing(0)
d.addWidget(self._hsep())
d.addWidget(self._build_dynamic_details())
d.addWidget(self._hsep())
d.addWidget(self._build_config_summary())
d.addWidget(self._hsep())
# ── Bande passante ───────────────────────────────────────────────
d.addWidget(self._section_header("📊 Bande passante"))
self._bw_graph = BandwidthGraph()
self._bw_graph.setFixedHeight(75)
d.addWidget(self._bw_graph)
d.addWidget(self._hsep())
# ── Journal ───────────────────────────────────────────────────────
d.addWidget(self._section_header("📋 Journal", btn_text="Effacer",
btn_slot=self._clear_log))
self._log_list = QListWidget()
self._log_list.setFixedHeight(78)
self._log_list.setStyleSheet(
f"QListWidget {{ background: {theme.BG_SUNKEN}; color: {theme.ACCENT_LIGHT};"
" border: none; font-size: 10px; font-family: Courier; }"
"QListWidget::item { padding: 1px 6px; }"
)
self._log_list.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
d.addWidget(self._log_list)
d.addWidget(self._hsep())
return self._details
# ── En-tête : identité + profil ─────────────────────────────────────
def _build_header(self) -> QWidget:
header = QWidget()
header.setFixedHeight(72)
header.setStyleSheet(f"background: {_DARK2};")
header.setFixedHeight(64)
header.setStyleSheet(f"background: {theme.BG_SUNKEN};")
h = QHBoxLayout(header)
h.setContentsMargins(14, 8, 14, 8)
self._icon_label = QLabel()
self._icon_label.setFixedSize(40, 40)
self._icon_label.setFixedSize(36, 36)
self._icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
h.addWidget(self._icon_label)
title_col = QVBoxLayout()
title_col.setSpacing(1)
title_lbl = QLabel("WGSecure")
title_lbl.setStyleSheet(
"color: white; font-size: 16px; font-weight: bold; background: transparent;"
f"color: {theme.TEXT}; font-size: 16px; font-weight: bold; background: transparent;"
)
self._profile_combo = QComboBox()
self._profile_combo.setFixedHeight(22)
self._profile_combo.setStyleSheet(
"QComboBox { background: #2e4057; color: #aed6f1; border: none;"
" border-radius: 3px; font-size: 10px; padding: 0 6px; }"
"QComboBox::drop-down { border: none; }"
"QComboBox QAbstractItemView { background: #2e4057; color: white; }"
)
self._profile_combo.currentTextChanged.connect(self._on_profile_changed)
title_col.addWidget(title_lbl)
title_col.addWidget(self._profile_combo)
h.addLayout(title_col)
h.addWidget(title_lbl)
h.addStretch()
# Ping badge
profile_col = QVBoxLayout()
profile_col.setSpacing(1)
profile_lbl = QLabel("Profil actif")
profile_lbl.setAlignment(Qt.AlignmentFlag.AlignRight)
profile_lbl.setStyleSheet(
f"color: {theme.TEXT_FAINT}; font-size: 9px; background: transparent;"
)
profile_col.addWidget(profile_lbl)
self._profile_combo = QComboBox()
self._profile_combo.setFixedHeight(22)
self._profile_combo.setMinimumWidth(150)
self._profile_combo.setStyleSheet(
f"QComboBox {{ background: {theme.BG_RAISED}; color: {theme.ACCENT_LIGHT};"
" border: none; border-radius: 3px; font-size: 10px; padding: 0 6px; }"
"QComboBox::drop-down { border: none; }"
f"QComboBox QAbstractItemView {{ background: {theme.BG_RAISED}; color: {theme.TEXT}; }}"
)
self._profile_combo.currentIndexChanged.connect(self._on_profile_changed)
profile_col.addWidget(self._profile_combo)
h.addLayout(profile_col)
return header
# ── Statut + action principale ───────────────────────────────────────
def _build_status_action(self) -> QWidget:
status_area = QWidget()
status_area.setStyleSheet(f"background: {theme.BG_RAISED};")
s = QVBoxLayout(status_area)
s.setContentsMargins(14, 12, 14, 12)
s.setSpacing(8)
top_row = QHBoxLayout()
top_row.setSpacing(8)
status_col = QVBoxLayout()
status_col.setSpacing(1)
self._status_badge = QLabel("● Déconnecté")
self._status_badge.setStyleSheet(
f"color: {theme.FAIL_TEXT}; font-size: 15px; font-weight: bold; background: transparent;"
)
status_col.addWidget(self._status_badge)
self._duration_label = QLabel("")
self._duration_label.setStyleSheet(
f"color: {theme.TEXT_FAINT}; font-size: 11px; background: transparent;"
)
status_col.addWidget(self._duration_label)
top_row.addLayout(status_col)
top_row.addStretch()
# Badge de ping : santé de cette connexion, donc juste à côté de son
# statut plutôt que dans l'en-tête d'identité.
self._ping_badge = QLabel("— ms")
self._ping_badge.setFixedSize(62, 22)
self._ping_badge.setFixedSize(66, 24)
self._ping_badge.setAlignment(Qt.AlignmentFlag.AlignCenter)
self._ping_badge.setStyleSheet(
f"border-radius: 11px; font-size: 10px; font-weight: bold; {_PING_CSS['offline']}"
f"border-radius: 12px; font-size: 10px; font-weight: bold;"
f" {theme.PING_CSS['offline']}"
)
h.addWidget(self._ping_badge)
top_row.addWidget(self._ping_badge, 0, Qt.AlignmentFlag.AlignVCenter)
s.addLayout(top_row)
# Copier clé publique
self._btn_copy_key = QPushButton("📋 Clé pub.")
self._btn_copy_key.setFixedHeight(26)
self._btn_copy_key.setStyleSheet(
"QPushButton { background: #2471a3; color: white; border-radius: 5px;"
" font-size: 10px; padding: 0 8px; border: none; }"
"QPushButton:hover { background: #1a5276; }"
"QPushButton:disabled { background: #2e4057; color: #5d6d7e; }"
self._btn_connect = QPushButton("Se connecter")
self._btn_connect.setFixedHeight(44)
self._btn_connect.setStyleSheet(
f"QPushButton {{ background: {theme.OK_SOLID}; color: {theme.TEXT}; font-size: 14px;"
" font-weight: bold; border-radius: 6px; border: none; }"
"QPushButton:hover { background: #27ae60; }"
f"QPushButton:disabled {{ background: {theme.BG_RAISED}; color: {theme.TEXT_FAINT}; }}"
)
self._btn_copy_key.clicked.connect(self._copy_public_key)
h.addWidget(self._btn_copy_key)
root.addWidget(header)
self._btn_connect.clicked.connect(self._on_connect)
s.addWidget(self._btn_connect)
# ── Statut ───────────────────────────────────────────────────
status_area = QWidget()
status_area.setFixedHeight(80)
status_area.setStyleSheet(f"background: {_CARD};")
s = QVBoxLayout(status_area)
s.setContentsMargins(10, 6, 10, 6)
s.setAlignment(Qt.AlignmentFlag.AlignCenter)
self._status_badge = QLabel("● Déconnecté")
self._status_badge.setAlignment(Qt.AlignmentFlag.AlignCenter)
self._status_badge.setStyleSheet(
"color: #e74c3c; font-size: 15px; font-weight: bold; background: transparent;"
# Repli/dépli du détail (transfert, config, bande passante, journal) :
# rattaché à l'action qu'il détaille, pas à l'en-tête.
self._btn_toggle_details = QPushButton("▾ Afficher le détail")
self._btn_toggle_details.setFixedHeight(20)
self._btn_toggle_details.setStyleSheet(
f"QPushButton {{ background: transparent; color: {theme.TEXT_FAINT};"
" font-size: 10px; border: none; }"
f"QPushButton:hover {{ color: {theme.TEXT}; }}"
)
s.addWidget(self._status_badge)
self._btn_toggle_details.clicked.connect(self._toggle_details)
s.addWidget(self._btn_toggle_details)
self._duration_label = QLabel("")
self._duration_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self._duration_label.setStyleSheet(
"color: #5d6d7e; font-size: 11px; background: transparent;"
)
s.addWidget(self._duration_label)
root.addWidget(status_area)
return status_area
root.addWidget(self._hsep())
# ── Infos ─────────────────────────────────────────────────────
info_frame = QFrame()
info_frame.setStyleSheet(f"background: {_DARK};")
info_frame.setAutoFillBackground(True)
il = QVBoxLayout(info_frame)
il.setContentsMargins(14, 8, 14, 8)
il.setSpacing(5)
# ── Détails dynamiques : transfert + handshake ───────────────────────
def _build_dynamic_details(self) -> QWidget:
frame = QWidget()
frame.setStyleSheet(f"background: {theme.BG};")
dl = QHBoxLayout(frame)
dl.setContentsMargins(14, 8, 14, 8)
dl.setSpacing(16)
self._info_labels: dict[str, QLabel] = {}
for key, icon_txt, label in [
("server", "🌐", "Serveur"),
("iface", "🔌", "Interface"),
("addr", "📍", "Adresse IP"),
("mfa", "🔐", "MFA"),
("rx_tx", "↕️ ", "Transfert"),
("rx_tx", "↕️", "Transfert"),
("handshake", "🤝", "Handshake"),
]:
col = QVBoxLayout()
col.setSpacing(2)
hdr = QLabel(f"{icon_txt} {label}")
hdr.setStyleSheet(
f"color: {theme.TEXT_FAINT}; font-size: 10px; background: transparent;"
)
col.addWidget(hdr)
val = self._lbl("", color=theme.ACCENT_LIGHT, size=13)
val.setWordWrap(True)
self._info_labels[key] = val
col.addWidget(val)
dl.addLayout(col, 1)
return frame
# ── Config statique (repliée visuellement) ───────────────────────────
def _build_config_summary(self) -> QWidget:
frame = QFrame()
frame.setStyleSheet(f"background: {theme.BG};")
il = QVBoxLayout(frame)
il.setContentsMargins(14, 8, 14, 8)
il.setSpacing(4)
for key, icon_txt, label in [
("server", "🌐", "Serveur"),
("iface", "🔌", "Interface"),
("addr", "📍", "Adresse IP"),
("mfa", "🔐", "MFA"),
]:
rw = QWidget()
rw.setStyleSheet("background: transparent;")
rl = QHBoxLayout(rw)
rl.setContentsMargins(0, 0, 0, 0)
rl.setSpacing(5)
rl.addWidget(self._lbl(icon_txt, fixed=20, color="#5d6d7e", size=11))
rl.addWidget(self._lbl(f"{label} :", fixed=100, color="#5d6d7e", size=12))
val = self._lbl("", color="#aed6f1", size=12)
rl.addWidget(self._lbl(icon_txt, fixed=18, color=theme.TEXT_FAINT, size=10))
rl.addWidget(self._lbl(f"{label} :", fixed=90, color=theme.TEXT_FAINT, size=11))
val = self._lbl("", color=theme.TEXT_MUTED, size=11)
val.setWordWrap(True)
self._info_labels[key] = val
rl.addWidget(val, 1)
il.addWidget(rw)
root.addWidget(info_frame)
root.addWidget(self._hsep())
return frame
# ── Graphique bande passante ───────────────────────────────────
bw_header = self._section_header("📊 Bande passante")
root.addWidget(bw_header)
self._bw_graph = BandwidthGraph()
self._bw_graph.setFixedHeight(75)
root.addWidget(self._bw_graph)
root.addWidget(self._hsep())
# ── Journal ───────────────────────────────────────────────────
log_hdr = self._section_header("📋 Journal", btn_text="Effacer",
btn_slot=self._clear_log)
root.addWidget(log_hdr)
self._log_list = QListWidget()
self._log_list.setFixedHeight(78)
self._log_list.setStyleSheet(
f"QListWidget {{ background: {_DARK2}; color: #aed6f1;"
" border: none; font-size: 10px; font-family: Courier; }"
"QListWidget::item { padding: 1px 6px; }"
)
self._log_list.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
root.addWidget(self._log_list)
root.addWidget(self._hsep())
# ── Boutons ───────────────────────────────────────────────────
# ── Pied de page : accès secondaires ─────────────────────────────────
def _build_footer(self) -> QWidget:
btn_area = QWidget()
btn_area.setStyleSheet(f"background: {_DARK2};")
bl = QVBoxLayout(btn_area)
btn_area.setStyleSheet(f"background: {theme.BG_SUNKEN};")
bl = QHBoxLayout(btn_area)
bl.setContentsMargins(14, 10, 14, 12)
bl.setSpacing(7)
self._btn_connect = QPushButton("Se connecter")
self._btn_connect.setFixedHeight(42)
self._btn_connect.setStyleSheet(
"QPushButton { background: #1e8449; color: white; font-size: 14px;"
" font-weight: bold; border-radius: 6px; border: none; }"
"QPushButton:hover { background: #27ae60; }"
"QPushButton:disabled { background: #2e4057; color: #5d6d7e; }"
)
self._btn_connect.clicked.connect(self._on_connect)
bl.addWidget(self._btn_connect)
secondary_row = QHBoxLayout()
secondary_row.setSpacing(6)
bl.setSpacing(6)
btn_admin = QPushButton("⚙️ Administrateur")
btn_admin.setFixedHeight(30)
btn_admin.setStyleSheet(
"QPushButton { background: #2e4057; color: white; border-radius: 6px;"
f"QPushButton {{ background: {theme.BG_RAISED}; color: {theme.TEXT}; border-radius: 6px;"
" font-size: 11px; border: none; }"
"QPushButton:hover { background: #3d5166; }"
f"QPushButton:hover {{ background: {theme.ACCENT_HOVER}; }}"
)
btn_admin.clicked.connect(self._open_admin)
secondary_row.addWidget(btn_admin)
bl.addWidget(btn_admin)
btn_history = QPushButton("📊 Historique")
btn_history.setFixedHeight(30)
btn_history.setStyleSheet(
"QPushButton { background: #2e4057; color: white; border-radius: 6px;"
f"QPushButton {{ background: {theme.BG_RAISED}; color: {theme.TEXT}; border-radius: 6px;"
" font-size: 11px; border: none; }"
"QPushButton:hover { background: #3d5166; }"
f"QPushButton:hover {{ background: {theme.ACCENT_HOVER}; }}"
)
btn_history.clicked.connect(self._open_history)
secondary_row.addWidget(btn_history)
bl.addWidget(btn_history)
bl.addLayout(secondary_row)
root.addWidget(btn_area)
return btn_area
# ── Repli/dépli du détail ─────────────────────────────────────────────
def _toggle_details(self):
# `QWidget.isVisible()` reflète la visibilité effective à l'écran,
# qui dépend de toute la chaîne de parents : avant que la fenêtre
# elle-même ne soit affichée, elle vaut toujours False. Un drapeau
# explicite évite ce piège.
self._details_expanded = not self._details_expanded
self._details.setVisible(self._details_expanded)
self._btn_toggle_details.setText(
"▴ Masquer le détail" if self._details_expanded else "▾ Afficher le détail"
)
self._sync_window_height()
def _sync_window_height(self):
"""Réajuste la hauteur fixe de la fenêtre à son contenu visible.
La fenêtre reste de taille fixe (pas de redimensionnement manuel),
mais cette taille change avec le repli/dépli du détail — il faut
donc lever la contrainte précédente avant de laisser le layout
recalculer sa hauteur naturelle, puis la reverrouiller.
"""
self.setMinimumHeight(0)
self.setMaximumHeight(16777215)
self.centralWidget().adjustSize()
self.adjustSize()
self.setFixedHeight(self.sizeHint().height())
# ── Helpers UI ───────────────────────────────────────────────────────
def _lbl(self, text: str, fixed: int = 0, color: str = "white",
def _lbl(self, text: str, fixed: int = 0, color: str = theme.TEXT,
size: int = 12) -> QLabel:
lbl = QLabel(text)
lbl.setStyleSheet(
@@ -303,18 +380,18 @@ class MainWindow(QMainWindow):
f = QFrame()
f.setFrameShape(QFrame.Shape.HLine)
f.setFixedHeight(1)
f.setStyleSheet(f"background: {_DARK2};")
f.setStyleSheet(f"background: {theme.BG_SUNKEN};")
return f
def _section_header(self, title: str, btn_text: str = "",
btn_slot=None) -> QWidget:
w = QWidget()
w.setFixedHeight(24)
w.setStyleSheet(f"background: {_DARK2};")
w.setStyleSheet(f"background: {theme.BG_SUNKEN};")
hl = QHBoxLayout(w)
hl.setContentsMargins(12, 0, 12, 0)
t = QLabel(title)
t.setStyleSheet("color: #5d6d7e; font-size: 10px; font-weight: bold;"
t.setStyleSheet(f"color: {theme.TEXT_FAINT}; font-size: 10px; font-weight: bold;"
" background: transparent;")
hl.addWidget(t)
hl.addStretch()
@@ -322,9 +399,9 @@ class MainWindow(QMainWindow):
b = QPushButton(btn_text)
b.setFixedHeight(18)
b.setStyleSheet(
"QPushButton { background: transparent; color: #5d6d7e;"
f"QPushButton {{ background: transparent; color: {theme.TEXT_FAINT};"
" font-size: 9px; border: none; padding: 0 4px; }"
"QPushButton:hover { color: white; }"
f"QPushButton:hover {{ color: {theme.TEXT}; }}"
)
b.clicked.connect(btn_slot)
hl.addWidget(b)
@@ -336,16 +413,18 @@ class MainWindow(QMainWindow):
def _reload_profiles(self):
self._profile_combo.blockSignals(True)
self._profile_combo.clear()
self._profile_combo.addItem(f"{self._cfg.active_profile} (actif)")
for name in self._cfg.list_profiles():
marker = "" if name == self._cfg.active_profile else " "
self._profile_combo.addItem(f"{marker}{name}")
profiles = self._cfg.list_profiles()
active = self._cfg.active_profile
if active not in profiles:
profiles = [active] + profiles
self._profile_combo.addItems(profiles)
idx = self._profile_combo.findText(active)
if idx >= 0:
self._profile_combo.setCurrentIndex(idx)
self._profile_combo.blockSignals(False)
def _on_profile_changed(self, text: str):
if "(actif)" in text:
return
name = text.lstrip("").strip()
def _on_profile_changed(self, index: int):
name = self._profile_combo.itemText(index)
if not name or name == self._cfg.active_profile:
return
if self._cfg.load_profile(name):
@@ -372,7 +451,7 @@ class MainWindow(QMainWindow):
self._add_log_item(ts, msg, kind)
def _add_log_item(self, ts: str, msg: str, kind: str):
color = _LOG_COLORS.get(kind, "#aed6f1")
color = theme.LOG_COLORS.get(kind, theme.ACCENT_LIGHT)
item = QListWidgetItem(f"[{ts}] {msg}")
item.setForeground(QColor(color))
self._log_list.addItem(item)
@@ -446,8 +525,8 @@ class MainWindow(QMainWindow):
def _set_ping_badge(self, _ms, text: str, css_key: str):
self._ping_badge.setText(text)
self._ping_badge.setStyleSheet(
"border-radius: 11px; font-size: 10px; font-weight: bold; "
+ _PING_CSS[css_key]
"border-radius: 12px; font-size: 10px; font-weight: bold; "
+ theme.PING_CSS[css_key]
)
def _check_reconnect(self):
@@ -484,7 +563,6 @@ class MainWindow(QMainWindow):
self._info_labels["mfa"].setText(
"Activé ✓" if self._cfg.mfa_enabled else "Désactivé"
)
self._btn_copy_key.setEnabled(bool(wg.get("client_public_key", "")))
# Auto-reconnect timer
if self._cfg.get("ui", "auto_reconnect"):
@@ -509,11 +587,11 @@ class MainWindow(QMainWindow):
self._connected_since = QDateTime.currentDateTime()
self._status_badge.setText("● Connecté")
self._status_badge.setStyleSheet(
"color: #27ae60; font-size: 15px; font-weight: bold; background: transparent;"
f"color: {theme.OK_TEXT}; font-size: 15px; font-weight: bold; background: transparent;"
)
self._btn_connect.setText("Se déconnecter")
self._btn_connect.setStyleSheet(
"QPushButton { background: #922b21; color: white; font-size: 14px;"
f"QPushButton {{ background: {theme.FAIL_SOLID}; color: {theme.TEXT}; font-size: 14px;"
" font-weight: bold; border-radius: 6px; border: none; }"
"QPushButton:hover { background: #c0392b; }"
)
@@ -522,7 +600,7 @@ class MainWindow(QMainWindow):
)
self._info_labels["handshake"].setText(info.get("last_handshake", ""))
self._duration_label.setStyleSheet(
"color: #a9dfbf; font-size: 11px; background: transparent;"
f"color: {theme.OK_TEXT}; font-size: 11px; background: transparent;"
)
self.setWindowIcon(icons.icon_connected())
else:
@@ -533,18 +611,18 @@ class MainWindow(QMainWindow):
self._prev_rx = self._prev_tx = None
self._status_badge.setText("● Déconnecté")
self._status_badge.setStyleSheet(
"color: #e74c3c; font-size: 15px; font-weight: bold; background: transparent;"
f"color: {theme.FAIL_TEXT}; font-size: 15px; font-weight: bold; background: transparent;"
)
self._btn_connect.setText("Se connecter")
self._btn_connect.setStyleSheet(
"QPushButton { background: #1e8449; color: white; font-size: 14px;"
f"QPushButton {{ background: {theme.OK_SOLID}; color: {theme.TEXT}; font-size: 14px;"
" font-weight: bold; border-radius: 6px; border: none; }"
"QPushButton:hover { background: #27ae60; }"
)
self._info_labels["rx_tx"].setText("")
self._info_labels["handshake"].setText("")
self._duration_label.setStyleSheet(
"color: #5d6d7e; font-size: 11px; background: transparent;"
f"color: {theme.TEXT_FAINT}; font-size: 11px; background: transparent;"
)
self.setWindowIcon(icons.icon_disconnected())
@@ -554,12 +632,6 @@ class MainWindow(QMainWindow):
# ------------------------------------------------------------------ #
# Actions
# ------------------------------------------------------------------ #
def _copy_public_key(self):
pub = self._cfg.wg.get("client_public_key", "")
if pub:
QApplication.clipboard().setText(pub)
self._add_log("Clé publique copiée dans le presse-papiers", "info")
def _on_connect(self):
"""Bascule connexion/déconnexion. Le travail réel part dans un thread."""
if self._worker is not None:
@@ -635,6 +707,12 @@ class MainWindow(QMainWindow):
self._session_id = hist.start_session(endpoint,
self._cfg.active_profile)
self._add_log(f"Connexion établie → {endpoint}", "success")
# `connect()` ajoute une ligne d'avertissement au message de
# succès quand le split-DNS n'a pas pu être appliqué — sans
# quoi cet échec restait invisible.
base, _, extra = msg.partition("\n")
if extra:
self._add_log(extra, "warning")
QTimer.singleShot(5000, self._update_ping)
else:
self._reconnect_failures += 1