Feature: "Letzte Fehler anzeigen" im Hilfe-Menue (Error-Ring ins UI)

Der Error-Ring aus v1.7.185 war bisher nur ueber die Debug-Server-URL mit
Token erreichbar — per RDP ist ein Menueklick drastisch schneller als curl.
Neuer Menuepunkt im Hilfe-Dropdown zeigt die letzten 200 WARN/ERROR-Eintraege
(Kopf: "X Fehler, Y Warnungen") im bestehenden Bestaetigungs-Dialog mit
aufklappbaren Details; der Bestaetigen-Knopf kopiert die komplette Liste in
die Zwischenablage — direkt verwertbar fuer Bug-Reports.

IPC-Kette nach dem GET_DEBUG_SETUP_CHECK-Muster (ipc.ts, main.ts mit direktem
error-ring-Import wie debug-server/support-bundle, preload, preload-api).
Read-only auf den In-Memory-Snapshot, kein neues CSS.
This commit is contained in:
Sucukdeluxe 2026-06-09 20:45:21 +02:00
parent 2a1a55401e
commit be4d54a6b5
5 changed files with 35 additions and 0 deletions

View File

@ -5,6 +5,7 @@ import { AddLinksPayload, AppSettings, DebridProvider, UpdateInstallProgress } f
import { AppController } from "./app-controller"; import { AppController } from "./app-controller";
import { IPC_CHANNELS } from "../shared/ipc"; import { IPC_CHANNELS } from "../shared/ipc";
import { getLogFilePath, logger } from "./logger"; import { getLogFilePath, logger } from "./logger";
import { getRecentErrors } from "./error-ring";
import { APP_NAME } from "./constants"; import { APP_NAME } from "./constants";
import { extractHttpLinksFromText } from "./utils"; import { extractHttpLinksFromText } from "./utils";
import { cleanupStaleSubstDrives, shutdownDaemon } from "./extractor"; import { cleanupStaleSubstDrives, shutdownDaemon } from "./extractor";
@ -626,6 +627,8 @@ function registerIpcHandlers(): void {
ipcMain.handle(IPC_CHANNELS.GET_DEBUG_SETUP_CHECK, async () => controller.getDebugSetupCheck()); ipcMain.handle(IPC_CHANNELS.GET_DEBUG_SETUP_CHECK, async () => controller.getDebugSetupCheck());
ipcMain.handle(IPC_CHANNELS.GET_RECENT_ERRORS, async () => getRecentErrors());
ipcMain.handle(IPC_CHANNELS.GET_TRACE_CONFIG, async () => controller.getTraceConfig()); ipcMain.handle(IPC_CHANNELS.GET_TRACE_CONFIG, async () => controller.getTraceConfig());
ipcMain.handle(IPC_CHANNELS.SET_TRACE_ENABLED, async (_event: IpcMainInvokeEvent, enabled: boolean, note?: string, durationMinutes?: number) => { ipcMain.handle(IPC_CHANNELS.SET_TRACE_ENABLED, async (_event: IpcMainInvokeEvent, enabled: boolean, note?: string, durationMinutes?: number) => {

View File

@ -69,6 +69,7 @@ const api: ElectronApi = {
openPackageLog: (packageId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.OPEN_PACKAGE_LOG, packageId), openPackageLog: (packageId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.OPEN_PACKAGE_LOG, packageId),
openItemLog: (itemId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.OPEN_ITEM_LOG, itemId), openItemLog: (itemId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.OPEN_ITEM_LOG, itemId),
getDebugSetupCheck: () => ipcRenderer.invoke(IPC_CHANNELS.GET_DEBUG_SETUP_CHECK), getDebugSetupCheck: () => ipcRenderer.invoke(IPC_CHANNELS.GET_DEBUG_SETUP_CHECK),
getRecentErrors: () => ipcRenderer.invoke(IPC_CHANNELS.GET_RECENT_ERRORS),
getTraceConfig: () => ipcRenderer.invoke(IPC_CHANNELS.GET_TRACE_CONFIG), getTraceConfig: () => ipcRenderer.invoke(IPC_CHANNELS.GET_TRACE_CONFIG),
setTraceEnabled: (enabled: boolean, note?: string, durationMinutes?: number) => ipcRenderer.invoke(IPC_CHANNELS.SET_TRACE_ENABLED, enabled, note, durationMinutes), setTraceEnabled: (enabled: boolean, note?: string, durationMinutes?: number) => ipcRenderer.invoke(IPC_CHANNELS.SET_TRACE_ENABLED, enabled, note, durationMinutes),
rotateDebugToken: (): Promise<{ path: string }> => ipcRenderer.invoke(IPC_CHANNELS.ROTATE_DEBUG_TOKEN), rotateDebugToken: (): Promise<{ path: string }> => ipcRenderer.invoke(IPC_CHANNELS.ROTATE_DEBUG_TOKEN),

View File

@ -3932,6 +3932,32 @@ export function App(): ReactElement {
} }
}; };
const onShowRecentErrors = async (): Promise<void> => {
closeMenus();
try {
const entries = await window.rd.getRecentErrors();
const errorCount = entries.filter((e) => e.level === "ERROR").length;
const warnCount = entries.filter((e) => e.level === "WARN").length;
const details = entries.map((e) => `${e.ts} [${e.level}] ${e.message}`).join("\n");
const copy = await askConfirmPrompt({
title: "Letzte Fehler",
message: entries.length === 0
? "Keine Fehler oder Warnungen seit dem App-Start aufgezeichnet."
: `${errorCount} Fehler, ${warnCount} Warnungen (letzte ${entries.length})`,
confirmLabel: entries.length > 0 ? "In Zwischenablage kopieren" : "Schließen",
cancelLabel: "Schließen",
details: details || undefined,
detailsLabel: "Einträge anzeigen"
});
if (copy && entries.length > 0) {
await navigator.clipboard.writeText(details);
showToast("Fehlerliste kopiert", 2600);
}
} catch (error) {
showToast(`Fehler-Ansicht fehlgeschlagen: ${String(error)}`, 3000);
}
};
const onRotateDebugToken = async (): Promise<void> => { const onRotateDebugToken = async (): Promise<void> => {
closeMenus(); closeMenus();
const confirmed = await askConfirmPrompt({ const confirmed = await askConfirmPrompt({
@ -4339,6 +4365,9 @@ export function App(): ReactElement {
<button className="menu-dropdown-item" onClick={() => { void onToggleSupportTrace(); }}> <button className="menu-dropdown-item" onClick={() => { void onToggleSupportTrace(); }}>
<span>{supportTraceEnabled ? "Support-Trace deaktivieren" : "Support-Trace aktivieren"}</span> <span>{supportTraceEnabled ? "Support-Trace deaktivieren" : "Support-Trace aktivieren"}</span>
</button> </button>
<button className="menu-dropdown-item" onClick={() => { void onShowRecentErrors(); }}>
<span>Letzte Fehler anzeigen</span>
</button>
<button className="menu-dropdown-item" onClick={() => { void onRunDebugSetupCheck(); }}> <button className="menu-dropdown-item" onClick={() => { void onRunDebugSetupCheck(); }}>
<span>Debug-Setup prüfen</span> <span>Debug-Setup prüfen</span>
</button> </button>

View File

@ -47,6 +47,7 @@ export const IPC_CHANNELS = {
OPEN_PACKAGE_LOG: "app:open-package-log", OPEN_PACKAGE_LOG: "app:open-package-log",
OPEN_ITEM_LOG: "app:open-item-log", OPEN_ITEM_LOG: "app:open-item-log",
GET_DEBUG_SETUP_CHECK: "app:get-debug-setup-check", GET_DEBUG_SETUP_CHECK: "app:get-debug-setup-check",
GET_RECENT_ERRORS: "app:get-recent-errors",
GET_TRACE_CONFIG: "app:get-trace-config", GET_TRACE_CONFIG: "app:get-trace-config",
SET_TRACE_ENABLED: "app:set-trace-enabled", SET_TRACE_ENABLED: "app:set-trace-enabled",
ROTATE_DEBUG_TOKEN: "app:rotate-debug-token", ROTATE_DEBUG_TOKEN: "app:rotate-debug-token",

View File

@ -66,6 +66,7 @@ export interface ElectronApi {
openPackageLog: (packageId: string) => Promise<void>; openPackageLog: (packageId: string) => Promise<void>;
openItemLog: (itemId: string) => Promise<void>; openItemLog: (itemId: string) => Promise<void>;
getDebugSetupCheck: () => Promise<DebugSetupCheckResult>; getDebugSetupCheck: () => Promise<DebugSetupCheckResult>;
getRecentErrors: () => Promise<Array<{ ts: string; level: string; message: string }>>;
getTraceConfig: () => Promise<SupportTraceConfig>; getTraceConfig: () => Promise<SupportTraceConfig>;
setTraceEnabled: (enabled: boolean, note?: string, durationMinutes?: number) => Promise<SupportTraceConfig>; setTraceEnabled: (enabled: boolean, note?: string, durationMinutes?: number) => Promise<SupportTraceConfig>;
rotateDebugToken: () => Promise<{ path: string }>; rotateDebugToken: () => Promise<{ path: string }>;