From 1aa51ca94322c718b5ef052ae69fad9dc2eed37f Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Thu, 23 Jul 2026 15:30:09 +0200 Subject: [PATCH] UI-Feinschliff: Settings-Tabs funktionieren, Details-Knopf tut etwas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Settings: Tabs waren reine Dekoration (kein onClick, isActive fest) — jetzt echte Tab-Navigation (Ripping/Verzeichnisse/APIs/Benachrichtigungen) + OMDb-Key-Feld im APIs-Tab - Laufwerk-Details: aufklappbares Panel (Pfad/Modell/Seriennummer/Disc) mit "Rippen starten" (POST /jobs) und "Auswerfen" (Eject-Endpoint) - Placebos raus: 4 tote Schnellaktionen, doppelte Geraete-Sektion mit zweitem totem Details-Knopf, toter Abmelden-Knopf Co-Authored-By: Claude Fable 5 --- docker/ui/src/App.tsx | 10 +- docker/ui/src/components/DeviceDiscovery.tsx | 80 ++++++++++++- docker/ui/src/pages/Dashboard.tsx | 116 +------------------ docker/ui/src/pages/Settings.tsx | 53 ++++++--- 4 files changed, 123 insertions(+), 136 deletions(-) diff --git a/docker/ui/src/App.tsx b/docker/ui/src/App.tsx index 66a0f8a..1e05aa5 100644 --- a/docker/ui/src/App.tsx +++ b/docker/ui/src/App.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { LayoutDashboard, Disc, Settings, LogOut, Sun, Moon, Terminal } from 'lucide-react' +import { LayoutDashboard, Disc, Settings, Sun, Moon, Terminal } from 'lucide-react' import { useDarkMode } from './context/ThemeContext' import Dashboard from './pages/Dashboard' import MetadataPreview from './pages/MetadataPreview' @@ -61,12 +61,8 @@ function App() { ))} -
- -
+ {/* Abmelden-Knopf entfernt (23.07.): es gibt (noch) keinen + Login-Flow im UI — ein toter Knopf gaukelt nur einen vor. */} {/* Main Content */} diff --git a/docker/ui/src/components/DeviceDiscovery.tsx b/docker/ui/src/components/DeviceDiscovery.tsx index 195f7ca..894d4ae 100644 --- a/docker/ui/src/components/DeviceDiscovery.tsx +++ b/docker/ui/src/components/DeviceDiscovery.tsx @@ -16,6 +16,9 @@ interface Device { export default function DeviceDiscovery() { const [devices, setDevices] = useState([]) const [loading, setLoading] = useState(true) + const [expandedId, setExpandedId] = useState(null) + const [actionFeedback, setActionFeedback] = useState(null) + const [actionBusy, setActionBusy] = useState(false) const { theme } = useDarkMode() const refreshDevices = async () => { @@ -29,6 +32,33 @@ export default function DeviceDiscovery() { } } + const startRip = async (device: Device) => { + setActionBusy(true) + setActionFeedback(null) + try { + const response = await api.post('/jobs', { device_path: device.path }) + setActionFeedback(`✓ Job angelegt (${response.data.id.slice(0, 8)}…) — Fortschritt im Dashboard`) + } catch (error: any) { + setActionFeedback(`✗ ${error?.response?.data?.detail || 'Job konnte nicht angelegt werden'}`) + } finally { + setActionBusy(false) + } + } + + const ejectDisc = async (device: Device) => { + setActionBusy(true) + setActionFeedback(null) + try { + await api.post(`/devices/${device.id}/eject`) + setActionFeedback('✓ Disc ausgeworfen') + refreshDevices() + } catch (error: any) { + setActionFeedback(`✗ ${error?.response?.data?.detail || 'Auswurf fehlgeschlagen'}`) + } finally { + setActionBusy(false) + } + } + useEffect(() => { refreshDevices() const interval = setInterval(refreshDevices, 5000) @@ -146,10 +176,56 @@ export default function DeviceDiscovery() { - + + {expandedId === device.id && ( +
+
+ Gerätepfad + {device.path} + Modell + {device.model || 'unbekannt'} + Seriennummer + {device.serial || '—'} + Disc + + {device.status === 'ready' ? `eingelegt (${device.type.toUpperCase()})` : 'keine'} + +
+ +
+ + +
+ + {actionFeedback && ( +

+ {actionFeedback} +

+ )} +
+ )} ))} diff --git a/docker/ui/src/pages/Dashboard.tsx b/docker/ui/src/pages/Dashboard.tsx index 6cffd5f..4f1126d 100644 --- a/docker/ui/src/pages/Dashboard.tsx +++ b/docker/ui/src/pages/Dashboard.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { Clock, Activity, HardDrive, BarChart, AlertCircle, CheckCircle, Disc, Play, Pause, SkipForward } from 'lucide-react' +import { Clock, Activity, AlertCircle, CheckCircle, Disc } from 'lucide-react' import { api } from '../lib/api' import { useDarkMode } from '../context/ThemeContext' import DeviceDiscovery from '../components/DeviceDiscovery' @@ -16,14 +16,6 @@ interface Job { title?: string } -interface Device { - id: string - name: string - type: 'cd' | 'dvd' | 'bluray' - path: string - status: 'empty' | 'ready' | 'ripping' -} - async function fetchJobs(): Promise { try { const response = await api.get('/jobs') @@ -33,15 +25,6 @@ async function fetchJobs(): Promise { } } -async function fetchDevices(): Promise { - try { - const response = await api.get('/devices') - return response.data - } catch { - return [] - } -} - function StatusBadge({ status }: { status: string }) { const { theme } = useDarkMode() const styles = theme === 'dark' @@ -136,19 +119,13 @@ function ProgressBar({ progress }: { progress: number }) { export default function Dashboard() { const [jobs, setJobs] = useState([]) - const [devices, setDevices] = useState([]) const [loading, setLoading] = useState(true) const { theme } = useDarkMode() useEffect(() => { const loadData = async () => { try { - const [jobsData, devicesData] = await Promise.all([ - fetchJobs(), - fetchDevices() - ]) - setJobs(jobsData) - setDevices(devicesData) + setJobs(await fetchJobs()) } finally { setLoading(false) } @@ -220,93 +197,10 @@ export default function Dashboard() { /> - {/* Quick Actions */} -
-

Schnellaktionen

-
- - - - -
-
- - {/* Devices */} -
-
-

Geräte

-
-
- {devices.length === 0 ? ( -
-
- -
-

Keine Geräte gefunden

-
- ) : ( -
- {devices.map(device => ( -
-
-
-
- -
-
-

{device.name}

-

{device.path}

-
-
- -
- -
- - {device.status === 'empty' ? 'Leer' : - device.status === 'ready' ? 'Bereit' : - 'Rippt'} - - -
-
- ))} -
- )} -
-
- {/* Recent Jobs */} + {/* Schnellaktionen + doppelte Geräte-Sektion sind raus (23.07.): alle + vier Knöpfe und der zweite Details-Button waren funktionslos — + die echte Geräte-Steuerung lebt in DeviceDiscovery oben. */}

Neueste Jobs

diff --git a/docker/ui/src/pages/Settings.tsx b/docker/ui/src/pages/Settings.tsx index f6fc8dc..907a97d 100644 --- a/docker/ui/src/pages/Settings.tsx +++ b/docker/ui/src/pages/Settings.tsx @@ -1,11 +1,12 @@ import { useState, useEffect } from 'react' -import { Settings, Save, Disc, Cpu, Database, Globe, AlertCircle, CheckCircle } from 'lucide-react' +import { Save, Disc, Database, Globe, AlertCircle, CheckCircle } from 'lucide-react' import { api } from '../lib/api' import { useDarkMode } from '../context/ThemeContext' interface SettingsState { tmdbApiKey: string tvdbApiKey: string + omdbApiKey: string outputDir: string movieDir: string seriesDir: string @@ -19,6 +20,7 @@ interface SettingsState { const defaultSettings: SettingsState = { tmdbApiKey: '', tvdbApiKey: '', + omdbApiKey: '', outputDir: '/app/media', movieDir: 'movies', seriesDir: 'series', @@ -29,8 +31,11 @@ const defaultSettings: SettingsState = { notificationWebhook: '', } +type SettingsTab = 'ripping' | 'verzeichnisse' | 'apis' | 'benachrichtigungen' + export default function SettingsPage() { const [settings, setSettings] = useState(defaultSettings) + const [activeTab, setActiveTab] = useState('ripping') const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [saved, setSaved] = useState(false) @@ -90,19 +95,18 @@ export default function SettingsPage() { )}
- {/* Tabs */} + {/* Tabs — echte Navigation (vorher reine Dekoration ohne onClick) */}
- - - - - + setActiveTab('ripping')} darkMode={theme === 'dark'} /> + setActiveTab('verzeichnisse')} darkMode={theme === 'dark'} /> + setActiveTab('apis')} darkMode={theme === 'dark'} /> + setActiveTab('benachrichtigungen')} darkMode={theme === 'dark'} />
{/* Content */}
{/* Ripping Settings */} -
+ {activeTab === 'ripping' &&

Ripping-Einstellungen @@ -160,10 +164,10 @@ export default function SettingsPage() { />

- + } {/* Output Directories */} -
+ {activeTab === 'verzeichnisse' &&

Ausgabeverzeichnisse @@ -223,10 +227,10 @@ export default function SettingsPage() { />

- + } {/* API Keys */} -
+ {activeTab === 'apis' &&

API-Schlüssel @@ -261,11 +265,27 @@ export default function SettingsPage() { placeholder="Dein TVDb API Key" /> + +
+ + handleChange('omdbApiKey', e.target.value)} + className={`w-full px-3 py-2 ${theme === 'dark' ? 'bg-slate-800 border-slate-600 text-slate-200' : 'bg-white border-slate-300 text-slate-900'} border rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all`} + placeholder="Dein OMDb API Key" + /> +

+ Zweite Quelle, wenn TMDB nichts findet — kostenloser Key auf omdbapi.com +

+
-

+
} {/* Webhook */} -
+ {activeTab === 'benachrichtigungen' &&

Webhook-Benachrichtigungen @@ -286,7 +306,7 @@ export default function SettingsPage() { Optional: URL für Benachrichtigungen bei Job-Ende (Discord, Slack, etc.)

-

+
} {/* Save Button */}
@@ -314,10 +334,11 @@ export default function SettingsPage() { ) } -function TabButton({ icon: Icon, label, isActive, darkMode }: { icon: any, label: string, isActive: boolean, darkMode: boolean }) { +function TabButton({ icon: Icon, label, isActive, onClick, darkMode }: { icon: any, label: string, isActive: boolean, onClick: () => void, darkMode: boolean }) { const { theme } = useDarkMode() return (