feat(nextcloud-status): Cloud-Formular zeigt nur deutsche Meldungen, Bildadresse auch per http (j9f)

- Vorabpruefung im Browser, API-Kennungen werden zu de/en-Texten
- Beschriftung Bildadresse mit neuem Hinweis zu http und https

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-08 14:08:21 +02:00
parent 443256164d
commit c30a82ec0f
8 changed files with 401 additions and 61 deletions
+41 -14
View File
@@ -97,16 +97,43 @@ export function logoSrc(
return instance.logoUrl ?? null;
}
/** Liest die NestJS-Fehlermeldung aus dem Antwortkoerper, faellt sonst auf einen Standardtext zurueck. */
async function readErrorMessage(res: Response, fallback: string): Promise<string> {
/**
* Fehler der Cloud-Routen mit Statuscode und stabiler Kennung (quick-261008-j9f,
* D-05). `code` ist `body.code` der API (z. B. `logoFetchInternal`) oder, bei
* Meldungen der globalen Eingabepruefung, die Kennung als Meldungstext; sonst
* `null`. Die Oberflaeche zeigt nie `message`, sondern uebersetzt die Kennung
* (siehe `cloud-form-errors.ts`); `message` bleibt fuer andere Aufrufer lesbar.
*/
export class NextcloudFormError extends Error {
public readonly status: number;
public readonly code: string | null;
constructor(status: number, code: string | null, message: string) {
super(message);
this.name = 'NextcloudFormError';
this.status = status;
this.code = code;
}
}
/** Liest Kennung und Meldung aus dem Antwortkoerper; ohne lesbaren Koerper gilt der Standardtext. */
async function readError(res: Response, fallback: string): Promise<NextcloudFormError> {
let code: string | null = null;
let message = fallback;
try {
const body = await res.json();
if (typeof body?.message === 'string') return body.message;
if (Array.isArray(body?.message) && body.message.length > 0) return String(body.message[0]);
if (typeof body?.code === 'string') code = body.code;
if (typeof body?.message === 'string') {
message = body.message;
} else if (Array.isArray(body?.message) && body.message.length > 0) {
message = String(body.message[0]);
// Meldungen der Eingabepruefung sind Kennungen (nur Buchstaben, klein beginnend).
if (code === null && /^[a-z][A-Za-z]*$/.test(message)) code = message;
}
} catch {
/* Antwort war kein JSON — Standardtext bleibt */
}
return fallback;
return new NextcloudFormError(res.status, code, message);
}
export interface NextcloudInstancePayload {
@@ -128,7 +155,7 @@ export async function createInstance(
credentials: 'include',
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error(await readErrorMessage(res, 'Failed to create nextcloud instance'));
if (!res.ok) throw await readError(res, 'Failed to create nextcloud instance');
return res.json();
}
@@ -143,27 +170,27 @@ export async function updateInstance(
credentials: 'include',
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error(await readErrorMessage(res, 'Failed to update nextcloud instance'));
if (!res.ok) throw await readError(res, 'Failed to update nextcloud instance');
return res.json();
}
/** DELETE /instances/:id — Cloud entfernen (nur Verwalten). */
export async function deleteInstance(id: string): Promise<void> {
const res = await fetch(`${BASE}/${id}`, { method: 'DELETE', credentials: 'include' });
if (!res.ok) throw new Error(await readErrorMessage(res, 'Failed to delete nextcloud instance'));
if (!res.ok) throw await readError(res, 'Failed to delete nextcloud instance');
}
/** POST /instances/check — "Jetzt pruefen" fuer die ganze Liste, liefert die frische Liste. */
export async function checkAll(): Promise<NextcloudList> {
const res = await fetch(`${BASE}/check`, { method: 'POST', credentials: 'include' });
if (!res.ok) throw new Error(await readErrorMessage(res, 'Failed to check nextcloud instances'));
if (!res.ok) throw await readError(res, 'Failed to check nextcloud instances');
return res.json();
}
/** POST /instances/:id/check — eine Cloud sofort pruefen, liefert die aktualisierte Kachel. */
export async function checkOne(id: string): Promise<NextcloudInstance> {
const res = await fetch(`${BASE}/${id}/check`, { method: 'POST', credentials: 'include' });
if (!res.ok) throw new Error(await readErrorMessage(res, 'Failed to check nextcloud instance'));
if (!res.ok) throw await readError(res, 'Failed to check nextcloud instance');
return res.json();
}
@@ -176,21 +203,21 @@ export async function uploadLogo(id: string, file: File): Promise<NextcloudInsta
credentials: 'include',
body: form,
});
if (!res.ok) throw new Error(await readErrorMessage(res, 'Failed to upload logo'));
if (!res.ok) throw await readError(res, 'Failed to upload logo');
return res.json();
}
/** DELETE /instances/:id/logo — hochgeladenes Logo entfernen (nur Verwalten). */
export async function removeLogo(id: string): Promise<NextcloudInstance> {
const res = await fetch(`${BASE}/${id}/logo`, { method: 'DELETE', credentials: 'include' });
if (!res.ok) throw new Error(await readErrorMessage(res, 'Failed to remove logo'));
if (!res.ok) throw await readError(res, 'Failed to remove logo');
return res.json();
}
/** POST /instances/:id/subscription — Glocke "Benachrichtigen" einschalten (jeder mit Modulzugriff). */
export async function subscribe(id: string): Promise<void> {
const res = await fetch(`${BASE}/${id}/subscription`, { method: 'POST', credentials: 'include' });
if (!res.ok) throw new Error(await readErrorMessage(res, 'Failed to subscribe'));
if (!res.ok) throw await readError(res, 'Failed to subscribe');
}
/** DELETE /instances/:id/subscription — Glocke ausschalten. */
@@ -199,7 +226,7 @@ export async function unsubscribe(id: string): Promise<void> {
method: 'DELETE',
credentials: 'include',
});
if (!res.ok) throw new Error(await readErrorMessage(res, 'Failed to unsubscribe'));
if (!res.ok) throw await readError(res, 'Failed to unsubscribe');
}
/** Ein abgewiesener Aufruf der Meldungsabfrage; `status` ist der HTTP-Status (401, 403 ...). */