tuning ui and adding adaptive themeing to extension

This commit is contained in:
Bryan Van Deusen
2026-01-25 18:47:06 -05:00
parent dc6755a0c2
commit 997f31ae27
23 changed files with 978 additions and 114 deletions
+5 -1
View File
@@ -24,8 +24,12 @@ async def list_credentials():
result = await session.execute(select(Credential))
credentials = result.scalars().all()
# Also return a simple map of platform -> has_credentials for UI convenience
platforms_with_creds = {c.platform: True for c in credentials}
return jsonify({
"items": [c.to_dict(include_data=False) for c in credentials]
"items": [c.to_dict(include_data=False) for c in credentials],
"platforms": platforms_with_creds,
})
+40
View File
@@ -128,6 +128,46 @@ async def retry_download(download_id: int):
}), 202
@bp.route("/recent-activity", methods=["GET"])
async def recent_activity():
"""Get recent noteworthy downloads: failures and completions with new files.
These are downloads that warrant user attention, not routine "no new content" runs.
Results are limited to the last 7 days by default.
"""
limit = min(int(request.args.get("limit", 10)), 50)
days = int(request.args.get("days", 7))
from datetime import timedelta
cutoff = datetime.utcnow() - timedelta(days=days)
async with current_app.db_engine.connect() as conn:
session = AsyncSession(bind=conn)
# Get failed downloads OR completed with files (noteworthy activity)
from sqlalchemy import or_
query = select(Download).where(
and_(
Download.created_at >= cutoff,
or_(
Download.status == DownloadStatus.FAILED,
and_(
Download.status == DownloadStatus.COMPLETED,
Download.file_count > 0
)
)
)
).order_by(Download.created_at.desc()).limit(limit)
result = await session.execute(query)
downloads = result.scalars().all()
return jsonify({
"items": [d.to_dict() for d in downloads],
"count": len(downloads),
})
@bp.route("/stats", methods=["GET"])
async def get_stats():
"""Get download statistics."""
+80 -1
View File
@@ -1,6 +1,7 @@
"""Settings API endpoints."""
import json
import os
from pathlib import Path
from quart import Blueprint, request, jsonify, current_app
from sqlalchemy import select
@@ -12,6 +13,45 @@ from app.config import get_settings
bp = Blueprint("settings", __name__)
def get_storage_stats():
"""Calculate storage statistics for the downloads directory."""
config = get_settings()
downloads_path = Path(config.downloads_path)
if not downloads_path.exists():
return {"total_size": 0, "total_size_formatted": "0 B", "file_count": 0}
total_size = 0
file_count = 0
try:
for root, dirs, files in os.walk(downloads_path):
for f in files:
fp = os.path.join(root, f)
try:
total_size += os.path.getsize(fp)
file_count += 1
except OSError:
pass
except Exception as e:
current_app.logger.error(f"Error calculating storage stats: {e}")
return {"total_size": 0, "total_size_formatted": "Error", "file_count": 0}
# Format size
def format_size(size):
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
if size < 1024:
return f"{size:.1f} {unit}"
size /= 1024
return f"{size:.1f} PB"
return {
"total_size": total_size,
"total_size_formatted": format_size(total_size),
"file_count": file_count,
}
async def get_or_create_extension_api_key(session: AsyncSession) -> str:
"""Get the extension API key, creating one if it doesn't exist."""
result = await session.execute(
@@ -72,7 +112,10 @@ async def get_all_settings():
api_key = await get_or_create_extension_api_key(session)
settings_dict[EXTENSION_API_KEY_SETTING] = api_key
return jsonify({"settings": settings_dict})
# Include storage statistics
storage_stats = get_storage_stats()
return jsonify({"settings": settings_dict, "storage_stats": storage_stats})
@bp.route("", methods=["PATCH"])
@@ -186,3 +229,39 @@ async def update_gallery_dl_config():
except Exception as e:
current_app.logger.error(f"Failed to write gallery-dl config: {e}")
return jsonify({"error": f"Failed to write config: {e}"}), 500
@bp.route("/logs", methods=["GET"])
async def get_logs():
"""Get recent download logs from download metadata.
Returns the stdout/stderr from recent downloads for debugging.
"""
from app.models.download import Download
from sqlalchemy import desc
limit = min(int(request.args.get("limit", 50)), 200)
async with current_app.db_engine.connect() as conn:
session = AsyncSession(bind=conn)
# Get recent downloads with output logs
query = select(Download).order_by(desc(Download.created_at)).limit(limit)
result = await session.execute(query)
downloads = result.scalars().all()
logs = []
for dl in downloads:
metadata = dl.metadata_ or {}
if metadata.get("stdout") or metadata.get("stderr") or dl.error_message:
logs.append({
"id": dl.id,
"url": dl.url,
"status": dl.status,
"created_at": dl.created_at.isoformat() + "Z" if dl.created_at else None,
"stdout": metadata.get("stdout", ""),
"stderr": metadata.get("stderr", ""),
"error_message": dl.error_message,
})
return jsonify({"logs": logs, "count": len(logs)})