🐛 fix(error): Affiche l'exception sur la page d'erreur 500

This commit is contained in:
Serreau Jovann
2026-02-06 14:20:28 +01:00
parent 4fa43333c5
commit 3d017c70be
5 changed files with 191 additions and 5 deletions

View File

@@ -104,3 +104,14 @@ export class UtmEvent extends HTMLElement {
}
}
}
// Chargement automatique d'Umami si consentement
if (sessionStorage.getItem('ldk_cookie') === 'accepted') {
if (!document.querySelector('script[src="https://tools-security.esy-web.dev/script.js"]')) {
const script = document.createElement('script');
script.defer = true;
script.src = "https://tools-security.esy-web.dev/script.js";
script.setAttribute('data-website-id', "38d713c3-3923-4791-875a-dfe5f45372c3");
document.head.appendChild(script);
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace App\Command;
use Doctrine\ORM\EntityManagerInterface;
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\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'app:db:reset-sequences',
description: 'Resets all PostgreSQL sequences to the max ID of their tables.',
)]
class ResetSequencesCommand extends Command
{
public function __construct(
private readonly EntityManagerInterface $entityManager
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$conn = $this->entityManager->getConnection();
// 1. Fetch all sequences linked to tables
$sql = "
SELECT
t.relname as table_name,
a.attname as column_name,
s.relname as sequence_name
FROM pg_class s
JOIN pg_depend d ON d.objid = s.oid
JOIN pg_class t ON d.refobjid = t.oid
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = d.refobjsubid
WHERE s.relkind = 'S';
";
try {
$sequences = $conn->fetchAllAssociative($sql);
} catch (\Exception $e) {
$io->error('Error fetching sequences: ' . $e->getMessage());
return Command::FAILURE;
}
$io->title('Resetting Database Sequences');
$io->progressStart(count($sequences));
$count = 0;
foreach ($sequences as $seq) {
$table = $seq['table_name'];
$column = $seq['column_name'];
$sequence = $seq['sequence_name'];
// 2. Get Max ID
// Using quote_identifier for safety
$maxSql = sprintf(
'SELECT MAX(%s) FROM %s',
$conn->quoteIdentifier($column),
$conn->quoteIdentifier($table)
);
$maxId = $conn->fetchOne($maxSql);
$maxId = $maxId ? (int)$maxId : 0;
// 3. Reset Sequence
// setval(sequence, next_value) -> we set it to maxId so next is maxId+1?
// Postgres setval(seq, val, true) sets current value. Next will be val+1.
// If table is empty (maxId=0), we can set to 1 (is_called=false) or just 1.
if ($maxId > 0) {
$conn->executeQuery(sprintf("SELECT setval('%s', %d)", $sequence, $maxId));
} else {
// Table empty, reset to 1
$conn->executeQuery(sprintf("SELECT setval('%s', 1, false)", $sequence));
}
$io->progressAdvance();
$count++;
}
$io->progressFinish();
$io->success(sprintf('Successfully reset %d sequences.', $count));
return Command::SUCCESS;
}
}

View File

@@ -44,6 +44,11 @@ class HomeController extends AbstractController
'customers' => $this->customerRepository->count([]),
'nbVisitor' => $stats['visitors'],
'nbView' => $stats['views'],
'source_google' => $stats['sources']['google'] ?? 0,
'source_facebook' => $stats['sources']['facebook'] ?? 0,
'source_instagram' => $stats['sources']['instagram'] ?? 0,
'source_tiktok' => $stats['sources']['tiktok'] ?? 0,
'source_other' => $stats['sources']['other'] ?? 0,
'statview' => "https://tools-security.esy-web.dev/share/o9j2XMjV4Trnnbfb",
'updates' => $updates,
'avg_lcp' => $this->sitePerformanceRepository->getMoyenStat('lcp'),
@@ -76,6 +81,7 @@ class HomeController extends AbstractController
}
});
$this->cache->delete('umami_stats_24h');
// 2. Récupération des Stats (Cache 15min)
return $this->cache->get('umami_stats_24h', function (ItemInterface $item) use ($token) {
$item->expiresAfter(900);
@@ -88,19 +94,65 @@ class HomeController extends AbstractController
$startAt = (time() - (24 * 3600)) * 1000;
$endAt = time() * 1000;
// 1. General Stats
$response = $this->httpClient->request('GET', self::UMAMI_BASE_URL . '/websites/' . self::UMAMI_WEBSITE_ID . '/stats', [
'headers' => ['Authorization' => "Bearer $token"],
'query' => ['startAt' => $startAt, 'endAt' => $endAt],
]);
$data = $response->toArray();
// 2. Referrer Metrics
$metricsResponse = $this->httpClient->request('GET', self::UMAMI_BASE_URL . '/websites/' . self::UMAMI_WEBSITE_ID . '/metrics', [
'headers' => ['Authorization' => "Bearer $token"],
'query' => [
'startAt' => $startAt,
'endAt' => $endAt,
'type' => 'referrer'
],
]);
$referrers = $metricsResponse->toArray();
dd($referrers);
// 3. Process Sources
$sources = [
'google' => 0,
'facebook' => 0,
'instagram' => 0,
'tiktok' => 0,
'other' => 0
];
foreach ($referrers as $ref) {
// Structure depends on API version, usually object with x (value) and y (count)
// Assuming generic format: ['x' => 'google.com', 'y' => 123]
$domain = strtolower($ref['x'] ?? '');
$count = $ref['y'] ?? 0;
if (str_contains($domain, 'google')) {
$sources['google'] += $count;
} elseif (str_contains($domain, 'facebook')) {
$sources['facebook'] += $count;
} elseif (str_contains($domain, 'instagram')) {
$sources['instagram'] += $count;
} elseif (str_contains($domain, 'tiktok')) {
$sources['tiktok'] += $count;
} else {
$sources['other'] += $count;
}
}
return [
'visitors' => $data['visitors']['value'] ?? $data['visitors'] ?? 0,
'views' => $data['pageviews']['value'] ?? $data['pageviews'] ?? 0
'views' => $data['pageviews']['value'] ?? $data['pageviews'] ?? 0,
'sources' => $sources
];
} catch (\Exception $e) {
return ['visitors' => 0, 'views' => 0];
return [
'visitors' => 0,
'views' => 0,
'sources' => [
'google' => 0, 'facebook' => 0, 'instagram' => 0, 'tiktok' => 0, 'other' => 0
]
];
}
});
}

View File

@@ -33,7 +33,11 @@ class LoginStatsSubscriber
$loginRegister->setUserAgent($request->headers->get('User-Agent'));
$this->entityManager->persist($loginRegister);
$this->entityManager->flush();
try {
$this->entityManager->persist($loginRegister);
$this->entityManager->flush();
} catch (\Exception $e) {
// Ignorer les erreurs de log pour ne pas bloquer le login
}
}
}

View File

@@ -25,6 +25,20 @@
</div>
{% endmacro %}
{% macro traffic_source(label, value, color, icon_path) %}
<div class="flex items-center justify-between p-4 rounded-2xl bg-white/5 border border-white/5 hover:bg-white/10 transition-colors">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-{{ color }}-500/20 flex items-center justify-center text-{{ color }}-400">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="{{ icon_path }}"/></svg>
</div>
<div>
<p class="text-[9px] font-black text-slate-400 uppercase tracking-widest mb-0.5">{{ label }}</p>
<p class="text-lg font-black text-white leading-none">{{ value|default(0) }}</p>
</div>
</div>
</div>
{% endmacro %}
{% import _self as macros %}
{% block body %}
@@ -60,6 +74,21 @@
{{ macros.business_card('Devis', devis_wait_sign, 'À faire signer', 'amber') }}
{{ macros.business_card('Clients', customers, 'Fiches uniques', 'emerald') }}
{# --- SOURCES DE TRAFIC --- #}
<section class="md:col-span-3 grid grid-cols-2 md:grid-cols-5 gap-4 bg-white/5 p-6 rounded-[2.5rem] border border-white/10 shadow-inner" aria-label="Sources de trafic">
<div class="col-span-2 md:col-span-5 flex items-center gap-4 mb-2 px-4">
<span class="h-[1px] flex-1 bg-white/10"></span>
<h4 class="text-[10px] font-black text-slate-300 uppercase tracking-[0.3em]">Sources de Trafic</h4>
<span class="h-[1px] flex-1 bg-white/10"></span>
</div>
{{ macros.traffic_source('Google', source_google, 'blue', 'M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z') }}
{{ macros.traffic_source('Facebook', source_facebook, 'indigo', 'M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z') }}
{{ macros.traffic_source('Instagram', source_instagram, 'pink', 'M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z') }}
{{ macros.traffic_source('TikTok', source_tiktok, 'slate', 'M19.59 6.69a4.83 4.83 0 01-3.77-4.25V2h-3.45v13.67a2.89 2.89 0 01-5.2 1.74 2.89 2.89 0 012.31-4.64 2.93 2.93 0 01.88.13V9.4a6.84 6.84 0 00-1-.05A6.33 6.33 0 005 20.1a6.34 6.34 0 0010.86-4.43v-7a8.16 8.16 0 004.77 1.52v-3.4a4.85 4.85 0 01-1-.1z') }}
{{ macros.traffic_source('Autre', source_other, 'orange', 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z') }}
</section>
{# --- SECTION PERFORMANCE (Core Web Vitals) --- #}
<section class="md:col-span-3 grid grid-cols-1 md:grid-cols-3 gap-6 bg-white/5 p-6 rounded-[2.5rem] border border-white/10 shadow-inner" aria-label="Performance Web Vitals">
<div class="md:col-span-3 flex items-center gap-4 mb-2 px-4">