Add app:infra:snapshot command, page reads from var/infra.json

The /admin/infra page was slow because Docker stats API blocks per container.
Now a cron (every 5min) generates var/infra.json via app:infra:snapshot,
and the page reads the static JSON file instantly.
Mount Docker socket in cron container for snapshot access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-03-26 11:19:26 +01:00
parent 7a370b1e02
commit d2fb17bf50
6 changed files with 78 additions and 2 deletions

View File

@@ -228,6 +228,13 @@
job: "docker compose -f /var/www/e-ticket/docker-compose-prod.yml exec -T php php bin/console app:stripe:sync --env=prod >> /var/log/e-ticket-stripe-sync.log 2>&1"
user: bot
- name: Configure infra snapshot cron (every 5 minutes)
cron:
name: "e-ticket infra snapshot"
minute: "*/5"
job: "docker compose -f /var/www/e-ticket/docker-compose-prod.yml exec -T php php bin/console app:infra:snapshot --env=prod >> /var/log/e-ticket-infra.log 2>&1"
user: bot
post_tasks:
- name: Disable maintenance mode
command: make maintenance_off

View File

@@ -179,8 +179,11 @@ services:
dockerfile: Dockerfile
container_name: e_ticket_cron
restart: unless-stopped
group_add:
- "${DOCKER_GID:-989}"
volumes:
- .:/app
- /var/run/docker.sock:/var/run/docker.sock:ro
depends_on:
pgbouncer:
condition: service_healthy

View File

@@ -2,3 +2,4 @@
0 * * * * echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] START app:monitor:messenger" >> /proc/1/fd/1 && php /app/bin/console app:monitor:messenger --env=dev >> /proc/1/fd/1 2>&1 && echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] END app:monitor:messenger" >> /proc/1/fd/1
0 3 * * * echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] START app:meilisearch:check-consistency" >> /proc/1/fd/1 && php /app/bin/console app:meilisearch:check-consistency --fix --env=dev >> /proc/1/fd/1 2>&1 && echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] END app:meilisearch:check-consistency" >> /proc/1/fd/1
0 */6 * * * echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] START app:stripe:sync" >> /proc/1/fd/1 && php /app/bin/console app:stripe:sync --env=dev >> /proc/1/fd/1 2>&1 && echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] END app:stripe:sync" >> /proc/1/fd/1
*/5 * * * * php /app/bin/console app:infra:snapshot --env=dev >> /proc/1/fd/1 2>&1

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Command;
use App\Service\InfraService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
/**
* @codeCoverageIgnore Infrastructure snapshot — reads Docker socket, /proc, network
*/
#[AsCommand(
name: 'app:infra:snapshot',
description: 'Generate infrastructure stats snapshot to var/infra.json',
)]
class InfraSnapshotCommand extends Command
{
public function __construct(
private InfraService $infra,
#[Autowire('%kernel.project_dir%')] private string $projectDir,
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$data = $this->infra->getAll();
$data['generated_at'] = (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM);
$path = $this->projectDir.'/var/infra.json';
file_put_contents($path, json_encode($data, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE));
$output->writeln(sprintf('Snapshot written to %s (%d bytes)', $path, filesize($path)));
return Command::SUCCESS;
}
}

View File

@@ -649,9 +649,21 @@ class AdminController extends AbstractController
}
#[Route('/infra', name: 'app_admin_infra', methods: ['GET'])]
public function infra(\App\Service\InfraService $infra): Response
public function infra(#[Autowire('%kernel.project_dir%')] string $projectDir): Response
{
return $this->render('admin/infra.html.twig', $infra->getAll());
$path = $projectDir.'/var/infra.json';
if (!file_exists($path)) {
return $this->render('admin/infra.html.twig', [
'snapshot_missing' => true,
'server' => [], 'containers' => [], 'redis_global' => ['connected' => false],
'redis_dbs' => [], 'postgres' => ['connected' => false], 'pgbouncer' => ['connected' => false],
'generated_at' => null,
]);
}
$data = json_decode(file_get_contents($path), true) ?? [];
return $this->render('admin/infra.html.twig', $data);
}
#[Route('/organisateurs/inviter', name: 'app_admin_invite_organizer', methods: ['GET', 'POST'])]

View File

@@ -3,6 +3,19 @@
{% block title %}Infrastructure{% endblock %}
{% block body %}
{% if snapshot_missing is defined and snapshot_missing %}
<div class="admin-card mb-4">
<p class="text-sm font-bold text-yellow-600">Aucun snapshot disponible. Lancez <code class="bg-gray-100 px-1">php bin/console app:infra:snapshot</code></p>
</div>
{% endif %}
{% if generated_at is defined and generated_at %}
<div class="flex items-center justify-between mb-4">
<p class="text-[10px] font-black uppercase tracking-widest text-gray-400">Derniere mise a jour : {{ generated_at|date('d/m/Y H:i:s') }}</p>
<a href="{{ path('app_admin_infra') }}" class="text-[10px] font-black uppercase tracking-widest text-gray-400 hover:text-gray-600">Rafraichir</a>
</div>
{% endif %}
<div class="grid grid-cols-1 xl:grid-cols-4 gap-4 min-h-[calc(100vh-80px)]">
{# ── COL 1: Serveur ────────────────────────────────── #}