Update
This commit is contained in:
+398
-117
@@ -1,26 +1,52 @@
|
||||
from __future__ import annotations
|
||||
from PyQt6.QtWidgets import (
|
||||
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QLabel, QPushButton, QFrame, QMessageBox, QInputDialog,
|
||||
QLineEdit,
|
||||
QLineEdit, QListWidget, QListWidgetItem, QApplication,
|
||||
QComboBox, QSizePolicy,
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QTimer
|
||||
from PyQt6.QtGui import QFont, QCloseEvent, QPalette, QColor
|
||||
from PyQt6.QtCore import Qt, QTimer, QDateTime
|
||||
from PyQt6.QtGui import QFont, QCloseEvent, QColor
|
||||
|
||||
from app.core.config import Config
|
||||
from app.core import wireguard as wg_core
|
||||
from app.core import mfa as mfa_core
|
||||
from app.core import log as conn_log
|
||||
from app.ui.mfa_dialog import MFADialog
|
||||
from app.ui.admin_window import AdminWindow
|
||||
from app.ui.bw_graph import BandwidthGraph
|
||||
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;",
|
||||
}
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self, config: Config, parent=None):
|
||||
super().__init__(parent)
|
||||
self._cfg = config
|
||||
self._connecting = False
|
||||
self._connected_since: QDateTime | None = None
|
||||
self._prev_rx: int | None = None
|
||||
self._prev_tx: int | None = None
|
||||
self._auto_reconnect_active = False
|
||||
|
||||
self.setWindowTitle("WGSecure")
|
||||
self.setFixedSize(400, 520)
|
||||
self.setFixedSize(430, 680)
|
||||
self.setWindowFlags(
|
||||
Qt.WindowType.Window
|
||||
| Qt.WindowType.WindowTitleHint
|
||||
@@ -29,147 +55,364 @@ class MainWindow(QMainWindow):
|
||||
)
|
||||
self.setWindowIcon(icons.icon_app())
|
||||
self._build_ui()
|
||||
|
||||
# Timers
|
||||
self._status_timer = QTimer(self)
|
||||
self._status_timer.timeout.connect(self._refresh_status)
|
||||
self._status_timer.start(3000)
|
||||
|
||||
self._clock_timer = QTimer(self)
|
||||
self._clock_timer.timeout.connect(self._tick_duration)
|
||||
self._clock_timer.start(1000)
|
||||
|
||||
self._bw_timer = QTimer(self)
|
||||
self._bw_timer.timeout.connect(self._update_bw)
|
||||
self._bw_timer.start(3000)
|
||||
|
||||
self._ping_timer = QTimer(self)
|
||||
self._ping_timer.timeout.connect(self._update_ping)
|
||||
self._ping_timer.start(10000)
|
||||
|
||||
self._reconnect_timer = QTimer(self)
|
||||
self._reconnect_timer.timeout.connect(self._check_reconnect)
|
||||
|
||||
self._refresh_status()
|
||||
self._reload_log()
|
||||
self._reload_profiles()
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Construction UI
|
||||
# ------------------------------------------------------------------ #
|
||||
def _build_ui(self):
|
||||
central = QWidget()
|
||||
central.setStyleSheet(f"background: {_DARK};")
|
||||
self.setCentralWidget(central)
|
||||
layout = QVBoxLayout(central)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
root = QVBoxLayout(central)
|
||||
root.setContentsMargins(0, 0, 0, 0)
|
||||
root.setSpacing(0)
|
||||
|
||||
# -- En-tête --------------------------------------------------
|
||||
# ── En-tête ──────────────────────────────────────────────────
|
||||
header = QWidget()
|
||||
header.setFixedHeight(72)
|
||||
header.setStyleSheet("background: #2c3e50;")
|
||||
h_layout = QHBoxLayout(header)
|
||||
h_layout.setContentsMargins(16, 10, 16, 10)
|
||||
header.setStyleSheet(f"background: {_DARK2};")
|
||||
h = QHBoxLayout(header)
|
||||
h.setContentsMargins(14, 8, 14, 8)
|
||||
|
||||
self._icon_label = QLabel()
|
||||
self._icon_label.setFixedSize(44, 44)
|
||||
self._icon_label.setFixedSize(40, 40)
|
||||
self._icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
h_layout.addWidget(self._icon_label)
|
||||
h.addWidget(self._icon_label)
|
||||
|
||||
title_col = QVBoxLayout()
|
||||
title_col.setSpacing(2)
|
||||
app_title = QLabel("WGSecure")
|
||||
app_title.setStyleSheet("color: white; font-size: 17px; font-weight: bold; background: transparent;")
|
||||
version_label = QLabel("v0.1.0 — WireGuard + MFA")
|
||||
version_label.setStyleSheet("color: #95a5a6; font-size: 10px; background: transparent;")
|
||||
title_col.addWidget(app_title)
|
||||
title_col.addWidget(version_label)
|
||||
h_layout.addLayout(title_col)
|
||||
h_layout.addStretch()
|
||||
layout.addWidget(header)
|
||||
title_col.setSpacing(1)
|
||||
title_lbl = QLabel("WGSecure")
|
||||
title_lbl.setStyleSheet(
|
||||
"color: white; 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.addStretch()
|
||||
|
||||
# -- Zone statut ----------------------------------------------
|
||||
# Ping badge
|
||||
self._ping_badge = QLabel("— ms")
|
||||
self._ping_badge.setFixedSize(62, 22)
|
||||
self._ping_badge.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self._ping_badge.setStyleSheet(
|
||||
f"border-radius: 11px; font-size: 10px; font-weight: bold; {_PING_CSS['offline']}"
|
||||
)
|
||||
h.addWidget(self._ping_badge)
|
||||
|
||||
# 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_copy_key.clicked.connect(self._copy_public_key)
|
||||
h.addWidget(self._btn_copy_key)
|
||||
root.addWidget(header)
|
||||
|
||||
# ── Statut ───────────────────────────────────────────────────
|
||||
status_area = QWidget()
|
||||
status_area.setFixedHeight(90)
|
||||
status_area.setStyleSheet("background: #ecf0f1;")
|
||||
s_layout = QVBoxLayout(status_area)
|
||||
s_layout.setContentsMargins(10, 10, 10, 10)
|
||||
s_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
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 = QLabel("● Déconnecté")
|
||||
self._status_badge.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self._status_badge.setStyleSheet("color: #e74c3c; font-size: 16px; font-weight: bold; background: transparent;")
|
||||
s_layout.addWidget(self._status_badge)
|
||||
self._status_badge.setStyleSheet(
|
||||
"color: #e74c3c; font-size: 15px; font-weight: bold; background: transparent;"
|
||||
)
|
||||
s.addWidget(self._status_badge)
|
||||
|
||||
self._status_sub = QLabel("")
|
||||
self._status_sub.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self._status_sub.setStyleSheet("color: #7f8c8d; font-size: 11px; background: transparent;")
|
||||
s_layout.addWidget(self._status_sub)
|
||||
layout.addWidget(status_area)
|
||||
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)
|
||||
|
||||
# -- Séparateur -----------------------------------------------
|
||||
sep = QFrame()
|
||||
sep.setFrameShape(QFrame.Shape.HLine)
|
||||
sep.setStyleSheet("color: #bdc3c7;")
|
||||
layout.addWidget(sep)
|
||||
root.addWidget(self._hsep())
|
||||
|
||||
# -- Section infos --------------------------------------------
|
||||
# ── Infos ─────────────────────────────────────────────────────
|
||||
info_frame = QFrame()
|
||||
info_frame.setFrameShape(QFrame.Shape.NoFrame)
|
||||
info_frame.setStyleSheet("background-color: #ffffff;")
|
||||
info_frame.setStyleSheet(f"background: {_DARK};")
|
||||
info_frame.setAutoFillBackground(True)
|
||||
i_layout = QVBoxLayout(info_frame)
|
||||
i_layout.setContentsMargins(16, 12, 16, 12)
|
||||
i_layout.setSpacing(8)
|
||||
il = QVBoxLayout(info_frame)
|
||||
il.setContentsMargins(14, 8, 14, 8)
|
||||
il.setSpacing(5)
|
||||
|
||||
self._info_labels: dict[str, QLabel] = {}
|
||||
rows = [
|
||||
("server", "Serveur"),
|
||||
("iface", "Interface"),
|
||||
("addr", "Adresse IP"),
|
||||
("mfa", "MFA"),
|
||||
("rx_tx", "Transfert"),
|
||||
("handshake", "Dernier handshake"),
|
||||
]
|
||||
for key, label in rows:
|
||||
row_w = QWidget()
|
||||
row_w.setStyleSheet("background: transparent;")
|
||||
row_layout = QHBoxLayout(row_w)
|
||||
row_layout.setContentsMargins(0, 0, 0, 0)
|
||||
row_layout.setSpacing(8)
|
||||
for key, icon_txt, label in [
|
||||
("server", "🌐", "Serveur"),
|
||||
("iface", "🔌", "Interface"),
|
||||
("addr", "📍", "Adresse IP"),
|
||||
("mfa", "🔐", "MFA"),
|
||||
("rx_tx", "↕️ ", "Transfert"),
|
||||
("handshake", "🤝", "Handshake"),
|
||||
]:
|
||||
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)
|
||||
val.setWordWrap(True)
|
||||
self._info_labels[key] = val
|
||||
rl.addWidget(val, 1)
|
||||
il.addWidget(rw)
|
||||
|
||||
lbl_key = QLabel(f"{label} :")
|
||||
lbl_key.setFixedWidth(130)
|
||||
lbl_key.setStyleSheet("color: #7f8c8d; font-size: 12px; background: transparent;")
|
||||
root.addWidget(info_frame)
|
||||
root.addWidget(self._hsep())
|
||||
|
||||
lbl_val = QLabel("—")
|
||||
lbl_val.setStyleSheet("color: #2c3e50; font-size: 12px; background: transparent;")
|
||||
lbl_val.setWordWrap(True)
|
||||
self._info_labels[key] = lbl_val
|
||||
# ── Graphique bande passante ───────────────────────────────────
|
||||
bw_header = self._section_header("📊 Bande passante")
|
||||
root.addWidget(bw_header)
|
||||
|
||||
row_layout.addWidget(lbl_key)
|
||||
row_layout.addWidget(lbl_val, 1)
|
||||
i_layout.addWidget(row_w)
|
||||
self._bw_graph = BandwidthGraph()
|
||||
self._bw_graph.setFixedHeight(75)
|
||||
root.addWidget(self._bw_graph)
|
||||
root.addWidget(self._hsep())
|
||||
|
||||
layout.addWidget(info_frame, 1) # stretch factor 1 — prend l'espace disponible
|
||||
# ── Journal ───────────────────────────────────────────────────
|
||||
log_hdr = self._section_header("📋 Journal", btn_text="Effacer",
|
||||
btn_slot=self._clear_log)
|
||||
root.addWidget(log_hdr)
|
||||
|
||||
# -- Séparateur bas -------------------------------------------
|
||||
sep2 = QFrame()
|
||||
sep2.setFrameShape(QFrame.Shape.HLine)
|
||||
sep2.setStyleSheet("color: #bdc3c7;")
|
||||
layout.addWidget(sep2)
|
||||
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 --------------------------------------------------
|
||||
# ── Boutons ───────────────────────────────────────────────────
|
||||
btn_area = QWidget()
|
||||
btn_area.setStyleSheet("background: #f8f9fa;")
|
||||
b_layout = QVBoxLayout(btn_area)
|
||||
b_layout.setContentsMargins(16, 12, 16, 12)
|
||||
b_layout.setSpacing(8)
|
||||
btn_area.setStyleSheet(f"background: {_DARK2};")
|
||||
bl = QVBoxLayout(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: #27ae60; color: white; font-size: 14px; "
|
||||
"font-weight: bold; border-radius: 6px; border: none; }"
|
||||
"QPushButton:hover { background: #219a52; }"
|
||||
"QPushButton:disabled { background: #bdc3c7; color: #ecf0f1; }"
|
||||
"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)
|
||||
b_layout.addWidget(self._btn_connect)
|
||||
bl.addWidget(self._btn_connect)
|
||||
|
||||
btn_admin = QPushButton("Panneau Administrateur")
|
||||
btn_admin.setFixedHeight(34)
|
||||
btn_admin = QPushButton("⚙️ Panneau Administrateur")
|
||||
btn_admin.setFixedHeight(30)
|
||||
btn_admin.setStyleSheet(
|
||||
"QPushButton { background: #dde1e7; color: #2c3e50; border-radius: 6px; "
|
||||
"font-size: 12px; border: none; }"
|
||||
"QPushButton:hover { background: #bdc3c7; color: #2c3e50; }"
|
||||
"QPushButton { background: #2e4057; color: white; border-radius: 6px;"
|
||||
" font-size: 11px; border: none; }"
|
||||
"QPushButton:hover { background: #3d5166; }"
|
||||
)
|
||||
btn_admin.clicked.connect(self._open_admin)
|
||||
b_layout.addWidget(btn_admin)
|
||||
layout.addWidget(btn_area)
|
||||
bl.addWidget(btn_admin)
|
||||
root.addWidget(btn_area)
|
||||
|
||||
# ── Helpers UI ───────────────────────────────────────────────────────
|
||||
def _lbl(self, text: str, fixed: int = 0, color: str = "white",
|
||||
size: int = 12) -> QLabel:
|
||||
lbl = QLabel(text)
|
||||
lbl.setStyleSheet(
|
||||
f"color: {color}; font-size: {size}px; background: transparent;"
|
||||
)
|
||||
if fixed:
|
||||
lbl.setFixedWidth(fixed)
|
||||
return lbl
|
||||
|
||||
def _hsep(self) -> QFrame:
|
||||
f = QFrame()
|
||||
f.setFrameShape(QFrame.Shape.HLine)
|
||||
f.setFixedHeight(1)
|
||||
f.setStyleSheet(f"background: {_DARK2};")
|
||||
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};")
|
||||
hl = QHBoxLayout(w)
|
||||
hl.setContentsMargins(12, 0, 12, 0)
|
||||
t = QLabel(title)
|
||||
t.setStyleSheet("color: #5d6d7e; font-size: 10px; font-weight: bold;"
|
||||
" background: transparent;")
|
||||
hl.addWidget(t)
|
||||
hl.addStretch()
|
||||
if btn_text and btn_slot:
|
||||
b = QPushButton(btn_text)
|
||||
b.setFixedHeight(18)
|
||||
b.setStyleSheet(
|
||||
"QPushButton { background: transparent; color: #5d6d7e;"
|
||||
" font-size: 9px; border: none; padding: 0 4px; }"
|
||||
"QPushButton:hover { color: white; }"
|
||||
)
|
||||
b.clicked.connect(btn_slot)
|
||||
hl.addWidget(b)
|
||||
return w
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Rafraîchissement du statut
|
||||
# Profils
|
||||
# ------------------------------------------------------------------ #
|
||||
def _reload_profiles(self):
|
||||
self._profile_combo.blockSignals(True)
|
||||
self._profile_combo.clear()
|
||||
self._profile_combo.addItem("⭐ Défaut (actif)")
|
||||
for name in self._cfg.list_profiles():
|
||||
marker = "✓ " if name == self._cfg.active_profile else " "
|
||||
self._profile_combo.addItem(f"{marker}{name}")
|
||||
self._profile_combo.blockSignals(False)
|
||||
|
||||
def _on_profile_changed(self, text: str):
|
||||
if "Défaut" in text:
|
||||
return
|
||||
name = text.lstrip("✓ ").strip()
|
||||
if not name or name == self._cfg.active_profile:
|
||||
return
|
||||
if self._cfg.load_profile(name):
|
||||
self._add_log(f"Profil chargé : {name}", "info")
|
||||
self._reload_profiles()
|
||||
self._refresh_status()
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Journal
|
||||
# ------------------------------------------------------------------ #
|
||||
def _reload_log(self):
|
||||
self._log_list.clear()
|
||||
for ev in conn_log.get_events(30):
|
||||
self._add_log_item(ev["ts"], ev["msg"], ev["kind"])
|
||||
self._log_list.scrollToBottom()
|
||||
|
||||
def _clear_log(self):
|
||||
conn_log.clear()
|
||||
self._log_list.clear()
|
||||
|
||||
def _add_log(self, msg: str, kind: str = "info"):
|
||||
conn_log.log_event(msg, kind)
|
||||
ts = QDateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss")
|
||||
self._add_log_item(ts, msg, kind)
|
||||
|
||||
def _add_log_item(self, ts: str, msg: str, kind: str):
|
||||
color = _LOG_COLORS.get(kind, "#aed6f1")
|
||||
item = QListWidgetItem(f"[{ts}] {msg}")
|
||||
item.setForeground(QColor(color))
|
||||
self._log_list.addItem(item)
|
||||
self._log_list.scrollToBottom()
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Timers
|
||||
# ------------------------------------------------------------------ #
|
||||
def _tick_duration(self):
|
||||
if self._connected_since is None:
|
||||
return
|
||||
secs = self._connected_since.secsTo(QDateTime.currentDateTime())
|
||||
h, rem = divmod(secs, 3600)
|
||||
m, s = divmod(rem, 60)
|
||||
self._duration_label.setText(f"Connecté depuis {h:02d}h {m:02d}m {s:02d}s")
|
||||
|
||||
def _update_bw(self):
|
||||
iface = self._cfg.wg.get("interface_name", "wgs0")
|
||||
result = wg_core.get_interface_bytes(iface)
|
||||
if result is None or self._prev_rx is None:
|
||||
self._prev_rx, self._prev_tx = (result or (0, 0))
|
||||
return
|
||||
rx_now, tx_now = result
|
||||
dt = 3.0
|
||||
rx_bps = max(0, rx_now - self._prev_rx) / dt
|
||||
tx_bps = max(0, tx_now - self._prev_tx) / dt
|
||||
self._prev_rx, self._prev_tx = rx_now, tx_now
|
||||
if wg_core.is_connected(self._cfg):
|
||||
self._bw_graph.push(rx_bps, tx_bps)
|
||||
else:
|
||||
self._bw_graph.push(0, 0)
|
||||
|
||||
def _update_ping(self):
|
||||
host = self._cfg.wg.get("server_endpoint", "")
|
||||
if not host or not wg_core.is_connected(self._cfg):
|
||||
self._ping_badge.setText("— ms")
|
||||
self._ping_badge.setStyleSheet(
|
||||
f"border-radius: 11px; font-size: 10px; font-weight: bold; {_PING_CSS['offline']}"
|
||||
)
|
||||
return
|
||||
ms = wg_core.ping_server(host)
|
||||
if ms is None:
|
||||
css = _PING_CSS["bad"]
|
||||
txt = "hors ligne"
|
||||
elif ms < 50:
|
||||
css = _PING_CSS["good"]
|
||||
txt = f"{ms} ms"
|
||||
elif ms < 200:
|
||||
css = _PING_CSS["medium"]
|
||||
txt = f"{ms} ms"
|
||||
else:
|
||||
css = _PING_CSS["bad"]
|
||||
txt = f"{ms} ms"
|
||||
self._ping_badge.setText(txt)
|
||||
self._ping_badge.setStyleSheet(
|
||||
f"border-radius: 11px; font-size: 10px; font-weight: bold; {css}"
|
||||
)
|
||||
|
||||
def _check_reconnect(self):
|
||||
if not self._cfg.get("ui", "auto_reconnect"):
|
||||
return
|
||||
try:
|
||||
connected = wg_core.is_connected(self._cfg)
|
||||
except Exception:
|
||||
connected = False
|
||||
if not connected and self._cfg.configured:
|
||||
self._add_log("Auto-reconnexion…", "warning")
|
||||
self._on_connect()
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Statut WireGuard
|
||||
# ------------------------------------------------------------------ #
|
||||
def _refresh_status(self):
|
||||
wg = self._cfg.wg
|
||||
@@ -181,6 +424,15 @@ 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"):
|
||||
interval = int(self._cfg.get("ui", "reconnect_interval") or 30) * 1000
|
||||
if not self._reconnect_timer.isActive():
|
||||
self._reconnect_timer.start(interval)
|
||||
else:
|
||||
self._reconnect_timer.stop()
|
||||
|
||||
try:
|
||||
info = wg_core.get_status_info(self._cfg)
|
||||
@@ -190,36 +442,47 @@ class MainWindow(QMainWindow):
|
||||
info = {}
|
||||
|
||||
if connected:
|
||||
self._status_badge.setText("● Connecté")
|
||||
if self._connected_since is None:
|
||||
self._connected_since = QDateTime.currentDateTime()
|
||||
self._status_badge.setText("● Connecté")
|
||||
self._status_badge.setStyleSheet(
|
||||
"color: #27ae60; font-size: 16px; font-weight: bold; background: transparent;"
|
||||
"color: #27ae60; font-size: 15px; font-weight: bold; background: transparent;"
|
||||
)
|
||||
self._btn_connect.setText("Se déconnecter")
|
||||
self._btn_connect.setStyleSheet(
|
||||
"QPushButton { background: #e74c3c; color: white; font-size: 14px; "
|
||||
"font-weight: bold; border-radius: 6px; border: none; }"
|
||||
"QPushButton { background: #922b21; color: white; font-size: 14px;"
|
||||
" font-weight: bold; border-radius: 6px; border: none; }"
|
||||
"QPushButton:hover { background: #c0392b; }"
|
||||
)
|
||||
self._info_labels["rx_tx"].setText(
|
||||
f"↓ {info.get('rx_bytes','—')} ↑ {info.get('tx_bytes','—')}"
|
||||
)
|
||||
self._info_labels["handshake"].setText(info.get("last_handshake", "—"))
|
||||
self._status_sub.setText(f"Interface active : {info.get('interface','—')}")
|
||||
self._duration_label.setStyleSheet(
|
||||
"color: #a9dfbf; font-size: 11px; background: transparent;"
|
||||
)
|
||||
self.setWindowIcon(icons.icon_connected())
|
||||
else:
|
||||
self._status_badge.setText("● Déconnecté")
|
||||
if self._connected_since is not None:
|
||||
self._connected_since = None
|
||||
self._duration_label.setText("")
|
||||
self._bw_graph.reset()
|
||||
self._prev_rx = self._prev_tx = None
|
||||
self._status_badge.setText("● Déconnecté")
|
||||
self._status_badge.setStyleSheet(
|
||||
"color: #e74c3c; font-size: 16px; font-weight: bold; background: transparent;"
|
||||
"color: #e74c3c; font-size: 15px; font-weight: bold; background: transparent;"
|
||||
)
|
||||
self._btn_connect.setText("Se connecter")
|
||||
self._btn_connect.setStyleSheet(
|
||||
"QPushButton { background: #27ae60; color: white; font-size: 14px; "
|
||||
"font-weight: bold; border-radius: 6px; border: none; }"
|
||||
"QPushButton:hover { background: #219a52; }"
|
||||
"QPushButton { background: #1e8449; color: white; 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._status_sub.setText("")
|
||||
self._duration_label.setStyleSheet(
|
||||
"color: #5d6d7e; font-size: 11px; background: transparent;"
|
||||
)
|
||||
self.setWindowIcon(icons.icon_disconnected())
|
||||
|
||||
pix = (icons.icon_connected() if connected else icons.icon_disconnected()).pixmap(36, 36)
|
||||
@@ -228,6 +491,12 @@ 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):
|
||||
if self._connecting:
|
||||
return
|
||||
@@ -238,37 +507,48 @@ class MainWindow(QMainWindow):
|
||||
|
||||
if connected:
|
||||
ok, msg = wg_core.disconnect(self._cfg)
|
||||
if not ok:
|
||||
QMessageBox.warning(self, "Erreur", f"Déconnexion échouée :\n{msg}")
|
||||
if ok:
|
||||
self._add_log("Tunnel WireGuard désactivé", "warning")
|
||||
else:
|
||||
self._add_log(f"Erreur déconnexion : {msg}", "error")
|
||||
QMessageBox.warning(self, "Erreur", msg)
|
||||
else:
|
||||
if not self._cfg.configured:
|
||||
QMessageBox.information(
|
||||
self, "Configuration manquante",
|
||||
"WireGuard n'est pas encore configuré.\n"
|
||||
"Ouvrez le panneau Administrateur pour paramétrer la connexion."
|
||||
"Ouvrez le panneau Administrateur."
|
||||
)
|
||||
return
|
||||
|
||||
if self._cfg.mfa_enabled:
|
||||
if not self._cfg.mfa_secret:
|
||||
QMessageBox.warning(
|
||||
self, "MFA non configuré",
|
||||
"Le MFA est activé mais aucun secret n'est configuré."
|
||||
)
|
||||
QMessageBox.warning(self, "MFA non configuré",
|
||||
"MFA activé mais aucun secret défini.")
|
||||
return
|
||||
dlg = MFADialog(self._cfg.mfa_secret, self)
|
||||
if dlg.exec() != MFADialog.DialogCode.Accepted or not dlg.is_verified():
|
||||
self._add_log("MFA annulé ou échoué", "warning")
|
||||
return
|
||||
self._add_log("Authentification MFA réussie ✓", "success")
|
||||
|
||||
self._btn_connect.setEnabled(False)
|
||||
self._btn_connect.setText("Connexion…")
|
||||
self._connecting = True
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
QApplication.processEvents()
|
||||
|
||||
ok, msg = wg_core.connect(self._cfg)
|
||||
self._connecting = False
|
||||
self._btn_connect.setEnabled(True)
|
||||
if not ok:
|
||||
|
||||
if ok:
|
||||
self._connected_since = QDateTime.currentDateTime()
|
||||
self._add_log(
|
||||
f"Connexion établie → {self._cfg.wg.get('server_endpoint','')}", "success"
|
||||
)
|
||||
QTimer.singleShot(5000, self._update_ping)
|
||||
else:
|
||||
self._add_log(f"Erreur : {msg}", "error")
|
||||
QMessageBox.warning(self, "Erreur de connexion", msg)
|
||||
|
||||
self._refresh_status()
|
||||
@@ -287,6 +567,7 @@ class MainWindow(QMainWindow):
|
||||
return
|
||||
dlg = AdminWindow(self._cfg, self)
|
||||
dlg.exec()
|
||||
self._reload_profiles()
|
||||
self._refresh_status()
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
|
||||
Reference in New Issue
Block a user