feat: add enhanced SABnzbd download dashboard with remaining data calculation and visual redesign
Add parse_size_bytes() and format_bytes() helper functions to convert and format file sizes. Calculate total remaining bytes from queue and include active_count, history_count, remaining_bytes, and formatted remaining in downloads API response. Update error responses to include new fields. Add media-mark.svg favicon with gradient background, golden lines, and green checkmark. Replace text logo with inline
This commit is contained in:
@@ -4,6 +4,7 @@ import os
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import threading
|
||||
import re
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
@@ -59,6 +60,23 @@ def sab_get(mode, **params):
|
||||
raise RuntimeError(data["error"])
|
||||
return data
|
||||
|
||||
def parse_size_bytes(value):
|
||||
if isinstance(value, (int, float)):
|
||||
return int(value)
|
||||
match = re.search(r"([\d.,]+)\s*(B|KB|MB|GB|TB)", str(value or ""), re.IGNORECASE)
|
||||
if not match:
|
||||
return 0
|
||||
number = float(match.group(1).replace(',', '.'))
|
||||
multiplier = {"B": 1, "KB": 1024, "MB": 1024**2, "GB": 1024**3, "TB": 1024**4}[match.group(2).upper()]
|
||||
return int(number * multiplier)
|
||||
|
||||
def format_bytes(size):
|
||||
value = float(size or 0)
|
||||
for unit in ("B", "KB", "MB", "GB", "TB"):
|
||||
if value < 1024 or unit == "TB":
|
||||
return f"{value:.1f} {unit}"
|
||||
value /= 1024
|
||||
|
||||
def release_languages(name):
|
||||
value = f" {name.lower().replace('.', ' ').replace('-', ' ')} "
|
||||
found = []
|
||||
@@ -90,7 +108,11 @@ def sab_downloads():
|
||||
|
||||
items = [make_item(item) for item in queue.get("slots", [])]
|
||||
items.extend(make_item(item, completed=True) for item in history.get("slots", [])[:15])
|
||||
return {"items": items, "queue_status": queue.get("status", "Idle"), "speed": queue.get("speed", "0 B/s"), "paused": bool(queue.get("paused_all")), "error": None}
|
||||
remaining_bytes = sum(parse_size_bytes(item.get("sizeleft")) for item in queue.get("slots", []))
|
||||
return {"items": items, "queue_status": queue.get("status", "Idle"), "speed": queue.get("speed", "0 B/s"),
|
||||
"paused": bool(queue.get("paused_all")), "active_count": len(queue.get("slots", [])),
|
||||
"history_count": len(history.get("slots", [])[:15]), "remaining_bytes": remaining_bytes,
|
||||
"remaining": format_bytes(remaining_bytes), "error": None}
|
||||
|
||||
def norm_lang(code):
|
||||
code = (code or "").strip().lower()
|
||||
@@ -257,12 +279,12 @@ def scan_status():
|
||||
@app.get("/api/downloads")
|
||||
def downloads():
|
||||
if not SAB_URL or not SAB_API_KEY:
|
||||
return jsonify({"items": [], "queue_status": "Nicht konfiguriert", "speed": "—", "paused": False, "error": "SAB_URL oder SAB_API_KEY ist nicht gesetzt."})
|
||||
return jsonify({"items": [], "queue_status": "Nicht konfiguriert", "speed": "—", "paused": False, "active_count": 0, "history_count": 0, "remaining": "—", "error": "SAB_URL oder SAB_API_KEY ist nicht gesetzt."})
|
||||
try:
|
||||
return jsonify(sab_downloads())
|
||||
except Exception as exc:
|
||||
log.exception("SABnzbd konnte nicht abgefragt werden")
|
||||
return jsonify({"items": [], "queue_status": "Fehler", "speed": "—", "paused": False, "error": str(exc)}), 502
|
||||
return jsonify({"items": [], "queue_status": "Fehler", "speed": "—", "paused": False, "active_count": 0, "history_count": 0, "remaining": "—", "error": str(exc)}), 502
|
||||
|
||||
@app.get("/health")
|
||||
def health(): return jsonify({"ok":True, "radarr_url":RADARR_URL, "sonarr_enabled":bool(SONARR_URL and SONARR_API_KEY), "sab_enabled":bool(SAB_URL and SAB_API_KEY)})
|
||||
|
||||
Reference in New Issue
Block a user