fix: Deinstaller, Verwaltungsfenster, Dashboard-Widerspruch, Mount-Tempo
Ampel / ampel (push) Successful in 30s
Ampel / ampel (push) Successful in 30s
Vier Meldungen des Commanders, alle nachgemessen.
1. DEINSTALLER LIEF NICHT MEHR - reproduziert mit echtem PowerShell:
Der Typ [System.Windows.Forms.MessageBox] wurde nicht gefunden.
`Add-Type -AssemblyName System.Windows.Forms` stand EINE ZEILE ZU SPAET, die
MessageBox wurde davor benutzt. Der Deinstaller starb also in seiner ersten
Arbeitszeile, jedes Mal. Zwei weitere Maengel gleich mit:
Kodierung war ASCII trotz Umlauten, und `Remove-Item -Recurse -Force
$PSScriptRoot` loescht den Ordner, in dem das laufende Skript liegt - das
klappt auf Windows nicht zuverlaessig (venv-DLLs sind geladen). Jetzt raeumt
ein losgeloestes cmd nach, sobald PowerShell weg ist. Der erzeugte Deinstaller
ist gegengeprueft: parst, BOM da, Umlaute intakt.
2. VERWALTUNGSFENSTER (Doppelklick aufs Tray). Zeigt Status, Aufgaben und Log,
plus Knoepfe fuer Rippy, Log-in-Rippy und Deinstallieren - Deinstallieren geht
damit auch aus dem Tray-Menue. Eigener PROZESS statt Fenster im Tray, weil
pystray und tkinter beide den Haupt-Thread wollen; tkinter statt WinForms,
weil es bei jeder Windows-Python-Installation dabei ist. Headless gerendert
und angesehen. Alles Fachliche kommt von Rippy (/capabilities, /jobs), damit
dort nicht eine zweite, abweichende Wahrheit steht.
3. DASHBOARD-WIDERSPRUCH. Oben stand "Akira im Laufwerk erkannt", die
Server-Status-Karte gleichzeitig "Bereit - keine Disc in Arbeit / Disc
einlegen". Zwei Aussagen, ein Blick. Die Karte fragt jetzt /devices und sagt
"Disc erkannt - wartet auf Rippen starten" samt Titel.
4. MOUNT-TEMPO. Der Commander: "150 Sekunden? Das ist verrueckt langsam." Recht
hat er. Gemessen ging die Zeit fast komplett in einen FEHLVERSUCH: `umount -l`
ist lazy, der Abbau passiert spaeter, und wer direkt danach mountet, riskiert
dass der Abbau hinter dem neuen Mount landet. Der erste Reparaturversuch
scheiterte dadurch regelmaessig - und der zweite kostete 30 s mount-Timeout
plus Pruefungen. Jetzt werden 1,5 s auf den Abbau gewartet, bevor neu
gemountet wird; die Wache prueft erstmals nach 3 s statt 10 s und benutzt zum
ERKENNEN die einfache schnelle Probe (die Doppelprobe steckt dort, wo der
Wettlauf lauert: direkt nach dem Mount).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,15 @@ interface WorkerLive {
|
||||
info?: { cpu_modell?: string, cpu_kerne?: string, cpu_simd?: string, extern?: string }
|
||||
}
|
||||
|
||||
// Ein Laufwerk aus GET /devices. Gebraucht, weil die Server-Status-Karte sonst
|
||||
// „keine Disc in Arbeit" behauptet, während oben auf DEMSELBEN Bildschirm
|
||||
// „Akira im Laufwerk erkannt" steht (Commander-Befund 26.07.2026).
|
||||
interface LaufwerkLive {
|
||||
id: string
|
||||
status: string
|
||||
disc?: { title?: string, year?: number | null, disc_type?: string }
|
||||
}
|
||||
|
||||
function posterUrl(meta?: JobMeta | null): string | null {
|
||||
const p = meta?.poster_path
|
||||
if (!p) return null
|
||||
@@ -80,6 +89,7 @@ export default function Dashboard() {
|
||||
const [rohdatenMitloeschen, setRohdatenMitloeschen] = useState(false)
|
||||
const [activeTab, setActiveTab] = useState<'all' | 'active' | 'queue' | 'completed' | 'failed'>('all')
|
||||
const [workersLive, setWorkersLive] = useState<WorkerLive[]>([])
|
||||
const [laufwerke, setLaufwerke] = useState<LaufwerkLive[]>([])
|
||||
const { toast } = useToast()
|
||||
|
||||
/*
|
||||
@@ -135,7 +145,7 @@ export default function Dashboard() {
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const [jobsData, sysData, capsData] = await Promise.all([
|
||||
const [jobsData, sysData, capsData, devData] = await Promise.all([
|
||||
fetchJobs(),
|
||||
api.get('/system/info').then(r => r.data).catch(() => null),
|
||||
// Für die ECHTE Online-Zahl: /capabilities kennt den Celery-Ping,
|
||||
@@ -143,10 +153,14 @@ export default function Dashboard() {
|
||||
// schnell (0,003 s, Ping läuft im Hintergrund) — er darf hier also
|
||||
// im 4-Sekunden-Takt mitlaufen.
|
||||
api.get('/capabilities').then(r => r.data?.workers || []).catch(() => []),
|
||||
// Die Laufwerke: Ohne sie behauptete die Server-Status-Karte
|
||||
// „keine Disc in Arbeit", während oben die erkannte Disc stand.
|
||||
api.get('/devices').then(r => r.data || []).catch(() => []),
|
||||
])
|
||||
setJobs(jobsData)
|
||||
if (sysData) setSystemInfo(sysData)
|
||||
setWorkersLive(capsData)
|
||||
setLaufwerke(devData)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
@@ -189,10 +203,23 @@ export default function Dashboard() {
|
||||
const workerOnline = workersLive.filter(w => w.online)
|
||||
const encoderWorker = workerOnline.filter(w => (w.encoders || []).length > 0)
|
||||
const hardwareWorker = workerOnline.filter(w => (w.encoders || []).some(e => !e.startsWith('cpu')))
|
||||
// Phase im Klartext — „processing" heißt rippen, „transcoding" komprimieren.
|
||||
const phaseText = !aktiverJob ? 'Bereit — keine Disc in Arbeit'
|
||||
: aktiverJob.status === 'transcoding' ? 'Kompression läuft (HandBrake)'
|
||||
: 'Rip läuft (MakeMKV, verlustfrei)'
|
||||
/*
|
||||
* Phase im Klartext — „processing" heißt rippen, „transcoding" komprimieren.
|
||||
*
|
||||
* Die eingelegte Disc gehört mit hinein (Commander-Befund 26.07.2026): Vorher
|
||||
* stand hier stur „Bereit — keine Disc in Arbeit / Disc einlegen, Rippy erkennt
|
||||
* sie selbst", während oben auf DEMSELBEN Bildschirm „Akira im Laufwerk
|
||||
* erkannt" prangte. Zwei Aussagen, ein Blick, Widerspruch — und der Nutzer
|
||||
* weiß nicht, welcher er glauben soll.
|
||||
*/
|
||||
const discImLaufwerk = laufwerke.find(l => l.disc?.title)
|
||||
const phaseText = aktiverJob
|
||||
? (aktiverJob.status === 'transcoding'
|
||||
? 'Kompression läuft (HandBrake)'
|
||||
: 'Rip läuft (MakeMKV, verlustfrei)')
|
||||
: discImLaufwerk
|
||||
? 'Disc erkannt — wartet auf „Rippen starten"'
|
||||
: 'Bereit — kein Datenträger im Laufwerk'
|
||||
|
||||
const filteredJobs = jobs.filter(j => {
|
||||
if (activeTab === 'active') return j.status === 'processing' || j.status === 'transcoding'
|
||||
@@ -537,7 +564,11 @@ export default function Dashboard() {
|
||||
<p className="text-[11px] font-mono text-slate-500">
|
||||
{queueJobs.length > 0
|
||||
? `${queueJobs.length} Job(s) warten in der Schlange`
|
||||
: 'Disc einlegen — Rippy erkennt sie selbst'}
|
||||
: discImLaufwerk
|
||||
? `${discImLaufwerk.disc!.title}`
|
||||
+ (discImLaufwerk.disc!.year ? ` (${discImLaufwerk.disc!.year})` : '')
|
||||
+ ' liegt bereit — oben auf „Rippen starten"'
|
||||
: 'Disc einlegen — Rippy erkennt sie selbst'}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user