@php
$stackBadge = $site['stack'] === 'laravel'
? 'bg-red-500/10 text-red-300 ring-red-500/20'
: 'bg-emerald-500/10 text-emerald-300 ring-emerald-500/20';
// Logo = vrai favicon du site. Prod → service DuckDuckGo (le plus fiable, renvoie
// un vrai PNG pour tout domaine) ; local-only → /favicon.ico. Cascade de replis :
// DuckDuckGo → /favicon.png → /favicon.ico → monogramme (les SPA renvoient du HTML
// sur /favicon.ico, d'où la cascade qui retombe proprement sur le monogramme).
$origin = $site['prod_url'] ?: $site['local_url'];
$host = parse_url($origin, PHP_URL_HOST);
$base = rtrim($origin, '/');
if (! empty($site['prod_url'])) {
$faviconPrimary = 'https://icons.duckduckgo.com/ip3/' . $host . '.ico';
$faviconFallb = $base . '/favicon.png,' . $base . '/favicon.ico';
} else {
$faviconPrimary = $base . '/favicon.png';
$faviconFallb = $base . '/favicon.ico';
}
// Logo dédié posé à la main : public/logos/{clé}.(png|svg|jpg|webp) → prioritaire
// sur le favicon auto (utile pour les apps privées/local sans favicon public, ex. Evio).
foreach (['png', 'svg', 'jpg', 'jpeg', 'webp'] as $ext) {
if (is_file(public_path("logos/{$site['key']}.$ext"))) {
$faviconFallb = $faviconPrimary . ',' . $faviconFallb; // l'auto devient un repli
$faviconPrimary = asset("logos/{$site['key']}.$ext") . '?v=1';
break;
}
}
$initials = strtoupper(mb_substr(preg_replace('/[^A-Za-z0-9]/u', '', $site['name']) ?: '?', 0, 2));
$palette = ['#6366f1','#0ea5e9','#10b981','#f59e0b','#ef4444','#8b5cf6','#ec4899','#14b8a6','#f97316','#3b82f6'];
$logoBg = $palette[abs(crc32($site['key'])) % count($palette)];
$auditable = $site['seo'] ?? true;
// Famille / marque (config/familles.php) + rang pour le tri groupé.
$famOrder = config('familles.order', []);
$famille = config('familles.map.' . $site['key']) ?? 'Divers';
$famRank = array_search($famille, $famOrder);
$famRank = $famRank === false ? 99 : $famRank;
@endphp
{{-- En-tête : logo + nom + état --}}
{{ $initials }}
{{ $site['name'] }}
{{ $site['stack'] }}
{{ $site['up'] ? 'en ligne · '.$site['status'] : ($site['status'] ? 'erreur '.$site['status'] : 'hors ligne') }}
@if (!empty($site['has_leads']))
· …
@endif
{{-- Accès aux supports --}}
@if ($site['admin_url'])
Admin →
@endif
Local ↗
@if ($site['prod_url'])
Prod ↗
@else
Prod —
@endif
{{-- Pastilles git : orange = à faire, gris = à jour --}}
{{-- Incidents récents (7 j) : muet quand il n'y a rien à signaler.
alerts.json est un journal d'événements : on annonce « il s'est passé
quelque chose », l'état réel du site est donné par la pastille ci-dessous. --}}
@php
$siteAlerts = ($alerts ?? [])[$site['key']] ?? [];
$critCount = count(array_filter($siteAlerts, fn ($a) => ($a['level'] ?? '') === 'crit'));
$tone = $critCount ? 'text-red-400' : 'text-amber-400';
$icons = ['down' => '🔴', 'ssl' => '🔒', 'seo' => '📉', 'lead' => '📥'];
@endphp
@if ($siteAlerts)
{{ count($siteAlerts) }} incident{{ count($siteAlerts) > 1 ? 's' : '' }}
7 j{{ $critCount ? ' · ' . $critCount . ' critique' . ($critCount > 1 ? 's' : '') : '' }}
›
@foreach (array_slice($siteAlerts, 0, 5) as $a)
{{ $icons[$a['type'] ?? ''] ?? '⚠️' }}
{{ $a['message'] ?? '' }}
{{ \Illuminate\Support\Carbon::parse($a['at'])->format('d/m H\hi') }}
@endforeach
@if (count($siteAlerts) > 5)
+ {{ count($siteAlerts) - 5 }} autre(s) · tout voir ›
@endif
@endif
{{-- Disponibilité : menu déroulant (état + historique des coupures, agent VPS 24/7) --}}
Disponibilité
›
{{-- Sauvegarde : dernière + historique. Muet si le site n'a rien à sauvegarder
(site statique sans base) ; rouge si trop ancienne ou en échec. --}}
@php
$bkHist = array_reverse(($backups ?? [])[$site['key']] ?? []); // plus récente d'abord
$bkLast = $bkHist[0] ?? null;
// diffInHours() renvoie un FLOTTANT (Carbon 3) : sans cast, l'écran
// affichait « il y a 2.2240379897222 h ».
$bkAge = $bkLast && ! empty($bkLast['at']) ? (int) \Illuminate\Support\Carbon::parse($bkLast['at'])->diffInHours(now()) : null;
$bkBad = $bkLast && (! ($bkLast['ok'] ?? true) || ($bkAge !== null && $bkAge > 48));
$bkTone = $bkBad ? 'text-red-400' : 'text-slate-500';
$bkDot = $bkBad ? 'bg-red-500' : 'bg-emerald-500';
@endphp
@if ($bkLast)
Sauvegarde
{{ $bkAge !== null ? ($bkAge < 1 ? "à l'instant" : ($bkAge < 24 ? "il y a {$bkAge} h" : 'il y a ' . intdiv($bkAge, 24) . ' j')) : '—' }}
@if (! empty($bkLast['size_mb'])) · {{ $bkLast['size_mb'] }} Mo @endif
›
@foreach (array_slice($bkHist, 0, 6) as $b)
{{ ($b['ok'] ?? true) ? '✅' : '❌' }}
{{ \Illuminate\Support\Carbon::parse($b['at'])->format('d/m H\hi') }}
{{ ! empty($b['size_mb']) ? $b['size_mb'] . ' Mo' : '' }}
@if (! empty($b['items'])) {{ implode(', ', $b['items']) }} @endif
@if (! empty($b['error'])) {{ $b['error'] }} @endif
@endforeach
@if (! empty($bkLast['server']))
serveur : {{ $bkLast['server'] }}
@endif
@endif
{{-- Rapport mensuel du site (généré par hub:monthly-report) --}}
@php $siteReports = ($reports ?? [])[$site['key']] ?? []; @endphp
@if ($siteReports)
@endif
{{-- Pied : adresses (publique + locale) + note d'audit --}}
@if ($site['prod_url'])
@endif
@if ($auditable)
@else
app privée
@endif