From 8549944be1fbda5309f987fc674a46572d80f8c3 Mon Sep 17 00:00:00 2001 From: Dymas Date: Mon, 25 May 2026 16:10:18 +0200 Subject: [PATCH] Show ani-cli version in config runtime info --- CHANGELOG.md | 4 ++++ README.md | 1 + VERSION | 2 +- app_support.py | 2 +- config_page.py | 19 +++++++++++++------ http_handler.py | 20 +++++++++++++++++++- test_app.py | 11 +++++++++++ 7 files changed, 50 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f25493d..4c1d99a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.40.6 - 2026-05-25 + +- Fixed the Config page `Dependencies` panel so it stretches across the full settings width, centered its dependency badges, and added the installed `ani-cli` version to the runtime info block. + ## 0.40.5 - 2026-05-25 - Moved the Config page `Dependencies` panel to the top as a full-width card and changed the remaining settings panels to a two-column layout that collapses back to one column at `1080px` and below. diff --git a/README.md b/README.md index e2c6d72..6439a6d 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,7 @@ Layout: - `Dependencies` stays at the top as a full-width runtime status card. - The remaining Config sections use two columns on larger screens and collapse to one column at `1080px` width and below. +- Runtime info shows both the `ani-cli-web` version and the installed `ani-cli` version. Saved settings are written to `.ani-cli-web/config.json`. diff --git a/VERSION b/VERSION index d3568f3..78deb9e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.40.5 +0.40.6 diff --git a/app_support.py b/app_support.py index acaa4ac..0d44e33 100644 --- a/app_support.py +++ b/app_support.py @@ -19,7 +19,7 @@ from uuid import uuid4 PROJECT_ROOT = Path(__file__).resolve().parent ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli" APP_NAME = "ani-cli-web" -VERSION = "0.40.5" +VERSION = "0.40.6" ALLANIME_BASE = "allanime.day" ALLANIME_API = f"https://api.{ALLANIME_BASE}" ALLANIME_REFERER = "https://allmanga.to" diff --git a/config_page.py b/config_page.py index e1b9ab0..0ad774b 100644 --- a/config_page.py +++ b/config_page.py @@ -134,7 +134,7 @@ CONFIG_HTML = r""" gap: 20px; min-width: 0; align-content: start; - justify-items: start; + justify-items: stretch; } h1, h2, p { margin: 0; } h1 { @@ -280,17 +280,22 @@ CONFIG_HTML = r""" flex-wrap: wrap; align-items: center; } + .deps { + justify-items: center; + text-align: center; + } + .deps .chips { + justify-content: center; + } .settings-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; align-items: start; + width: 100%; } .settings-main, .deps { - width: min(1180px, 100%); - } - .deps { - width: min(1180px, 100%); + width: 100%; } @media (max-width: 900px) { .app { grid-template-columns: 1fr; } @@ -325,9 +330,10 @@ CONFIG_HTML = r"""
-

Runtime info

+

Runtime info

Loading version...

+

Detecting ani-cli version...

Saved settings live in `.ani-cli-web/config.json` inside this project.

@@ -558,6 +564,7 @@ CONFIG_HTML = r""" async function loadVersion() { const data = await api("/api/version"); $("versionLine").textContent = `${data.name} ${data.version}`; + $("aniCliVersionLine").textContent = `ani-cli ${data.ani_cli_version || "Unavailable"}`; } $("saveConfigBtn").addEventListener("click", saveConfig); diff --git a/http_handler.py b/http_handler.py index 29687f7..611156e 100644 --- a/http_handler.py +++ b/http_handler.py @@ -6,7 +6,9 @@ import json import mimetypes import os import shutil +import subprocess import traceback +from functools import lru_cache from dataclasses import dataclass from http.cookies import SimpleCookie from http import HTTPStatus @@ -72,6 +74,22 @@ def dependency_status(): return result +@lru_cache(maxsize=1) +def installed_ani_cli_version(): + try: + completed = subprocess.run( + [ANI_CLI, "--version"], + check=True, + capture_output=True, + text=True, + timeout=6, + ) + except (OSError, subprocess.SubprocessError): + return "Unavailable" + version = str(completed.stdout or completed.stderr or "").strip() + return version or "Unavailable" + + class Handler(BaseHTTPRequestHandler): server_version = "AniCliWeb/1.0" remote_token_cookie_name = "ani_cli_web_token" @@ -380,7 +398,7 @@ class Handler(BaseHTTPRequestHandler): elif parsed.path == "/api/config": self.json(Handler._context(self).get_config_snapshot()) elif parsed.path == "/api/version": - self.json({"name": APP_NAME, "version": VERSION}) + self.json({"name": APP_NAME, "version": VERSION, "ani_cli_version": installed_ani_cli_version()}) elif parsed.path == "/api/dependencies": self.json(dependency_status()) elif parsed.path == "/api/search": diff --git a/test_app.py b/test_app.py index 88b3c6a..d6a887e 100644 --- a/test_app.py +++ b/test_app.py @@ -1748,6 +1748,14 @@ class HandlerRouteTests(unittest.TestCase): self.assertEqual(handler.json_payload["auto_download_mode"], "dub") self.assertEqual(handler.json_payload["auto_download_quality"], "720") + def test_version_route_includes_ani_cli_version(self): + handler = DummyHandler("/api/version") + APP.Handler.do_GET(handler) + self.assertEqual(handler.json_status, HTTPStatus.OK) + self.assertEqual(handler.json_payload["name"], APP.APP_NAME) + self.assertEqual(handler.json_payload["version"], APP.VERSION) + self.assertIn("ani_cli_version", handler.json_payload) + def test_config_post_accepts_discord_webhook_fields(self): body = b'{"download_dir":"/tmp/example","mode":"sub","quality":"best","discord_webhook_url":"https://discord.example/webhook","discord_webhook_events":["runtime_error","watchlist_refresh_failed","bad"]}' handler = DummyHandler("/api/config", body=body, content_length=len(body)) @@ -1964,6 +1972,9 @@ class TemplateHelperTests(unittest.TestCase): self.assertIn('
', APP.CONFIG_HTML) self.assertLess(APP.CONFIG_HTML.index('
'), APP.CONFIG_HTML.index('
')) self.assertIn("@media (max-width: 1080px)", APP.CONFIG_HTML) + self.assertIn("justify-items: stretch;", APP.CONFIG_HTML) + self.assertIn('id="aniCliVersionLine"', APP.CONFIG_HTML) + self.assertIn('ani-cli ${data.ani_cli_version || "Unavailable"}', APP.CONFIG_HTML) def test_search_page_sends_watchlist_auto_download_series(self): self.assertIn('auto_download_series: $("seasonInput").value || "1"', APP.INDEX_HTML)