feat(maintenance): Ajoute une page et un mode maintenance activable.
```
This commit is contained in:
Serreau Jovann
2026-01-21 14:00:54 +01:00
parent 9597e7ba17
commit e4f1fda1bd
7 changed files with 194 additions and 31 deletions

1
.env
View File

@@ -98,3 +98,4 @@ STRIPE_SECRET_KEY=sk_test_***
###< stripe/stripe-php ###
INTRANET_LOCK=true
TVA_ENABLED=false
MAINTENANCE_ENABLED=false

View File

@@ -1,29 +1,8 @@
# Nom du workflow
name: Symfony CI - Install, Test, Build, Attest & Deploy
# Déclencheurs du workflow
on:
push:
branches:
- master # Ou 'main'
pull_request:
types: [opened, synchronize, reopened]
branches:
- master # Ou 'main'
# Permissions nécessaires pour les actions utilisées
permissions:
contents: read
pull-requests: write
id-token: write
attestations: write
security-events: write # Requis pour Snyk pour poster les résultats
jobs:
deploy:
name: 🚀 Deploy to Production
runs-on: ubuntu-latest # N'oublie pas de préciser l'OS du runner
steps:
- name: Deploy with SSH & Ansible
- name: Deploy with SSH
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.SSH_HOST }}
@@ -31,4 +10,16 @@ jobs:
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: 22
script: |
cd /var/www/ludikevent-intranet && git pull && nohup sh ./update.sh
cd /var/www/ludikevent-intranet
# 1. Activer la maintenance
php bin/console app:maintenance on
# 2. Mise à jour du code
git pull
# 3. Exécuter ton script d'update (migrations, install, etc.)
sh ./update.sh
# 4. Désactiver la maintenance
php bin/console app:maintenance off

View File

@@ -24,3 +24,8 @@ services:
# Utilisation du listener de Nelmio (identifiant officiel)
$cspListener: '@nelmio_security.csp_listener'
App\Security\MaintenanceListener:
arguments:
$isMaintenance: '%env(MAINTENANCE_ENABLED)%'
tags:
- { name: kernel.event_listener, event: kernel.request, priority: 255 }

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
#[AsCommand(
name: 'app:maintenance',
description: 'Active ou désactive le mode maintenance',
)]
class MaintenanceCommand extends Command
{
protected function configure(): void
{
$this->addArgument('status', InputArgument::REQUIRED, 'on ou off');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$status = $input->getArgument('status');
$envFile = dirname(__DIR__, 2) . '/.env.local';
// Si .env.local n'existe pas, on prend le .env
if (!file_exists($envFile)) {
$envFile = dirname(__DIR__, 2) . '/.env';
}
$content = file_get_contents($envFile);
$value = ($status === 'on') ? 'true' : 'false';
// On remplace la valeur de la variable
if (preg_match('/MAINTENANCE_ENABLED=/', $content)) {
$content = preg_replace('/MAINTENANCE_ENABLED=(true|false)/', "MAINTENANCE_ENABLED=$value", $content);
} else {
$content .= "\nMAINTENANCE_ENABLED=$value";
}
file_put_contents($envFile, $content);
// Vider le cache pour appliquer le changement
$io->note("Mise à jour du fichier .env et nettoyage du cache...");
passthru('php bin/console cache:clear');
if ($status === 'on') {
$io->success('Mode maintenance ACTIVÉ ⚙️ (Le site est caché au public)');
} else {
$io->success('Mode maintenance DÉSACTIVÉ ✅ (Le site est en ligne)');
}
return Command::SUCCESS;
}
}

View File

@@ -43,6 +43,8 @@ class IntranetLocked
public function onControl(RequestEvent $requestEvent): void
{
// On ignore également le contrôle des services pour l'IP whitelisted
if ($this->isWhitelisted($requestEvent) || $this->isDebugRoute($requestEvent)) {
return;

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Twig\Environment;
class MaintenanceListener
{
private $twig;
private $isMaintenance;
private $ipsWhiteList;
public function __construct(Environment $twig, string $isMaintenance)
{
$this->twig = $twig;
$this->isMaintenance = filter_var($isMaintenance, FILTER_VALIDATE_BOOLEAN);
}
public function onKernelRequest(RequestEvent $event)
{
// On n'active la maintenance que sur la requête principale (pas les sous-requêtes)
if (!$event->isMainRequest()) {
return;
}
// Si la maintenance est désactivée, on ne fait rien
if (!$this->isMaintenance) {
return;
}
// Si l'IP du visiteur est dans la whitelist, on le laisse passer
// On affiche la page de maintenance
$content = $this->twig->render('security/maintenance.twig');
// On renvoie une réponse 503 (Service Unavailable)
$event->setResponse(new Response($content, 503));
}
}

View File

@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Maintenance en cours - Ludik Event</title>
{{ vite_asset('reserve.js') }}
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700;900&display=swap');
body { font-family: 'Inter', sans-serif; }
</style>
</head>
<body class="bg-slate-50 min-h-screen flex items-center justify-center p-6">
<div class="max-w-3xl w-full text-center">
{# --- BADGE --- #}
<div class="inline-block bg-amber-100 text-amber-600 px-4 py-1.5 rounded-full text-[10px] font-black uppercase tracking-widest mb-8 animate-pulse">
Mise à jour en cours ⚙️
</div>
{# --- TITRE IMPACTANT --- #}
<h1 class="text-6xl md:text-8xl font-black text-slate-900 uppercase italic tracking-tighter leading-[0.85] mb-8">
ON GONFLE <br><span class="text-blue-600">LE SITE !</span>
</h1>
{# --- MESSAGE --- #}
<div class="bg-white p-8 md:p-12 rounded-[3rem] shadow-xl border border-slate-100 relative overflow-hidden">
<p class="text-xl text-slate-600 font-medium italic leading-relaxed relative z-10">
On installe de nouvelles fonctionnalités pour rendre vos réservations encore plus ludiques.
<strong>On revient dans quelques minutes.</strong>
</p>
{# Décoration de fond (forme abstraite) #}
<div class="absolute -right-10 -bottom-10 w-40 h-40 bg-blue-50 rounded-full blur-3xl opacity-50"></div>
</div>
{# --- CONTACT D'URGENCE --- #}
<div class="mt-12 flex flex-col md:flex-row items-center justify-center gap-6">
<div class="flex flex-col">
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-1 text-center md:text-left">Besoin d'aide ?</span>
<a href="tel:0614172447" class="text-2xl font-black text-slate-900 italic hover:text-blue-600 transition-colors">
06 14 17 24 47
</a>
</div>
<div class="hidden md:block w-[1px] h-10 bg-slate-200"></div>
<div class="flex flex-col">
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-1 text-center md:text-left">Par écrit</span>
<a href="mailto:lilian@ludikevent.fr" class="text-2xl font-black text-slate-900 italic hover:text-blue-600 transition-colors">
lilian@ludikevent.fr
</a>
</div>
</div>
{# --- FOOTER --- #}
<p class="mt-16 text-[10px] font-black text-slate-300 uppercase tracking-[0.4em]">
Ludik Event &copy; {{ "now"|date("Y") }} - Hauts-de-France
</p>
</div>
</body>
</html>