feat: suppression individuelle de logs + trace obligatoire des suppressions
src/Controller/Admin/LogsController.php:
- purge(): compte les logs avant suppression, supprime tous les logs,
puis cree un nouveau log via logDirect() avec le message
"Suppression de tous les logs (X entrees supprimees)" pour garder
une trace de la purge meme apres suppression
- delete(): nouvelle route POST /admin/logs/{id}/delete, supprime un
log individuel puis cree un log de trace avec le message
"Suppression du log #X (action du date)" pour conserver l'historique
src/Service/AppLoggerService.php:
- logDirect(): nouvelle methode qui cree un AppLog avec une action
personnalisee sans passer par le dictionnaire ROUTE_LABELS
(utilisee pour les traces de suppression)
templates/admin/logs/index.html.twig:
- Bouton supprimer (croix rouge) ajoute a cote du bouton PDF
sur chaque ligne du tableau, avec confirmation data-confirm
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,18 +2,18 @@
|
||||
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Entity\AppLog;
|
||||
use App\Entity\User;
|
||||
use App\Repository\AppLogRepository;
|
||||
use App\Service\AppLoggerService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Dompdf\Dompdf;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
#[Route('/admin/logs', name: 'app_admin_logs')]
|
||||
@@ -35,7 +35,6 @@ class LogsController extends AbstractController
|
||||
20,
|
||||
);
|
||||
|
||||
// Verifier le HMAC de chaque log
|
||||
$hmacResults = [];
|
||||
foreach ($pagination as $log) {
|
||||
$hmacResults[$log->getId()] = $loggerService->verifyLog($log);
|
||||
@@ -48,14 +47,57 @@ class LogsController extends AbstractController
|
||||
}
|
||||
|
||||
#[Route('/purge', name: '_purge', methods: ['POST'])]
|
||||
public function purge(EntityManagerInterface $em): Response
|
||||
public function purge(EntityManagerInterface $em, AppLoggerService $loggerService): Response
|
||||
{
|
||||
$deleted = $em->createQueryBuilder()
|
||||
$count = (int) $em->createQueryBuilder()
|
||||
->select('COUNT(l.id)')
|
||||
->from('App\Entity\AppLog', 'l')
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
|
||||
$em->createQueryBuilder()
|
||||
->delete('App\Entity\AppLog', 'l')
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$this->addFlash('success', $deleted.' log(s) supprime(s).');
|
||||
$user = $this->getUser();
|
||||
$loggerService->logDirect(
|
||||
'DELETE',
|
||||
'/admin/logs/purge',
|
||||
'app_admin_logs_purge',
|
||||
'Suppression de tous les logs ('.$count.' entrees supprimees)',
|
||||
$user instanceof User ? $user : null,
|
||||
);
|
||||
|
||||
$this->addFlash('success', $count.' log(s) supprime(s).');
|
||||
|
||||
return $this->redirectToRoute('app_admin_logs');
|
||||
}
|
||||
|
||||
#[Route('/{id}/delete', name: '_delete', methods: ['POST'])]
|
||||
public function delete(int $id, AppLogRepository $repository, EntityManagerInterface $em, AppLoggerService $loggerService): Response
|
||||
{
|
||||
$log = $repository->find($id);
|
||||
|
||||
if (null === $log) {
|
||||
throw $this->createNotFoundException('Log introuvable.');
|
||||
}
|
||||
|
||||
$logInfo = 'Suppression du log #'.$log->getId().' ('.$log->getAction().' du '.$log->getCreatedAt()->format('d/m/Y H:i:s').')';
|
||||
|
||||
$em->remove($log);
|
||||
$em->flush();
|
||||
|
||||
$user = $this->getUser();
|
||||
$loggerService->logDirect(
|
||||
'DELETE',
|
||||
'/admin/logs/'.$id.'/delete',
|
||||
'app_admin_logs_delete',
|
||||
$logInfo,
|
||||
$user instanceof User ? $user : null,
|
||||
);
|
||||
|
||||
$this->addFlash('success', 'Log #'.$id.' supprime.');
|
||||
|
||||
return $this->redirectToRoute('app_admin_logs');
|
||||
}
|
||||
|
||||
@@ -66,6 +66,17 @@ class AppLoggerService
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Log direct avec action personnalisee (pour les suppressions de logs).
|
||||
*/
|
||||
public function logDirect(string $method, string $url, string $route, string $action, ?User $user = null, ?string $ip = null): void
|
||||
{
|
||||
$log = new AppLog($method, $url, $route, $action, $this->hmacSecret, $user, $ip);
|
||||
|
||||
$this->em->persist($log);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
public function verifyLog(AppLog $log): bool
|
||||
{
|
||||
return $log->verifyHmac($this->hmacSecret);
|
||||
|
||||
@@ -58,12 +58,17 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-3 py-2 text-center">
|
||||
<a href="{{ path('app_admin_logs_pdf', {id: log.id}) }}" class="btn-glass px-2 py-1 text-[9px] font-bold uppercase tracking-wider text-gray-600" target="_blank">PDF</a>
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
<a href="{{ path('app_admin_logs_pdf', {id: log.id}) }}" class="btn-glass px-2 py-1 text-[9px] font-bold uppercase tracking-wider text-gray-600" target="_blank">PDF</a>
|
||||
<form method="post" action="{{ path('app_admin_logs_delete', {id: log.id}) }}" data-confirm="Supprimer le log #{{ log.id }} ?">
|
||||
<button type="submit" class="btn-glass px-2 py-1 text-[9px] font-bold uppercase tracking-wider text-red-600" style="border-color: rgba(220,38,38,0.2);">✗</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="8" class="px-4 py-8 text-center text-gray-400 font-bold">Aucun log enregistre.</td>
|
||||
<td colspan="9" class="px-4 py-8 text-center text-gray-400 font-bold">Aucun log enregistre.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
Reference in New Issue
Block a user