✨ feat(templates): Améliore la lisibilité et l'esthétique de l'interface
Ce commit met à jour les couleurs et les styles de texte dans plusieurs
templates pour améliorer la lisibilité et l'esthétique globale de
l'interface utilisateur. Les couleurs de texte secondaires sont
ajustées pour un meilleur contraste.
```
204 lines
15 KiB
Twig
204 lines
15 KiB
Twig
{% extends 'dashboard/base.twig' %}
|
|
|
|
{% block title %}Traçabilité des actions{% endblock %}
|
|
{% block title_header %}Journal d' <span class="text-blue-500">Audit</span>{% endblock %}
|
|
|
|
{# HEADER : ACTIONS GLOBALES #}
|
|
{% block actions %}
|
|
<div class="flex items-center space-x-3">
|
|
{# Bouton Exporter XLSX #}
|
|
<a data-turbo="false" href="{{ path('app_crm_audit_logs', {extract: true, account: app.request.query.get('account')}) }}" 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>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block body %}
|
|
<div class="space-y-6">
|
|
|
|
{# BARRE DE FILTRES #}
|
|
{# BARRE DE FILTRES #}
|
|
<div class="backdrop-blur-xl bg-white/5 border border-white/5 p-6 rounded-[2rem] shadow-sm mb-8">
|
|
<form method="GET" action="{{ path('app_crm_audit_logs') }}" class="flex flex-col md:flex-row items-end gap-5">
|
|
|
|
{# 1. SELECT COMPTE #}
|
|
<div class="flex-1 min-w-[240px]">
|
|
<label for="account" class="block text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] mb-2 ml-2">Filtrer par compte</label>
|
|
<div class="relative">
|
|
<select name="account" id="account" class="w-full bg-slate-900/50 border border-white/10 rounded-xl px-4 py-3.5 text-sm text-white focus:border-blue-500 focus:ring-0 outline-none transition-all appearance-none cursor-pointer">
|
|
<option value="">Tous les utilisateurs</option>
|
|
{% for u in users_list %}
|
|
<option value="{{ u.id }}" {{ app.request.query.get('account') == u.id ? 'selected' : '' }}>
|
|
{{ u.firstName }} {{ u.name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
<div class="absolute inset-y-0 right-4 flex items-center pointer-events-nonetext-slate-300">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# 2. BOUTON SUBMIT (APPLIQUER) #}
|
|
<div class="w-full md:w-auto">
|
|
<button type="submit" class="w-full md:w-auto flex items-center justify-center space-x-2 px-8 py-3.5 bg-blue-600 hover:bg-blue-500 text-white rounded-xl shadow-lg shadow-blue-600/20 transition-all duration-300 group">
|
|
<svg class="w-4 h-4 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="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
|
</svg>
|
|
<span class="text-[10px] font-black uppercase tracking-[0.2em]">Filtrer</span>
|
|
</button>
|
|
</div>
|
|
|
|
{# 3. BOUTON RESET (Si un filtre est actif) #}
|
|
{% if app.request.query.get('account') %}
|
|
<div class="w-full md:w-auto">
|
|
<a data-turbo="false" href="{{ path('app_crm_audit_logs') }}" class="flex items-center justify-center px-4 py-3.5 text-slate-400 hover:text-rose-500 text-[10px] font-black uppercase tracking-widest transition-colors group">
|
|
<svg class="w-4 h-4 mr-2 group-hover:rotate-90 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
</svg>
|
|
Réinitialiser
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
</form>
|
|
</div>
|
|
{# TABLEAU DES LOGS #}
|
|
<div class="backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] shadow-2xl overflow-hidden">
|
|
|
|
{# HEADER TABLEAU #}
|
|
<div class="px-8 py-6 border-b border-white/5 flex items-center justify-between bg-white/5">
|
|
<div>
|
|
<h2 class="text-xl font-bold text-white tracking-tight">Historique d'activité</h2>
|
|
<p class="text-[10px] text-slate-300 font-bold uppercase tracking-widest mt-1">Sécurisé par signature cryptographique SHA-256</p>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<span class="px-4 py-1.5 bg-blue-500/10 text-blue-400 text-[10px] font-black uppercase rounded-lg border border-blue-500/20">
|
|
{{ 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-900/40 border-b border-white/5">
|
|
<th class="px-8 py-5 text-[11px] font-black text-slate-300 uppercase tracking-[0.2em]">Horodatage</th>
|
|
<th class="px-8 py-5 text-[11px] font-black text-slate-300 uppercase tracking-[0.2em]">Agent & Session</th>
|
|
<th class="px-8 py-5 text-[11px] font-black text-slate-300 uppercase tracking-[0.2em] text-center">Action</th>
|
|
<th class="px-8 py-5 text-[11px] font-black text-slate-300 uppercase tracking-[0.2em] text-center">Intégrité</th>
|
|
<th class="px-8 py-5 text-[11px] font-black text-slate-300 uppercase tracking-[0.2em]">Détails</th>
|
|
{% if is_granted('ROLE_ROOT') %}
|
|
<th class="px-8 py-5 text-[11px] font-black text-slate-300 uppercase tracking-[0.2em] text-right">Admin</th>
|
|
{% endif %}
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-white/5">
|
|
{% for log in logs %}
|
|
<tr class="hover:bg-white/5 transition-colors duration-150 group">
|
|
{# 1. DATE #}
|
|
<td class="px-8 py-6 whitespace-nowrap align-top">
|
|
<div class="text-sm font-bold text-white">{{ log.actionAt|date('d/m/Y') }}</div>
|
|
<div class="text-[10px] text-slate-300 font-mono mt-0.5 tracking-wider">{{ log.actionAt|date('H:i:s') }}</div>
|
|
</td>
|
|
|
|
{# 2. ADMIN & UA #}
|
|
<td class="px-8 py-6 whitespace-nowrap align-top">
|
|
<div class="flex items-start">
|
|
<div class="h-10 w-10 rounded-xl bg-blue-600 flex flex-shrink-0 items-center justify-center text-white font-black text-xs shadow-lg shadow-blue-600/20 group-hover:scale-110 transition-transform">
|
|
{{ log.account.firstName|first|upper }}{{ log.account.name|first|upper }}
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-sm font-bold 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-rose-500/10 text-rose-500 font-black uppercase border border-rose-500/20">Root</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="text-[10px] text-slate-300 font-medium truncate max-w-[180px]">{{ log.userAgent|default('Agent inconnu') }}</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
|
|
{# 3. TYPE D'ACTION (Mapping INFO inclus) #}
|
|
<td class="px-8 py-6 whitespace-nowrap text-center align-top">
|
|
{% set typeMapping = {
|
|
'CREATE': { 'label': 'CRÉATION', 'style': 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20' },
|
|
'DELETE': { 'label': 'SUPPRESSION', 'style': 'bg-rose-500/10 text-rose-500 border-rose-500/20' },
|
|
'UPDATE': { 'label': 'MODIFICATION', 'style': 'bg-blue-500/10 text-blue-500 border-blue-500/20' },
|
|
'AUTH': { 'label': 'CONNEXION', 'style': 'bg-amber-500/10 text-amber-500 border-amber-500/20' },
|
|
'VIEW': { 'label': 'CONSULTATION', 'style': 'bg-sky-500/10 text-sky-500 border-sky-500/20' },
|
|
'INFO': { 'label': 'INFO', 'style': 'bg-slate-500/10 text-slate-400 border-slate-500/20' },
|
|
'SECURITY_ALERT': { 'label': 'ALERTE', 'style': 'bg-orange-500/10 text-orange-500 border-orange-500/20' },
|
|
'SECURITY_CRITICAL': { 'label': 'CRITIQUE', 'style': 'bg-red-600 text-white border-red-700 animate-pulse font-black' },
|
|
'2FA_INVITE': { 'label': '2FA INVITE', 'style': 'bg-indigo-500/10 text-indigo-400 border-indigo-500/20' },
|
|
'2FA_DISABLED': { 'label': '2FA OFF', 'style': 'bg-rose-500/10 text-rose-500 border-rose-500/20 font-bold' }
|
|
} %}
|
|
{% set config = typeMapping[log.type] ?? { 'label': log.type, 'style': 'bg-slate-500/10 text-slate-300 border-white/10' } %}
|
|
<span class="px-3 py-1.5 rounded-lg text-[9px] font-black border uppercase tracking-[0.15em] {{ config.style }}">
|
|
{{ config.label }}
|
|
</span>
|
|
</td>
|
|
|
|
{# 4. INTÉGRITÉ #}
|
|
<td class="px-8 py-6 whitespace-nowrap align-top text-center">
|
|
{% if log.hashCode == log.generateSignature %}
|
|
<div class="inline-flex flex-col items-center text-emerald-500" title="Signature Valide">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7"/></svg>
|
|
<span class="text-[7px] font-black uppercase mt-1">Scellé</span>
|
|
</div>
|
|
{% else %}
|
|
<div class="inline-flex flex-col items-center text-rose-500 animate-bounce" title="Données corrompues !">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24"><path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
|
|
<span class="text-[7px] font-black uppercase mt-1">Altéré</span>
|
|
</div>
|
|
{% endif %}
|
|
</td>
|
|
|
|
{# 5. DÉTAILS #}
|
|
<td class="px-8 py-6 align-top">
|
|
<div class="text-sm text-slate-300 font-medium leading-relaxed max-w-lg mb-2">{{ log.message }}</div>
|
|
<code class="text-[9px] bg-black/40 px-2 py-1 rounded text-blue-400 font-mono border border-white/5">{{ log.path }}</code>
|
|
</td>
|
|
|
|
{# 6. ACTIONS ROOT #}
|
|
{% if is_granted('ROLE_ROOT') %}
|
|
<td class="px-8 py-6 whitespace-nowrap text-right align-top">
|
|
<form action="{{ path('app_crm_audit_logs_delete', {id: log.id}) }}" method="POST" onsubmit="return confirm('Confirmer la suppression définitive de cet enregistrement ?');">
|
|
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ log.id) }}">
|
|
<button type="submit" class="p-2 text-slate-300 hover:text-rose-500 hover:bg-rose-500/10 rounded-lg transition-all">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
|
</button>
|
|
</form>
|
|
</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="6" class="px-8 py-20 text-center italic text-slate-300 font-medium">
|
|
Aucun enregistrement trouvé dans le journal d'audit.
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{# PAGINATION #}
|
|
<div class="px-8 py-8 bg-black/20 border-t border-white/5">
|
|
<div class="flex flex-col md:flex-row items-center justify-between gap-6">
|
|
<div class="text-[10px] font-black text-slate-300 uppercase tracking-[0.2em]">
|
|
Page {{ logs.getCurrentPageNumber }} sur {{ (logs.getTotalItemCount / 25)|round(0, 'ceil') }}
|
|
</div>
|
|
<div class="navigation custom-pagination">
|
|
{{ knp_pagination_render(logs) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|