faed0d760f
- Glocke je Kachel (persönlich, Benutzen-Ebene), Tabelle NextcloudAlertSubscription mit RLS - Zwei-Fehlschläge-Regel und gemeldeter Zustand auf der Zeile, Anspruch vor dem Mailversand - Mail (nur Deutsch) über MailService, bis zu drei Versuche, Zugriff beim Senden erneut geprüft - Fehlercodes in der Mail als lesbarer Hinweis Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
93 lines
3.3 KiB
TypeScript
93 lines
3.3 KiB
TypeScript
/**
|
|
* nextcloud-alert-rules — reine Regeln der Benachrichtigung
|
|
* (quick-261002-kxc, L-02, L-03, D-K1, D-K2). Kein Nest, kein Prisma, kein
|
|
* Zugriff auf die Uhr: das Datum wird hereingereicht.
|
|
*
|
|
* ZWEI-FEHLSCHLAEGE-REGEL: ein einzelner fehlgeschlagener Abruf kann eine
|
|
* kurze Stoerung sein (Neustart hinter dem Proxy, Netzwackler). Deshalb gilt
|
|
* eine Cloud erst nach zwei aufeinanderfolgenden Fehlschlaegen als "nicht
|
|
* erreichbar". Der ERSTE Fehlschlag schreibt nur den Zaehler und den
|
|
* Zeitpunkt, alle Statusfelder und `lastCheckedAt` bleiben unangetastet — die
|
|
* Bewertung wird aus den gespeicherten Feldern berechnet und zeigt dadurch
|
|
* weiter den letzten guten Stand. Wartungsmodus, ausstehende
|
|
* Datenbankaktualisierung und abgelaufener Support kommen aus einer
|
|
* erfolgreichen Antwort bzw. dem Datum und zaehlen sofort.
|
|
*
|
|
* UEBERGANG: `decideAlert` vergleicht den zuletzt gemeldeten Zustand
|
|
* ('ok' | 'red') mit der Ampel. Grau aendert nichts.
|
|
*/
|
|
|
|
import type { RatingLevel } from './nextcloud-rating';
|
|
import type { NextcloudCheckResult } from './nextcloud-status-fetch';
|
|
|
|
/** Anzahl aufeinanderfolgender Fehlschlaege, ab der "nicht erreichbar" gilt. */
|
|
export const FAILURES_FOR_RED = 2;
|
|
/** Wartezeit bis zur Wiederholung nach dem ersten Fehlschlag. */
|
|
export const RETRY_DELAY_MS = 5 * 60 * 1000;
|
|
|
|
/** Zuletzt gemeldeter Zustand einer Cloud. */
|
|
export type AlertState = 'ok' | 'red';
|
|
|
|
/** 'down' = eben rot geworden, 'up' = wieder in Ordnung, null = nichts zu melden. */
|
|
export type AlertTransition = 'down' | 'up' | null;
|
|
|
|
export function decideAlert(prev: AlertState, level: RatingLevel): AlertTransition {
|
|
if (level === 'unknown') return null;
|
|
if (prev === 'ok' && level === 'red') return 'down';
|
|
if (prev === 'red' && level !== 'red') return 'up';
|
|
return null;
|
|
}
|
|
|
|
export interface StatusWritePlan {
|
|
/** 'ok' = Abruf gelungen, 'pending' = erster Fehlschlag, 'confirmed' = bestaetigter Fehlschlag. */
|
|
outcome: 'ok' | 'pending' | 'confirmed';
|
|
data: Record<string, unknown>;
|
|
}
|
|
|
|
export function planStatusWrite(
|
|
prevFailures: number,
|
|
result: NextcloudCheckResult,
|
|
now: Date,
|
|
): StatusWritePlan {
|
|
if (result.reachable) {
|
|
return {
|
|
outcome: 'ok',
|
|
data: {
|
|
reachable: result.reachable,
|
|
maintenance: result.maintenance,
|
|
needsDbUpgrade: result.needsDbUpgrade,
|
|
versionString: result.versionString,
|
|
edition: result.edition,
|
|
productName: result.productName,
|
|
errorKind: result.errorKind,
|
|
errorDetail: result.errorDetail,
|
|
lastCheckedAt: now,
|
|
consecutiveFailures: 0,
|
|
firstFailureAt: null,
|
|
},
|
|
};
|
|
}
|
|
|
|
const failures = prevFailures + 1;
|
|
if (failures < FAILURES_FOR_RED) {
|
|
// Erster Fehlschlag: Statusfelder und lastCheckedAt bleiben unberuehrt (D-K2).
|
|
return { outcome: 'pending', data: { consecutiveFailures: failures, firstFailureAt: now } };
|
|
}
|
|
return {
|
|
outcome: 'confirmed',
|
|
data: {
|
|
reachable: result.reachable,
|
|
maintenance: result.maintenance,
|
|
needsDbUpgrade: result.needsDbUpgrade,
|
|
versionString: result.versionString,
|
|
edition: result.edition,
|
|
productName: result.productName,
|
|
errorKind: result.errorKind,
|
|
errorDetail: result.errorDetail,
|
|
lastCheckedAt: now,
|
|
// Gedeckelt: eine dauerhaft rote Cloud zaehlt nicht endlos weiter.
|
|
consecutiveFailures: FAILURES_FOR_RED,
|
|
},
|
|
};
|
|
}
|