Commit Graph

70 Commits

Author SHA1 Message Date
3d38abd853 Astreinte : préférence personnelle et retour aligné sur les quarts d'horloge
- La gestion des astreintes devient un réglage par compte (Réglages →
  « Gestion des astreintes », actif par défaut) : désactivée, le bouton AS,
  les interventions, forfaits, repos de 11h, récapitulatif, ratios, stats
  et exports n'en parlent plus — sans rien supprimer, réactiver retrouve tout
- L'heure de retour au bureau tombe désormais toujours sur un quart d'heure
  d'horloge fixe (:00, :15, :30, :45), arrondie au supérieur après les 11h
  de repos (ex. fin comptée 23:54 → retour 11:00)
- Corrigé : enregistrer ses plages horaires écrasait le forfait d'astreinte

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-09 20:48:59 +02:00
e9983a28c7 Refonte de l'interface : sauvegarde automatique, menu d'état unique et panneau aujourd'hui
- Chaque heure validée enregistre le jour (plus de bouton ✓), avec un témoin
  « enregistré » et conservation du focus entre les champs
- Les boutons MA/AM/J/F/AS deviennent un bouton d'état unique à pastilles
  ouvrant un menu déroulant (congés, férié, astreinte)
- Panneau Aujourd'hui : sortie cible en grand et progression travaillé/dû
  de la semaine, rafraîchis à chaque saisie
- Polish général : coins arrondis, ombres douces, hiérarchie des soldes,
  états de focus visibles

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-09 20:34:27 +02:00
791b48b320 Astreinte : durée réelle arrondie au quart d'heure supérieur
Le décompte ne compte plus les quarts d'horloge (:00, :15, :30, :45)
entamés mais la durée réelle de chaque intervention arrondie au quart
d'heure supérieur (2, 5 ou 12 min → 15 ; 22 ou 28 → 30), même si
l'arrondi ne tombe pas juste sur l'horloge. Les minutes communes à deux
interventions restent comptées une seule fois, le repos de 11h s'appuie
sur la durée arrondie, et l'interface/README expliquent la nouvelle
méthode.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-09 20:12:39 +02:00
5506f1ba39 Le forfait d'astreinte ne compte plus dans les heures travaillées
Le temps travaillé, l'écart du jour et le solde de la semaine ne
restituent plus que les heures pointées. Le forfait des jours AS reste
suivi à part : ligne du jour et récapitulatif d'astreinte, libellés et
documentation ajustés en conséquence.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-09 07:26:28 +02:00
6bd0e91f27 Aère le récapitulatif d'astreinte : un bloc par jour lisible
Chaque jour devient un bloc avec en-tête (jour abrégé, forfait, total du
jour astreinte → pondéré) et interventions indentées dessous ; les jours
forfait-seul tiennent sur une ligne au lieu d'un pseudo-événement.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-08 23:40:03 +02:00
7d1601371c Récapitulatif d'astreinte : indique le forfait de chaque jour
Chaque jour d'astreinte déclaré affiche son forfait dans le récapitulatif,
même sans intervention saisie, et les totaux rappellent la somme des
forfaits — sans la mêler aux compteurs pondérés d'astreinte.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-08 23:35:58 +02:00
dc80e81db2 Ajoute les jours fériés, l'astreinte week-end 0h-0h et les ratios éditables
- Bouton F : marque un jour férié (dû 0, sans consommer de congé) ; il
  qualifie les règles « ferie » et étend la fenêtre d'astreinte du jour.
- Samedi, dimanche et fériés : l'astreinte compte de 0h00 à 0h00 ; un jour
  AS déclaré garde sa borne du lendemain 18h pour ne pas tronquer les
  interventions nocturnes.
- Ratios d'astreinte éditables à la main dans Réglages (nom, jours, fenêtre,
  ratio), avec ajout/suppression de règles et validation.
- Un quart d'heure entamé par deux interventions qui se suivent n'est compté
  qu'une fois : les lignes d'événements somment au total du jour.
- Décompte mieux expliqué dans l'interface : quarts d'horloge (:00/:15/:30/:45),
  fenêtre couverte par événement, exemple chiffré.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-08 23:30:15 +02:00
23271d736d Astreintes : fenêtre 18h-18h, week-end affiché, forfait réglable
Un jour d'astreinte déclaré couvre désormais 18:00 → 18:00 le lendemain :
les interventions hors fenêtre ne comptent ni au décompte ni pour les
11h de repos, et deux astreintes consécutives se recollent sans trou.
La fin de la dernière intervention est arrondie au quart d'heure entamé
avant de poser le retour. Samedi et dimanche apparaissent dans la
semaine (heures dues nulles) pour pouvoir y déclarer des astreintes,
dans les exports aussi. Un réglage par utilisateur ajoute une durée
forfaitaire au temps travaillé de chaque jour d'astreinte déclaré.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-08 23:06:59 +02:00
dc0e786428 Astreintes : sous-ligne pleine largeur sous la ligne du jour
Le bloc astreinte quitte la colonne après-midi (qui retrouvait une
hauteur normale quel que soit le contenu) pour une sous-ligne dédiée
sur toute la largeur du tableau, collée à la carte du jour sur mobile.
La sous-ligne se masque automatiquement quand le jour n'est pas
qualifié, y compris après les rafraîchissements HTMX.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-08 22:48:12 +02:00
83f5512cb6 Astreintes : interventions dans un menu dépliant fermé, cadre élargi
Les heures d'intervention (début, fin, description) passent en trois
colonnes alignées, cachées par défaut derrière un bouton « N
interventions » ; l'heure de retour et le total restent visibles.
L'état déplié survit aux rafraîchissements HTMX. Le conteneur passe
de 1100 à 1400 px pour que les lignes ne dépassent plus du cadre.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-08 22:40:06 +02:00
c208a17eec Décompte les heures d'astreinte par tranches de 15 min dans des compteurs pondérés séparés
La saisie astreinte devient une liste d'événements (début, fin, description,
nombre illimité, fin antérieure au début = intervention à cheval sur minuit).
Ces heures ne comptent jamais dans la semaine : elles sont décomptées à part
par pas de 15 minutes, toute tranche entamée comptant en totalité, et rangées
dans des compteurs selon le jour et l'heure (Nuit ×1,5, Journée semaine ×1,
Samedi ×1,5, Dimanche/férié ×2 ; jours fériés français calculés
automatiquement, règles ajustables dans config.json via astreinte_facteurs).
Le détail par événement et les totaux pondérés s'affichent sous le bandeau de
la semaine, et les exports .ods gagnent les colonnes Astreinte brute et
pondérée. Le retour au bureau (fin du dernier événement + 11h de repos) est
désormais dérivé des événements.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-08 22:26:47 +02:00
065d73e15c Ajoute les astreintes : saisie de la fin de dernière activité, retour au bureau décalé de 11h de repos
Un nouveau bouton AS marque un jour d'astreinte. Ce jour et son lendemain,
une saisie « Fin activité » enregistre l'heure de fin de la dernière
intervention hors horaires (elle peut être le matin si la nuit a dépassé
minuit). L'app en déduit l'heure de retour au bureau (fin + 11h de repos
consécutif) et retire du décompte de la semaine toute la présence avant
cette heure : heures dues réduites, présence pointée avant le retour non
comptée, préconisations Fin cible démarrées à l'heure de retour sans
avertissement de retard, demi-journée entièrement en repos considérée
comme remplie. Les exports .ods gagnent les colonnes Astreinte et Retour,
et les stats n'ont plus les retards liés au repos.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-08 22:05:10 +02:00
c31fcd28ec Met à jour le README : Principe, comptes, pages, présence/ntfy
La section Principe décrivait un rattrapage répandu sur matin+après-midi,
alors qu'il ne touche plus que les départs du soir depuis b9a75ff.
Ajoute aussi ce qui manquait : auth par compte, pages Statistiques/
Journaux/export, webhooks de présence et notifications ntfy, et
force_secure_cookies dans Configuration.
2026-08-20 17:43:21 +02:00
f3670f56fe Header : menu hamburger déroulant sur mobile
Sous 700px les liens de navigation étaient entassés/rognés dans le
bandeau ; ils passent maintenant dans un panneau déroulant ouvert par
un bouton ☰, sans rien changer à l'affichage desktop (les liens
restent des enfants flex directs de <nav> via display:contents).
2026-08-20 17:35:12 +02:00
c89747bda6 Corrige le champ heure : l'icône d'horloge mangeait le dernier chiffre
Champ trop étroit et texte centré : l'icône native (plus large sur
mobile) chevauchait la fin de l'heure saisie. Texte aligné à gauche,
largeur augmentée et padding de l'icône resserré.
2026-08-20 17:29:29 +02:00
7423b68ac9 Rend l'interface utilisable sur téléphone
Le nav et l'en-tête de semaine passent sur plusieurs lignes sous 700px,
le tableau hebdo devient une liste de cartes empilées par jour (au lieu
d'un scroll horizontal forcé), avec des cibles tactiles plus grandes.
Corrige au passage le popup calendrier qui débordait de l'écran sur mobile.
2026-08-20 17:26:59 +02:00
c1c063da51 Réglages: base_url documentée, boutons copier pour les URLs Tasker, saisie horaire native
Documente la clé base_url (schéma/domaine) déjà supportée par config.json pour les
liens email et URLs de présence, et ajoute un bouton "Copier" à côté de chaque URL
arrivée/départ. Passe aussi les champs horaires du tableau hebdomadaire en <input
type="time"> et journalise l'IP source des appels de présence Tasker.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 17:13:18 +02:00
3162968bfb Empêche la cible de proposer un départ déjà passé quand la semaine est en avance
La répartition de l'avance sur les départs du soir ne vérifiait que le
plancher aprem_fin, pas l'heure actuelle : sur une demi-journée en cours,
elle pouvait recommander de partir à une heure déjà écoulée.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 17:22:41 +02:00
205d14416a Merge remote-tracking branch 'origin/main'
# Conflicts:
#	app/calcul.py
2026-08-06 17:18:00 +02:00
b9a75fffea Répartit avance/retard sur les départs du soir, plus jamais sur le matin
Le rattrapage hebdomadaire ne portait que sur le retard et pouvait
s'étaler sur le matin quand il avait de la marge. Objectif demandé :
pouvoir planifier les horaires à l'avance pour arriver à 0 en fin de
semaine, en ajustant uniquement les heures de départ du soir - dans
un sens (retard) comme dans l'autre (avance, plafonnée par aprem_fin).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 17:12:33 +02:00
ae8b2927ef Saisie des heures au clavier uniquement dans le tableau hebdomadaire
Remplace les inputs type=time par des champs texte HH:MM pour supprimer
l'icône d'horloge et le sélecteur natif qui rognaient le texte.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 01:29:44 +02:00
627a247606 Resserre le tableau hebdomadaire pour qu'il tienne dans son cadre
Le tableau débordait horizontalement à cause de la colonne Cible. On force la
largeur à 100%, on réduit les paddings et on laisse le contenu de la cible se
replier sur deux lignes.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 01:26:25 +02:00
02c8eb5e95 Affiche les soldes sur 4 colonnes pour tenir sur une seule ligne
Passe la grille à 4 colonnes et réduit la taille des chiffres pour que le
quota quotidien apparaisse à côté des autres totaux sans repli.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 01:22:46 +02:00
8f91b7ff9a Affiche le quota d'heures dû par jour dans les soldes de la semaine
Rend visible la valeur quotidienne dérivée de la config globale à côté du
total travaillé et du dû hebdomadaire.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 01:21:06 +02:00
1e685ae1ee Règle les heures hebdomadaires et le nombre de jours via la config globale
Le quota quotidien est désormais dérivé d'heures_semaine / jours_par_semaine
au lieu d'être saisi directement. Rétrocompatible avec les anciennes configs
qui utilisent encore heures_jour.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 01:17:40 +02:00
843e2061fe Aligne l'aide et les libellés de l'interface sur Automation
Les pages /aide, /settings et /logs référeçaient encore Tasker (payant) pour
la détection de présence. Mise à jour de la procédure (Wi-Fi ou géofence +
action Trigger URL POST), des libellés et messages, et du test qui vérifiait
la présence du mot « Tasker ».

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 01:04:36 +02:00
0b30dda5e7 Remplace Tasker par Automation (FOSS) dans la doc de rappels
La section Android de docs/notifications.md pointait vers Tasker (payant).
Elle décrit désormais Automation (app FOSS sur F-Droid), avec deux méthodes
de déclenchement — Wi-Fi du bureau (recommandée) et géofence — et l'action
HTTP POST vers les URLs de présence existantes.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 01:04:30 +02:00
7751255963 Ajoute un rate-limiting stateful sur /login et /login/password
Les routes d'authentification n'avaient aucune limite de débit :
- POST /login permettait le mail-bombing SMTP vers n'importe quelle adresse
  du domaine autorisé (CSRF + flood).
- POST /login/password permettait le brute-force d'un mot de passe sans
  verrou (bcrypt est lent mais pas assez pour un attaquant patient).

Ajout d'un compteur stateful en mémoire (_LOGIN_ATTEMPTS), fenêtre
glissante de 60 s, configurable via config.login_rate_limit_per_min
(défaut 10, <= 0 désactive). Clés :
- "login:<ip>" pour POST /login (limite globale par IP)
- "pw:<ip>:<email>" pour POST /login/password (limite par compte)

Au-delà du quota, la route renvoie la page login avec le message
"Trop de tentatives. Réessaie dans une minute." plutôt que d'exécuter
le workflow. La limite par (ip, email) rend le brute-force d'un compte
impossible sans rotation d'IP.

Tests : déclenchement aux limites, indépendance par email, désactivation
via config, et helper main._LOGIN_ATTEMPTS.clear() pour isoler les tests.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 00:38:57 +02:00
ca99ca85a2 Ajoute les en-têtes de sécurité et le drapeau Secure sur le cookie
Le cookie de session était émis sans Secure : en cas de downgrade HTTP ou
de mixed content, il aurait transité en clair. Aucun en-tête de sécurité
n'était positionné (pas de CSP, pas de X-Frame-Options, pas de HSTS), ce
qui laissait le champ libre au clickjacking, aux sniffing MIME et aux
fuites via Referer.

Changements :

1. _set_auth_cookie accepte désormais la request : le drapeau Secure est
   posé automatiquement si scheme == 'https', ou si la nouvelle clé
   config.force_secure_cookies est vraie. En HTTP local (dev/test), le
   cookie reste sans Secure pour ne pas casser les navigateurs.

2. Le middleware CSRF injecte maintenant les en-têtes de sécurité sur
   toutes les réponses :
   - X-Content-Type-Options: nosniff
   - X-Frame-Options: DENY
   - Referrer-Policy: same-origin
   - Content-Security-Policy (default-src 'self', frame-ancestors 'none',
     base-uri 'self', avec autorisation pour Google Fonts et unsafe-inline
     pour les JS/CSS inline des templates semaine/stats ; à durcir plus tard)
   - Strict-Transport-Security (HTTPS uniquement)

Les nouveaux tests vérifient la présence des headers, le HSTS en HTTPS
uniquement, le Secure en HTTPS, l'absence de Secure en HTTP, et l'override
par force_secure_cookies.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 00:35:15 +02:00
c1a2f38044 Convertit les endpoints de présence en POST (anti CSRF trivial)
GET /presence/{token}/arrivee et /depart modifiaient l'état (save_presence,
append_log) : c'est anti-pattern REST, CSRF-trivial via <img src="..."> si
le token fuite, et le token se retrouvait dans les logs reverse-proxy,
l'historique navigateur et les referrers.

Les deux routes passent en POST. C'est un breaking change pour les clients
Tasker actuels, mais Tasker supporte HTTP POST nativement et la doc
docs/notifications.md est mise à jour (Tasker : HTTP Request méthode POST ;
script NetworkManager : curl -X POST).

Les tests existants qui utilisaient client.get(...) sur ces routes passent
en client.post(...). Un nouveau test vérifie que le GET renvoie désormais
405 Method Not Allowed et que le POST continue de fonctionner.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 00:32:50 +02:00
91623d5f98 Ajoute une protection CSRF stateless par vérification de l'Origin
Les routes POST/PUT/DELETE/PATCH n'avaient aucune protection CSRF explicite,
et reposaient uniquement sur le cookie SameSite=Lax (insuffisant pour les
navigateurs anciens). L'endpoint /login était particulièrement exposé car
déclenchable depuis n'importe quel site → mail-bombing SMTP.

Ajout d'un middleware qui, pour toute requête mutative, valide le header
Origin (ou Referer en fallback) quand il est présent : son hostname doit
correspondre au host attendu (config.base_url si définie, sinon le Host de
la requête). Une correspondance absente renvoie 403.

L'absence d'Origin est tolérée pour ne pas casser les clients non-navigateur
(Tasker, curl) qui ne l'envoient pas ; SameSite=Lax reste la protection
primaire pour ces cas.

Les nouveaux tests couvrent le rejet cross-site (Origin et Referer), le
cas légitime (Origin == Host), l'absence d'Origin (acceptée), et
l'immunité des GET.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 00:30:47 +02:00
c86e1aaa13 Bloque l'open redirect / phishing via le header Host
POST /login construisait le lien set-password envoyé par email avec
request.base_url, qui reflète directement le header Host contrôlable par le
client. Un attaquant pouvait envoyer POST /login avec Host: phishing.attacker
et déclencher l'envoi d'un email légitime contenant un lien vers son site,
avec un token réellement valide : phishing direct du mot de passe.

Ajout de main._safe_base_url(request) qui :
- utilise config.base_url si définie (nouvelle clé, schéma http/https requis) ;
- sinon accepte le Host seulement s'il correspond à allowed_email_domain
  (suffixe strict avec un point) ou à localhost/127.0.0.1/::1 (dev local) ;
- sinon retourne None.

POST /login refuse désormais l'envoi du mail si le Host n'est pas validé
(message "Domaine non autorisé"). Les routes /settings (affichage des URLs
Tasker à l'utilisateur lui-même) utilisent le helper avec fallback sur
request.base_url, car le HTML n'est pas envoyé par email.

data/config.example.json documente la nouvelle clé base_url. Les tests
mockent send_mail et vérifient : rejet d'un Host arbitraire, acceptation
d'un host suffixe du domaine autorisé, acceptation de localhost, primauté
de config.base_url sur le Host, et cas unitaires du helper.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 00:29:02 +02:00
dda8a83114 Bloque le XSS stocké via /pointage et échappe les fragments HTMX
POST /pointage acceptait n'importe quelle valeur pour les 4 champs
matin_entree/matin_sortie/aprem_entree/aprem_sortie : la fonction clean ne
faisait qu'un strip(). Comme ces valeurs sont réinjectées telles quelles
dans des f-strings HTML hors templates Jinja (qui seuls bénéficient de
l'autoescape), un payload type `"><svg/onload=...>` déclenchait une
exécution JS immédiate dans la réponse HTMX, sans CSP pour limiter l'impact.

Double défense appliquée :

1. Validation stricte en entrée : clean() refuse désormais tout ce qui ne
   matche pas _HHMM_RE (^([01]\d|2[0-3]):[0-5]\d$). Une valeur non conforme
   est traitée comme vide plutôt que stockée.
2. Échappement HTML systématique en sortie : un alias `_e = html.escape`
   est appliqué à toutes les valeurs dynamiques (date, heures, cibles)
   dans _oob_calc, _oob_week_extras, _oob_time_cells et
   _htmx_conge_and_soldes. Les ids, data-date, value, hx-post et contenus
   textuels sont protégés.

Les nouveaux tests test_security couvrent plusieurs payloads XSS, vérifient
que la valeur n'est ni persistée ni reflétée, et qu'un pointage HHMM
valide continue à fonctionner.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 00:26:01 +02:00
0329359072 Bloque le SSRF via ntfy_server par validation stricte
Le champ ntfy_server du formulaire /settings était utilisé tel quel dans
urllib.request.urlopen sans aucune validation. Un attaquant pouvait pointer
le serveur vers 169.254.169.254 (metadata AWS/GCP), le réseau interne, ou un
service protégé, et comme la reminder_loop relance l'URL toutes les 5 min le
SSRF devenait persistant. Le header Authorization étant aussi contrôlable,
des requêtes authentifiées vers des services internes étaient possibles.

Ajout de notifications.is_safe_ntfy_server() qui valide : schéma http(s),
absence de credentials embarqués, refus de localhost et de toutes les IP
littérales privées (loopback, link-local, 10/8, 172.16/12, 192.168/16,
fc00::/7, ::1, multicast, réservé). send_ntfy et POST /settings appellent
ce validateur avant toute ouverture de connexion ou persistance ; un serveur
refusé déclenche une redirection /settings?ssrf_error=1 côté UI.

Les tests simulant un échec réseau (127.0.0.1:1) passent sur un host public
injoignable (.invalid), et un nouveau bloc test_security couvre les cas
loopback/private/bad-scheme/credentials et la non-ouverture de socket vers
une IP privée.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 00:23:20 +02:00
8dc2a3fa4c Sécurise le cookie de session par signature HMAC
Le cookie user_id était lu tel quel depuis la requête, ce qui permettait à
n'importe qui de forger `user_id=victime` dans DevTools/curl et d'accéder à
toutes les routes "authentifiées" (lecture/écriture des pointages, export,
régénération des tokens Tasker, etc.). Le mot de passe bcrypt ne servait
donc à rien.

Le cookie est maintenant au format `<user_id>.<hmac_sha256_hex>` signé avec
une clé persistée dans /data/secret.key (auto-générée, stable entre
redémarrages). La vérification utilise hmac.compare_digest pour éviter les
fuites par timing. Toutes les routes appellent _get_user qui valide la
signature à chaque requête.

Les tests de test_logs.py récupèrent désormais le cookie réellement signé
par le serveur, et un nouveau module test_security.py couvre le rejet des
cookies forgés/tamponnés/rejoués.

Le DATA_DIR temporaire des tests est centralisé dans conftest.py pour éviter
les collisions entre modules de test.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 00:20:50 +02:00
55efac9cdd Trace les changements de réglages dans le journal utilisateur
Toute modification des paramètres (topic/serveur/jeton ntfy, plages
horaires, régénération du token de présence Tasker) est désormais
enregistrée dans /logs sous un nouveau type "Réglages", avec un onglet
dédié. Chaque entrée résume ce qui a changé (valeurs des plages, plage
des rappels, topic, jeton défini/vide, URLs invalidées), pour aider à
diagnostiquer a posteriori un problème de configuration.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 00:09:40 +02:00
1d8a273ffb Ajoute la régénèse du token de présence Tasker et élargit le bandeau de test ntfy
Le bouton "Régénérer le token" de la carte Tasker crée un nouveau token
secret et invalide immédiatement les anciennes URLs, au cas où elles
auraient fuité. La fonction regenerate_presence_token préserve les
autres réglages notif (topic, serveur, jeton d'accès, plage). Au passage
le message de retour du bouton de test ntfy occupe désormais toute la
largeur de la carte au lieu d'être limité à 420px.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 00:05:34 +02:00
076b5a4fa2 Ajoute le support d'un jeton d'accès pour les serveurs ntfy protégés
Certains serveurs ntfy auto-hébergés (ex: ntfy.arkel.fr) renvoient
403 Forbidden sans authentification. Un nouveau champ "Jeton d'accès"
(optionnel, type password) sur /settings permet de renseigner un token
qui sera envoyé comme "Authorization: Bearer ..." à chaque publication,
pour les rappels réels comme pour le bouton de test. La page /aide
documente le cas 403 et la marche à suivre.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-20 00:01:12 +02:00
2ee317a46e Ajoute un bouton de test de la notification ntfy depuis les réglages
Permet de vérifier en un clic que le serveur et le topic ntfy
renseignés fonctionnent, avant d'attendre un vrai rappel. Le bouton
envoie une notification de test via la même chaîne que les rappels
réels (donc tracée dans /logs), et affiche le résultat inline.
La fonction send_ntfy retourne désormais un booléen de succès pour
permettre ce retour utilisateur.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-19 23:57:48 +02:00
f82f443ec7 Ajoute une page d'aide en ligne pour configurer ntfy et Tasker
La nouvelle route /aide regroupe dans l'app les étapes d'installation
de l'app ntfy (Play Store/F-Droid, abonnement au topic, piège
optimisation batterie, option auto-hébergé) et de Tasker (permissions
localisation, profils Enter/Exit avec HTTP Request), plus une section
vérification via /logs et un dépannage. Elle est accessible depuis la
nav et depuis les deux cartes ntfy/Tasker de /settings. Les 11 tests
de test_logs.py couvrent en plus les routes /logs et /aide, le logging
présence, l'isolation par user et la rotation.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-19 23:51:07 +02:00
5fdef77e2b Ajoute un journal par utilisateur des notifications ntfy et de la présence Tasker
Chaque utilisateur a désormais son propre fichier logs.json dans son
dossier users/<id>/, alimenté automatiquement quand un rappel ntfy est
envoyé (ou échoue) et quand Tasker signale une arrivée ou un départ.
Une nouvelle page /logs, accessible depuis la nav, affiche ces entrées
avec filtres (Tout / Notifications / Présence), et le journal tourne à
500 entrées pour rester compact.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-19 23:46:08 +02:00
ef30820c39 Propose systématiquement les horaires du matin et de l'après-midi
La colonne Cible affichait un horaire seulement pour les demi-journées
avec du rattrapage à faire ; les demi-journées au rythme normal
n'affichaient rien. Désormais chaque demi-journée restante (matin et
après-midi) propose sa plage entrée → sortie, qu'il y ait du retard ou
non, pour donner un planning complet à l'utilisateur.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-19 23:37:17 +02:00
27bcd7a0b4 Affiche l'heure d'arrivée en plus du départ dans les préconisations
La colonne "Cible" (anciennement "Fin cible") montre désormais une plage
complète entrée → sortie pour chaque demi-journée à rattraper, au lieu
d'une heure de départ seule. Moins ambigu pour l'utilisateur qui sait
aussi quand arriver.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-19 23:25:16 +02:00
d3c991f8d6 Respecte la pause déjeuner dans les préconisations et ajoute les heures visées
La cible matin ne dépasse plus jamais matin_fin, garantissant la pause de
midi ; le nominal qui déborde bascule sur l'après-midi du même jour.
Ajoute deux réglages optionnels par utilisateur (arrivée visée le matin,
départ visé le soir) qui orientent la répartition du rattrapage en deux
passes : d'abord dans le cadre visé, puis au-delà si nécessaire.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 11:57:46 +02:00
45790b2b95 export 2026-07-17 23:52:41 +02:00
5e9d974875 calcul des heures 2026-07-17 23:51:32 +02:00
12666e73f4 Corrige le double comptage des demi-journées en cours et plafonne les cibles matin
Trois corrections sur les préconisations :
- Une demi-journée en cours (entrée pointée sans sortie, aujourd'hui) était
  comptée deux fois — temps écoulé dans le travaillé ET nominal complet comme
  capacité restante — ce qui faisait disparaître le rattrapage pendant la
  journée et produisait des cibles en manque du temps déjà écoulé. Le temps
  écoulé est réintégré au besoin planifié.
- La cible matin est plafonnée à la reprise de l'après-midi ; un créneau
  saturé reporte son surplus sur les créneaux encore ouverts (waterfill)
  au lieu d'afficher une sortie matin absurde.
- Une semaine entièrement pointée mais en déficit affiche désormais un
  bandeau "Semaine terminée avec -X de manque" au lieu de rien.
2026-07-17 23:33:01 +02:00
436f923148 Corrige le décompte des demi-journées restantes et le rendu des champs horaires
Une demi-journée passée non renseignée était exclue à tort du calcul de
rattrapage (l'appli permet de saisir ses heures après coup, un jour passé
vide n'est pas une perte définitive) : une semaine entièrement vide affichait
"reste à faire ... sur 2 demi-journées" au lieu de 10. Corrige aussi le
chevauchement de l'icône d'horloge sur les champs <input type="time"> de la
page Réglages (largeur trop juste pour le texte + l'icône native).
2026-07-17 23:21:55 +02:00
d87bf6a212 Rend les plages horaires configurables par utilisateur plutôt que globalement
Chaque compte a désormais son propre data/users/<user>/plages.json, réglable
depuis une nouvelle carte "Plages horaires" sur /settings (avec validation
HH:MM et ordre matin < aprem). Le calcul, les stats de conformité et les
avertissements de saisie lisent la config de l'utilisateur courant au lieu
du plages global. La valeur globale de data/config.json reste lue comme
valeur de repli tant qu'un utilisateur n'a rien personnalisé.
2026-07-17 23:15:06 +02:00
dd8848a115 Met à jour le README pour la répartition des préconisations par demi-journée
Documente le nouveau comportement (répartition sur les demi-journées restantes
plutôt que report en fin de journée) et la ligne "Reste à faire" du bandeau.
2026-07-17 23:05:48 +02:00