Files
VITEC-website/packages/vitec/Resources/Private/Templates/Ssr/Index.html
T
orascheandClaude Fable 5.1 5121eaa000 Client-Error-Endpoint POST /api/vitec/client-error (0072-FE / 0077-BE)
Neue PSR-15-Middleware ClientErrorMiddleware: nimmt Browser-Fehler des
React-Frontends als JSON {url, message, stack, release, userAgent}
entgegen und antwortet 204 ohne Body. Schutz: 30 Meldungen pro Minute
und IP (Sliding Window ueber TYPO3-RateLimiter, 429 + Retry-After),
8-KB-Body-Limit (413, Content-Length und tatsaechliche Bytes), kein
JSON-Objekt oder leere message = 400, GET = 405. Keine Session, keine
Cookies; der /_json-Praefix funktioniert ebenfalls.

Speicherung in der neuen Tabelle tx_vitec_clienterrorlog mit
anonymisierter IP (IPv4 letztes Oktett 0, IPv6 /48). Sichtbar als
Tabelle "Client errors (last 24 h)" im Modul VITEC SSR (mit Stack als
Aufklapper) und als Zeile "Client errors reported (24 h / 7 d)" in der
Dashboard-Kachel VITEC: SSR statistics.

CLI vitec:client-error-cleanup [--days=30] loescht Eintraege aelter als
30 Tage (taeglich per Cron).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-10 17:25:23 +01:00

182 lines
8.9 KiB
HTML

<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<h1>VITEC SSR — Control &amp; Protocol</h1>
<div class="row">
<div class="col-md-6">
<h3>Status</h3>
<table class="table table-striped">
<tr>
<th>SSR delivery</th>
<td>
<f:if condition="{flagOn}">
<f:then><span class="badge badge-success">ON — pages served from the VPS</span></f:then>
<f:else><span class="badge badge-secondary">OFF — SPA as before</span></f:else>
</f:if>
<br><small>Toggle: Site Management &rsaquo; Settings &rsaquo; VITEC</small>
</td>
</tr>
<tr>
<th>SSR server</th>
<td>
<f:if condition="{health}">
<f:then>
<span class="badge badge-success">running</span> {health.responseMs} ms<br>
Release <strong>{health.release.release}</strong>
(commit {health.release.commit})<br>
Cache: {health.cachedPages} pages / {health.cacheMB} MB
</f:then>
<f:else>
<span class="badge badge-danger">not reachable</span> {healthError}
</f:else>
</f:if>
</td>
</tr>
<tr>
<th>Cache warming</th>
<td>
<f:if condition="{health.warming.running}">
<f:then>
<span class="badge badge-info">running</span>
{health.warming.done} of {health.warming.queued} pages rendered
({health.warming.skipped} still fresh, {health.warming.failed} failed)
<br><small>Reload this module to follow the progress.</small>
</f:then>
<f:else>
<f:if condition="{health.warming.startedAt}">
<f:then>
Last run: {health.warming.done} of {health.warming.queued} pages rendered
({health.warming.skipped} still fresh,
<f:if condition="{health.warming.failed}">
<f:then><span class="badge badge-danger">{health.warming.failed} failed</span></f:then>
<f:else>0 failed</f:else>
</f:if>)
<f:if condition="{health.warming.aborted}"> <span class="badge badge-warning">aborted by restart</span></f:if>
</f:then>
<f:else><span class="text-muted">no run since the server started</span></f:else>
</f:if>
</f:else>
</f:if>
</td>
</tr>
<tr>
<th>Purge configuration</th>
<td>
<f:if condition="{configured}">
<f:then><span class="badge badge-success">ok</span></f:then>
<f:else><span class="badge badge-warning">var/vitec-ssr-purge.env missing</span></f:else>
</f:if>
</td>
</tr>
</table>
</div>
<div class="col-md-6">
<h3>Purge cache</h3>
<form action="{f:be.uri(route: 'web_vitecssr.purge')}" method="post" class="form-group">
<label for="purge-paths">Paths (one per line or comma-separated):</label>
<textarea class="form-control" id="purge-paths" name="paths" rows="3"
placeholder="/products/aligo&#10;/insights/news"></textarea>
<input type="hidden" name="mode" value="paths">
<div style="margin-top:8px">
<button type="submit" class="btn btn-primary">Purge these paths</button>
</div>
</form>
<form action="{f:be.uri(route: 'web_vitecssr.purge')}" method="post"
onsubmit="return confirm('Really purge the ENTIRE SSR cache?');">
<input type="hidden" name="mode" value="all">
<button type="submit" class="btn btn-warning">Purge everything (*)</button>
<small class="text-muted"> — needed after menu/footer changes while auto-purge is off</small>
</form>
<h3 style="margin-top:24px">Warm cache</h3>
<form action="{f:be.uri(route: 'web_vitecssr.warm')}" method="post"
onsubmit="return confirm('This renders every sitemap page (800+) in the background and sends several thousand requests to TYPO3 within a few minutes. Continue?');">
<button type="submit" class="btn btn-success">Warm cache (all sitemap pages)</button>
<small class="text-muted"> — pre-renders every page in the background, so no visitor has to
wait for a first render. Useful after "Purge everything" or a deploy; takes a few minutes.</small>
</form>
</div>
</div>
<h3>Purge protocol <small>(last 30)</small></h3>
<table class="table table-striped table-sm">
<thead><tr><th>Time</th><th>Trigger</th><th>Paths</th><th>Result</th><th>Duration</th></tr></thead>
<tbody>
<f:for each="{purgeLog}" as="row">
<tr>
<td><f:format.date format="Y-m-d H:i:s">@{row.crdate}</f:format.date></td>
<td>{row.trigger_source}</td>
<td><code>{row.paths}</code></td>
<td>
<f:if condition="{row.success}">
<f:then><span class="badge badge-success">ok</span></f:then>
<f:else><span class="badge badge-danger">HTTP {row.http_code}</span> {row.message}</f:else>
</f:if>
</td>
<td>{row.duration_ms} ms</td>
</tr>
</f:for>
</tbody>
</table>
<h3>Health samples <small>(last 10 — history via cron <code>vitec:ssr-health</code>, every 5 minutes)</small></h3>
<table class="table table-striped table-sm">
<thead><tr><th>Time</th><th>Status</th><th>Release</th><th>Cache</th><th>Response time</th></tr></thead>
<tbody>
<f:for each="{healthLog}" as="row">
<tr>
<td><f:format.date format="Y-m-d H:i:s">@{row.crdate}</f:format.date></td>
<td>
<f:if condition="{row.ok}">
<f:then><span class="badge badge-success">ok</span></f:then>
<f:else><span class="badge badge-danger">down</span></f:else>
</f:if>
</td>
<td>{row.release_id}</td>
<td>{row.cached_pages} pages / {row.cache_mb} MB</td>
<td>{row.response_ms} ms</td>
</tr>
</f:for>
</tbody>
</table>
<h3>Client errors <small>(last 24 h — {clientErrorCount24h} report<f:if condition="{clientErrorCount24h} != 1">s</f:if>, newest 50 shown)</small></h3>
<p class="text-muted" style="font-size:11px">
Browser-side failures reported by the React frontend via <code>POST /api/vitec/client-error</code>
(JS errors, unhandled promise rejections, element error boundaries and "chunk reload" after a deploy).
Limited to 30 reports per minute and IP, 8 KB per report; IPs are stored anonymized.
Kept for 30 days (cron <code>vitec:client-error-cleanup</code>).
</p>
<table class="table table-striped table-sm">
<thead><tr><th>Time</th><th>Release</th><th>Message</th><th>Page</th><th>Browser</th><th>IP</th></tr></thead>
<tbody>
<f:for each="{clientErrors}" as="row">
<tr>
<td style="white-space:nowrap"><f:format.date format="Y-m-d H:i:s">@{row.crdate}</f:format.date></td>
<td><code>{row.release}</code></td>
<td>
<f:if condition="{row.is_chunk_reload}">
<f:then><span class="badge badge-warning">chunk reload</span> </f:then>
<f:else><span class="badge badge-danger">error</span> </f:else>
</f:if>
{row.message}
<f:if condition="{row.stack}">
<details style="margin-top:4px">
<summary style="cursor:pointer" class="text-muted">stack</summary>
<pre style="font-size:11px;white-space:pre-wrap;max-height:240px;overflow:auto">{row.stack}</pre>
</details>
</f:if>
</td>
<td><a href="{row.url}" target="_blank" rel="noopener" title="{row.url}">{row.path}</a></td>
<td title="{row.user_agent}"><small>{row.ua_short}</small></td>
<td><small>{row.ip}</small></td>
</tr>
</f:for>
<f:if condition="!{clientErrors}">
<tr><td colspan="6" class="text-muted">No client errors reported in the last 24 hours.</td></tr>
</f:if>
</tbody>
</table>
</html>