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:
13
app/main.py
13
app/main.py
@ -595,6 +595,11 @@ def settings_save(
|
|||||||
"(défini)" if ntfy_token.strip() else "(vide)",
|
"(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)
|
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)
|
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)
|
new_token = regenerate_presence_token(user_id)
|
||||||
logger.warning("token de présence régénéré pour %r", 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("/")
|
base_url = str(request.base_url).rstrip("/")
|
||||||
arrivee_url = f"{base_url}/presence/{new_token}/arrivee"
|
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)
|
logger.info("réglages plages mis à jour: user_id=%r plages=%r", user_id, fields)
|
||||||
save_plages(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)
|
return RedirectResponse("/settings?plages_saved=1", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -272,7 +272,7 @@ def toggle_conge(date: str, type_conge: str, user_id: str = ""):
|
|||||||
|
|
||||||
# ── Journaux (notifications + présence) ───────────────────────────────────────
|
# ── Journaux (notifications + présence) ───────────────────────────────────────
|
||||||
|
|
||||||
LOG_TYPES = ("notif", "presence")
|
LOG_TYPES = ("notif", "presence", "settings")
|
||||||
MAX_LOGS = 500
|
MAX_LOGS = 500
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="logs-tabs" role="tablist">
|
<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">
|
<a class="ltab{% if filter == 'all' %} active{% endif %}" href="/logs" role="tab">
|
||||||
Tout <span class="ltab-count">{{ counts.all }}</span>
|
Tout <span class="ltab-count">{{ counts.all }}</span>
|
||||||
</a>
|
</a>
|
||||||
@ -20,6 +20,9 @@
|
|||||||
<a class="ltab{% if filter == 'presence' %} active{% endif %}" href="/logs?filter=presence" role="tab">
|
<a class="ltab{% if filter == 'presence' %} active{% endif %}" href="/logs?filter=presence" role="tab">
|
||||||
Présence <span class="ltab-count">{{ counts.presence }}</span>
|
Présence <span class="ltab-count">{{ counts.presence }}</span>
|
||||||
</a>
|
</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>
|
</div>
|
||||||
|
|
||||||
{% set visible = logs if filter == 'all' else logs | selectattr('type', 'equalto', filter) | list %}
|
{% set visible = logs if filter == 'all' else logs | selectattr('type', 'equalto', filter) | list %}
|
||||||
@ -29,7 +32,7 @@
|
|||||||
{% for entry in visible %}
|
{% for entry in visible %}
|
||||||
<div class="log-row log-{{ entry.type }}{% if entry.event == 'échec' %} log-failed{% endif %}">
|
<div class="log-row log-{{ entry.type }}{% if entry.event == 'échec' %} log-failed{% endif %}">
|
||||||
<div class="log-ts">{{ entry.ts }}</div>
|
<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">
|
<div class="log-body">
|
||||||
<span class="log-event">{{ entry.event }}</span>
|
<span class="log-event">{{ entry.event }}</span>
|
||||||
{% if entry.message %}<span class="log-msg">{{ entry.message }}</span>{% endif %}
|
{% if entry.message %}<span class="log-msg">{{ entry.message }}</span>{% endif %}
|
||||||
@ -105,6 +108,7 @@
|
|||||||
}
|
}
|
||||||
.log-notif .log-type { color: var(--accent); }
|
.log-notif .log-type { color: var(--accent); }
|
||||||
.log-presence .log-type { color: var(--accent-2); }
|
.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-body { display: flex; flex-direction: column; gap: .15rem; }
|
||||||
.log-event {
|
.log-event {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|||||||
@ -299,3 +299,50 @@ def test_regenerate_preserves_other_notif_fields():
|
|||||||
assert after["ntfy_topic"] == "topic-x"
|
assert after["ntfy_topic"] == "topic-x"
|
||||||
assert after["ntfy_server"] == "https://ntfy.arkel.fr"
|
assert after["ntfy_server"] == "https://ntfy.arkel.fr"
|
||||||
assert after["ntfy_token"] == "tk_xyz"
|
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()
|
||||||
|
|||||||
Reference in New Issue
Block a user