v0.7.0 : instance unique, handshake ré-affiché, sudoers fiabilisé, À propos refaite

- Instance unique : un second lancement réveille l'instance active.
- Autostart et reconnexion auto activés par défaut (nouvelle installation).
- Page « À propos » refaite en 5 onglets (contenu séparé dans app_info.py).
- Corrige le handshake/RX-TX qui ne s'affichaient plus (wg show sans élévation).
- Corrige le diagnostic sudo trompeur (sondes qui ne matchaient jamais les règles).
- Corrige make setup-sudoers bloqué hors session graphique (pkexec -> sudo), idempotent.
- Corrige le README : Kill Switch inexistant retiré, ordre d'élévation réel documenté.
This commit is contained in:
2026-09-01 19:18:24 +02:00
parent 6251a3ea8e
commit 8c09c02362
13 changed files with 590 additions and 104 deletions
+241 -58
View File
@@ -1,3 +1,5 @@
import re
from PyQt6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QTabWidget, QWidget,
QLabel, QLineEdit, QPushButton, QSpinBox, QCheckBox,
@@ -861,69 +863,250 @@ class AdminWindow(QDialog):
# Onglet À propos
# ------------------------------------------------------------------ #
def _tab_about(self) -> QWidget:
w = QWidget()
w.setStyleSheet(theme.TAB_CSS)
w.setAutoFillBackground(True)
layout = QVBoxLayout(w)
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.setSpacing(12)
layout.setContentsMargins(30, 24, 30, 24)
"""Page « À propos », en cinq sous-onglets (modèle QuiOulé/TuxMarket).
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)
Le contenu (fonctions, garanties, pile technique, auteur) vit dans
`app/app_info.py`, séparé de sa présentation ici — le rendre
modifiable sans toucher au Qt. Le journal est lu depuis
CHANGELOG.md à l'affichage, jamais recopié.
"""
from app import app_info
w, lay = self._dark_page("️ À propos", theme.BANNER["about"])
lay.setContentsMargins(0, 0, 0, 0)
lay.setSpacing(0)
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)
lay.addWidget(self._about_header(app_info))
version_lbl = QLabel("Version 0.6.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("Septembre 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()
sub = QTabWidget()
sub.setStyleSheet(theme.TABBAR_CSS)
sub.addTab(self._subtab_about_features(app_info), "⭐ Fonctions")
sub.addTab(self._subtab_about_guarantees(app_info), "🛡️ Garanties")
sub.addTab(self._subtab_about_tech(app_info), "🧱 Technique")
sub.addTab(self._subtab_about_changelog(app_info), "🕓 Journal")
sub.addTab(self._subtab_about_author(app_info), "👤 Auteur")
lay.addWidget(sub)
return w
# ── En-tête identité : icône, nom, accroche, pastilles d'état ────────
def _about_header(self, info) -> QWidget:
from app.ui.icons import icon_app
box = QWidget()
box.setStyleSheet(f"background: {theme.BG_RAISED};")
v = QVBoxLayout(box)
v.setContentsMargins(20, 16, 20, 14)
v.setSpacing(6)
v.setAlignment(Qt.AlignmentFlag.AlignCenter)
logo = QLabel()
logo.setPixmap(icon_app().pixmap(72, 72))
logo.setAlignment(Qt.AlignmentFlag.AlignCenter)
v.addWidget(logo)
name = QLabel(info.APP_NAME)
name.setAlignment(Qt.AlignmentFlag.AlignCenter)
nf = QFont(); nf.setPointSize(18); nf.setBold(True)
name.setFont(nf)
name.setStyleSheet(f"color: {theme.TEXT};")
v.addWidget(name)
tagline = QLabel(info.TAGLINE)
tagline.setAlignment(Qt.AlignmentFlag.AlignCenter)
tagline.setWordWrap(True)
tagline.setStyleSheet(f"color: {theme.TEXT_MUTED}; font-size: 12px;")
v.addWidget(tagline)
pills = QWidget()
pills.setStyleSheet("background: transparent;")
ph = QHBoxLayout(pills)
ph.setContentsMargins(0, 4, 0, 0)
ph.setSpacing(8)
ph.setAlignment(Qt.AlignmentFlag.AlignCenter)
for text, color in self._about_pills(info):
pill = QLabel(text)
pill.setStyleSheet(
f"background: {color}; color: white; border-radius: 9px;"
" padding: 3px 10px; font-size: 11px; font-weight: bold;"
)
ph.addWidget(pill)
v.addWidget(pills)
return box
def _about_pills(self, info) -> list[tuple[str, str]]:
"""Pastilles de version/licence, plus l'état réel de MFA et des
droits sudo — lu en direct, jamais une valeur supposée."""
from app.utils.platform_utils import (
has_root_privileges, is_windows, privilege_report,
)
pills = [
(f"v{info.VERSION}", theme.ACCENT),
(info.LICENSE, "#5b6b7a"),
]
if self._cfg.mfa_enabled:
pills.append(("🔐 MFA activée", theme.OK_SOLID))
else:
pills.append(("🔐 MFA désactivée", "#5b6b7a"))
if not is_windows():
if has_root_privileges():
pills.append(("⚠️ Exécuté en root", theme.WARN_SOLID))
else:
rows = privilege_report()
if rows and all(ok for _, ok, _ in rows):
pills.append(("✅ sudoers configuré", theme.OK_SOLID))
elif rows:
pills.append(("⚠️ dialogue à chaque appel", theme.WARN_SOLID))
return pills
# ── Sous-onglet : fonctions ───────────────────────────────────────────
def _subtab_about_features(self, info) -> QWidget:
page, v = self._test_page()
area = QScrollArea()
area.setWidgetResizable(True)
area.setStyleSheet(theme.scrollarea_style())
inner = QWidget()
inner.setStyleSheet(f"background: {theme.BG};")
iv = QVBoxLayout(inner)
iv.setContentsMargins(0, 0, 8, 0)
iv.setSpacing(10)
for title, desc in info.FEATURES:
iv.addWidget(self._feature_row(title, desc))
iv.addStretch()
area.setWidget(inner)
v.addWidget(area)
return page
def _feature_row(self, title: str, desc: str) -> QWidget:
row = QWidget()
row.setStyleSheet("background: transparent;")
rv = QVBoxLayout(row)
rv.setContentsMargins(0, 0, 0, 0)
rv.setSpacing(2)
t = QLabel(title)
tf = QFont(); tf.setBold(True)
t.setFont(tf)
t.setStyleSheet(f"color: {theme.TEXT};")
rv.addWidget(t)
d = QLabel(desc)
d.setWordWrap(True)
d.setStyleSheet(f"color: {theme.TEXT_MUTED}; font-size: 11px;")
rv.addWidget(d)
return row
# ── Sous-onglet : garanties ───────────────────────────────────────────
def _subtab_about_guarantees(self, info) -> QWidget:
page, v = self._test_page()
v.addWidget(self._hint(info.GUARANTEES_INTRO))
area = QScrollArea()
area.setWidgetResizable(True)
area.setStyleSheet(theme.scrollarea_style())
inner = QWidget()
inner.setStyleSheet(f"background: {theme.BG};")
iv = QVBoxLayout(inner)
iv.setContentsMargins(0, 0, 8, 0)
iv.setSpacing(8)
for item in info.GUARANTEES:
lbl = QLabel("" + item)
lbl.setWordWrap(True)
lbl.setStyleSheet(f"color: {theme.TEXT}; font-size: 12px;")
iv.addWidget(lbl)
iv.addStretch()
area.setWidget(inner)
v.addWidget(area)
return page
# ── Sous-onglet : pile technique ──────────────────────────────────────
def _subtab_about_tech(self, info) -> QWidget:
page, v = self._test_page()
grp = QGroupBox("Pile technique")
g = QFormLayout(grp)
for layer, tool in info.TECH_STACK:
layer_lbl = QLabel(layer)
lf = QFont(); lf.setBold(True)
layer_lbl.setFont(lf)
tool_lbl = QLabel(tool)
tool_lbl.setWordWrap(True)
tool_lbl.setStyleSheet(f"color: {theme.TEXT_MUTED};")
g.addRow(layer_lbl, tool_lbl)
v.addWidget(grp)
v.addStretch()
return page
# ── Sous-onglet : journal (lu depuis CHANGELOG.md) ────────────────────
def _subtab_about_changelog(self, info) -> QWidget:
page, v = self._test_page()
view = QTextEdit()
view.setReadOnly(True)
view.setStyleSheet(theme.result_view_style("idle", font_size=12))
parts = []
for entry in info.parse_changelog():
parts.append(
f"<p style='color:{theme.ACCENT_LIGHT};font-weight:bold;"
f"font-size:13px;margin-bottom:2px'>{entry['title']}</p>"
)
for section in entry["sections"]:
parts.append(f"<b>{section['name']}</b><ul style='margin-top:2px'>")
for item in section["items"]:
parts.append(
"<li>" + re.sub(r"\*\*(.+?)\*\*", r"<b>\1</b>", item) + "</li>"
)
parts.append("</ul>")
view.setHtml("".join(parts) or "<i>Aucun changelog trouvé.</i>")
v.addWidget(view)
return page
# ── Sous-onglet : auteur ───────────────────────────────────────────────
def _subtab_about_author(self, info) -> QWidget:
page, v = self._test_page()
v.setAlignment(Qt.AlignmentFlag.AlignCenter)
card = QWidget()
card.setStyleSheet(f"background: {theme.BG_RAISED}; border-radius: 8px;")
cv = QVBoxLayout(card)
cv.setContentsMargins(24, 20, 24, 20)
cv.setSpacing(6)
cv.setAlignment(Qt.AlignmentFlag.AlignCenter)
initial = QLabel(info.AUTHOR_NAME[:1].upper())
initial.setFixedSize(56, 56)
initial.setAlignment(Qt.AlignmentFlag.AlignCenter)
initial.setStyleSheet(
f"background: {theme.ACCENT}; color: white; border-radius: 28px;"
" font-size: 22px; font-weight: bold;"
)
cv.addWidget(initial, alignment=Qt.AlignmentFlag.AlignCenter)
name = QLabel(info.AUTHOR_NAME)
nf = QFont(); nf.setPointSize(14); nf.setBold(True)
name.setFont(nf)
name.setAlignment(Qt.AlignmentFlag.AlignCenter)
cv.addWidget(name)
role = QLabel(info.AUTHOR_ROLE)
role.setAlignment(Qt.AlignmentFlag.AlignCenter)
role.setStyleSheet(f"color: {theme.TEXT_MUTED};")
cv.addWidget(role)
repo = QLineEdit(info.REPO_URL)
repo.setReadOnly(True)
repo.setAlignment(Qt.AlignmentFlag.AlignCenter)
repo.setFixedWidth(320)
cv.addWidget(repo, alignment=Qt.AlignmentFlag.AlignCenter)
disclosure = QLabel(
"Application développée avec l'assistance de l'IA, sous la "
f"direction et la supervision de {info.AUTHOR_NAME}."
)
disclosure.setWordWrap(True)
disclosure.setAlignment(Qt.AlignmentFlag.AlignCenter)
disclosure.setFixedWidth(320)
disclosure.setStyleSheet(f"color: {theme.TEXT_FAINT}; font-size: 11px;")
cv.addWidget(disclosure)
v.addStretch()
v.addWidget(card, alignment=Qt.AlignmentFlag.AlignCenter)
v.addStretch()
return page
# ------------------------------------------------------------------ #
# Chargement / sauvegarde
# ------------------------------------------------------------------ #