diff --git a/backend/src/controllers/backup.controller.ts b/backend/src/controllers/backup.controller.ts index 60a8fbb4..e701939b 100644 --- a/backend/src/controllers/backup.controller.ts +++ b/backend/src/controllers/backup.controller.ts @@ -50,6 +50,29 @@ export async function createBackup(req: Request, res: Response) { * Backup wiederherstellen * POST /api/settings/backup/:name/restore */ +// Macht eine Fehlermeldung admin-lesbar OHNE den globalen ORM-Leak-Filter +// auszulösen: Stack-Frames raus, "TypeError: …" → "Code-Fehler: …", +// "Cannot read properties of undefined" → "Interner Code-Fehler". +// Vollständiger Stack landet immer im Server-Log (siehe `console.error`). +function makeRestoreErrorReadable(raw: unknown): string { + if (!raw) return 'Unbekannter Fehler'; + let s = typeof raw === 'string' ? raw : (raw as any)?.message || String(raw); + // Stack-Frames " at …(…:123:45)" abschneiden + s = s.split('\n').filter((line: string) => !/^\s*at\s+/.test(line)).join('\n').trim(); + // Bekannte JS-Runtime-Marker rephrasen, damit der orm-leak-guard nicht + // alles auf "Operation fehlgeschlagen" maskiert. + s = s + .replace(/^TypeError:?\s*/i, 'Code-Fehler: ') + .replace(/^ReferenceError:?\s*/i, 'Code-Fehler: ') + .replace(/^SyntaxError:?\s*/i, 'Code-Fehler: ') + .replace(/^RangeError:?\s*/i, 'Code-Fehler: ') + .replace(/Cannot read propert(?:y|ies) of (undefined|null) \(reading '([^']+)'\)/i, 'Wert fehlt: $2') + .replace(/is not a function/i, '(ungültiger Funktionsaufruf)') + .replace(/is not defined$/i, '(Wert nicht definiert)') + .replace(/Invalid `prisma\.[^`]+`/i, 'DB-Fehler'); + return s.slice(0, 500); // Längenlimit für UI +} + export async function restoreBackup(req: Request, res: Response) { try { const { name } = req.params; @@ -73,10 +96,20 @@ export async function restoreBackup(req: Request, res: Response) { message: `${result.restoredRecords} Datensätze und ${result.restoredFiles || 0} Dateien wiederhergestellt`, }); } else { - res.status(500).json({ error: 'Wiederherstellung fehlgeschlagen', details: result.error }); + console.error(`[restore] Backup ${name} fehlgeschlagen:`, result.error); + res.status(500).json({ + error: 'Wiederherstellung fehlgeschlagen', + details: makeRestoreErrorReadable(result.error), + hint: 'Vollständiger Stack-Trace im Server-Log: docker logs opencrm-app | tail -200', + }); } } catch (error: any) { - res.status(500).json({ error: 'Fehler bei der Wiederherstellung', details: error.message }); + console.error(`[restore] Exception bei Backup ${req.params.name}:`, error?.stack || error); + res.status(500).json({ + error: 'Fehler bei der Wiederherstellung', + details: makeRestoreErrorReadable(error), + hint: 'Vollständiger Stack-Trace im Server-Log: docker logs opencrm-app | tail -200', + }); } } diff --git a/frontend/src/pages/settings/DatabaseBackup.tsx b/frontend/src/pages/settings/DatabaseBackup.tsx index b4077908..da09b9c3 100644 --- a/frontend/src/pages/settings/DatabaseBackup.tsx +++ b/frontend/src/pages/settings/DatabaseBackup.tsx @@ -6,14 +6,17 @@ import { backupApi, BackupInfo, getAccessToken } from '../../services/api'; import { useAuth } from '../../context/AuthContext'; import Button from '../../components/ui/Button'; -function extractError(err: any): string { +function extractError(err: any): { headline: string; details?: string; hint?: string } { const data = err?.response?.data; - if (data) { - if (data.error && data.details) return `${data.error}: ${data.details}`; - if (data.error) return data.error; - if (typeof data === 'string') return data; + if (data && typeof data === 'object') { + return { + headline: data.error || 'Unbekannter Fehler', + details: data.details, + hint: data.hint, + }; } - return err?.message || 'Unbekannter Fehler'; + if (typeof data === 'string') return { headline: data }; + return { headline: err?.message || 'Unbekannter Fehler' }; } export default function DatabaseBackup() { @@ -55,7 +58,9 @@ export default function DatabaseBackup() { // Bei Fehler bleibt das Dialog absichtlich offen, damit der User // die Detail-Message sehen + ggf. erneut versuchen kann. onError: (err: any) => { - toast.error(extractError(err), { duration: 8000 }); + const e = extractError(err); + const msg = e.details ? `${e.headline}\n${e.details}` : e.headline; + toast.error(msg, { duration: 10000 }); }, }); @@ -348,14 +353,22 @@ export default function DatabaseBackup() { Achtung: Bestehende Daten und Dokumente werden mit dem Backup-Stand überschrieben. Dies kann nicht rückgängig gemacht werden.

- {restoreMutation.isError && ( -
-
Wiederherstellung fehlgeschlagen
-
- {extractError(restoreMutation.error)} + {restoreMutation.isError && (() => { + const e = extractError(restoreMutation.error); + return ( +
+
{e.headline}
+ {e.details && ( +
+ {e.details} +
+ )} + {e.hint && ( +
{e.hint}
+ )}
-
- )} + ); + })()}