La présence d'une clé pré-partagée se devinait au fait que le champ soit rempli. Un serveur qui n'en utilise pas — le cas par défaut de WireGuard — déclenchait donc un avertissement à chaque diagnostic, et une clé oubliée dans le champ finissait écrite dans la configuration alors que le serveur n'en attendait aucune, ce qui casse le handshake aussi sûrement qu'une clé manquante. Une case à cocher tranche désormais, et le diagnostic distingue les quatre états au lieu d'un avertissement unique. Le drapeau vaut None par défaut et non False : _merge() recopie le défaut dans toute configuration antérieure, et False aurait désactivé la PSK d'un tunnel qui fonctionnait. Refonte des onglets, qui passaient de sept à cinq : - WireGuard et Clés se chevauchaient — la clé publique du serveur se saisissait dans l'un pendant que l'assistant qui la produit vivait dans l'autre, et les quatre façons de sortir une configuration étaient réparties entre les deux. Fusionnés en sous-onglets Tunnel, Clés, Profils & fichiers. - Les trois tests s'empilaient sur une page unique où le rapport de diagnostic se retrouvait comprimé. Chacun a son sous-onglet. - MFA et Sécurité répondent à la même question et sont regroupés. - La section des droits parlait de sudo et de setup-sudoers sous Windows, où rien de tout cela n'existe, et concluait « privilèges root » sur un rapport simplement vide. Elle vérifie maintenant l'élévation UAC et WireGuard. Chaque onglet s'ouvre sur un encart expliquant ce qu'on y décide, et les couleurs — vingt-quatre valeurs en dur, cinq bandeaux sans rapport de teinte, des blocs de résultat au fond incohérent — passent par app/ui/theme.py.
191 lines
7.7 KiB
Python
191 lines
7.7 KiB
Python
"""Jetons de couleur et fragments de feuille de style partagés.
|
|
|
|
Les couleurs étaient écrites en dur au fil des widgets : vingt-quatre valeurs
|
|
distinctes dans le seul panneau d'administration, cinq bandeaux d'onglets sans
|
|
rapport de teinte entre eux, et des blocs de résultat dont le fond ne
|
|
correspondait ni à celui de la page ni à celui de leurs voisins. Tout passe
|
|
désormais par ce module — une couleur ne se choisit plus au cas par cas.
|
|
"""
|
|
|
|
# ── Fonds ────────────────────────────────────────────────────────────────────
|
|
BG = "#1c2833" # fond de l'application et de toutes les pages
|
|
BG_SUNKEN = "#17202a" # zones en creux : configurations, blocs de code
|
|
BG_RAISED = "#253545" # éléments posés sur le fond : listes, en-têtes secondaires
|
|
BORDER = "rgba(255,255,255,0.18)"
|
|
BORDER_SOFT = "rgba(255,255,255,0.10)"
|
|
|
|
# ── Textes ───────────────────────────────────────────────────────────────────
|
|
TEXT = "white"
|
|
TEXT_MUTED = "rgba(255,255,255,0.70)"
|
|
TEXT_FAINT = "rgba(255,255,255,0.50)"
|
|
|
|
# ── Accent ───────────────────────────────────────────────────────────────────
|
|
ACCENT = "#2471a3"
|
|
ACCENT_HOVER = "#1a5276"
|
|
ACCENT_ACTIVE = "#154360"
|
|
ACCENT_LIGHT = "#5dade2"
|
|
|
|
# ── États ────────────────────────────────────────────────────────────────────
|
|
# Chaque état porte un trio : teinte du texte, fond translucide, bouton plein.
|
|
OK_TEXT, OK_BG, OK_SOLID = "#a9dfbf", "rgba(39,174,96,0.18)", "#1e8449"
|
|
WARN_TEXT, WARN_BG, WARN_SOLID = "#f7dc6f", "rgba(241,196,15,0.15)", "#b7950b"
|
|
FAIL_TEXT, FAIL_BG, FAIL_SOLID = "#f1948a", "rgba(231,76,60,0.18)", "#c0392b"
|
|
SKIP_TEXT, SKIP_BG = "rgba(255,255,255,0.45)", "rgba(255,255,255,0.05)"
|
|
|
|
_STATE = {
|
|
"ok": (OK_TEXT, OK_BG),
|
|
"warn": (WARN_TEXT, WARN_BG),
|
|
"fail": (FAIL_TEXT, FAIL_BG),
|
|
"skip": (SKIP_TEXT, SKIP_BG),
|
|
"idle": (TEXT_MUTED, "transparent"),
|
|
}
|
|
|
|
# ── Bandeaux d'onglets ───────────────────────────────────────────────────────
|
|
# Cinq teintes calées sur la même luminosité et la même saturation : les
|
|
# onglets restent distinguables sans que l'un paraisse plus clair ou plus
|
|
# saturé que les autres, ce qui était le cas des valeurs choisies à la main.
|
|
BANNER = {
|
|
"wireguard": "#1a5276",
|
|
"keys": "#1a4a6e",
|
|
"mfa": "#4a2a6e",
|
|
"test": "#1a4a7a",
|
|
"settings": "#155e52",
|
|
"security": "#7a3b1a",
|
|
"about": "#243b53",
|
|
}
|
|
|
|
|
|
def result_style(state: str, font_size: int = 11) -> str:
|
|
"""Feuille de style d'un bloc de résultat, selon son état.
|
|
|
|
Les blocs affichaient chacun leur propre fond rgba, recopié à la main :
|
|
même état, teintes différentes d'un widget à l'autre.
|
|
"""
|
|
color, bg = _STATE.get(state, _STATE["idle"])
|
|
return (f"padding: 8px; border-radius: 6px; font-size: {font_size}px;"
|
|
f" background: {bg}; color: {color};")
|
|
|
|
|
|
def result_view_style(state: str = "idle", font_size: int = 12) -> str:
|
|
"""Même chose pour un QTextEdit en lecture seule (rapport défilant)."""
|
|
color, bg = _STATE.get(state, _STATE["idle"])
|
|
return (
|
|
f"QTextEdit {{ background: {bg}; color: {color};"
|
|
f" border: 1px solid {BORDER_SOFT}; border-radius: 6px;"
|
|
f" padding: 8px; font-size: {font_size}px; }}"
|
|
f"QScrollBar:vertical {{ background: transparent; width: 8px;"
|
|
f" border: none; }}"
|
|
f"QScrollBar::handle:vertical {{ background: {BG_RAISED};"
|
|
f" border-radius: 4px; }}"
|
|
"QScrollBar::add-line:vertical,"
|
|
"QScrollBar::sub-line:vertical { height: 0; }"
|
|
)
|
|
|
|
|
|
def scrollarea_style() -> str:
|
|
return (
|
|
f"QScrollArea {{ border: none; background: {BG}; }}"
|
|
f"QScrollBar:vertical {{ background: {BG}; width: 8px; border: none; }}"
|
|
f"QScrollBar::handle:vertical {{ background: {BG_RAISED};"
|
|
f" border-radius: 4px; }}"
|
|
"QScrollBar::add-line:vertical,"
|
|
"QScrollBar::sub-line:vertical { height: 0; }"
|
|
)
|
|
|
|
|
|
def banner_style(color: str) -> str:
|
|
return (f"background: {color}; color: {TEXT}; font-size: 13px;"
|
|
" font-weight: bold; padding-left: 8px;")
|
|
|
|
|
|
#: CSS sombre commun à toutes les pages.
|
|
TAB_CSS = f"""
|
|
QWidget {{ background-color: {BG}; color: {TEXT}; }}
|
|
QLabel {{ color: {TEXT}; background: transparent; }}
|
|
QCheckBox {{ color: {TEXT}; background: transparent; }}
|
|
QGroupBox {{
|
|
color: {TEXT};
|
|
background: transparent;
|
|
border: 1px solid {BORDER};
|
|
border-radius: 5px;
|
|
margin-top: 10px;
|
|
font-weight: bold;
|
|
}}
|
|
QGroupBox::title {{
|
|
subcontrol-origin: margin;
|
|
left: 10px;
|
|
padding: 0 4px;
|
|
color: {TEXT};
|
|
}}
|
|
QLineEdit {{
|
|
background: rgba(255,255,255,0.10);
|
|
color: {TEXT};
|
|
border: 1px solid {BORDER};
|
|
border-radius: 4px;
|
|
padding: 4px 6px;
|
|
}}
|
|
QLineEdit:read-only {{ background: rgba(255,255,255,0.06); }}
|
|
QLineEdit:disabled {{ background: rgba(255,255,255,0.04);
|
|
color: {TEXT_FAINT}; }}
|
|
QSpinBox {{
|
|
background: rgba(255,255,255,0.10);
|
|
color: {TEXT};
|
|
border: 1px solid {BORDER};
|
|
border-radius: 4px;
|
|
padding: 3px 6px;
|
|
}}
|
|
QSpinBox::up-button, QSpinBox::down-button {{
|
|
background: rgba(255,255,255,0.15);
|
|
}}
|
|
QTextEdit {{
|
|
background: {BG_SUNKEN};
|
|
color: #a8d8ea;
|
|
border: 1px solid {BORDER};
|
|
border-radius: 4px;
|
|
}}
|
|
QPushButton {{
|
|
background: {ACCENT};
|
|
color: {TEXT};
|
|
border-radius: 5px;
|
|
padding: 7px 12px;
|
|
border: none;
|
|
}}
|
|
QPushButton:hover {{ background: {ACCENT_HOVER}; }}
|
|
QPushButton:checked {{ background: {ACCENT_ACTIVE}; }}
|
|
QPushButton:disabled {{ background: rgba(255,255,255,0.08);
|
|
color: {TEXT_FAINT}; }}
|
|
"""
|
|
|
|
#: Barre d'onglets, utilisée par le panneau principal et les sous-onglets.
|
|
TABBAR_CSS = f"""
|
|
QTabWidget::pane {{ border: none; background: {BG}; }}
|
|
QTabBar::tab {{
|
|
background: {BG_RAISED};
|
|
color: {TEXT_MUTED};
|
|
padding: 6px 14px;
|
|
border: none;
|
|
}}
|
|
QTabBar::tab:selected {{ background: {ACCENT}; color: {TEXT}; }}
|
|
QTabBar::tab:hover:!selected {{ background: {ACCENT_HOVER};
|
|
color: {TEXT}; }}
|
|
"""
|
|
|
|
|
|
def primary_button_style(solid: str = OK_SOLID, hover: str = "#27ae60") -> str:
|
|
"""Bouton d'action principale d'une page (lancer un test, par exemple)."""
|
|
return (f"QPushButton {{ padding: 9px; background: {solid}; color: {TEXT};"
|
|
f" border-radius: 5px; font-weight: bold; border: none; }}"
|
|
f"QPushButton:hover {{ background: {hover}; }}")
|
|
|
|
|
|
def hint_style() -> str:
|
|
"""Encart d'introduction posé en tête de chaque onglet.
|
|
|
|
Assez contrasté pour se détacher du fond de page, assez sobre pour ne pas
|
|
concurrencer le contenu : un liseré d'accent à gauche, pas de cadre plein.
|
|
"""
|
|
return (f"background: rgba(36,113,163,0.16);"
|
|
f" border-left: 3px solid {ACCENT_LIGHT};"
|
|
f" border-radius: 4px; padding: 9px 11px;"
|
|
f" color: {TEXT_MUTED}; font-size: 11px;")
|