Files
ludikevent_crm/templates/dashboard/audit_logs.twig
Serreau Jovann aba456e5ca ```
 feat(caddy): Améliore la sécurité avec CSP et headers standards

 feat(templates): Met à jour le logo sur la page d'inscription réussie

 feat(knp_paginator): Ajoute la configuration pour le style Tailwind

 feat(audit_logs): Crée la page de traçabilité des actions

 feat(logs): Ajoute le contrôleur pour gérer les logs d'audit

 feat(AppLogger): Enregistre l'user agent dans les logs d'audit

 feat(AccountController): Supprime l'appel inutile de l'EventAdminCreate

 feat(AuditLogRepository): Récupère les logs en excluant les ROOT

 feat(base): Ajoute la structure de base pour le dashboard
```
2026-01-15 20:08:04 +01:00

140 lines
11 KiB
Twig

{% extends 'dashboard/base.twig' %}
{% block title %}Traçabilité des actions{% endblock %}
{# BOUTON EXTRACTION DANS LE HEADER #}
{% block actions %}
<a href="{{ path('app_crm_audit_logs', {extract: true}) }}" target="_blank" class="flex items-center space-x-2 px-5 py-2.5 bg-emerald-600 hover:bg-emerald-700 text-white text-sm font-bold rounded-xl transition-all shadow-lg shadow-emerald-500/20 group">
<svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<span>Exporter XLSX</span>
</a>
{% endblock %}
{% block body %}
<div class="w-full bg-white dark:bg-[#1e293b] rounded-2xl border border-slate-200 dark:border-slate-800 shadow-sm overflow-hidden">
{# Header du tableau avec statistiques rapides #}
<div class="px-8 py-6 border-b border-slate-100 dark:border-slate-800 flex items-center justify-between bg-slate-50/30 dark:bg-slate-800/30">
<div>
<h2 class="text-xl font-bold text-slate-800 dark:text-white tracking-tight">Journal d'Audit</h2>
<p class="text-xs text-slate-500 dark:text-slate-400 mt-1">Historique complet des interactions sur l'Intranet</p>
</div>
<div class="flex items-center">
<span class="px-4 py-1.5 bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 text-[10px] font-black uppercase rounded-lg border border-blue-100 dark:border-blue-800/50">
{{ logs.getTotalItemCount }} ENREGISTREMENTS
</span>
</div>
</div>
<div class="overflow-x-auto custom-scrollbar">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-slate-50/50 dark:bg-slate-900/40 border-b border-slate-100 dark:border-slate-800">
<th class="px-8 py-4 text-[11px] font-bold text-slate-400 uppercase tracking-[0.2em]">Horodatage</th>
<th class="px-8 py-4 text-[11px] font-bold text-slate-400 uppercase tracking-[0.2em]">Administrateur & Appareil</th>
<th class="px-8 py-4 text-[11px] font-bold text-slate-400 uppercase tracking-[0.2em] text-center">Action</th>
<th class="px-8 py-4 text-[11px] font-bold text-slate-400 uppercase tracking-[0.2em]">Détails de l'activité</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 dark:divide-slate-800">
{% for log in logs %}
<tr class="hover:bg-slate-50/50 dark:hover:bg-slate-800/30 transition-colors duration-150">
{# 1. DATE & HEURE #}
<td class="px-8 py-4 whitespace-nowrap align-top">
<div class="text-sm font-bold text-slate-700 dark:text-slate-200">{{ log.actionAt|date('d/m/Y') }}</div>
<div class="text-[10px] text-slate-400 font-mono mt-0.5 tracking-wider">{{ log.actionAt|date('H:i:s') }}</div>
</td>
{# 2. ADMINISTRATEUR + USER AGENT #}
<td class="px-8 py-4 whitespace-nowrap align-top">
<div class="flex items-start">
<div class="h-10 w-10 rounded-xl bg-gradient-to-br from-blue-500 to-indigo-600 flex flex-shrink-0 items-center justify-center text-white font-bold text-xs shadow-md mt-0.5">
{{ log.account.firstName|first|upper }}{{ log.account.name|first|upper }}
</div>
<div class="ml-4">
<div class="text-sm font-bold text-slate-800 dark:text-white flex items-center mb-0.5">
{{ log.account.firstName }} {{ log.account.name }}
{% if 'ROLE_ROOT' in log.account.roles %}
<span class="ml-2 px-1.5 py-0.5 rounded text-[8px] bg-red-600/10 text-red-600 dark:bg-red-500/20 dark:text-red-400 font-black uppercase tracking-tighter border border-red-200 dark:border-red-900/50">Root</span>
{% endif %}
</div>
<div class="text-[11px] text-slate-400 dark:text-slate-500 mb-2 font-medium">{{ log.account.email }}</div>
{# Bloc User Agent avec icône SVG dynamique #}
{% if log.userAgent %}
{% set ua = log.userAgent|lower %}
<div class="flex items-center text-[10px] text-slate-500 dark:text-slate-400 bg-slate-100/50 dark:bg-slate-900/50 px-2 py-1.5 rounded-lg border border-slate-200/50 dark:border-slate-800 max-w-[260px] group/ua">
<span class="mr-2 flex-shrink-0">
{% if 'firefox' in ua %}
<svg class="w-3.5 h-3.5 text-orange-500" fill="currentColor" viewBox="0 0 24 24"><path d="M23.9 12c0 6.6-5.4 12-11.9 12C5.4 24 0 18.6 0 12S5.4 0 12 0c6.5 0 11.9 5.4 11.9 12zM10.8 4.7c-.5.1-1.3.4-1.3.4s.8-.2 1.3-.3c1.5-.4 3.1-.2 4.4.6 1.3.7 2.2 2 2.5 3.4.1.7.1 1.5-.1 2.2-.2 1-1.2 2.2-1.2 2.2s.8-.9 1-1.8c.3-1.4-.1-2.9-1.2-4-1.1-1.1-2.6-1.6-4.2-1.4-1.4.1-2.1.8-2.6 1.4-.5.6-.7 1.4-.6 2.1.1.7.5 1.4 1.1 1.8.6.4 1.3.5 2 .4.7-.1 1.3-.5 1.7-1.1.4-.6.5-1.3.3-2-.1-.7-.5-1.3-1-1.7s-1.2-.5-1.9-.4c-.7.1-1.3.5-1.7 1.1-.3.5-.4 1.1-.3 1.7.1.5.3.9.7 1.2.4.3.8.4 1.3.3.5-.1.9-.3 1.2-.7.2-.4.3-.8.2-1.3-.1-.4-.3-.7-.6-.9-.3-.2-.6-.3-1-.2-.3 0-.6.1-.8.4-.2.2-.3.5-.2.8.1.3.3.5.6.6.3.1.6 0 .8-.2.2-.2.3-.4.2-.7 0-.3-.2-.5-.5-.6-.2-.1-.5 0-.7.2s-.3.4-.2.7z"/></svg>
{% elseif 'edg/' in ua %}
<svg class="w-3.5 h-3.5 text-blue-500" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm0 18.75c-3.728 0-6.75-3.022-6.75-6.75s3.022-6.75 6.75-6.75 6.75 3.022 6.75 6.75-3.022 6.75-6.75 6.75z"/></svg>
{% elseif 'chrome' in ua %}
<svg class="w-3.5 h-3.5 text-blue-400" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0C8.21 0 4.83 1.75 2.64 4.5l3.96 6.84A6.004 6.004 0 0 1 12 6h10.36A12.012 12.012 0 0 0 12 0zm-1.04 13.5l-5.12-8.88A11.936 11.936 0 0 0 0 12c0 6.07 4.51 11.08 10.36 11.92l3.96-6.84a6.012 6.012 0 0 1-3.36-3.58zm12.4-7.5H12a6.002 6.002 0 0 1 3.36 9.42l-5.12 8.88C10.74 23.9 11.36 24 12 24c6.63 0 12-5.37 12-12 0-2.12-.55-4.12-1.52-5.88z"/></svg>
{% elseif 'safari' in ua and 'chrome' not in ua %}
<svg class="w-3.5 h-3.5 text-sky-600" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"/><path d="M14.5 9.5L9.5 14.5M14.5 9.5L12 3M14.5 9.5L21 12M9.5 14.5L12 21M9.5 14.5L3 12"/></svg>
{% else %}
<svg class="w-3.5 h-3.5 text-slate-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
{% endif %}
</span>
<span class="truncate font-mono opacity-80" title="{{ log.userAgent }}">
{{ log.userAgent }}
</span>
</div>
{% endif %}
</div>
</div>
</td>
{# 3. BADGE ACTION #}
<td class="px-8 py-4 whitespace-nowrap text-center align-top">
{% set typeStyles = {
'CREATE': 'bg-emerald-500/10 text-emerald-600 border-emerald-500/20',
'DELETE': 'bg-rose-500/10 text-rose-600 border-rose-500/20',
'VIEW': 'bg-sky-500/10 text-sky-600 border-sky-500/20',
'AUTH': 'bg-amber-500/10 text-amber-600 border-amber-500/20'
} %}
<span class="px-3 py-1.5 rounded-lg text-[10px] font-black border uppercase tracking-widest {{ typeStyles[log.type] ?? 'bg-slate-500/10 text-slate-500 border-slate-500/20' }}">
{{ log.type }}
</span>
</td>
{# 4. MESSAGE ET CHEMIN #}
<td class="px-8 py-4 align-top">
<div class="text-sm text-slate-600 dark:text-slate-300 font-medium leading-relaxed max-w-2xl">{{ log.message }}</div>
<div class="mt-2 flex items-center">
<span class="text-[9px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-tighter mr-2">URL :</span>
<code class="text-[10px] bg-slate-100 dark:bg-slate-900 px-2 py-0.5 rounded text-blue-500 dark:text-blue-400 font-mono">
{{ log.path }}
</code>
</div>
</td>
</tr>
{% else %}
<tr>
<td colspan="4" class="px-8 py-12 text-center">
<div class="text-slate-400 dark:text-slate-600 italic">Aucun log trouvé dans cette période.</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{# FOOTER & PAGINATION #}
<div class="px-8 py-6 bg-slate-50/50 dark:bg-slate-900/40 border-t border-slate-100 dark:border-slate-800">
<div class="flex flex-col md:flex-row items-center justify-between gap-6">
<div class="text-xs font-semibold text-slate-400 uppercase tracking-widest">
Page {{ logs.getCurrentPageNumber }} — Affichage de {{ logs|length }} logs
</div>
<div class="navigation shadow-sm rounded-xl overflow-hidden">
{{ knp_pagination_render(logs) }}
</div>
</div>
</div>
</div>
{% endblock %}