Drei Praxis-Bugs: tote NAS-Mounts reparierbar, HandBrake-Versionen konsistent, Encoder-Wahl beim Rip
Ampel / ampel (push) Successful in 28s

1) Speicher-Mounts robust (Befund: toter CIFS-Mount nach NAS-Ausfall/Rebuild —
   mounted:false, verschwand aus 'Verfuegbare Ziele', Neu-Anlegen -> 409, man
   sass fest):
   - mounts.py: ist_erreichbar() (listdir, soft-Mount bricht schnell ab),
     ist_gemountet() faengt OSError toter Mounts, aushaengen() mit
     umount -l Fallback, reparieren() (lazy abhaengen + frisch mounten).
   - /storage-mounts liefert 'reachable'; POST bei existierendem Namen:
     aktiv -> 409, tot -> automatische Reparatur mit neuen Angaben;
     neuer POST /storage-mounts/{name}/repair (gespeicherte Zugangsdaten).
   - /storage-targets crasht nicht mehr an totem Mount (os.path.ismount
     OSError abgefangen).
   - UI: eigene 'Netzwerk-Mounts'-Liste mit Status (aktiv/nicht erreichbar/
     getrennt) + Reparieren- und Entfernen-Knopf — tote Mounts sind sichtbar
     und wiederherstellbar statt zu verschwinden.

2) HandBrake-Versionen konsistent (Befund: Docker 1.6.1, Windows-Skript
   fest 1.9.2, Update-Check meldet 1.11.2 — verwirrend):
   - Windows-Installer zieht jetzt DYNAMISCH die neueste Version (GitHub
     latest, Fallback 1.11.2) — passt zum Update-Check.
   - Update-UI erklaert klar: Docker = stabiles Debian-Paket (bewusst aelter,
     kein Fehler), Windows = neueste. MakeMKV-Update zeigt den Befehl.

3) Encoder-/Worker-Auswahl beim Rip (Feature):
   - Celery worker_direct=True: jeder Worker konsumiert zusaetzlich seine
     Direkt-Queue. API-Helper transcode_queue(node) routet gezielt an den
     gewaehlten Worker, faellt aber sicher auf die geteilte transcode-Queue
     zurueck, wenn er offline ist (kein Haengenbleiben).
   - /capabilities liefert den Celery-Node je Worker; POST /jobs nimmt
     transcode_node (-> Job-meta); rip_disc + retry-transcode routen danach.
   - Rip-Dialog: Encoder-/Worker-Dropdown, sichtbar ab 2 Online-Workern.
   - ping_worker-Task zum Verifizieren des gezielten Routings.
   - Nebenfund gefixt: DeviceDiscovery leitete die Titel-Auswahl (titles)
     gar nicht an die API weiter — jetzt titles + transcode_node.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-24 18:19:57 +02:00
co-authored by Claude Fable 5
parent fe1387b1ec
commit 4ba02047db
10 changed files with 360 additions and 47 deletions
+66 -1
View File
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'
import { HardDrive, Plus, Trash2, CheckCircle, AlertCircle, RefreshCw, Folder, File, ArrowUp, FolderPlus } from 'lucide-react'
import { HardDrive, Plus, Trash2, CheckCircle, AlertCircle, AlertTriangle, RefreshCw, Wrench, Folder, File, ArrowUp, FolderPlus } from 'lucide-react'
import { api } from '../lib/api'
import { useToast } from '../context/ToastContext'
import { Card, CardHeader, CardTitle, CardContent } from './ui/Card'
@@ -11,6 +11,7 @@ interface Mount {
type: string
source: string
mounted: boolean
reachable?: boolean
has_credentials: boolean
}
@@ -149,6 +150,20 @@ export default function StorageMounts() {
}
}
const reparieren = async (mountName: string) => {
setBusy(true)
try {
const r = await api.post(`/storage-mounts/${mountName}/repair`)
toast(r.data.writable ? 'success' : 'error',
r.data.writable ? `${mountName}" neu verbunden` : `${mountName}" verbunden, aber nur lesbar`)
laden()
} catch (e: any) {
toast('error', e?.response?.data?.detail || 'Reparatur fehlgeschlagen — NAS erreichbar?')
} finally {
setBusy(false)
}
}
return (
<div className="space-y-6">
{/* Vorhandene Ziele */}
@@ -190,6 +205,56 @@ export default function StorageMounts() {
</CardContent>
</Card>
{/* Netzwerk-Mounts mit Status — zeigt AUCH tote/getrennte Mounts, damit
man sie reparieren oder entfernen kann (Bug 24.07.: ein toter NAS-Mount
verschwand aus „Verfügbare Ziele" und ließ sich nicht neu anlegen). */}
{mounts.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<HardDrive size={20} className="text-amber-500" />
<span>Netzwerk-Mounts</span>
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2.5">
{mounts.map(m => {
const aktiv = m.mounted && m.reachable !== false
const tot = m.mounted && m.reachable === false
return (
<div key={m.name} className="flex items-center justify-between gap-3 p-3.5 rounded-xl border border-slate-200/80 dark:border-slate-800 bg-slate-50 dark:bg-slate-950/80">
<div className="flex items-center gap-3 min-w-0">
{aktiv
? <CheckCircle size={18} className="text-emerald-500 flex-shrink-0" />
: tot
? <AlertTriangle size={18} className="text-amber-500 flex-shrink-0" />
: <AlertCircle size={18} className="text-slate-400 flex-shrink-0" />}
<div className="min-w-0">
<span className="text-sm font-medium text-slate-900 dark:text-slate-200">{m.name}</span>
<span className="text-xs ml-2 font-mono text-slate-500 dark:text-slate-400">{m.source}</span>
<p className={`text-xs ${aktiv ? 'text-emerald-600 dark:text-emerald-400' : tot ? 'text-amber-600 dark:text-amber-400' : 'text-slate-500 dark:text-slate-400'}`}>
{aktiv ? 'aktiv & erreichbar' : tot ? 'verbunden, aber NICHT erreichbar (NAS aus/weg?)' : 'getrennt'}
</p>
</div>
</div>
<div className="flex items-center gap-1.5 flex-shrink-0">
{!aktiv && (
<Button variant="secondary" size="sm" onClick={() => reparieren(m.name)} disabled={busy} title="Neu verbinden (gespeicherte Zugangsdaten)">
<Wrench size={14} /> Reparieren
</Button>
)}
<button onClick={() => entfernen(m.name)} disabled={busy} className="p-1.5 rounded-lg text-rose-500 hover:bg-rose-500/10 transition-colors" title="Entfernen">
<Trash2 size={16} />
</button>
</div>
</div>
)
})}
</div>
</CardContent>
</Card>
)}
{/* Ordner-Verwaltung */}
<Card>
<button