diff --git a/Makefile b/Makefile index e56514f..82f6106 100644 --- a/Makefile +++ b/Makefile @@ -47,8 +47,10 @@ build: cd frontend && npm run build # ── Produktion (auf dem Server ausführen, im Projektverzeichnis) ────────────── +# --force-recreate: auch MediaMTX kommt neu hoch und liest die +# mediamtx.yml frisch — sonst bleibt eine Konfigänderung ohne Wirkung prod: - docker compose up -d --build + docker compose up -d --build --force-recreate @echo "horror läuft: https://horror.marha.de" # Der Sync läuft im Container, ein backend/.venv gibt es dort nicht diff --git a/backend/app/main.py b/backend/app/main.py index 3ef2309..6dd68bb 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -354,8 +354,12 @@ async def aufnahme_loeschen( raise HTTPException(status_code=403, detail="Nur für den Host") try: anzahl = await clips.aufnahme_loeschen(start, dauer) + except httpx.HTTPStatusError as e: + raise HTTPException( + status_code=502, detail=f"MediaMTX lehnt ab ({e.response.status_code})." + ) except httpx.HTTPError: - raise HTTPException(status_code=502, detail="Löschen hat nicht geklappt.") + raise HTTPException(status_code=502, detail="Die MediaMTX-API ist nicht erreichbar.") return {"segmente": anzahl} diff --git a/frontend/src/components/VerwaltungTab.vue b/frontend/src/components/VerwaltungTab.vue index 19ae257..60a4647 100644 --- a/frontend/src/components/VerwaltungTab.vue +++ b/frontend/src/components/VerwaltungTab.vue @@ -79,7 +79,6 @@ const loeschFrage = ref(null) async function aufnahmenLaden() { if (!props.host) return - aufnahmenFehler.value = '' loeschFrage.value = null try { const resp = await fetch('/api/aufnahmen') @@ -96,9 +95,14 @@ async function aufnahmeLoeschen(s) { loeschFrage.value = s.start return } + aufnahmenFehler.value = '' const params = new URLSearchParams({ start: s.start, dauer: s.dauer }) const resp = await fetch(`/api/aufnahmen?${params}`, { method: 'DELETE' }) - if (!resp.ok) aufnahmenFehler.value = 'Löschen hat nicht geklappt.' + if (!resp.ok) { + // Die Meldung vom Server sagt, woran es lag — sie bleibt stehen + const daten = await resp.json().catch(() => ({})) + aufnahmenFehler.value = daten.detail || 'Löschen hat nicht geklappt.' + } await aufnahmenLaden() } diff --git a/frontend/src/components/WatchedTab.vue b/frontend/src/components/WatchedTab.vue index 4bd3241..6f8a395 100644 --- a/frontend/src/components/WatchedTab.vue +++ b/frontend/src/components/WatchedTab.vue @@ -25,9 +25,7 @@ const laden = ref(true) // Ausgeblendete Filme bleiben für den Host sichtbar, damit er sie // zurückholen kann. Die Liste kommt jüngster zuerst — so liegt er links. -const seiten = computed(() => - props.host ? items.value : items.value.filter((e) => !e.hidden) -) +const seiten = computed(() => (props.host ? items.value : items.value.filter((e) => !e.hidden))) const buch = ref(null) const aktuell = ref(0) @@ -236,7 +234,7 @@ async function laden_() { watch( () => props.ich?.id, - () => laden_() + () => laden_(), ) onMounted(() => { @@ -284,7 +282,20 @@ onUnmounted(() => { /> -

{{ aktuell + 1 }} / {{ seiten.length }}

+

+ + {{ aktuell + 1 }} / {{ seiten.length }} + +

@@ -345,7 +356,10 @@ onUnmounted(() => { position: absolute; bottom: -1.7rem; left: 50%; - padding: 0.15rem 0.6rem; + display: flex; + gap: 0.4rem; + align-items: center; + padding: 0.15rem 0.4rem; color: var(--muted); font-size: 0.75rem; transform: translateX(-50%); @@ -353,6 +367,22 @@ onUnmounted(() => { border-radius: 10px; } +.stand button { + padding: 0 0.3rem; + color: var(--muted); + font-size: 1.1rem; + line-height: 1; +} + +.stand button:hover:not(:disabled) { + color: var(--text); +} + +.stand button:disabled { + opacity: 0.3; + cursor: default; +} + .note { padding: 2.5rem 0; color: var(--muted);