feat(ansible/caddy): Supprime CSP statique et Permissions-Policy obsolète
🐛 fix(assets/admin): Corrige la gestion du menu admin et des flashs
 feat(Twig/ViteAssetExtension): Ajoute CSP nonce et gère les favicons
🐛 fix(Entity/AuditLog): Corrige la relation ManyToOne avec Account
 feat: Ajoute NelmioSecurityBundle pour gérer la sécurité CSP
```
This commit is contained in:
Serreau Jovann
2026-01-15 20:35:46 +01:00
parent 2aa0ce5c1e
commit 75c419ba06
12 changed files with 387 additions and 115 deletions

View File

@@ -1,76 +1,121 @@
import './admin.scss'
import * as Turbo from "@hotwired/turbo"
// Cette fonction initialise tous les écouteurs d'événements
/**
* Initialise les composants de l'interface d'administration.
* Cette fonction est appelée à chaque chargement de page par Turbo.
*/
function initAdminLayout() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebar-overlay');
const toggleBtn = document.getElementById('sidebar-toggle');
const settingsToggle = document.getElementById('settings-toggle');
const settingsSubmenu = document.getElementById('settings-submenu');
const settingsChevron = document.getElementById('settings-chevron');
// --- TOGGLE SIDEBAR MOBILE ---
// --- 1. GESTION DE LA SIDEBAR (MOBILE) ---
if (toggleBtn && sidebar && overlay) {
// On clone pour éviter de doubler les events avec Turbo
toggleBtn.replaceWith(toggleBtn.cloneNode(true));
const newToggleBtn = document.getElementById('sidebar-toggle');
newToggleBtn.addEventListener('click', () => {
toggleBtn.onclick = () => {
sidebar.classList.toggle('-translate-x-full');
overlay.classList.toggle('hidden');
});
};
overlay.addEventListener('click', () => {
overlay.onclick = () => {
sidebar.classList.add('-translate-x-full');
overlay.classList.add('hidden');
});
};
}
// --- GESTION SOUS-MENU PARAMÈTRES ---
if (settingsToggle) {
settingsToggle.replaceWith(settingsToggle.cloneNode(true));
const newSettingsToggle = document.getElementById('settings-toggle');
// --- 2. GESTION DU DROPDOWN (PARAMÈTRES) ---
if (settingsToggle && settingsSubmenu) {
const settingsChevron = settingsToggle.querySelector('svg:last-child');
newSettingsToggle.addEventListener('click', (e) => {
/**
* Alterne l'état du dropdown avec une animation de glissement.
* @param {boolean} show - Forcer l'ouverture ou la fermeture
* @param {boolean} animate - Activer ou non la transition CSS
*/
const toggleDropdown = (show, animate = true) => {
if (!animate) settingsSubmenu.style.transition = 'none';
if (show) {
settingsSubmenu.classList.remove('hidden');
// scrollHeight permet de calculer la hauteur réelle du contenu
settingsSubmenu.style.maxHeight = settingsSubmenu.scrollHeight + "px";
settingsChevron?.classList.add('rotate-180');
localStorage.setItem('admin_settings_open', 'true');
} else {
settingsSubmenu.style.maxHeight = "0px";
settingsChevron?.classList.remove('rotate-180');
localStorage.setItem('admin_settings_open', 'false');
// On cache l'élément après l'animation pour l'accessibilité
if (animate) {
setTimeout(() => {
if (settingsSubmenu.style.maxHeight === "0px") {
settingsSubmenu.classList.add('hidden');
}
}, 300);
} else {
settingsSubmenu.classList.add('hidden');
}
}
if (!animate) {
// Forcer un recalcul pour réactiver la transition proprement
settingsSubmenu.offsetHeight;
settingsSubmenu.style.transition = '';
}
};
// Événement de clic
settingsToggle.onclick = (e) => {
e.preventDefault();
if (settingsSubmenu) settingsSubmenu.classList.toggle('hidden');
if (settingsChevron) settingsChevron.classList.toggle('rotate-180');
});
const isClosed = settingsSubmenu.style.maxHeight === "0px" || settingsSubmenu.classList.contains('hidden');
toggleDropdown(isClosed);
};
// Persistance : Garder ouvert si on est dans une sous-route admin
if (window.location.pathname.includes('administrateur')) {
if (settingsSubmenu) settingsSubmenu.classList.remove('hidden');
if (settingsChevron) settingsChevron.classList.add('rotate-180');
// --- PERSISTANCE ---
// On vérifie si on est sur une page appartenant au menu ou si l'utilisateur l'avait laissé ouvert
const isSettingsRoute = window.location.pathname.includes('/crm/administrateur') ||
window.location.pathname.includes('/crm/logs');
const wasOpen = localStorage.getItem('admin_settings_open') === 'true';
if (isSettingsRoute || wasOpen) {
toggleDropdown(true, false); // Ouverture immédiate sans animation
}
// --- HIGHLIGHT DU LIEN ACTIF ---
settingsSubmenu.querySelectorAll('a').forEach(link => {
if (window.location.pathname === link.getAttribute('href')) {
link.classList.add('text-blue-600', 'dark:text-blue-400', 'font-semibold');
link.classList.remove('text-slate-500');
}
});
}
// --- GESTION DES MESSAGES FLASH (Auto-suppression 10s) ---
const flashes = document.querySelectorAll('.flash-message');
flashes.forEach((flash) => {
// Supprime le message après 10 secondes
// --- 3. GESTION DES MESSAGES FLASH (Auto-suppression) ---
document.querySelectorAll('.flash-message').forEach((flash) => {
setTimeout(() => {
// Animation de sortie
flash.classList.add('opacity-0', 'translate-x-10');
// Retrait du DOM après l'animation
setTimeout(() => flash.remove(), 500);
}, 10000);
}, 8000);
});
}
// --- CORRECTIF DATA-TURBO-CONFIRM ---
// Force l'affichage de la confirmation native sur les liens avec data-turbo-confirm
// Turbo 7+ intercepte les clics, on réimplémente une confirmation native simple
document.addEventListener("turbo:click", (event) => {
const message = event.target.getAttribute("data-turbo-confirm");
const message = event.target.closest("[data-turbo-confirm]")?.getAttribute("data-turbo-confirm");
if (message && !confirm(message)) {
event.preventDefault();
}
});
// S'exécute au premier chargement ET à chaque navigation Turbo
// Exécution au chargement initial et à chaque navigation Turbo
document.addEventListener('turbo:load', initAdminLayout);
// Fermer la sidebar mobile avant que Turbo ne mette en cache la page
// Nettoyage avant la mise en cache de Turbo (évite les bugs visuels au retour arrière)
document.addEventListener('turbo:before-cache', () => {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebar-overlay');