Files
VITEC-website/packages/vitec/Classes/Widgets/SsrStatsWidget.php
T
orascheandClaude Opus 5.5 62f3f5394e SSR dashboard tiles in the cockpit look
The three SSR widgets render dark VITEC tiles like the cockpit module: status with pulse, cache ring and last purge, statistics with the 24 h availability strip and number tiles, errors with hit rate ring and split bar. Shared markup in CockpitHtml, styles in dashboard-ssr.css loaded per widget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 10:59:49 +01:00

103 lines
4.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Widgets;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Dashboard\Widgets\AdditionalCssInterface;
use TYPO3\CMS\Dashboard\Widgets\WidgetConfigurationInterface;
use TYPO3\CMS\Dashboard\Widgets\WidgetInterface;
/**
* Dashboard tile: purge and health statistics from the two protocol
* tables. History depth = what vitec:ssr-health sampling (cron, every
* 5 minutes) and the purge log hold (14 days). Cockpit look (CockpitHtml):
* availability strip of the last 24 h plus number tiles.
*/
final class SsrStatsWidget implements WidgetInterface, AdditionalCssInterface
{
public function __construct(
private readonly WidgetConfigurationInterface $configuration,
private readonly ConnectionPool $connectionPool,
) {}
public function renderWidgetContent(): string
{
$h = CockpitHtml::class;
$now = time();
$d1 = $now - 86400;
$d7 = $now - 7 * 86400;
$purges24h = $this->count('tx_vitec_ssrpurgelog', 'crdate > ' . $d1);
$purges7d = $this->count('tx_vitec_ssrpurgelog', 'crdate > ' . $d7);
$failed7d = $this->count('tx_vitec_ssrpurgelog', 'crdate > ' . $d7 . ' AND success = 0');
$full7d = $this->count('tx_vitec_ssrpurgelog', "crdate > " . $d7 . " AND paths = '*'");
$avgMs = $this->scalar('SELECT ROUND(AVG(response_ms)) FROM tx_vitec_ssrhealthlog WHERE crdate > ' . $d1 . ' AND ok = 1');
$maxCache = $this->scalar('SELECT MAX(cache_mb) FROM tx_vitec_ssrhealthlog WHERE crdate > ' . $d7);
// Browser-side errors reported via POST /api/vitec/client-error.
$clientErrors24h = $this->count('tx_vitec_clienterrorlog', 'crdate > ' . $d1);
$clientErrors7d = $this->count('tx_vitec_clienterrorlog', 'crdate > ' . $d7);
$strip = $h::strip($this->connectionPool);
$uptime = $strip['uptime'];
$uptimeTone = $uptime === null ? '' : ($uptime >= 99.5 ? 'vcw-ok' : ($uptime >= 95 ? 'vcw-warn' : 'vcw-bad'));
$html = '<div class="vcw-strip-h"><div><div class="vcw-label">Availability · 24 h</div>'
. '<div class="vcw-num ' . $uptimeTone . '">' . ($uptime === null ? '–' : number_format($uptime, 1)) . '<small>%</small></div></div>'
. '<div class="vcw-sub vcw-right">' . $strip['samples'] . ' samples<br>' . $strip['slow'] . ' slow · '
. $strip['down'] . ' down</div></div>'
. $strip['html'];
$tile = static fn(string $label, string $value, string $sub = '', string $tone = ''): string =>
'<div class="vcw-tile"><div class="vcw-label">' . $h::e($label) . '</div><div class="vcw-val ' . $tone . '">'
. $value . '</div>' . ($sub !== '' ? '<div class="vcw-sub">' . $sub . '</div>' : '') . '</div>';
$html .= '<div class="vcw-tiles">'
. $tile('Purges', $purges24h . '<small>/ ' . $purges7d . '</small>', '24 h / 7 days')
. $tile('Full purges', (string)$full7d, 'last 7 days', $full7d > 0 ? 'vcw-warn' : '')
. $tile('Failed purges', (string)$failed7d, 'last 7 days', $failed7d > 0 ? 'vcw-bad' : 'vcw-ok')
. $tile('Avg. response', $avgMs !== null ? $avgMs . '<small>ms</small>' : '–', 'last 24 h')
. $tile('Max. cache', $maxCache !== null ? $maxCache . '<small>MB</small>' : '–', 'last 7 days')
. $tile('Client errors', $clientErrors24h . '<small>/ ' . $clientErrors7d . '</small>', '24 h / 7 days', $clientErrors24h > 0 ? 'vcw-warn' : 'vcw-ok')
. '</div>';
if ($strip['samples'] === 0) {
$html .= '<p class="vcw-dim">No samples yet: the cron job <code>vitec:ssr-health</code> fills this every 5 minutes.</p>';
}
$html .= '<div class="vcw-foot">' . $h::moduleLink('Charts and protocol in the cockpit') . '</div>';
return $h::wrap($html);
}
public function getOptions(): array
{
return [];
}
public function getCssFiles(): array
{
return [CockpitHtml::CSS];
}
private function count(string $table, string $where): int
{
try {
return (int)$this->connectionPool->getConnectionForTable($table)
->executeQuery('SELECT COUNT(*) FROM ' . $table . ' WHERE ' . $where)->fetchOne();
} catch (\Throwable) {
return 0;
}
}
private function scalar(string $sql): ?string
{
try {
$v = $this->connectionPool->getConnectionForTable('tx_vitec_ssrhealthlog')
->executeQuery($sql)->fetchOne();
return $v === null || $v === false ? null : (string)$v;
} catch (\Throwable) {
return null;
}
}
}