diff --git a/docker/ui/src/components/RipTargetModal.tsx b/docker/ui/src/components/RipTargetModal.tsx index 0aca650..bcd9b5e 100644 --- a/docker/ui/src/components/RipTargetModal.tsx +++ b/docker/ui/src/components/RipTargetModal.tsx @@ -4,7 +4,7 @@ import { api } from '../lib/api' import { Modal } from './ui/Modal' import { Button } from './ui/Button' import { Input, Select } from './ui/Input' -import { automatikWarnung, externWarnung, sprachName } from '../lib/encoder' +import { automatikWarnung, externWarnung, freigabenAusMapping, sprachName } from '../lib/encoder' interface TargetConfig { id: string @@ -244,6 +244,20 @@ export default function RipTargetModal({ isOpen, initialType, discTitle, deviceI // erreicht (Punkt 6 des Savepoints v3.16). Am 26.07.2026 fiel genau das erst // NACH dem Rip auf, weil nur der Worker selbst prüfte. const gewaehlterWorker = workers.find(w => w.node === encoderNode) + /* + * Ein-Klick-Abhilfe: dasselbe Ziel, aber auf der Freigabe, die der gewählte + * Worker erreicht. Aus /app/media/movies wird /app/media//movies. + * + * Nur wenn es überhaupt eine Freigabe gibt und das Ziel noch nicht darauf + * liegt — sonst stünde ein Knopf da, der nichts tut. + */ + const zielAufFreigabe = (() => { + const freigabe = freigabenAusMapping(gewaehlterWorker?.info?.pfad_map)[0] + if (!freigabe || !zielPfad) return '' + const unterordner = zielPfad.split('/').filter(Boolean).pop() || '' + const neu = `/app/media/${freigabe}/${unterordner}` + return neu === zielPfad ? '' : neu + })() const pfadWarnung = selectedType === 'music' ? null : encoderNode @@ -411,6 +425,21 @@ export default function RipTargetModal({ isOpen, initialType, discTitle, deviceI
⚠️ Dieser Encoder kann so nicht arbeiten.

{pfadWarnung}

+ {/* Ein Knopf statt einer Wegbeschreibung: Er legt das Ziel NUR FÜR + DIESEN RIP auf die Freigabe, die der Worker erreicht — ohne die + globale Ablage anzufassen. „Geh in die Einstellungen" mitten im + Dialog ist lästig, und wer den Rip jetzt starten will, will + jetzt eine Lösung. */} + {zielAufFreigabe && ( + + )}
)} diff --git a/docker/ui/src/lib/encoder.ts b/docker/ui/src/lib/encoder.ts index 28e3ea2..85cb1e4 100644 --- a/docker/ui/src/lib/encoder.ts +++ b/docker/ui/src/lib/encoder.ts @@ -100,6 +100,18 @@ export function pfadAbgedeckt(pfad: string, mapping: string): boolean { }) } +/** + * Namen der Speicherziele, die dieser Worker über sein Mapping erreicht. + * Aus „/app/media/rippy=\\NAS\rippy" wird ["rippy"]. + */ +export function freigabenAusMapping(mapping?: string): string[] { + return (mapping || '').split(';') + .map(p => p.split('=', 1)[0]) + .filter(Boolean) + .map(p => p.replace(/^\/app\/media\//, '')) + .filter(n => n && !n.includes('/')) +} + /** * Warnt, BEVOR gerippt wird, wenn der gewählte Encoder die Pfade nicht * erreichen kann. null = alles in Ordnung (oder der Worker läuft im Container, @@ -128,14 +140,41 @@ export function externWarnung( + 'von außen nicht erreichbar. Oben eine Netzwerk-Freigabe als ' + 'Arbeitsverzeichnis wählen.' } - const fehlt: string[] = [] - if (!pfadAbgedeckt(arbeitsPfad, mapping)) fehlt.push(`Rohdaten (${arbeitsPfad})`) - if (!pfadAbgedeckt(zielPfad, mapping)) fehlt.push(`Ziel (${zielPfad})`) - if (fehlt.length === 0) return null - return `„${worker.name}" erreicht ${fehlt.join(' und ')} nicht — seine ` - + `Übersetzung deckt nur „${mapping}" ab. Beides muss auf einer Freigabe ` - + 'liegen, die diese Maschine sieht, sonst scheitert die Kompression ' - + '(die Rohdaten bleiben dabei erhalten).' + const zielFehlt = !pfadAbgedeckt(zielPfad, mapping) + const arbeitFehlt = !pfadAbgedeckt(arbeitsPfad, mapping) + if (!zielFehlt && !arbeitFehlt) return null + + /* + * Die Meldung muss sagen, WAS ZU TUN IST — nicht nur, was klemmt. + * + * Commander-Rückmeldung 26.07.2026: „Das ist ja quatsch. Mein PC hat das Ziel + * als Worker direkt auf dem PC eingebunden." Er hatte die Freigabe wirklich + * als Netzlaufwerk gemountet — nur war das ARBEITSVERZEICHNIS darauf gelegt + * und die ABLAGE nicht. Das Ziel lag weiter auf der VM-Platte + * (/app/media/movies), und dorthin kommt sein PC nicht. + * + * Die alte Meldung war sachlich richtig und trotzdem unbrauchbar: Sie nannte + * den Container-Pfad und die Mapping-Zeichenkette, also genau die zwei Dinge, + * die ein Nicht-Entwickler nicht deuten kann. Jetzt steht der Handgriff drin, + * inklusive des Namens der Freigabe, die dieser Worker wirklich erreicht. + */ + const freigabe = freigabenAusMapping(mapping)[0] || '' + + const teile: string[] = [] + if (zielFehlt) { + teile.push(`Die Ablage (${zielPfad}) liegt nicht auf der Freigabe.`) + if (freigabe) { + teile.push(`Abhilfe: Einstellungen → Ripping → „Ablage" auf ` + + `„${freigabe}" stellen — das ist die Freigabe, die „${worker.name}" ` + + 'wirklich erreicht.') + } + } + if (arbeitFehlt) { + teile.push(`Das Arbeitsverzeichnis (${arbeitsPfad || 'Container-Platte'}) ` + + 'liegt nicht auf der Freigabe — oben umstellen.') + } + teile.push('Ohne das scheitert die Kompression (die Rohdaten bleiben erhalten).') + return teile.join(' ') } /**