UX-Runde: echte Disc-Titel via BD-Metadaten, Preview-Crash, Dashboard, Ziele
Ampel / ampel (push) Failing after 38s
Ampel / ampel (push) Failing after 38s
Commander-Befunde 23.07. abends:
1. Metadaten "klappen nicht": Wurzel = kryptisches Volume-Label + kein
TMDB-Key. Jetzt: API mountet die Disc read-only (CAP_SYS_ADMIN ist da)
und liest den KLARTEXT-Titel aus BDMV/META/DL/bdmt_*.xml; dazu
progressive Suchdegradation (Titel-Varianten) und OMDb-Unscharf-Suche
(s= + imdbID-Nachladen) — mit dem vorhandenen OMDb-Key gibt es damit
Titel/Jahr/Poster auch ohne TMDB
2. Weisses Fenster beim Pre-Scan: Lucide-Icons sind forwardRef —
Direktaufruf getIcon(...)({size}) crashte die Seite; als JSX gerendert
3. Dashboard sortiert: System-Leiste (Encoder + aktiver Job) oben,
Disc/Geraete -> Stats -> Jobs, Live-Log ans Ende
4. Ziel-Dialog mit ORDNER-BROWSER (GET /browse, nur unter /app/media);
Schnellwahl nutzt die Settings-Unterordner statt Hartkodierung;
Tab "Verzeichnisse" in "Speicherziele" aufgegangen (redundant)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -120,8 +120,21 @@ function ProgressBar({ progress }: { progress: number }) {
|
||||
)
|
||||
}
|
||||
|
||||
interface WorkerInfo {
|
||||
name: string
|
||||
encoders: string[]
|
||||
}
|
||||
|
||||
const ENCODER_KURZ: Record<string, string> = {
|
||||
'cpu-x264': 'H.264 (CPU)',
|
||||
'cpu-x265': 'H.265 (CPU)',
|
||||
'vaapi': 'VAAPI ⚡',
|
||||
'nvenc': 'NVENC ⚡',
|
||||
}
|
||||
|
||||
export default function Dashboard() {
|
||||
const [jobs, setJobs] = useState<Job[]>([])
|
||||
const [workers, setWorkers] = useState<WorkerInfo[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const { theme } = useDarkMode()
|
||||
|
||||
@@ -135,10 +148,13 @@ export default function Dashboard() {
|
||||
}
|
||||
|
||||
loadData()
|
||||
api.get('/capabilities').then(r => setWorkers(r.data.workers || [])).catch(() => {})
|
||||
const interval = setInterval(loadData, 5000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
const aktiverJob = jobs.find(j => j.status === 'processing' || j.status === 'transcoding')
|
||||
|
||||
const stats = {
|
||||
pending: jobs.filter(j => j.status === 'pending').length,
|
||||
processing: jobs.filter(j => j.status === 'processing').length,
|
||||
@@ -162,11 +178,36 @@ export default function Dashboard() {
|
||||
<p className={`text-sm ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>Übersicht über alle Ripping-Jobs und Geräte</p>
|
||||
</div>
|
||||
|
||||
{/* Device Discovery Section */}
|
||||
<DeviceDiscovery />
|
||||
{/* System-Leiste: Encoder + aktiver Job auf einen Blick */}
|
||||
<div className={`flex flex-wrap items-center gap-x-6 gap-y-2 rounded-xl border px-5 py-3 text-sm ${theme === 'dark' ? 'bg-slate-800 border-slate-700' : 'bg-white border-slate-200'}`}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Activity size={16} className="text-indigo-500" />
|
||||
<span className={theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}>Encoder:</span>
|
||||
{workers.length === 0 ? (
|
||||
<span className={theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}>kein Worker</span>
|
||||
) : (
|
||||
workers.flatMap(w => w.encoders).filter((e, i, a) => a.indexOf(e) === i).map(e => (
|
||||
<span key={e} className={`text-xs px-2 py-0.5 rounded ${e.startsWith('cpu') ? (theme === 'dark' ? 'bg-slate-700 text-slate-300' : 'bg-slate-100 text-slate-600') : 'bg-emerald-600/20 text-emerald-500 font-medium'}`}>
|
||||
{ENCODER_KURZ[e] || e}
|
||||
</span>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock size={16} className="text-indigo-500" />
|
||||
<span className={theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}>Aktiv:</span>
|
||||
{aktiverJob ? (
|
||||
<span className={theme === 'dark' ? 'text-slate-200' : 'text-slate-700'}>
|
||||
{aktiverJob.status === 'transcoding' ? 'Komprimieren' : 'Rippen'} · {aktiverJob.progress} %
|
||||
</span>
|
||||
) : (
|
||||
<span className={theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}>kein Job</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Live Log Section */}
|
||||
<LiveLogSection />
|
||||
{/* Laufwerke + erkannte Disc */}
|
||||
<DeviceDiscovery />
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 transition-colors duration-300">
|
||||
@@ -271,6 +312,9 @@ export default function Dashboard() {
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Live-Log ganz unten — Detail-Ansicht, nicht Haupt-Info */}
|
||||
<LiveLogSection />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user