SSR cockpit and dashboard tiles: info button with tooltip

Every tile of the module and the three SSR dashboard widgets has a round question mark button top right. Hovering it or focusing it with the keyboard shows a tooltip that explains what the tile shows. Pure CSS, anchored to the card so it never leaves the tile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
orascheandClaude Opus 5.5 committed 2026-11-11 11:05:33 +01:00
1 parent 62f3f5394e
commit 994f9f2a1b
7 files changed
+110 -6

No files matched your search

+13 -2
View File
@@ -24,9 +24,20 @@ final class CockpitHtml
return htmlspecialchars($value, ENT_QUOTES);
}
public static function wrap(string $inner): string
/**
* Tile wrapper; $info becomes the round "?" button top right whose
* tooltip explains the tile (CSS only: hover or keyboard focus).
*/
public static function wrap(string $inner, string $info = '', string $id = ''): string
{
return '<div class="vc-widget">' . $inner . '</div>';
$button = '';
if ($info !== '') {
$tipId = 'vcw-tip-' . ($id !== '' ? $id : substr(md5($info), 0, 8));
$button = '<span class="vcw-info"><button type="button" class="vcw-info-btn" aria-label="About this tile"'
. ' aria-describedby="' . $tipId . '">?</button><span class="vcw-tip" role="tooltip" id="' . $tipId . '">'
. self::e($info) . '</span></span>';
}
return '<div class="vc-widget">' . $button . $inner . '</div>';
}
public static function pill(string $tone, string $text, string $title = ''): string
@@ -33,7 +33,8 @@ final class SsrErrorsWidget implements WidgetInterface, AdditionalCssInterface
if ($latest === null) {
return $h::wrap('<p class="vcw-dim">No /__stats samples yet. They arrive with the next '
. '<code>vitec:ssr-health</code> cron run (every 5 minutes) once the node release '
. 'provides <code>/__stats</code>.</p>');
. 'provides <code>/__stats</code>.</p>', 'Cache hit rate and error counters of the SSR node, '
. 'filled by the health cron every 5 minutes.', 'errors');
}
$hit = (int)$latest['stat_hit'];
@@ -76,7 +77,11 @@ final class SsrErrorsWidget implements WidgetInterface, AdditionalCssInterface
. '</div>';
$html .= '<p class="vcw-note">Totals count since the node last started; a deploy resets them. '
. 'Error details: <code>journalctl -u vitec-ssr</code> on the VPS.</p>';
return $h::wrap($html);
return $h::wrap($html, 'How well the SSR cache works and whether the node has problems. The ring is the '
. 'share of requests answered from the cache since the node last started; the bar splits them into '
. 'served from cache, stale and refreshed in the background, and rendered fresh. Render errors are pages '
. 'the node could not render, backend errors are failed requests to TYPO3. Down samples are failed '
. 'health checks in the last 24 hours.', 'errors');
}
public function getOptions(): array
@@ -66,7 +66,10 @@ final class SsrStatsWidget implements WidgetInterface, AdditionalCssInterface
$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);
return $h::wrap($html, 'Availability of the SSR node over the last 24 hours, one bar per 15 minutes: '
. 'green ok, yellow slower than 400 ms, red down, grey no sample. Below are counts from the purge and '
. 'health logs: purges, full purges and failed purges, average response time, the largest cache size '
. 'and the browser errors reported by the frontend.', 'stats');
}
public function getOptions(): array
@@ -84,7 +84,10 @@ final class SsrStatusWidget implements WidgetInterface, AdditionalCssInterface
}
$html .= '<div class="vcw-foot">' . $h::moduleLink() . '</div>';
return $h::wrap($html);
return $h::wrap($html, 'Live state of the SSR node on the VPS: whether it answers and how fast, '
. 'how full its page cache is against the limit, whether SSR delivery is switched on, which frontend '
. 'release runs, and the most recent cache purge. The link opens the cockpit with charts, protocol '
. 'and the purge and warm buttons.', 'status');
}
public function getOptions(): array