```
✨ feat(maintenance): Ajoute une page et un mode maintenance activable.
```
This commit is contained in:
1
.env
1
.env
@@ -98,3 +98,4 @@ STRIPE_SECRET_KEY=sk_test_***
|
|||||||
###< stripe/stripe-php ###
|
###< stripe/stripe-php ###
|
||||||
INTRANET_LOCK=true
|
INTRANET_LOCK=true
|
||||||
TVA_ENABLED=false
|
TVA_ENABLED=false
|
||||||
|
MAINTENANCE_ENABLED=false
|
||||||
|
|||||||
@@ -1,34 +1,25 @@
|
|||||||
# Nom du workflow
|
deploy:
|
||||||
name: Symfony CI - Install, Test, Build, Attest & Deploy
|
name: 🚀 Deploy to Production
|
||||||
|
runs-on: ubuntu-latest # N'oublie pas de préciser l'OS du runner
|
||||||
|
steps:
|
||||||
|
- name: Deploy with SSH
|
||||||
|
uses: appleboy/ssh-action@v1.0.0
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.SSH_HOST }}
|
||||||
|
username: ${{ secrets.SSH_USER }}
|
||||||
|
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
port: 22
|
||||||
|
script: |
|
||||||
|
cd /var/www/ludikevent-intranet
|
||||||
|
|
||||||
# Déclencheurs du workflow
|
# 1. Activer la maintenance
|
||||||
on:
|
php bin/console app:maintenance on
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master # Ou 'main'
|
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened]
|
|
||||||
branches:
|
|
||||||
- master # Ou 'main'
|
|
||||||
|
|
||||||
# Permissions nécessaires pour les actions utilisées
|
# 2. Mise à jour du code
|
||||||
permissions:
|
git pull
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
id-token: write
|
|
||||||
attestations: write
|
|
||||||
security-events: write # Requis pour Snyk pour poster les résultats
|
|
||||||
|
|
||||||
jobs:
|
# 3. Exécuter ton script d'update (migrations, install, etc.)
|
||||||
deploy:
|
sh ./update.sh
|
||||||
name: 🚀 Deploy to Production
|
|
||||||
steps:
|
# 4. Désactiver la maintenance
|
||||||
- name: Deploy with SSH & Ansible
|
php bin/console app:maintenance off
|
||||||
uses: appleboy/ssh-action@v1.0.0
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.SSH_HOST }}
|
|
||||||
username: ${{ secrets.SSH_USER }}
|
|
||||||
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
|
||||||
port: 22
|
|
||||||
script: |
|
|
||||||
cd /var/www/ludikevent-intranet && git pull && nohup sh ./update.sh
|
|
||||||
|
|||||||
@@ -24,3 +24,8 @@ services:
|
|||||||
# Utilisation du listener de Nelmio (identifiant officiel)
|
# Utilisation du listener de Nelmio (identifiant officiel)
|
||||||
$cspListener: '@nelmio_security.csp_listener'
|
$cspListener: '@nelmio_security.csp_listener'
|
||||||
|
|
||||||
|
App\Security\MaintenanceListener:
|
||||||
|
arguments:
|
||||||
|
$isMaintenance: '%env(MAINTENANCE_ENABLED)%'
|
||||||
|
tags:
|
||||||
|
- { name: kernel.event_listener, event: kernel.request, priority: 255 }
|
||||||
|
|||||||
59
src/Command/MaintenanceCommand.php
Normal file
59
src/Command/MaintenanceCommand.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,6 +43,8 @@ class IntranetLocked
|
|||||||
|
|
||||||
public function onControl(RequestEvent $requestEvent): void
|
public function onControl(RequestEvent $requestEvent): void
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
// On ignore également le contrôle des services pour l'IP whitelisted
|
// On ignore également le contrôle des services pour l'IP whitelisted
|
||||||
if ($this->isWhitelisted($requestEvent) || $this->isDebugRoute($requestEvent)) {
|
if ($this->isWhitelisted($requestEvent) || $this->isDebugRoute($requestEvent)) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
40
src/Security/MaintenanceListener.php
Normal file
40
src/Security/MaintenanceListener.php
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
65
templates/security/maintenance.twig
Normal file
65
templates/security/maintenance.twig
Normal 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 © {{ "now"|date("Y") }} - Hauts-de-France
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user