' . $inner . '';
}
public static function pill(string $tone, string $text, string $title = ''): string
{
return '' . self::e($text) . '';
}
/**
* 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 '
'
. ''
. '' . self::e($center) . '
';
}
/**
* 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 = '';
foreach ($windows as $i => $w) {
$from = $end - (96 - $i) * $slot;
$label = date('H:i', $from) . '–' . date('H:i', $from + $slot) . ' · ';
if ($w === null) {
$html .= '';
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 .= '';
}
$html .= '
' . date('H:i', $end - 96 * $slot) . ''
. date('H:i', $end - 48 * $slot) . 'now
';
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 '' . self::e($label) . ' →';
}
}