SSR alert mail recipients maintainable in the site settings
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>
This commit is contained in:
1 parent
994f9f2a1b
commit
90cb2faaf9
6 files changed
+81
-6
No files matched your search
@@ -206,15 +206,20 @@ final class SsrHealthSampleCommand extends Command
|
||||
|
||||
/**
|
||||
* Mails are best effort: a mail problem must never break the sampler
|
||||
* (the cron would stop logging history). Recipients come from
|
||||
* SSR_ALERT_EMAIL in var/vitec-ssr-purge.env - unset means "alerts
|
||||
* off", which the output states so the cron log shows why.
|
||||
* (the cron would stop logging history). Recipients come from the site
|
||||
* setting vitec.ssr.alertEmails (Site Management › Settings › VITEC),
|
||||
* fallback SSR_ALERT_EMAIL in var/vitec-ssr-purge.env - none at all
|
||||
* means "alerts off", which the output states so the cron log shows why.
|
||||
*/
|
||||
private function sendAlert(string $subject, string $body, OutputInterface $output): void
|
||||
{
|
||||
$recipients = GeneralUtility::trimExplode(',', $this->purgeService->getAlertEmail(), true);
|
||||
$alert = $this->purgeService->getAlertRecipients();
|
||||
if ($alert['invalid'] !== []) {
|
||||
$output->writeln('<comment>skipped invalid alert addresses: ' . implode(', ', $alert['invalid']) . '</comment>');
|
||||
}
|
||||
$recipients = $alert['recipients'];
|
||||
if ($recipients === []) {
|
||||
$output->writeln('<comment>transition detected but SSR_ALERT_EMAIL is not configured - no mail sent</comment>');
|
||||
$output->writeln('<comment>transition detected but no alert recipients are configured (Site settings › VITEC or SSR_ALERT_EMAIL) - no mail sent</comment>');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -70,6 +70,7 @@ final class SsrController
|
||||
$lastSample = $healthLog[0] ?? null;
|
||||
$clientErrors = $this->clientErrors(50);
|
||||
$cockpit = $this->cockpitData($health, $lastSample);
|
||||
$alert = $this->purgeService->getAlertRecipients();
|
||||
|
||||
$settingsUri = '';
|
||||
if ($GLOBALS['BE_USER']->isAdmin()) {
|
||||
@@ -92,6 +93,10 @@ final class SsrController
|
||||
'cockpitJson' => json_encode($cockpit, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT),
|
||||
'settingsUri' => $settingsUri,
|
||||
'failoverAfter' => $this->purgeService->getFailoverAfter(),
|
||||
'alert' => $alert,
|
||||
'alertList' => implode(', ', $alert['recipients']),
|
||||
'alertCount' => count($alert['recipients']),
|
||||
'alertInvalid' => implode(', ', $alert['invalid']),
|
||||
'chunkCount24h' => count(array_filter($clientErrors, static fn(array $r): bool => $r['is_chunk_reload'])),
|
||||
'jsErrorCount24h' => count(array_filter($clientErrors, static fn(array $r): bool => !$r['is_chunk_reload'])),
|
||||
'flagOn' => file_exists(Environment::getVarPath() . '/vitec-ssr-on.flag'),
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace Evomedien\Vitec\Service;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Http\RequestFactory;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Talks to the SSR node's POST /__purge endpoint (contract 0054-FE) and
|
||||
@@ -24,6 +26,7 @@ final class SsrPurgeService
|
||||
public function __construct(
|
||||
private readonly RequestFactory $requestFactory,
|
||||
private readonly ConnectionPool $connectionPool,
|
||||
private readonly SiteFinder $siteFinder,
|
||||
) {}
|
||||
|
||||
public static function getConfigPath(): string
|
||||
@@ -66,6 +69,50 @@ final class SsrPurgeService
|
||||
return trim($this->getConfig()['SSR_ALERT_EMAIL'] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Who gets the vitec:ssr-health alert mails. Maintained by editors in
|
||||
* Site Management › Settings › VITEC (vitec.ssr.alertEmails, one
|
||||
* address per entry); the first site with at least one valid address
|
||||
* wins. Without such a list, SSR_ALERT_EMAIL from the env file is the
|
||||
* fallback. Invalid entries are skipped and reported back.
|
||||
*
|
||||
* @return array{recipients: list<string>, source: string, invalid: list<string>}
|
||||
*/
|
||||
public function getAlertRecipients(): array
|
||||
{
|
||||
$invalid = [];
|
||||
try {
|
||||
foreach ($this->siteFinder->getAllSites() as $site) {
|
||||
$list = $site->getSettings()->get('vitec.ssr.alertEmails', []);
|
||||
if (is_string($list)) {
|
||||
$list = GeneralUtility::trimExplode(',', $list, true);
|
||||
}
|
||||
$valid = [];
|
||||
foreach ((array)$list as $address) {
|
||||
$address = trim((string)$address);
|
||||
if ($address === '') {
|
||||
continue;
|
||||
}
|
||||
if (GeneralUtility::validEmail($address)) {
|
||||
$valid[] = $address;
|
||||
} else {
|
||||
$invalid[] = $address;
|
||||
}
|
||||
}
|
||||
if ($valid !== []) {
|
||||
return ['recipients' => array_values(array_unique($valid)), 'source' => 'site settings', 'invalid' => $invalid];
|
||||
}
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// No site configuration readable (e.g. broken YAML): use the env fallback.
|
||||
}
|
||||
$env = array_values(array_filter(
|
||||
GeneralUtility::trimExplode(',', $this->getAlertEmail(), true),
|
||||
static fn(string $a): bool => GeneralUtility::validEmail($a)
|
||||
));
|
||||
return ['recipients' => $env, 'source' => $env !== [] ? 'var/vitec-ssr-purge.env' : '', 'invalid' => $invalid];
|
||||
}
|
||||
|
||||
/**
|
||||
* Consecutive DOWN samples after which vitec:ssr-health fails over to
|
||||
* the SPA (SSR_FAILOVER_AFTER, default 2 = ~10 minutes at the 5-minute
|
||||
|
||||
@@ -134,3 +134,10 @@ settings:
|
||||
category: VITEC
|
||||
type: bool
|
||||
default: true
|
||||
|
||||
vitec.ssr.alertEmails:
|
||||
label: 'SSR alert mail recipients'
|
||||
description: 'E-mail addresses that get the alert mails of the SSR health check: node down, node back, automatic fallback to the SPA. One address per entry; invalid addresses are skipped. Empty: the addresses from SSR_ALERT_EMAIL in var/vitec-ssr-purge.env are used.'
|
||||
category: VITEC
|
||||
type: stringlist
|
||||
default: []
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<!-- 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 and sends an alert mail.</span></span>
|
||||
<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}">
|
||||
@@ -67,6 +67,15 @@
|
||||
</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>
|
||||
|
||||
Reference in new issue
Block a user