From 0b47e4a625c0cc0b250a2eb155bfbab7e084257d Mon Sep 17 00:00:00 2001 From: Antoine Date: Fri, 17 Jul 2026 22:16:48 +0200 Subject: [PATCH] =?UTF-8?q?Corrige=20le=20rendu=20de=20la=20page=20mot=20d?= =?UTF-8?q?e=20passe,=20masque=20la=20nav=20si=20non=20connect=C3=A9,=20r?= =?UTF-8?q?=C3=A9duit=20le=20score=20spam=20des=20emails,=20et=20rend=20le?= =?UTF-8?q?=20niveau=20de=20log=20configurable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Les styles .login-* étaient définis uniquement dans login.html, donc set_password.html s'affichait sans aucun style. Déplacé dans base.html. - La nav affichait les liens même sans session active. - mail.py ajoute Message-ID/Date et encode en quoted-printable (au lieu de base64) pour réduire le score spam (MISSING_MID, MISSING_DATE, MIME_BASE64_TEXT). - log_level devient une directive de config.json plutôt qu'une variable d'environnement. - README: section démarrage rapide (copie de config.example.json, compose.override.yml pour les surcharges locales). --- README.md | 20 +++++++++-- app/mail.py | 8 +++-- app/main.py | 3 +- app/models.py | 1 + app/templates/base.html | 78 +++++++++++++++++++++++++++++++++++++++- app/templates/login.html | 77 --------------------------------------- data/config.example.json | 1 + 7 files changed, 104 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 5fb5193..4cdac94 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,21 @@ Saisie des horaires de travail avec calcul en temps réel de l'heure de sortie o --- +## Démarrage rapide + +Avant le premier lancement, copie et adapte le fichier de config (voir +[Configuration](#configuration) plus bas) : + +```bash +cp data/config.example.json data/config.json +``` + +Pour surcharger `compose.yml` localement (ports, variables d'env, volumes...) sans +modifier le fichier versionné, crée un `compose.override.yml` — il est chargé +automatiquement par `docker compose` et déjà ignoré par git. + +--- + ## Principe La semaine cible est **39h** (7h48 / jour). Les directives imposent des plages de présence minimum : @@ -35,14 +50,13 @@ Les plages qui ne respectent pas les minimums sont signalées en rouge dès la s ## Lancement ```bash -cp data/config.example.json data/config.json # à adapter à ton domaine docker compose up -d ``` Ouvre [http://localhost:8000](http://localhost:8000). Les données (pointages, congés, comptes) sont stockées dans `data/` et ignorées par git.\ -Seul `data/config.example.json` est versionné — copie-le en `data/config.json` et adapte-le. +Seul `data/config.example.json` est versionné. --- @@ -53,6 +67,7 @@ Seul `data/config.example.json` est versionné — copie-le en `data/config.json ```json { "heures_jour": 7.8, + "log_level": "DEBUG", "allowed_email_domain": "exemple.fr", "mail_from": "no-reply@exemple.fr", "smtp": { @@ -66,6 +81,7 @@ Seul `data/config.example.json` est versionné — copie-le en `data/config.json ``` - `heures_jour` — `7.8` = 7h48 = 39h ÷ 5 jours. Modifier cette valeur recentre tous les calculs. +- `log_level` — `DEBUG`, `INFO`, `WARNING` ou `ERROR`. Contrôle la verbosité des logs (`docker logs`). - `allowed_email_domain` — seules les adresses de ce domaine peuvent se connecter. - `mail_from` — adresse expéditrice des emails de connexion. - `smtp` — serveur relais utilisé pour l'envoi (host, port, identifiants, TLS). diff --git a/app/mail.py b/app/mail.py index fc08418..caafc3e 100644 --- a/app/mail.py +++ b/app/mail.py @@ -1,7 +1,8 @@ """Mail delivery via a configured SMTP relay (host/port/credentials in config.json).""" import logging import smtplib -from email.mime.text import MIMEText +from email.message import EmailMessage +from email.utils import make_msgid, formatdate logger = logging.getLogger("pointeuse.mail") @@ -16,10 +17,13 @@ def send_mail(to_addr: str, subject: str, body: str, from_addr: str, smtp_cfg: d password = smtp_cfg.get("password") use_tls = smtp_cfg.get("use_tls", True) - msg = MIMEText(body) + msg = EmailMessage() + msg.set_content(body, cte="quoted-printable") # évite MIME_BASE64_TEXT msg["Subject"] = subject msg["From"] = from_addr msg["To"] = to_addr + msg["Message-ID"] = make_msgid(domain=from_addr.split("@", 1)[1]) + msg["Date"] = formatdate(localtime=True) logger.info( "envoi mail: to=%r from=%r subject=%r via smtp=%s:%s user=%r tls=%r", diff --git a/app/main.py b/app/main.py index 4ae5b05..d0facdd 100644 --- a/app/main.py +++ b/app/main.py @@ -1,7 +1,6 @@ import asyncio import json import logging -import os import re import secrets from datetime import datetime, timedelta @@ -26,7 +25,7 @@ from classement import compute_classement from notifications import reminder_loop logging.basicConfig( - level=getattr(logging, os.environ.get("LOG_LEVEL", "DEBUG").upper(), logging.DEBUG), + level=getattr(logging, load_config().get("log_level", "DEBUG").upper(), logging.DEBUG), format="%(asctime)s %(levelname)s %(name)s: %(message)s", ) logger = logging.getLogger("pointeuse") diff --git a/app/models.py b/app/models.py index f0f7c42..9bdc674 100644 --- a/app/models.py +++ b/app/models.py @@ -8,6 +8,7 @@ CONFIG_FILE = DATA_DIR / "config.json" DEFAULT_CONFIG = { "heures_jour": 7.8, # 7h48 = 39h/semaine + "log_level": "DEBUG", # DEBUG, INFO, WARNING ou ERROR "allowed_email_domain": "", # ex: "sdv.fr" — seules ces adresses peuvent se connecter "mail_from": "", # ex: "no-reply@sdv.fr" — expéditeur des emails de connexion "smtp": { diff --git a/app/templates/base.html b/app/templates/base.html index acc83ab..cebd014 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -840,6 +840,82 @@ 50% { transform: scale(1.25); } } + /* ── Login / set-password ── */ + .login-wrap { + display: flex; + justify-content: center; + align-items: flex-start; + padding-top: 4rem; + } + .login-card { + background: var(--surface); + border: 1.5px solid var(--rule); + padding: 2.5rem 3rem; + width: 380px; + max-width: 100%; + display: flex; + flex-direction: column; + gap: 1.5rem; + } + .login-header { display: flex; flex-direction: column; gap: .35rem; } + .login-eyebrow { + font-family: var(--mono); + font-size: 10px; + letter-spacing: .12em; + text-transform: uppercase; + color: var(--muted); + } + .login-title { + font-family: var(--mono); + font-size: 22px; + font-weight: 500; + color: var(--text); + letter-spacing: -.01em; + } + .login-error { + background: var(--neg-bg); + border-left: 3px solid var(--neg); + color: var(--neg); + font-size: 12px; + padding: .6rem .9rem; + font-family: var(--mono); + } + .login-form { display: flex; flex-direction: column; gap: .75rem; } + .login-label { + font-size: 11px; + font-weight: 500; + letter-spacing: .06em; + text-transform: uppercase; + color: var(--muted); + } + .login-input { + font-family: var(--mono); + font-size: 15px; + padding: .6rem .75rem; + border: 1.5px solid var(--rule); + background: var(--ground); + color: var(--text); + outline: none; + transition: border-color .15s; + } + .login-input:focus { border-color: var(--accent); } + .login-hint { font-size: 11px; color: var(--muted); } + .login-btn { + margin-top: .5rem; + font-family: var(--mono); + font-size: 13px; + font-weight: 500; + letter-spacing: .08em; + text-transform: uppercase; + padding: .7rem 1.5rem; + background: var(--text); + color: var(--surface); + border: none; + cursor: pointer; + transition: background .15s; + } + .login-btn:hover { background: var(--accent); } + /* ── Chart zoom modal ── */ .chart-modal { position: fixed; @@ -928,11 +1004,11 @@