From 3bd2a7bee7ac64223f5d30575508d708cd3a2d55 Mon Sep 17 00:00:00 2001 From: arabcoders Date: Sun, 17 May 2026 20:21:58 +0300 Subject: [PATCH] refactor: minor ui cleanup --- API.md | 1 - app/routes/api/images.py | 49 ++++-- app/routes/api/system.py | 1 - app/tests/test_images_routes.py | 46 ++++++ app/tests/test_system_routes.py | 1 - ui/app/components/LimitsPage.vue | 48 +++--- ui/app/layouts/default.vue | 4 +- ui/app/pages/browser/[...slug].vue | 2 +- ui/app/pages/changelog.vue | 2 +- ui/app/pages/conditions.vue | 2 +- ui/app/pages/console.vue | 2 +- ui/app/pages/dl_fields.vue | 2 +- ui/app/pages/docs/[...slug].vue | 2 +- ui/app/pages/history.vue | 2 +- ui/app/pages/index.vue | 2 +- ui/app/pages/logs.vue | 248 ++++++++++------------------- ui/app/pages/notifications.vue | 4 +- ui/app/pages/presets.vue | 2 +- ui/app/pages/task_definitions.vue | 4 +- ui/app/pages/tasks.vue | 2 +- ui/app/types/limits.ts | 1 - ui/app/utils/media.ts | 16 +- ui/tests/utils/media.test.ts | 12 +- 23 files changed, 225 insertions(+), 230 deletions(-) diff --git a/API.md b/API.md index c1a5d5a5..b30b3d12 100644 --- a/API.md +++ b/API.md @@ -2602,7 +2602,6 @@ or an error: { "downloads": { "paused": false, - "live_bypasses_limits": true, "global": { "limit": 20, "active": 3, diff --git a/app/routes/api/images.py b/app/routes/api/images.py index 164e9479..6abeca72 100644 --- a/app/routes/api/images.py +++ b/app/routes/api/images.py @@ -4,7 +4,7 @@ import random import time from datetime import UTC, datetime from typing import Any -from urllib.parse import urlparse +from urllib.parse import urlparse, urlsplit, urlunsplit from aiohttp import web from aiohttp.web import Request, Response @@ -22,6 +22,27 @@ LOG: logging.Logger = logging.getLogger(__name__) IS_REQUESTING_BACKGROUND: bool = False +def _safe_url(url: str | None) -> str: + if not url: + return "" + + try: + parsed = urlsplit(url) + except Exception: + return str(url) + + netloc: str = parsed.netloc + if parsed.username or parsed.password: + host: str = parsed.hostname or "" + if parsed.port: + host: str = f"{host}:{parsed.port}" + netloc: str = f"redacted:redacted@{host}" if host else "redacted:redacted" + + query: str = "redacted" if parsed.query else "" + fragment: str = "redacted" if parsed.fragment else "" + return urlunsplit((parsed.scheme, netloc, parsed.path, query, fragment)) + + @route("GET", "api/thumbnail/", "get_thumbnail") async def get_thumbnail(request: Request, config: Config) -> Response: """ @@ -52,9 +73,10 @@ async def get_thumbnail(request: Request, config: Config) -> Response: use_curl=use_curl, ) proxy = ytdlp_args.get("proxy") + safe_url = _safe_url(url) client = get_async_client(proxy=proxy, use_curl=use_curl) - LOG.debug(f"Fetching thumbnail from '{url}'.") + LOG.debug(f"Fetching thumbnail from '{safe_url}'.") response = await client.request( method="GET", url=url, @@ -64,7 +86,7 @@ async def get_thumbnail(request: Request, config: Config) -> Response: ) if response.status_code != web.HTTPOk.status_code: - LOG.error(f"Failed to fetch thumbnail from '{url}'. Status code: {response.status_code}.") + LOG.error(f"Failed to fetch thumbnail from '{safe_url}'. Status code: {response.status_code}.") return web.json_response(data={"error": "failed to retrieve the thumbnail."}, status=response.status_code) return web.Response( @@ -81,7 +103,7 @@ async def get_thumbnail(request: Request, config: Config) -> Response: }, ) except Exception as e: - LOG.error(f"Error fetching thumbnail from '{url}'. '{e}'.") + LOG.error(f"Error fetching thumbnail from '{safe_url}'. '{e}'.") return web.json_response( data={"error": "failed to retrieve the thumbnail."}, status=web.HTTPInternalServerError.status_code ) @@ -110,7 +132,8 @@ async def get_background(request: Request, config: Config, cache: Cache) -> Resp try: IS_REQUESTING_BACKGROUND = True - backend = random.choice(config.pictures_backends) + backend: str = random.choice(config.pictures_backends) + safe_backend: str = _safe_url(backend) CACHE_KEY_BING = "random_background_bing" CACHE_KEY = "random_background" @@ -135,12 +158,12 @@ async def get_background(request: Request, config: Config, cache: Cache) -> Resp ) ytdlp_args: dict = YTDLPOpts.get_instance().preset(name=config.default_preset).get_all() - use_curl = resolve_curl_transport() - request_headers = build_request_headers( + use_curl: bool = resolve_curl_transport() + request_headers: dict = build_request_headers( user_agent=request.headers.get("User-Agent", ytdlp_args.get("user_agent", Globals.get_random_agent())), use_curl=use_curl, ) - proxy = ytdlp_args.get("proxy") + proxy: str | None = ytdlp_args.get("proxy") client = get_async_client(proxy=proxy, use_curl=use_curl) if backend.startswith("https://www.bing.com/HPImageArchive.aspx"): @@ -159,10 +182,12 @@ async def get_background(request: Request, config: Config, cache: Cache) -> Resp status=web.HTTPInternalServerError.status_code, ) - backend = f"https://www.bing.com{img_url}" + backend: str = f"https://www.bing.com{img_url}" + safe_backend: str = _safe_url(backend) await cache.aset(key=CACHE_KEY_BING, value=backend, ttl=3600 * 24) else: backend = await cache.aget(CACHE_KEY_BING) + safe_backend = _safe_url(backend if isinstance(backend, str) else None) if not isinstance(backend, str) or not backend: return web.json_response( @@ -170,7 +195,7 @@ async def get_background(request: Request, config: Config, cache: Cache) -> Resp status=web.HTTPInternalServerError.status_code, ) - LOG.debug(f"Requesting random picture from '{backend!s}'.") + LOG.debug(f"Requesting random picture from '{safe_backend}'.") response = await client.request( method="GET", @@ -198,7 +223,7 @@ async def get_background(request: Request, config: Config, cache: Cache) -> Resp await cache.aset(key=CACHE_KEY, value=data, ttl=3600) - LOG.debug(f"Random background image from '{backend!s}' cached.") + LOG.debug(f"Random background image from '{safe_backend}' cached.") return web.Response( body=data.get("content"), @@ -210,7 +235,7 @@ async def get_background(request: Request, config: Config, cache: Cache) -> Resp }, ) except Exception as e: - LOG.error(f"Failed to request random background image from '{backend!s}'.'. '{e!s}'.") + LOG.error(f"Failed to request random background image from '{safe_backend}'. '{e!s}'.") return web.json_response( data={"error": "failed to retrieve the random background image."}, status=web.HTTPInternalServerError.status_code, diff --git a/app/routes/api/system.py b/app/routes/api/system.py index f45f5f3f..d96506db 100644 --- a/app/routes/api/system.py +++ b/app/routes/api/system.py @@ -464,7 +464,6 @@ async def system_limits(queue: DownloadQueue, config: Config, encoder: Encoder) data={ "downloads": { "paused": queue.is_paused(), - "live_bypasses_limits": True, "global": { "limit": config.max_workers, "active": len(active_non_live), diff --git a/app/tests/test_images_routes.py b/app/tests/test_images_routes.py index 97bce698..417b5209 100644 --- a/app/tests/test_images_routes.py +++ b/app/tests/test_images_routes.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging from typing import Generator from unittest.mock import AsyncMock @@ -95,3 +96,48 @@ async def test_thumb_reject(monkeypatch: pytest.MonkeyPatch) -> None: assert response.status == web.HTTPForbidden.status_code assert response.text == '{"error": "Invalid hostname."}' + + +@pytest.mark.asyncio +async def test_bg_log_redact(caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch) -> None: + config = Config.get_instance() + config.pictures_backends = ["https://user:pass@example.com/bg.jpg?apitoken=secret#frag"] + req = make_mocked_request("GET", "/api/random/background") + + class DummyCache: + def has(self, _key: str) -> bool: + return False + + async def aset(self, **_kwargs) -> None: + return None + + client = AsyncMock() + client.request.side_effect = RuntimeError("boom") + + monkeypatch.setattr(images, "get_async_client", lambda **_kwargs: client) + monkeypatch.setattr(images, "resolve_curl_transport", lambda: False) + monkeypatch.setattr(images, "build_request_headers", lambda **_kwargs: {}) + monkeypatch.setattr(images.Globals, "get_random_agent", staticmethod(lambda: "agent")) + monkeypatch.setattr( + images.YTDLPOpts, + "get_instance", + staticmethod( + lambda: type( + "Opts", + (), + { + "preset": lambda self, name: self, + "get_all": lambda self: {}, + }, + )() + ), + ) + + with caplog.at_level(logging.ERROR): + response = await images.get_background(req, config, DummyCache()) + + assert response.status == web.HTTPInternalServerError.status_code + logs = caplog.text + assert "apitoken=secret" not in logs + assert "user:pass@" not in logs + assert "https://redacted:redacted@example.com/bg.jpg?redacted#redacted" in logs diff --git a/app/tests/test_system_routes.py b/app/tests/test_system_routes.py index eefbfda5..ce616045 100644 --- a/app/tests/test_system_routes.py +++ b/app/tests/test_system_routes.py @@ -151,7 +151,6 @@ class TestSystemLimitsEndpoint: body = json.loads(response.body.decode("utf-8")) assert body["downloads"]["paused"] is False - assert body["downloads"]["live_bypasses_limits"] is True assert body["downloads"]["global"] == { "limit": 10, "active": 2, diff --git a/ui/app/components/LimitsPage.vue b/ui/app/components/LimitsPage.vue index a424342d..e186e476 100644 --- a/ui/app/components/LimitsPage.vue +++ b/ui/app/components/LimitsPage.vue @@ -1,17 +1,14 @@