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>
This commit is contained in:
1 parent
cda26947f2
commit
62f3f5394e
5 files changed
+409
-97
No files matched your search
@@ -5,15 +5,17 @@ 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).
|
||||
* 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
|
||||
final class SsrStatsWidget implements WidgetInterface, AdditionalCssInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly WidgetConfigurationInterface $configuration,
|
||||
@@ -22,6 +24,7 @@ final class SsrStatsWidget implements WidgetInterface
|
||||
|
||||
public function renderWidgetContent(): string
|
||||
{
|
||||
$h = CockpitHtml::class;
|
||||
$now = time();
|
||||
$d1 = $now - 86400;
|
||||
$d7 = $now - 7 * 86400;
|
||||
@@ -30,42 +33,40 @@ final class SsrStatsWidget implements WidgetInterface
|
||||
$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 = '*'");
|
||||
|
||||
$samples24h = $this->count('tx_vitec_ssrhealthlog', 'crdate > ' . $d1);
|
||||
$ok24h = $this->count('tx_vitec_ssrhealthlog', 'crdate > ' . $d1 . ' AND ok = 1');
|
||||
$uptime = $samples24h > 0 ? round($ok24h / $samples24h * 100, 1) . ' %' : '-';
|
||||
$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
|
||||
// (ClientErrorMiddleware); details in the VITEC SSR module.
|
||||
// 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);
|
||||
|
||||
$rows = [
|
||||
['Purges (24 h / 7 days)', $purges24h . ' / ' . $purges7d],
|
||||
['of which full purges (7 d)', (string)$full7d],
|
||||
['Failed purges (7 d)', $failed7d > 0
|
||||
? '<span class="badge badge-danger">' . $failed7d . '</span>'
|
||||
: '<span class="badge badge-success">0</span>'],
|
||||
['Availability (24 h)', htmlspecialchars($uptime)
|
||||
. ' <small>(' . $ok24h . '/' . $samples24h . ' samples)</small>'],
|
||||
['Avg. response time (24 h)', $avgMs !== null ? $avgMs . ' ms' : '-'],
|
||||
['Max. cache size (7 d)', $maxCache !== null ? $maxCache . ' MB' : '-'],
|
||||
['Client errors reported (24 h / 7 d)', ($clientErrors24h > 0
|
||||
? '<span class="badge badge-warning">' . $clientErrors24h . '</span>'
|
||||
: '<span class="badge badge-success">0</span>') . ' / ' . $clientErrors7d],
|
||||
];
|
||||
$strip = $h::strip($this->connectionPool);
|
||||
$uptime = $strip['uptime'];
|
||||
$uptimeTone = $uptime === null ? '' : ($uptime >= 99.5 ? 'vcw-ok' : ($uptime >= 95 ? 'vcw-warn' : 'vcw-bad'));
|
||||
|
||||
$html = '<table class="table table-striped table-sm"><tbody>';
|
||||
foreach ($rows as [$label, $value]) {
|
||||
$html .= '<tr><th style="width:55%">' . $label . '</th><td>' . $value . '</td></tr>';
|
||||
$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 .= '</tbody></table>';
|
||||
if ($samples24h === 0) {
|
||||
$html .= '<p class="text-muted" style="font-size:11px">No samples yet: the cron job for <code>vitec:ssr-health</code> fills this every 5 minutes.</p>';
|
||||
}
|
||||
return $html;
|
||||
$html .= '<div class="vcw-foot">' . $h::moduleLink('Charts and protocol in the cockpit') . '</div>';
|
||||
return $h::wrap($html);
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
@@ -73,6 +74,11 @@ final class SsrStatsWidget implements WidgetInterface
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getCssFiles(): array
|
||||
{
|
||||
return [CockpitHtml::CSS];
|
||||
}
|
||||
|
||||
private function count(string $table, string $where): int
|
||||
{
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user