fix(ui+api): das "Neuladen" war HTTP 429 - und "Neu" komprimierte immer
Ampel / ampel (push) Successful in 30s

Zwei Commander-Befunde, beide mit derselben Wurzel: Rippy hat sich selbst
ausgebremst und dann geschwiegen.

## "Wenn der Worker installiert ist, wird dieser Bereich oft neu geladen"

Gemessen statt geraten. Die Antworten von /jobs und /capabilities waren ueber
zwanzig Sekunden byteweise identisch, alle Endpunkte antworteten unter 30 ms -
es wurde also gar nichts neu geladen. Im nginx-Log standen dagegen 97 Antworten
mit HTTP 429.

Drei Fehler griffen ineinander:

1. Das Limit war zu klein fuer Rippy selbst: 100 Anfragen/min, waehrend ein
   offener Tab 111/min verursacht (Dashboard 75 + Log-Kasten 24 + Laufwerke 12)
   und der Windows-Tray weitere 12/min dazulegt.
2. Der nginx gab die Client-Adresse nicht weiter. Fuer die API kam damit ALLES
   von 172.19.0.6 - Browser, zweiter Tab und Tray teilten sich einen Eimer
   (812 von 876 Anfragen). Das erklaert die Kopplung an den Worker: tray.py
   fragt /api/jobs ueber Port 80, also durch denselben Proxy.
3. Ein abgewiesener Abruf leerte das UI. `catch(() => [])` heisst "es gibt
   keine Jobs" - richtig waere "ich weiss gerade nichts Neues". Fuer einen Takt
   stand "Keine Jobs", die Zaehler sprangen auf (0), vier Sekunden spaeter war
   alles zurueck.

Behoben: X-Real-IP im nginx, Grenze auf 600/min mit vorgerechneter Herleitung,
jeder Fehlschlag laesst den alten Stand stehen (null statt []), axios bekommt
eine Zeitgrenze, und das Dashboard trennt schnelle Daten (Jobs/Laufwerke, 4 s)
von langsamen (Hardware/Worker/Ablagen, 12 s) - 75/min werden zu 30/min.
Ein greifendes Limit steht ab jetzt im Log, gedrosselt auf eine Meldung pro
Client und Minute.

## "Hier gibt es den Button 'neu' aber WAS wird dann gemacht?"

Immer die Komprimierung - auch bei einem Job, dessen RIP abgebrochen war. Am
26.07.2026 waeren aus 5,1 GB Bruchstueck (von rund 40 GB) brav ein Film
geworden, der bei 12 % aufhoert.

Die Phase war nach `status = "failed"` nicht mehr feststellbar, also wird sie
jetzt vermerkt (rip_fertig in den Job-Metadaten: false beim Rip-Start, true bei
der Uebergabe an die Kompression). Daraus folgt die Beschriftung: "Neu
komprimieren", "Neu rippen" - oder bei Bestandsjobs ohne Vermerk ein Dialog,
der beide Wege erklaert und die Groesse der Rohdaten als Entscheidungshilfe
nennt. Geraten wird nicht. Fuer den Rip-Fall gibt es POST
/jobs/{id}/retry-rip: neuer Job mit neuer ID (sonst laege das Bruchstueck im
Roh-Verzeichnis des neuen Rips), Titel/Ablage/Sprachwahl uebernommen, mit
ehrlicher Absage wenn keine Disc im Laufwerk liegt.

10 neue Tests (289 gruen), darunter eine Kopplungspruefung: der Name der
Phasen-Marke muss in worker/tasks.py und api/phasen.py zusammenpassen - genau
diese Sorte Auseinanderdriften hat die Zombie-Erkennung ein Release lang blind
gemacht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-26 16:49:10 +02:00
co-authored by Claude Opus 5
parent f7555a17d1
commit bfb13f44a5
11 changed files with 811 additions and 42 deletions
+6 -1
View File
@@ -4,4 +4,9 @@ import axios from 'axios'
// den api-Container weiter. Vorher stand in vier Dateien http://localhost:8000
// hartkodiert: vom PC aus fragte der Browser damit den PC SELBST nach
// Laufwerken und Jobs — deshalb blieb das UI immer leer.
export const api = axios.create({ baseURL: '/api' })
// Zeitgrenze, damit ein hängender Aufruf nicht ewig offen bleibt und sich die
// Abfragen des 4-Sekunden-Takts stapeln (Befund 26.07.2026: axios wartet ohne
// `timeout` unbegrenzt). 20 s sind reichlich für jeden Endpunkt — die
// langsamsten sind der Update-Check und der Titel-Scan-Start, alles andere
// antwortet in Millisekunden.
export const api = axios.create({ baseURL: '/api', timeout: 20000 })