Corrige le rendu de la page mot de passe, masque la nav si non connecté,

réduit le score spam des emails, et rend le niveau de log configurable

- 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).
This commit is contained in:
Antoine
2026-07-17 22:16:48 +02:00
parent 2d4179169b
commit 0b47e4a625
7 changed files with 104 additions and 84 deletions

View File

@ -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).

View File

@ -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",

View File

@ -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")

View File

@ -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": {

View File

@ -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 @@
<nav>
<a href="/" class="nav-brand"><span class="nav-dot"></span>Décompte Horaire</a>
<span class="spacer"></span>
{% if current_user is defined and current_user %}
<a href="/" class="nav-link">Semaine courante</a>
<a href="/stats" class="nav-link">Statistiques</a>
<a href="/classement" class="nav-link">Classement</a>
<a href="/settings" class="nav-link">Réglages</a>
{% if current_user is defined and current_user %}
<span class="nav-user">{{ current_user }}</span>
<a href="/logout" class="nav-link nav-logout">Déconnexion</a>
{% endif %}

View File

@ -53,81 +53,4 @@
{% endif %}
</div>
</div>
<style>
.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); }
</style>
{% endblock %}

View File

@ -1,5 +1,6 @@
{
"heures_jour": 7.8,
"log_level": "DEBUG",
"allowed_email_domain": "exemple.fr",
"mail_from": "no-reply@exemple.fr",
"smtp": {