fix(audit) : eviter les echecs silencieux du journal d'audit
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 1m38s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 10m14s

- log + toast d'erreur dans le catch de loadEntries : evite qu'une RangeError
  de toIso (date invalide) ou une 500 API laisse l'utilisateur devant une
  table vide indistinguable d'un filtre a zero resultat
- onMounted : Promise.all pour charger entity types et liste en parallele
  (TTFD divise par 2 sur backend lent), comportement promis par le commentaire
- filtre dates : :max sur Du et :min sur Au empechent une plage inversee a la
  saisie (le backend renvoyait 0 ligne sans diag)
- i18n : audit.error.title / message
This commit is contained in:
2026-05-28 14:16:53 +02:00
parent 5c4bc32827
commit e611f4c65a
2 changed files with 35 additions and 16 deletions
+4
View File
@@ -85,6 +85,10 @@
}, },
"empty": "Aucune activité enregistrée", "empty": "Aucune activité enregistrée",
"no_results": "Aucun résultat pour ces filtres", "no_results": "Aucun résultat pour ces filtres",
"error": {
"title": "Erreur",
"message": "Impossible de charger le journal d'audit. Vérifiez les filtres ou réessayez."
},
"timeline": { "timeline": {
"empty": "Aucun historique", "empty": "Aucun historique",
"load_more": "Voir plus" "load_more": "Voir plus"
+31 -16
View File
@@ -68,9 +68,17 @@
<MalioAccordionItem :title="t('audit.filters.date_range')" value="dates"> <MalioAccordionItem :title="t('audit.filters.date_range')" value="dates">
<div class="grid grid-cols-[auto_1fr] items-center gap-x-3 gap-y-4"> <div class="grid grid-cols-[auto_1fr] items-center gap-x-3 gap-y-4">
<span>{{ t('audit.filters.date_from') }}</span> <span>{{ t('audit.filters.date_from') }}</span>
<MalioDateTime v-model="draftDateFrom" /> <!-- Borne le picker "Du" par la valeur "Au" pour interdire une plage
inversee a la saisie (le backend renverrait silencieusement 0 ligne). -->
<MalioDateTime
v-model="draftDateFrom"
:max="draftDateTo ?? undefined"
/>
<span>{{ t('audit.filters.date_to') }}</span> <span>{{ t('audit.filters.date_to') }}</span>
<MalioDateTime v-model="draftDateTo" /> <MalioDateTime
v-model="draftDateTo"
:min="draftDateFrom ?? undefined"
/>
</div> </div>
</MalioAccordionItem> </MalioAccordionItem>
@@ -161,6 +169,7 @@ import type { AuditLogEntry, AuditLogFilters } from '~/shared/types'
const { t, te } = useI18n() const { t, te } = useI18n()
const { can } = usePermissions() const { can } = usePermissions()
const { fetchLogsCached, fetchEntityTypes } = useAuditLog() const { fetchLogsCached, fetchEntityTypes } = useAuditLog()
const toast = useToast()
// Traduit un identifiant `module.Entity` (ex: `core.User`, `sites.Site`) en // Traduit un identifiant `module.Entity` (ex: `core.User`, `sites.Site`) en
// libelle lisible via la cle i18n `audit.entity.<module>_<entity>`. Si aucune // libelle lisible via la cle i18n `audit.entity.<module>_<entity>`. Si aucune
@@ -333,13 +342,19 @@ async function loadEntries(): Promise<void> {
if (token !== requestToken) return if (token !== requestToken) return
entries.value = data.member ?? [] entries.value = data.member ?? []
totalItems.value = data.totalItems ?? 0 totalItems.value = data.totalItems ?? 0
} catch { } catch (err) {
// En cas d'echec (reseau, 403, 500...), on reset l'etat pour ne pas // useAuditLog appelle useApi avec { toast: false } pour ne pas multiplier
// laisser l'utilisateur croire que les donnees affichees sont a jour. // les toasts, donc c'est ici qu'on fait remonter l'erreur. Sans ce log+toast,
// Le toast d'erreur est deja emis par `useApi()` via useAuditLog. // une RangeError de `toIso` (date invalide) ou une 500 API laissait l'utilisateur
// devant une table vide indistinguable d'un filtre a zero resultat.
if (token === requestToken) { if (token === requestToken) {
entries.value = [] entries.value = []
totalItems.value = 0 totalItems.value = 0
console.error('[audit-log] loadEntries failed', err)
toast.error({
title: t('audit.error.title'),
message: t('audit.error.message'),
})
} }
} finally { } finally {
if (token === requestToken) { if (token === requestToken) {
@@ -397,15 +412,15 @@ function onPerPageChange(value: number): void {
} }
onMounted(async () => { onMounted(async () => {
// Charge les entity types en parallele de la liste principale : un // Charge les entity types ET la liste principale en parallele (TTFD divise
// echec du premier endpoint (ex: reseau flaky) ne doit pas empecher // par 2 sur un backend lent). Le `.catch` du premier garantit qu'un echec
// le tableau d'audit de s'afficher. En cas d'erreur, on laisse le // de /audit-log-entity-types ne bloque pas l'affichage du tableau —
// filtre vide — l'utilisateur pourra quand meme consulter le journal. // l'utilisateur perd juste le filtre, pas la page entiere.
try { await Promise.all([
entityTypes.value = await fetchEntityTypes() fetchEntityTypes()
} catch { .then(types => { entityTypes.value = types })
entityTypes.value = [] .catch(() => { entityTypes.value = [] }),
} loadEntries(),
await loadEntries() ])
}) })
</script> </script>