New site setting vitec.ssr.alertEmails (Site Management, Settings, VITEC; one address per entry). vitec:ssr-health reads the recipients from there, invalid entries are skipped and logged, SSR_ALERT_EMAIL in the env file stays as fallback. The cockpit shows the recipients in the live band. Pre-filled with the address that was in the env file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
322 lines
25 KiB
HTML
322 lines
25 KiB
HTML
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||
xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
|
||
data-namespace-typo3-fluid="true">
|
||
|
||
<f:layout name="Module" />
|
||
|
||
<f:section name="Content">
|
||
|
||
<div class="vitec-cockpit" id="ssr-cockpit" data-json="{cockpitJson}">
|
||
|
||
<h1 class="vc-sr">VITEC SSR — Control & Protocol</h1>
|
||
|
||
<div class="vc-grid">
|
||
|
||
<!-- Live band: node state, release, delivery switch, 24 h availability -->
|
||
<section class="vc-card vc-hero vc-s12" aria-label="Live status">
|
||
<span class="vc-info"><button type="button" class="vc-info-btn" aria-label="About this tile" aria-describedby="vc-tip-live">?</button><span class="vc-tip" role="tooltip" id="vc-tip-live">Live state of the SSR node on the VPS. The dot pulses green while the node answers its health check. Delivery shows whether visitors get server-rendered pages; the switch is in the Site settings. The strip shows the last 24 hours in 15-minute windows: green ok, yellow slower than 400 ms, red down, grey no sample. After {failoverAfter} failed checks in a row the cron switches back to the SPA. Alert mails go to the addresses under Site settings › VITEC › SSR alert mail recipients; hover “Alert mail” to see them.</span></span>
|
||
<div class="vc-hero-top">
|
||
<div class="vc-live">
|
||
<f:if condition="{health}">
|
||
<f:then>
|
||
<span class="vc-pulse" id="vc-pulse" aria-hidden="true"></span>
|
||
<div>
|
||
<h2 class="vc-live-title" id="vc-live-title">SSR <span class="vc-ok-text">online</span></h2>
|
||
<p class="vc-live-sub">
|
||
<f:if condition="{flagOn}">
|
||
<f:then>Pages are rendered on the VPS</f:then>
|
||
<f:else>Node is ready, but delivery is off: TYPO3 serves the SPA</f:else>
|
||
</f:if>
|
||
<f:if condition="{lastSample}"> · last check <span title="{f:format.date(date: '@{lastSample.crdate}', format: 'Y-m-d H:i:s')}">{lastSampleAge} ago</span></f:if>
|
||
</p>
|
||
</div>
|
||
</f:then>
|
||
<f:else>
|
||
<span class="vc-pulse is-down" id="vc-pulse" aria-hidden="true"></span>
|
||
<div>
|
||
<h2 class="vc-live-title" id="vc-live-title">SSR <span class="vc-bad-text">not reachable</span></h2>
|
||
<p class="vc-live-sub">{healthError} · after {failoverAfter} failed checks in a row the cron switches delivery back to the SPA</p>
|
||
</div>
|
||
</f:else>
|
||
</f:if>
|
||
</div>
|
||
<dl class="vc-facts">
|
||
<div class="vc-fact">
|
||
<dt>Delivery</dt>
|
||
<dd>
|
||
<f:if condition="{flagOn}">
|
||
<f:then><span class="vc-pill is-ok">ON</span></f:then>
|
||
<f:else><span class="vc-pill is-muted">OFF</span></f:else>
|
||
</f:if>
|
||
<f:if condition="{settingsUri}">
|
||
<f:then><a href="{settingsUri}" class="vc-link" title="Site Management › Settings › VITEC › SSR delivery. Switching on purges the node cache once.">Site settings</a></f:then>
|
||
<f:else><span class="vc-dim" title="Site Management › Settings › VITEC › SSR delivery">via Site settings</span></f:else>
|
||
</f:if>
|
||
</dd>
|
||
</div>
|
||
<f:if condition="{health}">
|
||
<div class="vc-fact"><dt>Release</dt><dd><code>{health.release.release}</code></dd></div>
|
||
<div class="vc-fact"><dt>Commit</dt><dd><code>{health.release.commit}</code></dd></div>
|
||
</f:if>
|
||
<div class="vc-fact">
|
||
<dt>Purge webhook</dt>
|
||
<dd>
|
||
<f:if condition="{configured}">
|
||
<f:then><span class="vc-pill is-ok" title="Endpoint and secret from var/vitec-ssr-purge.env">configured</span></f:then>
|
||
<f:else><span class="vc-pill is-warn" title="var/vitec-ssr-purge.env is missing or incomplete">not configured</span></f:else>
|
||
</f:if>
|
||
</dd>
|
||
</div>
|
||
<div class="vc-fact">
|
||
<dt>Alert mail</dt>
|
||
<dd>
|
||
<f:if condition="{alertCount}">
|
||
<f:then><span class="vc-pill {f:if(condition: alertInvalid, then: 'is-warn', else: 'is-ok')}" title="{alertList} (from {alert.source}){f:if(condition: alertInvalid, then: ' · skipped, not valid: {alertInvalid}')}">{alertCount} recipient<f:if condition="{alertCount} != 1">s</f:if></span></f:then>
|
||
<f:else><span class="vc-pill is-warn" title="No alert mails are sent. Add addresses in Site Management › Settings › VITEC › SSR alert mail recipients.">none</span></f:else>
|
||
</f:if>
|
||
</dd>
|
||
</div>
|
||
<div class="vc-fact">
|
||
<dt>Auto failover</dt>
|
||
<dd><span class="vc-pill is-navy" title="Cron vitec:ssr-health every 5 minutes: alert mail and fallback to the SPA after {failoverAfter} failed checks in a row">after {failoverAfter} failed checks</span></dd>
|
||
</div>
|
||
</dl>
|
||
</div>
|
||
<div class="vc-uptime">
|
||
<div class="vc-uptime-h">
|
||
<span>Availability, last 24 hours · one bar per 15 minutes</span>
|
||
<span id="vc-uptime-sum">–</span>
|
||
</div>
|
||
<div class="vc-bars" id="vc-bars" role="img" aria-label="Availability of the SSR node over the last 24 hours"></div>
|
||
<div class="vc-uptime-f" id="vc-uptime-axis"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Gauges -->
|
||
<section class="vc-card vc-s3" aria-label="Cache hit rate">
|
||
<span class="vc-info"><button type="button" class="vc-info-btn" aria-label="About this tile" aria-describedby="vc-tip-hit">?</button><span class="vc-tip" role="tooltip" id="vc-tip-hit">Share of page requests in the last 24 hours that the node answered from its cache, either fresh or stale and refreshed in the background. All others had to be rendered first. Higher is better. It drops after a full purge until the cache is warm again. The arrow compares with the 24 hours before.</span></span>
|
||
<div class="vc-tile">
|
||
<svg class="vc-ring" width="96" height="96" viewBox="0 0 96 96" aria-hidden="true"><circle class="vc-track" cx="48" cy="48" r="40"/><circle class="vc-val is-ok" id="vc-ring-hit" cx="48" cy="48" r="40"/></svg>
|
||
<div class="vc-tile-body">
|
||
<div class="vc-label">Hit rate · 24 h</div>
|
||
<div class="vc-big"><span id="vc-hit">–</span><small>%</small></div>
|
||
<div class="vc-sub" id="vc-hit-trend">served from cache or refreshed in the background</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="vc-card vc-s3" aria-label="Response time">
|
||
<span class="vc-info"><button type="button" class="vc-info-btn" aria-label="About this tile" aria-describedby="vc-tip-ms">?</button><span class="vc-tip" role="tooltip" id="vc-tip-ms">How long TYPO3 needs to reach the node, measured when this page loads and every 15 seconds after. Median and p95 come from the cron samples of the last 24 hours, taken every 5 minutes. The curve shows the last 4 hours. Values above 400 ms turn the availability strip yellow.</span></span>
|
||
<div class="vc-label">Response time</div>
|
||
<div class="vc-big"><span id="vc-ms"><f:if condition="{health}"><f:then>{health.responseMs}</f:then><f:else>–</f:else></f:if></span><small>ms</small></div>
|
||
<div class="vc-sub">median 24 h <b id="vc-ms-median">–</b> · p95 <b id="vc-ms-p95">–</b></div>
|
||
<svg class="vc-spark" id="vc-spark" viewBox="0 0 300 46" preserveAspectRatio="none" aria-hidden="true"></svg>
|
||
</section>
|
||
|
||
<section class="vc-card vc-s3" aria-label="Cache fill">
|
||
<span class="vc-info"><button type="button" class="vc-info-btn" aria-label="About this tile" aria-describedby="vc-tip-cache">?</button><span class="vc-tip" role="tooltip" id="vc-tip-cache">How much of the node page cache is in use: pages and megabytes against the configured limit (SSR_CACHE_LIMIT_MB). When the cache is full, the pages used least recently drop out. After “Purge everything” it falls to almost zero until “Warm cache” fills it again.</span></span>
|
||
<div class="vc-tile">
|
||
<svg class="vc-ring" width="96" height="96" viewBox="0 0 96 96" aria-hidden="true"><circle class="vc-track" cx="48" cy="48" r="40"/><circle class="vc-val is-sky" id="vc-ring-cache" cx="48" cy="48" r="40"/></svg>
|
||
<div class="vc-tile-body">
|
||
<div class="vc-label">Cache fill</div>
|
||
<div class="vc-big"><span id="vc-cache-mb">{cockpit.cacheMb}</span><small id="vc-cache-limit"><f:if condition="{cacheLimitMb}"><f:then>/ {cacheLimitMb} MB</f:then><f:else>MB</f:else></f:if></small></div>
|
||
<div class="vc-sub"><b id="vc-cache-pages">{cockpit.cachedPages}</b> pages cached<span id="vc-cache-pct"></span></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="vc-card vc-s3" aria-label="Client errors">
|
||
<span class="vc-info"><button type="button" class="vc-info-btn" aria-label="About this tile" aria-describedby="vc-tip-errtile">?</button><span class="vc-tip" role="tooltip" id="vc-tip-errtile">Errors that visitor browsers reported from the React frontend in the last 24 hours. A chunk reload happens when an open tab still runs an older bundle after a deploy; that is usually harmless. JS errors point to a real bug. The bars show reports per hour, details are in the Client errors card below.</span></span>
|
||
<div class="vc-label">Client errors · 24 h</div>
|
||
<div class="vc-big">{clientErrorCount24h}<small>report<f:if condition="{clientErrorCount24h} != 1">s</f:if></small></div>
|
||
<div class="vc-sub">{chunkCount24h} chunk reload<f:if condition="{chunkCount24h} != 1">s</f:if> · {jsErrorCount24h} JS error<f:if condition="{jsErrorCount24h} != 1">s</f:if></div>
|
||
<div class="vc-mini" id="vc-mini" role="img" aria-label="Client errors per hour, last 24 hours"></div>
|
||
</section>
|
||
|
||
<!-- Traffic chart -->
|
||
<section class="vc-card vc-s8 vc-chart-card" aria-label="Traffic and cache">
|
||
<span class="vc-info"><button type="button" class="vc-info-btn" aria-label="About this tile" aria-describedby="vc-tip-traffic">?</button><span class="vc-tip" role="tooltip" id="vc-tip-traffic">Requests per hour, or per 4 hours in the 7-day view, split by how the node answered: served from cache, stale and refreshed in the background, or rendered fresh. The orange line is the response time, red triangles mark purges. Spikes of fresh renders usually follow a purge or a warm run. Source: node counters sampled every 5 minutes.</span></span>
|
||
<div class="vc-card-h">
|
||
<h2 class="vc-card-t"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M3 3v18h18M7 15l4-5 4 3 5-7"/></svg>Traffic and cache</h2>
|
||
<div class="vc-seg" role="group" aria-label="Time range">
|
||
<button type="button" data-range="24h" aria-pressed="true">24 h</button>
|
||
<button type="button" data-range="7d" aria-pressed="false">7 days</button>
|
||
</div>
|
||
</div>
|
||
<div class="vc-legend">
|
||
<span><i class="is-navy"></i>Served from cache</span>
|
||
<span><i class="is-sky"></i>Stale, refreshed in background</span>
|
||
<span><i class="is-warn"></i>Rendered fresh</span>
|
||
<span><i class="is-orange is-round"></i>Response time</span>
|
||
<span><i class="is-bad is-tri"></i>Purge</span>
|
||
</div>
|
||
<div class="vc-chart"><canvas id="vc-traffic" aria-label="Requests by cache result, with response time and purges"></canvas></div>
|
||
<p class="vc-empty" id="vc-traffic-empty" hidden>No traffic counters in this range yet. The cron <code>vitec:ssr-health</code> collects them every 5 minutes.</p>
|
||
<dl class="vc-stats">
|
||
<div><dt>Requests</dt><dd id="vc-st-req">–</dd></div>
|
||
<div><dt>Hit rate</dt><dd id="vc-st-hit">–</dd></div>
|
||
<div><dt>Render errors</dt><dd id="vc-st-err">–</dd></div>
|
||
<div><dt>Purges</dt><dd id="vc-st-purge">–</dd></div>
|
||
</dl>
|
||
</section>
|
||
|
||
<!-- Actions -->
|
||
<section class="vc-card vc-s4" aria-label="Actions">
|
||
<span class="vc-info"><button type="button" class="vc-info-btn" aria-label="About this tile" aria-describedby="vc-tip-actions">?</button><span class="vc-tip" role="tooltip" id="vc-tip-actions">Warm cache renders every sitemap page in advance, and the ring shows the progress live. Purge paths drops single pages from the cache, for example after a change the auto-purge did not catch. Purge everything empties the whole cache, which is needed after menu or footer changes and after a deploy; run Warm cache right after. Both heavy actions ask for confirmation.</span></span>
|
||
<div class="vc-card-h"><h2 class="vc-card-t"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M13 2 4 14h7l-1 8 9-12h-7z"/></svg>Actions</h2></div>
|
||
<div class="vc-actions">
|
||
|
||
<form action="{f:be.uri(route: 'web_vitecssr.warm')}" method="post" class="vc-act"
|
||
data-confirm-title="Warm the whole cache?"
|
||
data-confirm="Renders every sitemap page (about 850) in the background and sends a few thousand requests to TYPO3. Takes about one minute with a warm TYPO3 cache, up to ten minutes with a cold one."
|
||
data-confirm-ok="Start warming" data-confirm-severity="primary">
|
||
<div class="vc-act-h"><h3 class="vc-act-t">Warm cache</h3><span class="vc-pill" id="vc-warm-pill" hidden></span></div>
|
||
<div class="vc-warm">
|
||
<div class="vc-warm-ring">
|
||
<svg width="92" height="92" viewBox="0 0 96 96" aria-hidden="true" class="vc-ring"><circle class="vc-track" cx="48" cy="48" r="40"/><circle class="vc-val is-orange is-fast" id="vc-ring-warm" cx="48" cy="48" r="40"/></svg>
|
||
<div class="vc-warm-pct" id="vc-warm-pct">–</div>
|
||
</div>
|
||
<div class="vc-warm-meta" id="vc-warm-meta">no run since the server started</div>
|
||
</div>
|
||
<div class="vc-btn-row">
|
||
<button type="submit" class="vc-btn is-primary" id="ssr-warm-button" {f:if(condition: health.warming.running, then: 'disabled="disabled"')}>
|
||
<core:icon identifier="actions-play" size="small" /><span id="vc-warm-label">{f:if(condition: health.warming.running, then: 'Warming …', else: 'Warm cache')}</span>
|
||
</button>
|
||
</div>
|
||
<p class="vc-help">Renders every sitemap page in advance, so no visitor waits for a first render. Run it after “Purge everything” or a deploy.</p>
|
||
</form>
|
||
|
||
<form action="{f:be.uri(route: 'web_vitecssr.purge')}" method="post" class="vc-act">
|
||
<h3 class="vc-act-t">Purge paths</h3>
|
||
<label class="vc-lbl" for="purge-paths">Paths, one per line</label>
|
||
<textarea class="vc-input" id="purge-paths" name="paths" rows="3" placeholder="/products/aligo /insights/news"></textarea>
|
||
<input type="hidden" name="mode" value="paths">
|
||
<div class="vc-btn-row"><button type="submit" class="vc-btn is-ghost"><core:icon identifier="actions-refresh" size="small" />Purge these paths</button></div>
|
||
<p class="vc-help">Drops those pages, all hosts and query variants, from the SSR cache. They render again on their next request.</p>
|
||
</form>
|
||
|
||
<form action="{f:be.uri(route: 'web_vitecssr.purge')}" method="post" class="vc-act"
|
||
data-confirm-title="Purge the entire SSR cache?"
|
||
data-confirm="Every cached page is dropped and rendered again on its next request. Run “Warm cache” afterwards so visitors do not wait for the first render."
|
||
data-confirm-ok="Purge everything" data-confirm-severity="danger">
|
||
<h3 class="vc-act-t">Purge everything</h3>
|
||
<p class="vc-help">For menu or footer changes while auto-purge is off, and after a frontend deploy.</p>
|
||
<input type="hidden" name="mode" value="all">
|
||
<div class="vc-btn-row"><button type="submit" class="vc-btn is-danger"><core:icon identifier="actions-delete" size="small" />Purge everything</button></div>
|
||
</form>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Purge activity -->
|
||
<section class="vc-card vc-s7" aria-label="Purge activity">
|
||
<span class="vc-info"><button type="button" class="vc-info-btn" aria-label="About this tile" aria-describedby="vc-tip-activity">?</button><span class="vc-tip" role="tooltip" id="vc-tip-activity">Every cache purge, newest first. The colour shows the trigger: auto-purge when an editor saves, manual from this module, context menu from the page tree, cli for commands and deploys, switch-on when SSR delivery was turned on. Result and duration are on the right. Switch between 30, 100 and 500 entries.</span></span>
|
||
<div class="vc-card-h">
|
||
<h2 class="vc-card-t"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>Purge activity</h2>
|
||
<div class="vc-seg" role="group" aria-label="Entries to show">
|
||
<a href="{moduleUri}&limit=30" aria-pressed="{f:if(condition: '{purgeLimit} == 30', then: 'true', else: 'false')}">30</a>
|
||
<a href="{moduleUri}&limit=100" aria-pressed="{f:if(condition: '{purgeLimit} == 100', then: 'true', else: 'false')}">100</a>
|
||
<a href="{moduleUri}&limit=500" aria-pressed="{f:if(condition: '{purgeLimit} == 500', then: 'true', else: 'false')}">500</a>
|
||
</div>
|
||
</div>
|
||
<div class="vc-legend vc-legend-tl">
|
||
<span><i class="is-ok is-round"></i>auto-purge</span>
|
||
<span><i class="is-orange is-round"></i>manual</span>
|
||
<span><i class="is-navy is-round"></i>context menu</span>
|
||
<span><i class="is-sky is-round"></i>cli / deploy</span>
|
||
<span><i class="is-warn is-round"></i>switch-on</span>
|
||
</div>
|
||
<f:if condition="{purgeLog}">
|
||
<f:then>
|
||
<ol class="vc-timeline">
|
||
<f:for each="{purgeLog}" as="row">
|
||
<f:if condition="{row.day_first}"><li class="vc-day">{row.day_label}</li></f:if>
|
||
<li class="vc-ev">
|
||
<time title="{f:format.date(date: '@{row.crdate}', format: 'Y-m-d H:i:s')}"><f:format.date format="H:i">@{row.crdate}</f:format.date></time>
|
||
<span class="vc-dot is-{row.trigger_tone}"></span>
|
||
<div class="vc-ev-main">
|
||
<div class="vc-ev-line">
|
||
<span class="vc-pill is-{row.trigger_tone}">{row.trigger_label}</span>
|
||
<f:if condition="{row.trigger_who}"><span class="vc-dim">{row.trigger_who}</span></f:if>
|
||
</div>
|
||
<div class="vc-paths">
|
||
<f:if condition="{row.paths_all}">
|
||
<f:then><span class="vc-pill is-warn">all pages (*)</span></f:then>
|
||
<f:else>
|
||
<f:for each="{row.paths_first}" as="p"><span class="vc-path" title="{p}">{p}</span></f:for>
|
||
<f:if condition="{row.paths_rest}">
|
||
<details class="vc-more">
|
||
<summary>+<f:count subject="{row.paths_rest}" /> more</summary>
|
||
<f:for each="{row.paths_rest}" as="p"><span class="vc-path" title="{p}">{p}</span></f:for>
|
||
</details>
|
||
</f:if>
|
||
</f:else>
|
||
</f:if>
|
||
</div>
|
||
</div>
|
||
<div class="vc-ev-r">
|
||
<f:if condition="{row.success}">
|
||
<f:then><span class="vc-pill is-ok">ok</span></f:then>
|
||
<f:else><span class="vc-pill is-bad" title="{row.message}">HTTP {row.http_code}</span></f:else>
|
||
</f:if>
|
||
<span>{row.duration_ms} ms</span>
|
||
</div>
|
||
</li>
|
||
</f:for>
|
||
</ol>
|
||
</f:then>
|
||
<f:else><p class="vc-empty">No purges logged yet. Saving a page, the context menu “Purge from SSR cache” or the buttons on the right create the first entry.</p></f:else>
|
||
</f:if>
|
||
</section>
|
||
|
||
<!-- Client errors -->
|
||
<section class="vc-card vc-s5" aria-label="Client errors">
|
||
<span class="vc-info"><button type="button" class="vc-info-btn" aria-label="About this tile" aria-describedby="vc-tip-errors">?</button><span class="vc-tip" role="tooltip" id="vc-tip-errors">Every report from visitor browsers in the last 24 hours, newest first. Open an entry for release, URL, browser, anonymized IP and stack trace. “old bundle” means the tab ran an older release than the node. Reports are limited to 30 per minute and IP and are kept for 30 days.</span></span>
|
||
<div class="vc-card-h">
|
||
<h2 class="vc-card-t"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 3 2 21h20zM12 10v4M12 17.5v.5"/></svg>Client errors</h2>
|
||
<span class="vc-pill is-sky" title="Reported by the React frontend via POST /api/vitec/client-error; max. 30 per minute and IP, IPs anonymized, cleaned up after 30 days by cron vitec:client-error-cleanup">kept 30 days</span>
|
||
</div>
|
||
<dl class="vc-err-sum">
|
||
<div><dt>24 h</dt><dd>{clientErrorCount24h}</dd></div>
|
||
<div><dt>Chunk reloads</dt><dd class="vc-warn-text">{chunkCount24h}</dd></div>
|
||
<div><dt>JS errors</dt><dd class="vc-bad-text">{jsErrorCount24h}</dd></div>
|
||
</dl>
|
||
<f:if condition="{clientErrors}">
|
||
<f:then>
|
||
<div class="vc-errs">
|
||
<f:for each="{clientErrors}" as="row">
|
||
<details class="vc-err">
|
||
<summary>
|
||
<f:if condition="{row.is_chunk_reload}">
|
||
<f:then><span class="vc-pill is-warn">chunk reload</span></f:then>
|
||
<f:else><span class="vc-pill is-bad">error</span></f:else>
|
||
</f:if>
|
||
<span class="vc-err-msg">{row.message}<small>{row.path}</small></span>
|
||
<span class="vc-err-t"><f:format.date format="H:i">@{row.crdate}</f:format.date></span>
|
||
</summary>
|
||
<div class="vc-err-body">
|
||
<dl class="vc-kv">
|
||
<dt>Reported</dt><dd><f:format.date format="Y-m-d H:i:s">@{row.crdate}</f:format.date> · entry #{row.uid}</dd>
|
||
<dt>Release</dt><dd><f:if condition="{row.release}"><f:then><code>{row.release}</code><f:if condition="{health.release.release}"><f:if condition="{row.release} != {health.release.release}"> <span class="vc-pill is-warn">old bundle</span></f:if></f:if></f:then><f:else><span class="vc-dim">not transmitted</span></f:else></f:if></dd>
|
||
<dt>URL</dt><dd><f:if condition="{row.url}"><f:then><a class="vc-link" href="{row.url}" target="_blank" rel="noopener">{row.url}</a></f:then><f:else><span class="vc-dim">not transmitted</span></f:else></f:if></dd>
|
||
<dt>Browser</dt><dd><f:if condition="{row.user_agent}"><f:then>{row.user_agent}</f:then><f:else><span class="vc-dim">not transmitted</span></f:else></f:if></dd>
|
||
<dt>IP</dt><dd><code>{row.ip}</code> <span class="vc-dim">anonymized</span></dd>
|
||
</dl>
|
||
<f:if condition="{row.stack}">
|
||
<f:then><pre class="vc-stack">{row.stack}</pre></f:then>
|
||
<f:else><p class="vc-dim">No stack transmitted.</p></f:else>
|
||
</f:if>
|
||
</div>
|
||
</details>
|
||
</f:for>
|
||
</div>
|
||
</f:then>
|
||
<f:else><p class="vc-empty">No client errors in the last 24 hours. The frontend reports JS errors and chunk reloads here as they happen.</p></f:else>
|
||
</f:if>
|
||
</section>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</f:section>
|
||
</html>
|