Praxis-Feedback-Runde: CIFS-Cap-Fix, TMDB v3-Keys, 4K-UHD-Typ, Vollautomatik, Job-Verwaltung, Worker-Namen
Ampel / ampel (push) Successful in 29s
Ampel / ampel (push) Successful in 29s
Zwei bewiesene Bug-Fixes:
- SMB-Mount 'Unable to apply new capability set': mount.cifs hebt
CAP_DAC_READ_SEARCH an, die in Dockers Default-Caps fehlt (auf der VM
reproduziert: Bounding-Set a82425fb ohne Bit 2; mit der Capability
verschwindet der Fehler). Fix: cap_add DAC_READ_SEARCH fuer den
api-Container.
- TMDB fiel still aus: Client konnte nur v4-Bearer-Tokens, der uebliche
32-Hex-v3-Key bekam 401 und die Suche lieferte nur OMDb. Jetzt beide
Key-Arten (ist_v4_token + api_key-Query-Param lt.
developer.themoviedb.org, mit Tests) — damit kommen auch die deutschen
Texte an (language=de-DE war ueberall schon gesetzt). Neu:
GET /metadata/status + 'Verbindung pruefen' in Einstellungen -> APIs
(Live-Check am Cache vorbei).
Features aus dem Commander-Feedback:
- 4K UHD als eigener Disc-Typ: classify >= 55 GiB (BD-66/BD-100; BD-50
bleibt bluray), beide detection.py + Tests, eigene Badge-Farbe in
Dashboard/Laufwerken, Prescan-Label '4K UHD'. UHD-Rip-Fehler 'Failed to
open disc' (Code 11) bekommt Klartext: LibreDrive-Firmware noetig.
- Vollautomatik (Setting autoRipStart): Disc erkannt -> Rip startet ohne
Popup in den Schnellwahl-Ordner (Serie->Serien, sonst Filme, CD->Musik).
- Job-Verwaltung: 'Neu komprimieren' nur noch mit can_retry (Rohdaten
liegen wirklich da), DELETE /jobs/{id} + 'Erledigte aufraeumen'
(Dateien bleiben immer), 'Alle herunterladen' im Job-Detail
(gestaffelte Einzel-Downloads statt Server-Zip von 40-GB-Dateien).
- Worker zuordenbar: WORKER_NAME-Env als stabiler Anzeigename (fixt auch
die Offline-Leichen nach Rebuilds), info.hostname/ip gemeldet,
Online-Abgleich ueber hostname, DELETE /workers/{name} + Papierkorb im
UI, Quelle-Anzeige an der Disc-Karte ('Quelle: TMDB - 99 % sicher').
- Ripping-Tab nach Medium gegliedert (Video/Audio-CD/Allgemein) +
Untertitel-Klartext (--all-audio/--all-subtitles bleiben komplett),
CD -> Musik-Vorauswahl im Ziel-Dialog, Ordner-Verwaltung beschriftet
und standardmaessig eingeklappt.
- Docs: SAVEPOINT v3.3, ROADMAP Etappe 14 + Ideen (nativer
Windows-Worker, deutsche OMDb-Texte via TMDB-Find), README.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ interface SettingsState {
|
||||
ripAllTracks: boolean
|
||||
mainFeatureOnly: boolean
|
||||
autoEject: boolean
|
||||
autoRipStart: boolean
|
||||
notificationWebhook: string
|
||||
transcodeEnabled: boolean
|
||||
transcodePreset: string
|
||||
@@ -37,6 +38,7 @@ const defaultSettings: SettingsState = {
|
||||
ripAllTracks: true,
|
||||
mainFeatureOnly: false,
|
||||
autoEject: true,
|
||||
autoRipStart: false,
|
||||
notificationWebhook: '',
|
||||
transcodeEnabled: true,
|
||||
transcodePreset: 'H.265 MKV 1080p30',
|
||||
@@ -80,9 +82,29 @@ export default function SettingsPage() {
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [saved, setSaved] = useState(false)
|
||||
const [testBusy, setTestBusy] = useState(false)
|
||||
const [quellenStatus, setQuellenStatus] = useState<Record<string, string> | null>(null)
|
||||
const [quellenBusy, setQuellenBusy] = useState(false)
|
||||
const { theme } = useDarkMode()
|
||||
const { toast } = useToast()
|
||||
|
||||
const quellenPruefen = async () => {
|
||||
setQuellenBusy(true)
|
||||
try {
|
||||
const r = await api.get('/metadata/status')
|
||||
setQuellenStatus(r.data)
|
||||
const kaputt = Object.entries(r.data).filter(([, v]) => v === 'fehler')
|
||||
if (kaputt.length === 0) {
|
||||
toast('success', 'Metadaten-Quellen geprüft — alles in Ordnung')
|
||||
} else {
|
||||
toast('error', `Quelle mit Problem: ${kaputt.map(([k]) => k.toUpperCase()).join(', ')}`)
|
||||
}
|
||||
} catch {
|
||||
toast('error', 'Prüfung fehlgeschlagen — läuft die API?')
|
||||
} finally {
|
||||
setQuellenBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
api.get('/capabilities')
|
||||
.then(r => setWorkers(r.data.workers || []))
|
||||
@@ -182,20 +204,28 @@ export default function SettingsPage() {
|
||||
Ripping-Einstellungen
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4 transition-colors duration-300">
|
||||
<div className="flex items-center justify-between p-4 transition-colors duration-300">
|
||||
<div className="space-y-6 transition-colors duration-300">
|
||||
{/* ── Video: DVD · Blu-ray · 4K UHD ─────────────────────── */}
|
||||
<p className={`text-xs font-semibold uppercase tracking-wider ${theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}`}>
|
||||
📀 Video — DVD · Blu-ray · 4K UHD
|
||||
</p>
|
||||
|
||||
<div className={`flex items-center justify-between p-4 transition-colors duration-300 ${theme === 'dark' ? 'bg-slate-700/50 rounded-lg' : 'bg-slate-50 rounded-lg'}`}>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`p-2 rounded-lg transition-colors duration-300 ${theme === 'dark' ? 'bg-indigo-900/30 text-indigo-400' : 'bg-indigo-100 text-indigo-600'}`}>
|
||||
<Disc size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className={`font-medium ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>Alle Tracks rippen</h3>
|
||||
<p className={`text-sm transition-colors duration-300 ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>Rippe alle Tracks einer CD (nicht nur Main Feature)</p>
|
||||
<h3 className={`font-medium ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>Vollautomatik: sofort rippen</h3>
|
||||
<p className={`text-sm transition-colors duration-300 ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>
|
||||
Disc erkannt → Rip startet ohne Nachfrage in den passenden Schnellwahl-Ordner
|
||||
(Serie → Serien, sonst Filme; CD → Musik). Aus = wie bisher per „Rippen starten"-Dialog.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Toggle
|
||||
checked={settings.ripAllTracks}
|
||||
onChange={(v) => handleChange('ripAllTracks', v)}
|
||||
<Toggle
|
||||
checked={settings.autoRipStart}
|
||||
onChange={(v) => handleChange('autoRipStart', v)}
|
||||
darkMode={theme === 'dark'}
|
||||
/>
|
||||
</div>
|
||||
@@ -210,13 +240,46 @@ export default function SettingsPage() {
|
||||
<p className={`text-sm transition-colors duration-300 ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>Rippe bei DVDs/Blu-rays nur das Hauptfeature</p>
|
||||
</div>
|
||||
</div>
|
||||
<Toggle
|
||||
checked={settings.mainFeatureOnly}
|
||||
onChange={(v) => handleChange('mainFeatureOnly', v)}
|
||||
<Toggle
|
||||
checked={settings.mainFeatureOnly}
|
||||
onChange={(v) => handleChange('mainFeatureOnly', v)}
|
||||
darkMode={theme === 'dark'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className={`text-xs px-1 ${theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}`}>
|
||||
🌐 Sprachen & Untertitel: MakeMKV sichert grundsätzlich ALLE Ton-Spuren und
|
||||
Untertitel verlustfrei, und auch die Kompression behält alle (--all-audio /
|
||||
--all-subtitles) — gerade bei Animes gehen also weder O-Ton noch Subs verloren.
|
||||
</p>
|
||||
|
||||
{/* ── Audio-CD ──────────────────────────────────────────── */}
|
||||
<p className={`text-xs font-semibold uppercase tracking-wider pt-2 ${theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}`}>
|
||||
🎵 Audio-CD
|
||||
</p>
|
||||
|
||||
<div className={`flex items-center justify-between p-4 transition-colors duration-300 ${theme === 'dark' ? 'bg-slate-700/50 rounded-lg' : 'bg-slate-50 rounded-lg'}`}>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`p-2 rounded-lg transition-colors duration-300 ${theme === 'dark' ? 'bg-blue-900/30 text-blue-400' : 'bg-blue-100 text-blue-600'}`}>
|
||||
<Disc size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className={`font-medium ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>Alle Tracks rippen</h3>
|
||||
<p className={`text-sm transition-colors duration-300 ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>Rippe alle Tracks einer CD (FLAC + MusicBrainz-Tags)</p>
|
||||
</div>
|
||||
</div>
|
||||
<Toggle
|
||||
checked={settings.ripAllTracks}
|
||||
onChange={(v) => handleChange('ripAllTracks', v)}
|
||||
darkMode={theme === 'dark'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ── Allgemein ─────────────────────────────────────────── */}
|
||||
<p className={`text-xs font-semibold uppercase tracking-wider pt-2 ${theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}`}>
|
||||
Allgemein
|
||||
</p>
|
||||
|
||||
<div className={`flex items-center justify-between p-4 transition-colors duration-300 ${theme === 'dark' ? 'bg-slate-700/50 rounded-lg' : 'bg-slate-50 rounded-lg'}`}>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`p-2 rounded-lg transition-colors duration-300 ${theme === 'dark' ? 'bg-emerald-900/30 text-emerald-400' : 'bg-emerald-100 text-emerald-600'}`}>
|
||||
@@ -408,7 +471,39 @@ export default function SettingsPage() {
|
||||
<Globe size={20} className={theme === 'dark' ? 'text-indigo-400' : 'text-indigo-600'} />
|
||||
API-Schlüssel
|
||||
</h2>
|
||||
|
||||
|
||||
{/* Live-Prüfung: funktioniert der eingetragene Key WIRKLICH? */}
|
||||
<div className={`rounded-lg p-4 mb-4 flex flex-wrap items-center gap-3 ${theme === 'dark' ? 'bg-slate-700/50' : 'bg-slate-50'}`}>
|
||||
<button
|
||||
onClick={quellenPruefen}
|
||||
disabled={quellenBusy}
|
||||
className="px-4 py-2 rounded-lg bg-indigo-600 text-white text-sm font-medium hover:bg-indigo-700 transition-colors disabled:opacity-40"
|
||||
>
|
||||
{quellenBusy ? 'Prüfe…' : 'Verbindung prüfen'}
|
||||
</button>
|
||||
{quellenStatus ? (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{Object.entries(quellenStatus).map(([quelle, status]) => (
|
||||
<span
|
||||
key={quelle}
|
||||
className={`text-xs px-2.5 py-1 rounded-full font-medium ${
|
||||
status === 'ok' ? 'bg-emerald-600/20 text-emerald-500'
|
||||
: status === 'kein_key' ? (theme === 'dark' ? 'bg-slate-800 text-slate-400' : 'bg-slate-200 text-slate-500')
|
||||
: 'bg-rose-600/20 text-rose-500'
|
||||
}`}
|
||||
>
|
||||
{quelle.toUpperCase()}: {status === 'ok' ? '✓ funktioniert' : status === 'kein_key' ? 'kein Key' : '✗ Fehler'}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<span className={`text-xs ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>
|
||||
Prüft live gegen TMDB/OMDb — nach dem Speichern eines neuen Keys hier klicken.
|
||||
Beide TMDB-Key-Arten funktionieren (v3-Schlüssel und v4-Token).
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 transition-colors duration-300">
|
||||
<div className="space-y-2 transition-colors duration-300">
|
||||
<label className={`block text-sm font-medium ${theme === 'dark' ? 'text-slate-300' : 'text-slate-700'}`}>
|
||||
|
||||
Reference in New Issue
Block a user