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
This commit is contained in:
Jacquin Antoine
2026-07-20 00:09:40 +02:00
parent 1d8a273ffb
commit 55efac9cdd
4 changed files with 67 additions and 3 deletions

View File

@ -595,6 +595,11 @@ def settings_save(
"(défini)" if ntfy_token.strip() else "(vide)",
)
save_notif_config(user_id, ntfy_topic, ntfy_server, rappel_debut_h, rappel_fin_h, ntfy_token)
append_log(
user_id, "settings", "Réglages ntfy",
f"topic={ntfy_topic or '(vide)'} server={ntfy_server} plage={rappel_debut_h}h-{rappel_fin_h}h "
f"jeton={'défini' if ntfy_token.strip() else '(vide)'}",
)
return RedirectResponse("/settings?saved=1", status_code=303)
@ -639,6 +644,7 @@ def settings_regenerate_presence_token(request: Request):
new_token = regenerate_presence_token(user_id)
logger.warning("token de présence régénéré pour %r", user_id)
append_log(user_id, "settings", "Token présence régénéré", "Les URLs Tasker ont été invalidées et remplacées.")
base_url = str(request.base_url).rstrip("/")
arrivee_url = f"{base_url}/presence/{new_token}/arrivee"
@ -691,6 +697,13 @@ def settings_plages_save(
logger.info("réglages plages mis à jour: user_id=%r plages=%r", user_id, fields)
save_plages(user_id, fields)
append_log(
user_id, "settings", "Plages horaires",
f"matin {fields['matin_debut']}{fields['matin_fin']}, "
f"aprem {fields['aprem_debut']}{fields['aprem_fin']}, "
f"reprise {fields['pause_dejeuner_fin']}"
+ (f", visées {fields['arrivee_visee']}{fields['depart_vise']}" if fields['arrivee_visee'] or fields['depart_vise'] else ""),
)
return RedirectResponse("/settings?plages_saved=1", status_code=303)

View File

@ -272,7 +272,7 @@ def toggle_conge(date: str, type_conge: str, user_id: str = ""):
# ── Journaux (notifications + présence) ───────────────────────────────────────
LOG_TYPES = ("notif", "presence")
LOG_TYPES = ("notif", "presence", "settings")
MAX_LOGS = 500

View File

@ -10,7 +10,7 @@
</div>
<div class="logs-tabs" role="tablist">
{% set counts = {'all': logs|length, 'notif': logs | selectattr('type', 'equalto', 'notif') | list | length, 'presence': logs | selectattr('type', 'equalto', 'presence') | list | length} %}
{% set counts = {'all': logs|length, 'notif': logs | selectattr('type', 'equalto', 'notif') | list | length, 'presence': logs | selectattr('type', 'equalto', 'presence') | list | length, 'settings': logs | selectattr('type', 'equalto', 'settings') | list | length} %}
<a class="ltab{% if filter == 'all' %} active{% endif %}" href="/logs" role="tab">
Tout <span class="ltab-count">{{ counts.all }}</span>
</a>
@ -20,6 +20,9 @@
<a class="ltab{% if filter == 'presence' %} active{% endif %}" href="/logs?filter=presence" role="tab">
Présence <span class="ltab-count">{{ counts.presence }}</span>
</a>
<a class="ltab{% if filter == 'settings' %} active{% endif %}" href="/logs?filter=settings" role="tab">
Réglages <span class="ltab-count">{{ counts.settings }}</span>
</a>
</div>
{% set visible = logs if filter == 'all' else logs | selectattr('type', 'equalto', filter) | list %}
@ -29,7 +32,7 @@
{% for entry in visible %}
<div class="log-row log-{{ entry.type }}{% if entry.event == 'échec' %} log-failed{% endif %}">
<div class="log-ts">{{ entry.ts }}</div>
<div class="log-type">{{ 'Notification' if entry.type == 'notif' else 'Présence' }}</div>
<div class="log-type">{{ {'notif': 'Notification', 'presence': 'Présence', 'settings': 'Réglages'}[entry.type] }}</div>
<div class="log-body">
<span class="log-event">{{ entry.event }}</span>
{% if entry.message %}<span class="log-msg">{{ entry.message }}</span>{% endif %}
@ -105,6 +108,7 @@
}
.log-notif .log-type { color: var(--accent); }
.log-presence .log-type { color: var(--accent-2); }
.log-settings .log-type { color: var(--conge-txt); }
.log-body { display: flex; flex-direction: column; gap: .15rem; }
.log-event {
font-weight: 500;

View File

@ -299,3 +299,50 @@ def test_regenerate_preserves_other_notif_fields():
assert after["ntfy_topic"] == "topic-x"
assert after["ntfy_server"] == "https://ntfy.arkel.fr"
assert after["ntfy_token"] == "tk_xyz"
# ── Changements de paramètres tracés dans le journal ─────────────────────────
def test_settings_save_logged():
"""Sauvegarder les réglages ntfy doit écrire une entrée de journal."""
models.save_notif_config("alice", ntfy_topic="t", ntfy_server="https://ntfy.sh")
before = len([l for l in models.load_logs("alice") if l["type"] == "settings"])
client.post("/settings", cookies=COOKIES, data={
"ntfy_topic": "topic-new",
"ntfy_server": "https://ntfy.arkel.fr",
"ntfy_token": "tk_abc",
"rappel_debut_h": "8",
"rappel_fin_h": "19",
})
after = [l for l in models.load_logs("alice") if l["type"] == "settings"]
assert len(after) == before + 1
latest = after[0]
assert latest["event"] == "Réglages ntfy"
assert "topic-new" in latest["message"]
assert "ntfy.arkel.fr" in latest["message"]
def test_settings_plages_save_logged():
"""Sauvegarder les plages horaires doit écrire une entrée de journal."""
before = len([l for l in models.load_logs("alice") if l["type"] == "settings"])
client.post("/settings/plages", cookies=COOKIES, data={
"matin_debut": "09:00", "matin_fin": "12:00",
"aprem_debut": "14:00", "aprem_fin": "17:00",
"pause_dejeuner_fin": "13:30",
"arrivee_visee": "", "depart_vise": "",
})
after = [l for l in models.load_logs("alice") if l["type"] == "settings"]
assert len(after) == before + 1
latest = after[0]
assert latest["event"] == "Plages horaires"
assert "09:00" in latest["message"]
def test_regenerate_presence_token_logged():
"""Régénérer le token de présence doit écrire une entrée de journal."""
before = len([l for l in models.load_logs("alice") if l["type"] == "settings"])
client.post("/settings/regenerate-presence-token", cookies=COOKIES)
after = [l for l in models.load_logs("alice") if l["type"] == "settings"]
assert len(after) == before + 1
latest = after[0]
assert "régénéré" in latest["event"].lower()