fetchHealth();
$lastPurge = $this->lastPurgeRow();
$limit = $this->purgeService->getCacheLimitMb();
// Head: pulse + state
if ($health === null) {
$head = '
'
. '
SSR not reachable
'
. '
The cron falls back to the SPA after '
. $this->purgeService->getFailoverAfter() . ' failed checks in a row
';
} else {
$ok = (bool)($health['ok'] ?? false);
$head = ''
. '
SSR online' : 'vcw-warn">reports errors') . '
'
. '
' . ($flagOn ? 'Pages are rendered on the VPS' : 'Delivery is off: TYPO3 serves the SPA')
. ' · ' . (int)$health['responseMs'] . ' ms
';
}
$html = '' . $head . '
';
// Cache ring + facts
$html .= '';
if ($health !== null) {
$mb = (int)($health['cacheMB'] ?? 0);
$pages = (int)($health['cachedPages'] ?? 0);
$frac = $limit > 0 ? $mb / $limit : 0.0;
$html .= '
' . $h::ring($frac, 'sky', $limit > 0 ? round($frac * 100) . ' %' : $mb . ' MB')
. '
Cache
' . number_format($pages, 0, '.', ',')
. 'pages
' . $mb . ($limit > 0 ? ' / ' . $limit : '') . ' MB
';
}
$html .= '
'
. '- Delivery
- ' . ($flagOn ? $h::pill('ok', 'ON') : $h::pill('muted', 'OFF')) . '
';
if ($health !== null) {
$html .= '- Release
' . $h::e((string)($health['release']['release'] ?? '?')) . ' '
. '- Commit
' . $h::e((string)($health['release']['commit'] ?? '?')) . ' ';
}
$html .= '- Webhook
- ' . ($this->purgeService->isConfigured()
? $h::pill('ok', 'configured')
: $h::pill('warn', 'not configured', 'var/vitec-ssr-purge.env is missing or incomplete')) . '
'
. '
';
// Last purge
if ($lastPurge !== null) {
[$label, $tone, $who] = $h::trigger((string)$lastPurge['trigger_source']);
$paths = trim((string)$lastPurge['paths']);
$html .= 'Last purge'
. '' . date('d.m. H:i', (int)$lastPurge['crdate']) . ''
. $h::pill($tone, $label) . ($who !== '' ? ' ' . $h::e($who) . '' : '')
. ' ' . ((int)$lastPurge['success'] ? $h::pill('ok', 'ok') : $h::pill('bad', 'failed'))
. ''
. ($paths === '*' ? 'all pages (*)' : $h::e(mb_strimwidth($paths, 0, 70, '…'))) . '
';
}
$html .= '';
return $h::wrap($html);
}
public function getOptions(): array
{
return [];
}
public function getCssFiles(): array
{
return [CockpitHtml::CSS];
}
private function fetchHealth(): ?array
{
$endpoint = $this->purgeService->getHealthEndpoint();
if ($endpoint === '') {
return null;
}
$start = microtime(true);
try {
$response = $this->requestFactory->request($endpoint, 'GET', [
'timeout' => 3, 'connect_timeout' => 2, 'http_errors' => false,
]);
if ($response->getStatusCode() !== 200) {
return null;
}
$data = json_decode((string)$response->getBody(), true);
if (!is_array($data)) {
return null;
}
$data['responseMs'] = (int)round((microtime(true) - $start) * 1000);
return $data;
} catch (\Throwable) {
return null;
}
}
private function lastPurgeRow(): ?array
{
try {
$qb = $this->connectionPool->getQueryBuilderForTable('tx_vitec_ssrpurgelog');
$row = $qb->select('*')->from('tx_vitec_ssrpurgelog')
->orderBy('uid', 'DESC')->setMaxResults(1)
->executeQuery()->fetchAssociative();
return $row ?: null;
} catch (\Throwable) {
return null;
}
}
private function fmtDate(string $iso): string
{
$ts = strtotime($iso);
return $ts ? date('Y-m-d H:i', $ts) : '?';
}
}