/** * 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; } 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, }, }; }