Files
rippy/docker/ui/src/App.tsx
T
Hitonabi 3a4eb25392
Ampel / ampel (push) Successful in 29s
Track-Auswahl-Tabelle, nativer Windows-Worker (via UI angeboten), Anleitungs-Tab
Volle Titel-Auswahl vor dem Rip (Etappe-12-Rest):
- worker.tasks.scan_tracks: makemkvcon info -> Titel-Liste (Dauer/Groesse/
  Kapitel, apdefs-Attr 8/9/11) in die settings-Tabelle 'tracks:<device>';
  API: POST /devices/{n}/scan-tracks + GET /devices/{n}/tracks (Polling).
- Rip-Dialog: 'Disc scannen' -> Tabelle mit Checkboxen, Vorauswahl ab
  5 min, Groessen-Summe; gewaehlte Titel als meta.titles in den Job.
- rip_titel_auswahl: ein makemkvcon-Aufruf je Titel (mkv kann nur einen
  Titel oder all), Fortschritt anteilig. Parser-Tests dabei.

Nativer Windows-Worker OHNE Docker (Commander-Wunsch):
- Die Rippy-Instanz versorgt sich selbst: GET /worker-setup/windows
  liefert install.ps1, /worker-setup/paket den Worker-Code als Zip
  (api-Dockerfile kopiert docker/worker/*.py als worker_dist/ ins Image).
- install.ps1: Python-Check, venv + requirements, HandBrakeCLI 1.9.2 vom
  offiziellen GitHub-Release (URL per HEAD verifiziert), start-worker.bat
  mit celery -Q transcode --pool=solo (Celery-Doku: Windows nur solo),
  optional -Autostart via schtasks onlogon.
- tasks.py laedt jetzt auch ohne fcntl (Import-Guard) — rip_disc ist auf
  solchen Workern hart verriegelt (Klartext-Fehler statt Crash).
- Einstellungen -> Worker: Varianten-Umschalter Linux(Docker)/Windows(nativ)
  mit passenden Copy-Paste-Befehlen.

Anleitungs-Tab: Rippy erklaert sich selbst (Disc-Weg, Serien, NAS,
Media-Server, Benachrichtigungen, Key, Worker, Downloads, FAQ).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:18:08 +02:00

102 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react'
import { LayoutDashboard, Disc, Settings, Sun, Moon, Terminal, BookOpen } from 'lucide-react'
import { api } from './lib/api'
import { useDarkMode } from './context/ThemeContext'
import Dashboard from './pages/Dashboard'
import AnleitungPage from './pages/Anleitung'
import SettingsPage from './pages/Settings'
import LogsPage from './pages/Logs'
import FirstRunWizard from './components/FirstRunWizard'
// Metadaten-Seite entfernt (24.07., Abnahme durch den Commander): die
// Korrektur läuft komplett über das „Nicht korrekt?"-Popup am Dashboard.
type Page = 'dashboard' | 'anleitung' | 'settings' | 'logs'
function App() {
const [page, setPage] = useState<Page>('dashboard')
const [setupDone, setSetupDone] = useState<boolean | null>(null)
const { theme, toggleTheme } = useDarkMode()
useEffect(() => {
api.get('/setup')
.then(r => setSetupDone(!!r.data.done))
.catch(() => setSetupDone(true)) // API nicht erreichbar → kein Wizard-Zwang
}, [])
if (setupDone === false) {
return <FirstRunWizard onDone={() => setSetupDone(true)} />
}
const navItems = [
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard },
{ id: 'anleitung', label: 'Anleitung', icon: BookOpen },
{ id: 'logs', label: 'Logs', icon: Terminal },
{ id: 'settings', label: 'Einstellungen', icon: Settings },
]
return (
<div className="min-h-screen bg-white text-slate-900 dark:bg-slate-900 dark:text-slate-100 font-sans transition-colors duration-300">
<div className="flex h-screen overflow-hidden">
{/* Sidebar */}
<aside className="w-64 bg-slate-900 text-slate-100 flex flex-col">
<div className="p-6">
<div className="flex items-center gap-3">
<div className="p-2 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl">
<Disc size={28} className="text-white" />
</div>
<div>
<h1 className="text-xl font-bold tracking-tight">Rippy</h1>
<p className="text-xs text-slate-400 dark:text-slate-500">Automated Ripping</p>
</div>
</div>
</div>
<button
onClick={toggleTheme}
className="mx-3 flex items-center justify-center gap-2 px-3 py-2 rounded-lg bg-slate-800/50 text-slate-400 hover:bg-slate-700 hover:text-white transition-all duration-200"
title={theme === 'dark' ? 'Light Mode' : 'Light Mode'}
>
{theme === 'dark' ? <Sun size={18} /> : <Moon size={18} />}
</button>
<nav className="flex-1 px-3 py-2 space-y-1">
{navItems.map((item) => (
<button
key={item.id}
onClick={() => setPage(item.id as Page)}
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-200 ${
page === item.id
? 'bg-indigo-600 text-white shadow-lg shadow-indigo-500/30'
: 'text-slate-400 hover:bg-slate-800 hover:text-white'
}`}
>
<item.icon size={20} />
<span className="font-medium">{item.label}</span>
</button>
))}
</nav>
{/* Abmelden-Knopf entfernt (23.07.): es gibt (noch) keinen
Login-Flow im UI — ein toter Knopf gaukelt nur einen vor. */}
<footer className="px-4 py-4 text-center text-xs text-slate-500 border-t border-slate-800">
Created with <span className="text-rose-400"></span> by LucyAI, Claude and KrBrZ
</footer>
</aside>
{/* Main Content */}
<main className="flex-1 overflow-auto bg-slate-50 dark:bg-slate-950">
<div className="max-w-7xl mx-auto p-6">
{page === 'dashboard' && <Dashboard />}
{page === 'anleitung' && <AnleitungPage />}
{page === 'logs' && <LogsPage />}
{page === 'settings' && <SettingsPage />}
</div>
</main>
</div>
</div>
)
}
export default App