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
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Widgets;
|
||||
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Shared markup for the three SSR dashboard tiles, in the look of the
|
||||
* "VITEC SSR" cockpit module (dark VITEC navy, orange accents).
|
||||
* The dashboard inserts widget HTML without running scripts, so rings
|
||||
* and bars are drawn here as plain SVG/HTML. Styles:
|
||||
* Resources/Public/Css/dashboard-ssr.css (scoped to .vc-widget).
|
||||
*/
|
||||
final class CockpitHtml
|
||||
{
|
||||
public const CSS = 'EXT:vitec/Resources/Public/Css/dashboard-ssr.css';
|
||||
|
||||
public static function e(string $value): string
|
||||
{
|
||||
return htmlspecialchars($value, ENT_QUOTES);
|
||||
}
|
||||
|
||||
public static function wrap(string $inner): string
|
||||
{
|
||||
return '<div class="vc-widget">' . $inner . '</div>';
|
||||
}
|
||||
|
||||
public static function pill(string $tone, string $text, string $title = ''): string
|
||||
{
|
||||
return '<span class="vcw-pill is-' . self::e($tone) . '"'
|
||||
. ($title !== '' ? ' title="' . self::e($title) . '"' : '') . '>' . self::e($text) . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Ring gauge, 0..1, with a short text in the middle.
|
||||
*/
|
||||
public static function ring(float $fraction, string $tone, string $center, int $size = 76): string
|
||||
{
|
||||
$c = 2 * M_PI * 40;
|
||||
$offset = $c * (1 - max(0.0, min(1.0, $fraction)));
|
||||
return '<div class="vcw-ring" style="width:' . $size . 'px;height:' . $size . 'px">'
|
||||
. '<svg viewBox="0 0 96 96" width="' . $size . '" height="' . $size . '" aria-hidden="true">'
|
||||
. '<circle class="vcw-track" cx="48" cy="48" r="40"/>'
|
||||
. '<circle class="vcw-val is-' . self::e($tone) . '" cx="48" cy="48" r="40" stroke-dasharray="'
|
||||
. round($c, 2) . '" stroke-dashoffset="' . round($offset, 2) . '"/></svg>'
|
||||
. '<span class="vcw-ring-c">' . self::e($center) . '</span></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Same labels and tones as the purge timeline in the module.
|
||||
*
|
||||
* @return array{0: string, 1: string, 2: string} [label, tone, user]
|
||||
*/
|
||||
public static function trigger(string $source): array
|
||||
{
|
||||
$who = '';
|
||||
if (str_contains($source, ':')) {
|
||||
[$source, $who] = explode(':', $source, 2);
|
||||
}
|
||||
return match ($source) {
|
||||
'module' => ['manual', 'orange', $who],
|
||||
'contextmenu' => ['context menu', 'navy', $who],
|
||||
'editor-save', 'editor-command' => ['auto-purge', 'ok', $who],
|
||||
'switch-on' => ['switch-on', 'warn', $who],
|
||||
default => [$source, 'sky', $who],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Availability strip of the last 24 hours, 96 windows of 15 minutes,
|
||||
* from the health log (cron vitec:ssr-health every 5 minutes).
|
||||
*
|
||||
* @return array{html: string, uptime: ?float, slow: int, down: int, samples: int}
|
||||
*/
|
||||
public static function strip(ConnectionPool $pool): array
|
||||
{
|
||||
$slot = 900;
|
||||
$end = intdiv(time(), $slot) * $slot + $slot;
|
||||
$windows = array_fill(0, 96, null);
|
||||
$ok = 0;
|
||||
$n = 0;
|
||||
try {
|
||||
$rows = $pool->getConnectionForTable('tx_vitec_ssrhealthlog')->executeQuery(
|
||||
'SELECT crdate, ok, response_ms FROM tx_vitec_ssrhealthlog WHERE crdate > ?',
|
||||
[$end - 96 * $slot]
|
||||
)->fetchAllAssociative();
|
||||
} catch (\Throwable) {
|
||||
$rows = [];
|
||||
}
|
||||
foreach ($rows as $r) {
|
||||
$i = 95 - intdiv($end - 1 - (int)$r['crdate'], $slot);
|
||||
if ($i < 0 || $i > 95) {
|
||||
continue;
|
||||
}
|
||||
$n++;
|
||||
$isOk = (int)$r['ok'] === 1;
|
||||
$ok += $isOk ? 1 : 0;
|
||||
$state = !$isOk ? 2 : ((int)$r['response_ms'] > 400 ? 1 : 0);
|
||||
if ($windows[$i] === null || $state > $windows[$i]['s']) {
|
||||
$windows[$i] = ['s' => $state, 'ms' => (int)$r['response_ms']];
|
||||
}
|
||||
}
|
||||
$slow = 0;
|
||||
$down = 0;
|
||||
$html = '<div class="vcw-bars" role="img" aria-label="Availability of the SSR node over the last 24 hours">';
|
||||
foreach ($windows as $i => $w) {
|
||||
$from = $end - (96 - $i) * $slot;
|
||||
$label = date('H:i', $from) . '–' . date('H:i', $from + $slot) . ' · ';
|
||||
if ($w === null) {
|
||||
$html .= '<i class="is-none" title="' . self::e($label . 'no sample') . '"></i>';
|
||||
continue;
|
||||
}
|
||||
$cls = ['', 'is-slow', 'is-down'][$w['s']];
|
||||
$slow += $w['s'] === 1 ? 1 : 0;
|
||||
$down += $w['s'] === 2 ? 1 : 0;
|
||||
$text = $w['s'] === 2 ? 'down' : ($w['s'] === 1 ? 'slow, ' : 'ok, ') . ($w['s'] === 2 ? '' : $w['ms'] . ' ms');
|
||||
$html .= '<i class="' . $cls . '" title="' . self::e($label . $text) . '"></i>';
|
||||
}
|
||||
$html .= '</div><div class="vcw-axis"><span>' . date('H:i', $end - 96 * $slot) . '</span><span>'
|
||||
. date('H:i', $end - 48 * $slot) . '</span><span>now</span></div>';
|
||||
return [
|
||||
'html' => $html,
|
||||
'uptime' => $n > 0 ? round($ok / $n * 100, 1) : null,
|
||||
'slow' => $slow,
|
||||
'down' => $down,
|
||||
'samples' => $n,
|
||||
];
|
||||
}
|
||||
|
||||
public static function moduleLink(string $label = 'Open the SSR cockpit'): string
|
||||
{
|
||||
try {
|
||||
$uri = (string)GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute('web_vitecssr');
|
||||
} catch (\Throwable) {
|
||||
return '';
|
||||
}
|
||||
return '<a class="vcw-link" href="' . self::e($uri) . '">' . self::e($label) . ' →</a>';
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user