Neun gemeldete Fehler, und die Frage, welcher Build gerade läuft
Ein grosser Commit, weil die Punkte einander bedingen: ohne lesbare Fehlermeldung war die Serienerkennung nicht zu diagnostizieren, und ohne Build-Kennung nicht einmal festzustellen, ob überhaupt der gebaute Stand läuft. Zwei der neun Meldungen betrafen Funktionen, die seit Stunden im Quelltext stehen. Version und Build. Die Version steht fest; daneben eine Kennung aus sechs Zeichen, die sich bei jedem Docker-Bau ändert. Sie steht in der Verwaltung, unter /health und im Protokoll bei jedem Start. Damit ist "reden wir vom selben Build?" in zwei Sekunden beantwortet statt in einem halben Tag. Serien. Lag eine Datei direkt im Serienordner und trug vor der Folgennummer nichts, wurde der ganze Dateiname zum Serientitel: aus 95 Serien wurden 737, jede mit einer Folge, bei jedem Lauf neu. Dazu lief der Scan in Verweise hinein — derselbe Ordner unter zwei Namen ergab zwei Serien. Und weder neue noch umgezogene Dateien standen in der Pfadliste des Laufs, weshalb ein doppelt gesehener Pfad eine zweite Zeile mit gleichem (LibraryId, Path) anlegte und der Eindeutigkeitsindex den ganzen Speichervorgang scheitern liess. Jede Serienanlage wird jetzt im Tagesprotokoll begründet. Fehlermeldungen. "An error occurred while saving the entity changes" stand englisch in einer deutschen Oberfläche und nannte weder Ursache noch nächsten Schritt. Jetzt geht der vollständige Text ins Protokoll und ein deutscher Satz ohne Fehlercode in die Anzeige — auch für "Failed to fetch", das aus dem Browser kam und nie übersetzt wurde. Protokolle. Die Ansicht holte die ganze Datei und zeigte 800 Zeilen davon; bei zwei Megabyte brach die Verbindung ab. Gelesen wird jetzt serverseitig rückwärts, mit einem Knopf für mehr. Auswahlfelder. Die drei <select> zeichnete das Betriebssystem: ein helles Systemmenü mitten in der dunklen Oberfläche. Ersetzt durch ein eigenes Listenfeld mit Tastaturbedienung. Bildwahl. Drei Raster untereinander in einem scrollenden Dialog hiess zwei Scrollebenen übereinander. Jetzt ein Reiter je Bildtyp. Sprache. Die Serversprache stand in der Serverliste zwischen Name, Metadaten und Fassung — gesucht und nicht gefunden. Sie hat jetzt einen eigenen Abschnitt ganz oben. Dazu: ein Knopf, der den erfassten Inhalt einer Mediathek verwirft, ohne Name und Pfad mitzunehmen; ein Abschnitt Speicher mit den drei Pfaden, ihrer Belegung und dem Leeren des Bildspeichers; und die Korrektur einer Zuordnung frischt nur noch die Kachel auf statt die ganze Seite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ab8a66d898
commit
ac04901ccc
+55
-4
@@ -113,7 +113,10 @@ export type Vorschlag = {
|
||||
};
|
||||
export type Hinweis = { text: string; logoUrl: string; url: string };
|
||||
export type Systemauskunft = {
|
||||
version: string; serverName: string | null;
|
||||
version: string;
|
||||
/** Die Kennung dieses Abbilds — bei jedem Bau neu, „DEV" ausserhalb eines Containers. */
|
||||
build: string;
|
||||
serverName: string | null;
|
||||
needsSetup: boolean; attributions: Hinweis[];
|
||||
};
|
||||
|
||||
@@ -169,6 +172,12 @@ export const meineSprache = (): string | null => sitzung.wer?.language ?? null;
|
||||
/* ---------- Anfragen ---------- */
|
||||
|
||||
export class ApiFehler extends Error {
|
||||
// **Der Name wird gesetzt, nicht geerbt.** Nach dem Bündeln heisst die
|
||||
// Klasse anders, und `instanceof` über Modulgrenzen ist nicht verlässlich;
|
||||
// `name` überlebt beides. Daran erkennt `meldung()`, dass der Text vom
|
||||
// Server stammt und bereits deutsch ist.
|
||||
override readonly name = 'ApiFehler';
|
||||
|
||||
constructor(public status: number, nachricht: string) { super(nachricht); }
|
||||
}
|
||||
|
||||
@@ -182,7 +191,24 @@ async function anfrage<T>(pfad: string, opts: RequestInit = {}): Promise<T> {
|
||||
kopf.set('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
const antwort = await fetch(`/api/v1${pfad}`, { ...opts, headers: kopf });
|
||||
// **Einmal nachfassen, bevor gemeldet wird.**
|
||||
//
|
||||
// Während eines Scans über tausende Dateien kam es vor, dass eine Anfrage
|
||||
// ins Leere lief und der Bilddialog mit „Failed to fetch" stehenblieb —
|
||||
// beim zweiten Versuch von Hand ging es sofort. Ein einzelner Aussetzer ist
|
||||
// kein Fehler, den der Benutzer sehen muss.
|
||||
//
|
||||
// Nur bei einem Netzfehler und nur bei lesenden Anfragen: ein zweites POST
|
||||
// könnte dasselbe zweimal anlegen, und das wäre schlimmer als die Meldung.
|
||||
const lesend = !opts.method || opts.method.toUpperCase() === 'GET';
|
||||
let antwort: Response;
|
||||
try {
|
||||
antwort = await fetch(`/api/v1${pfad}`, { ...opts, headers: kopf });
|
||||
} catch (aussetzer) {
|
||||
if (!lesend || !(aussetzer instanceof TypeError)) throw aussetzer;
|
||||
await new Promise(r => setTimeout(r, 400));
|
||||
antwort = await fetch(`/api/v1${pfad}`, { ...opts, headers: kopf });
|
||||
}
|
||||
|
||||
// **Eine 401 ist noch kein Grund, die Anmeldung wegzuwerfen.**
|
||||
//
|
||||
@@ -580,17 +606,42 @@ export const zurueckziehen = (ids: string[]) =>
|
||||
export const reihenfolgeSetzen = (ids: string[]) =>
|
||||
anfrage<void>('/libraries/order', { method: 'PUT', body: JSON.stringify({ ids }) });
|
||||
|
||||
/** Wirft den erfassten Inhalt einer Mediathek weg — die Dateien bleiben liegen.
|
||||
*
|
||||
* **Wofür.** Steht nach einer falschen Zuordnungsregel ein Bestand da, den kein
|
||||
* Scan mehr geradezieht, ist das der kurze Weg: die Einträge weg, einmal neu
|
||||
* scannen. Mediathek, Pfad und Einstellungen bleiben.
|
||||
*/
|
||||
export const mediathekZuruecksetzen = (bibliothek: string) =>
|
||||
anfrage<void>(`/libraries/${bibliothek}/reset`, { method: 'POST' });
|
||||
|
||||
export const pruefungSetzen = (bibliothek: string, an: boolean) =>
|
||||
anfrage<void>(`/libraries/${bibliothek}/review`,
|
||||
{ method: 'PUT', body: JSON.stringify({ reviewNewItems: an }) });
|
||||
|
||||
export type Speicherort = {
|
||||
name: string; variable: string; pfad: string;
|
||||
belegt: number; frei: number | null; bilder: number | null;
|
||||
};
|
||||
|
||||
/** Wo der Server seine Dateien ablegt und wie viel davon belegt ist. */
|
||||
export const speicherorte = () => anfrage<Speicherort[]>('/system/storage');
|
||||
|
||||
/** Leert den Bildspeicher. Ungefährlich — Bilder werden bei Bedarf neu geholt. */
|
||||
export const bildspeicherLeeren = () =>
|
||||
anfrage<{ freed: number }>('/system/storage/images', { method: 'DELETE' });
|
||||
|
||||
export type Protokolltag = { date: string; bytes: number };
|
||||
|
||||
/** Welche Tage protokolliert sind, neueste zuerst. */
|
||||
export const protokolltage = () => anfrage<Protokolltag[]>('/system/logs');
|
||||
|
||||
/** Die Adresse, unter der ein Protokolltag liegt — zum Lesen und Herunterladen. */
|
||||
export const protokollAdresse = (tag: string) => `/api/v1/system/logs/${tag}`;
|
||||
/** Die Adresse eines Protokolltags.
|
||||
*
|
||||
* Mit `zeilen` kommt nur das Ende — das ist, was die Ansicht braucht. Ohne
|
||||
* kommt alles, und das ist der Weg zum Herunterladen. */
|
||||
export const protokollAdresse = (tag: string, zeilen?: number) =>
|
||||
`/api/v1/system/logs/${tag}` + (zeilen ? `?tail=${zeilen}` : '');
|
||||
|
||||
export type VermissteDatei = {
|
||||
fileId: string; path: string; versionLabel: string | null;
|
||||
|
||||
Reference in New Issue
Block a user