feat(nextcloud-status): erneute Prüfung nach Ausfall und Hinweis auf der Kachel
- Wiederholungsauftrag jede Minute für Clouds mit einem Fehlschlag älter als fünf Minuten - Neue Adresse setzt den Prüfstand zurück, der gemeldete Zustand bleibt - Kachel: Hinweis Prüfung fehlgeschlagen, Fehlercodes als lesbarer Text mit Tooltip Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import de from '@/messages/de.json';
|
||||
import en from '@/messages/en.json';
|
||||
import { type ErrorHintKey, errorHint } from './error-hint';
|
||||
|
||||
describe('errorHint', () => {
|
||||
it.each([
|
||||
['tls', 'ERR_TLS_CERT_ALTNAME_INVALID', { key: 'certName' }],
|
||||
['tls', 'HOSTNAME_MISMATCH', { key: 'certName' }],
|
||||
['tls', 'CERT_HAS_EXPIRED', { key: 'certExpired' }],
|
||||
['tls', 'DEPTH_ZERO_SELF_SIGNED_CERT', { key: 'certUntrusted' }],
|
||||
['tls', 'SELF_SIGNED_CERT_IN_CHAIN', { key: 'certUntrusted' }],
|
||||
['tls', 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', { key: 'certUntrusted' }],
|
||||
['tls', 'IRGENDWAS', { key: 'fallback' }],
|
||||
['network', 'ENOTFOUND', { key: 'dnsNotFound' }],
|
||||
['network', 'EAI_AGAIN', { key: 'dnsNotFound' }],
|
||||
['network', 'ECONNREFUSED', { key: 'connectionRefused' }],
|
||||
['network', 'ETIMEDOUT', { key: 'timeout' }],
|
||||
['timeout', null, { key: 'timeout' }],
|
||||
['http-status', 'HTTP 502', { key: 'httpStatus', code: '502' }],
|
||||
['http-status', 'HTTP 404', { key: 'httpStatus', code: '404' }],
|
||||
['http-status', 'kaputt', { key: 'fallback' }],
|
||||
['network', 'ECONNRESET', { key: 'fallback' }],
|
||||
['network', null, { key: 'fallback' }],
|
||||
['redirect', 'Zu viele Weiterleitungen', { key: 'fallback' }],
|
||||
[null, null, { key: 'fallback' }],
|
||||
])('%s / %s', (kind, detail, expected) => {
|
||||
expect(errorHint(kind, detail)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('jeder Schluessel hat in Deutsch und Englisch einen Text; Deutsch wie vereinbart', () => {
|
||||
const keys: ErrorHintKey[] = [
|
||||
'certName',
|
||||
'certExpired',
|
||||
'certUntrusted',
|
||||
'dnsNotFound',
|
||||
'connectionRefused',
|
||||
'timeout',
|
||||
'httpStatus',
|
||||
'fallback',
|
||||
];
|
||||
for (const key of keys) {
|
||||
expect(de.nextcloudStatus.errorHint[key], `de ${key}`).toBeTruthy();
|
||||
expect(en.nextcloudStatus.errorHint[key], `en ${key}`).toBeTruthy();
|
||||
}
|
||||
expect(de.nextcloudStatus.errorHint).toMatchObject({
|
||||
certName: 'Zertifikat passt nicht zur Adresse',
|
||||
certExpired: 'Zertifikat abgelaufen',
|
||||
certUntrusted: 'Zertifikat nicht vertrauenswürdig',
|
||||
dnsNotFound: 'Adresse nicht gefunden',
|
||||
connectionRefused: 'Verbindung abgelehnt',
|
||||
timeout: 'Zeitüberschreitung',
|
||||
httpStatus: 'Server antwortet mit Fehler {code}',
|
||||
fallback: 'Verbindungsfehler',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Lesbare Hinweise zu fehlgeschlagenen Abrufen (quick-261002-kxc). Die API
|
||||
* liefert nur Fehlerart (`errorKind`) und eine Kurzkennung (`errorDetail`,
|
||||
* z. B. `ERR_TLS_CERT_ALTNAME_INVALID`, `ECONNREFUSED`, `HTTP 502`); die Kachel
|
||||
* zeigt daraus einen Klartext, die Rohkennung bleibt als Tooltip erhalten.
|
||||
* Dieselben Regeln gelten fuer den Mailtext (`describeCheckError` in der API).
|
||||
*/
|
||||
|
||||
export type ErrorHintKey =
|
||||
| 'certName'
|
||||
| 'certExpired'
|
||||
| 'certUntrusted'
|
||||
| 'dnsNotFound'
|
||||
| 'connectionRefused'
|
||||
| 'timeout'
|
||||
| 'httpStatus'
|
||||
| 'fallback';
|
||||
|
||||
export interface ErrorHint {
|
||||
key: ErrorHintKey;
|
||||
/** Nur bei `httpStatus`: der Statuscode. */
|
||||
code?: string;
|
||||
}
|
||||
|
||||
const CERT_NAME = new Set(['ERR_TLS_CERT_ALTNAME_INVALID', 'HOSTNAME_MISMATCH']);
|
||||
const CERT_EXPIRED = new Set(['CERT_HAS_EXPIRED']);
|
||||
const CERT_UNTRUSTED = new Set([
|
||||
'DEPTH_ZERO_SELF_SIGNED_CERT',
|
||||
'SELF_SIGNED_CERT_IN_CHAIN',
|
||||
'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
|
||||
'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',
|
||||
'CERT_UNTRUSTED',
|
||||
'CERT_NOT_YET_VALID',
|
||||
'CERT_REVOKED',
|
||||
]);
|
||||
const DNS = new Set(['ENOTFOUND', 'EAI_AGAIN']);
|
||||
const TIMEOUT = new Set(['ETIMEDOUT', 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_HEADERS_TIMEOUT']);
|
||||
|
||||
export function errorHint(
|
||||
errorKind: string | null | undefined,
|
||||
errorDetail: string | null | undefined,
|
||||
): ErrorHint {
|
||||
const detail = errorDetail ?? '';
|
||||
if (errorKind === 'tls') {
|
||||
if (CERT_NAME.has(detail)) return { key: 'certName' };
|
||||
if (CERT_EXPIRED.has(detail)) return { key: 'certExpired' };
|
||||
if (CERT_UNTRUSTED.has(detail)) return { key: 'certUntrusted' };
|
||||
return { key: 'fallback' };
|
||||
}
|
||||
if (errorKind === 'timeout') return { key: 'timeout' };
|
||||
if (errorKind === 'http-status') {
|
||||
const match = /^HTTP (\d{3})$/.exec(detail);
|
||||
return match ? { key: 'httpStatus', code: match[1] } : { key: 'fallback' };
|
||||
}
|
||||
if (errorKind === 'network') {
|
||||
if (DNS.has(detail)) return { key: 'dnsNotFound' };
|
||||
if (detail === 'ECONNREFUSED') return { key: 'connectionRefused' };
|
||||
if (TIMEOUT.has(detail)) return { key: 'timeout' };
|
||||
}
|
||||
return { key: 'fallback' };
|
||||
}
|
||||
Reference in New Issue
Block a user