feat(worker): der externe Worker wird erwachsen - Log in Rippy, Anzeige, Slots
Ampel / ampel (push) Successful in 28s

Commander 26.07.2026: "der externe Encoder Worker ist ein bisschen duenn - der
koennte noch viel mehr." Drei Punkte, alle am Tray.

1. LOG IN RIPPY STATT TXT-DATEI (ausdruecklich gewuenscht). Das Tray schrieb sein
   Log nach %LOCALAPPDATA% und oeffnete es im Editor - wer wissen wollte, warum
   der Worker nichts tut, musste sich an den PC setzen. Neue Bruecke
   (logbruecke.py) meldet die wichtigen Zeilen nach Rippy, Quelle "w:<name>",
   und die Logs-Seite hat jetzt Knoepfe je Quelle: "was macht mein PC" ist ein
   Klick. Der Filter konnte Quellen schon immer, es gab nur keinen Knopf.

   Durchgelassen wird WENIG und mit Grund: Die Job-Meldungen stehen laengst in
   Rippy (tasks.py schreibt sie selbst). Es fehlte, was DANEBEN passiert und den
   Worker unbrauchbar macht, ohne dass ein Job existiert - hochgefahren oder
   nicht, Verbindung zu Redis/Postgres, Abstuerze. Alles andere fliegt weg:
   Celery ist bei --loglevel=info gespraechig, die logs-Tabelle hat keine
   Aufraeumung, und ein zugemuelltes Log ist so unbrauchbar wie keins. Dazu eine
   Drossel (30 Zeilen/Minute), die MELDET, wieviel sie verschluckt hat.
   Zeilenformat woertlich aus dem laufenden Container abgenommen (Celery 5.4.0).

   Die lokale Datei bleibt - sie ist genau dann die einzige Auskunft, wenn Rippy
   nicht erreichbar ist.

2. DAS TRAY ZEIGT, WAS LAEUFT. Vorher stand dort "laeuft" oder "gestoppt" - auf
   einer Maschine, die stundenlang an einem Film rechnet, ist das keine Auskunft.
   Jetzt Titel, Prozent und Restzeit, geholt von Rippys /jobs. Bewusst dieselbe
   Quelle wie das Dashboard, damit im Tray nicht eine zweite, abweichende
   Schaetzung steht.

   Dazu: Windows schlaeft nicht mehr mitten im Encode ein
   (SetThreadExecutionState, ohne ES_DISPLAY_REQUIRED - der Bildschirm darf
   ausgehen). Die Sperre wird zurueckgenommen, sobald nichts laeuft, und auch bei
   einem harten Ende des Trays - sonst schlaeft der PC nie wieder ein und niemand
   weiss warum.

3. MEHRERE ENCODES GLEICHZEITIG. Der Worker lief fest mit --pool=solo und nahm
   genau EINEN Auftrag an. Der Installer fragt die Zahl jetzt (GUI: Feld neben
   dem Namen, mit der erkannten Kernzahl daneben), Vorbelegung ab 12 Kernen
   zwei, sonst einer: HandBrake nutzt schon alle Kerne, aber x265 skaliert nicht
   linear. Auf Windows gibt es keinen prefork-Pool (kein fork) - deshalb
   --pool=threads, was hier passt, weil die Arbeit ein Kind-Prozess ist und der
   Thread nur wartet.

GUI-Layout headless gerendert und angesehen (nichts ueberlappt, 16 Kerne
korrekt erkannt), beide .ps1 mit echtem PowerShell 5.1 geprueft, BOM und CRLF
erhalten, .exe neu gebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-26 14:17:14 +02:00
parent be04a9772e
commit 87484d6863
7 changed files with 587 additions and 21 deletions
+39 -2
View File
@@ -70,10 +70,28 @@ export default function LogsPage() {
}
}
const filteredLogs = filter === 'all'
? logs
const filteredLogs = filter === 'all'
? logs
: logs.filter(log => log.level === filter || log.source === filter)
/*
* Quellen zum Anklicken (Commander-Wunsch 26.07.2026: „bessere Log-Ansichten
* — kein txt file, direkt von Rippy Logs").
*
* Der Filter konnte Quellen schon immer, es gab nur keinen Knopf dafür — man
* musste die Zeilen mit dem Auge durchsuchen. Seit dieser Runde meldet auch
* der externe Windows-Worker hierher (worker/logbruecke.py, Quelle „w:<name>"),
* und damit wird die Auswahl erst richtig nützlich: „was macht mein PC?" ist
* ein Klick.
*
* Die Liste wird aus den WIRKLICH vorhandenen Zeilen gebaut, nicht aus einer
* festen Aufzählung — sonst fehlt jede neue Maschine.
*/
const quellen = [...new Set(logs.map(l => l.source).filter(Boolean))].sort()
// „w:tobisnicerpc" ist als Knopfbeschriftung unschön.
const quelleLabel = (q: string) => q.startsWith('w:') ? `Worker ${q.slice(2)}` : q
const stats = {
info: logs.filter(l => l.level === 'info').length,
warning: logs.filter(l => l.level === 'warning').length,
@@ -162,6 +180,25 @@ export default function LogsPage() {
{f === 'all' ? 'Alle' : getLevelLabel(f)}
</button>
))}
{quellen.length > 1 && (
<>
<span className="text-xs font-medium text-slate-500 dark:text-slate-400 mx-2">Quelle:</span>
{quellen.map(q => (
<button
key={q}
onClick={() => setFilter(q)}
className={`px-3 py-1 rounded-full text-xs font-medium transition-all ${
filter === q
? 'bg-indigo-500 text-white font-bold shadow-md shadow-indigo-500/20'
: 'bg-slate-200/70 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-700'
}`}
>
{quelleLabel(q)}
</button>
))}
</>
)}
</div>
<div className="max-h-[600px] overflow-y-auto">