Show ani-cli version in config runtime info
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
# Changelog
|
# 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
|
## 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.
|
- 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.
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ Layout:
|
|||||||
|
|
||||||
- `Dependencies` stays at the top as a full-width runtime status card.
|
- `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.
|
- 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`.
|
Saved settings are written to `.ani-cli-web/config.json`.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ from uuid import uuid4
|
|||||||
PROJECT_ROOT = Path(__file__).resolve().parent
|
PROJECT_ROOT = Path(__file__).resolve().parent
|
||||||
ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli"
|
ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli"
|
||||||
APP_NAME = "ani-cli-web"
|
APP_NAME = "ani-cli-web"
|
||||||
VERSION = "0.40.5"
|
VERSION = "0.40.6"
|
||||||
ALLANIME_BASE = "allanime.day"
|
ALLANIME_BASE = "allanime.day"
|
||||||
ALLANIME_API = f"https://api.{ALLANIME_BASE}"
|
ALLANIME_API = f"https://api.{ALLANIME_BASE}"
|
||||||
ALLANIME_REFERER = "https://allmanga.to"
|
ALLANIME_REFERER = "https://allmanga.to"
|
||||||
|
|||||||
+13
-6
@@ -134,7 +134,7 @@ CONFIG_HTML = r"""<!doctype html>
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
align-content: start;
|
align-content: start;
|
||||||
justify-items: start;
|
justify-items: stretch;
|
||||||
}
|
}
|
||||||
h1, h2, p { margin: 0; }
|
h1, h2, p { margin: 0; }
|
||||||
h1 {
|
h1 {
|
||||||
@@ -280,17 +280,22 @@ CONFIG_HTML = r"""<!doctype html>
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.deps {
|
||||||
|
justify-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.deps .chips {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
.settings-grid {
|
.settings-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
.settings-main, .deps {
|
.settings-main, .deps {
|
||||||
width: min(1180px, 100%);
|
width: 100%;
|
||||||
}
|
|
||||||
.deps {
|
|
||||||
width: min(1180px, 100%);
|
|
||||||
}
|
}
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.app { grid-template-columns: 1fr; }
|
.app { grid-template-columns: 1fr; }
|
||||||
@@ -325,9 +330,10 @@ CONFIG_HTML = r"""<!doctype html>
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="settings">
|
<section class="settings">
|
||||||
<h2>Runtime info</h2>
|
<h2>Runtime info</h2>
|
||||||
<div class="stack muted">
|
<div class="stack muted">
|
||||||
<p id="versionLine">Loading version...</p>
|
<p id="versionLine">Loading version...</p>
|
||||||
|
<p id="aniCliVersionLine">Detecting ani-cli version...</p>
|
||||||
<p>Saved settings live in `.ani-cli-web/config.json` inside this project.</p>
|
<p>Saved settings live in `.ani-cli-web/config.json` inside this project.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -558,6 +564,7 @@ CONFIG_HTML = r"""<!doctype html>
|
|||||||
async function loadVersion() {
|
async function loadVersion() {
|
||||||
const data = await api("/api/version");
|
const data = await api("/api/version");
|
||||||
$("versionLine").textContent = `${data.name} ${data.version}`;
|
$("versionLine").textContent = `${data.name} ${data.version}`;
|
||||||
|
$("aniCliVersionLine").textContent = `ani-cli ${data.ani_cli_version || "Unavailable"}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
$("saveConfigBtn").addEventListener("click", saveConfig);
|
$("saveConfigBtn").addEventListener("click", saveConfig);
|
||||||
|
|||||||
+19
-1
@@ -6,7 +6,9 @@ import json
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import subprocess
|
||||||
import traceback
|
import traceback
|
||||||
|
from functools import lru_cache
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from http.cookies import SimpleCookie
|
from http.cookies import SimpleCookie
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
@@ -72,6 +74,22 @@ def dependency_status():
|
|||||||
return result
|
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):
|
class Handler(BaseHTTPRequestHandler):
|
||||||
server_version = "AniCliWeb/1.0"
|
server_version = "AniCliWeb/1.0"
|
||||||
remote_token_cookie_name = "ani_cli_web_token"
|
remote_token_cookie_name = "ani_cli_web_token"
|
||||||
@@ -380,7 +398,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
elif parsed.path == "/api/config":
|
elif parsed.path == "/api/config":
|
||||||
self.json(Handler._context(self).get_config_snapshot())
|
self.json(Handler._context(self).get_config_snapshot())
|
||||||
elif parsed.path == "/api/version":
|
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":
|
elif parsed.path == "/api/dependencies":
|
||||||
self.json(dependency_status())
|
self.json(dependency_status())
|
||||||
elif parsed.path == "/api/search":
|
elif parsed.path == "/api/search":
|
||||||
|
|||||||
+11
@@ -1748,6 +1748,14 @@ class HandlerRouteTests(unittest.TestCase):
|
|||||||
self.assertEqual(handler.json_payload["auto_download_mode"], "dub")
|
self.assertEqual(handler.json_payload["auto_download_mode"], "dub")
|
||||||
self.assertEqual(handler.json_payload["auto_download_quality"], "720")
|
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):
|
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"]}'
|
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))
|
handler = DummyHandler("/api/config", body=body, content_length=len(body))
|
||||||
@@ -1964,6 +1972,9 @@ class TemplateHelperTests(unittest.TestCase):
|
|||||||
self.assertIn('<div class="settings-grid">', APP.CONFIG_HTML)
|
self.assertIn('<div class="settings-grid">', APP.CONFIG_HTML)
|
||||||
self.assertLess(APP.CONFIG_HTML.index('<section class="deps" id="deps"></section>'), APP.CONFIG_HTML.index('<div class="settings-grid">'))
|
self.assertLess(APP.CONFIG_HTML.index('<section class="deps" id="deps"></section>'), APP.CONFIG_HTML.index('<div class="settings-grid">'))
|
||||||
self.assertIn("@media (max-width: 1080px)", APP.CONFIG_HTML)
|
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):
|
def test_search_page_sends_watchlist_auto_download_series(self):
|
||||||
self.assertIn('auto_download_series: $("seasonInput").value || "1"', APP.INDEX_HTML)
|
self.assertIn('auto_download_series: $("seasonInput").value || "1"', APP.INDEX_HTML)
|
||||||
|
|||||||
Reference in New Issue
Block a user