Add website favicon support

This commit is contained in:
Dymas
2026-07-09 18:15:52 +02:00
parent 34c77ed9bb
commit ba408877b5
10 changed files with 35 additions and 2 deletions
+4
View File
@@ -1,5 +1,9 @@
# Changelog
## 0.48.2 - 2026-07-09
- Added support for a checked-in `favicon.png`, serving it from the app and wiring it into the website pages so browsers show the custom favicon.
## 0.48.1 - 2026-07-09
- Changed queue inserts so adding a download that exactly matches an existing failed job now reuses that failed entry and resets it to pending instead of creating another duplicate failed row.
+2 -1
View File
@@ -2,7 +2,7 @@
Local web UI for a system-wide `ani-cli` install.
Current version: `0.48.1`
Current version: `0.48.2`
## What it does
@@ -15,6 +15,7 @@ Current version: `0.48.1`
- Auto-download missing episodes for `Watching` entries after later refreshes, with a per-show `Source name` override for `ani-cli` search
- Move fully completed libraries into Jellyfin TV or movie folders with a tracked background handoff job
- Send optional Discord webhook notifications
- Serve a checked-in `favicon.png` as the site favicon
## Pages
+1 -1
View File
@@ -1 +1 @@
0.48.1
0.48.2
+1
View File
@@ -21,6 +21,7 @@ CONFIG_HTML = r"""<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ani-cli web - config</title>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">
<style>
:root {
color-scheme: dark;
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

+9
View File
@@ -27,6 +27,7 @@ from app_support import (
CLIENT_DISCONNECT_ERRORS,
MAX_JSON_BODY_BYTES,
MODE_CHOICES,
PROJECT_ROOT,
VERSION,
client_address_is_local,
configured_remote_path_roots,
@@ -46,6 +47,8 @@ from app_support import (
validate_discord_webhook_url,
)
FAVICON_PATH = PROJECT_ROOT / "favicon.png"
@dataclass(frozen=True)
class HandlerContext:
add_to_watchlist: object
@@ -382,6 +385,7 @@ class Handler(BaseHTTPRequestHandler):
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{APP_NAME} sign in</title>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">
<style>
:root {{
color-scheme: dark;
@@ -545,6 +549,11 @@ class Handler(BaseHTTPRequestHandler):
self.ensure_client_access()
if parsed.path == "/":
self.html(Handler._context(self).index_html)
elif parsed.path in {"/favicon.png", "/favicon.ico"}:
if not FAVICON_PATH.exists():
self.error(HTTPStatus.NOT_FOUND, "Favicon not found")
return
self.file(FAVICON_PATH)
elif parsed.path == "/queue":
self.html(Handler._context(self).queue_html)
elif parsed.path == "/config":
+1
View File
@@ -11,6 +11,7 @@ QUEUE_HTML = r"""<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ani-cli web queue</title>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">
<style>
:root {
color-scheme: dark;
+1
View File
@@ -11,6 +11,7 @@ INDEX_HTML = r"""<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ani-cli web</title>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">
<style>
:root {
color-scheme: dark;
+15
View File
@@ -176,6 +176,14 @@ class NetworkGuardTests(unittest.TestCase):
self.assertIn("Username", body)
self.assertIn("Password", body)
def test_favicon_route_serves_checked_in_png(self):
handler = DummyHandler("/favicon.png")
handler.command = "GET"
APP.Handler.do_GET(handler)
self.assertEqual(handler.file_path, http_handler.FAVICON_PATH)
def test_remote_login_page_escapes_hidden_next_value(self):
handler = DummyHandler('/?next="><script>alert(1)</script>')
handler.client_address = ("192.168.1.25", 8421)
@@ -3482,6 +3490,13 @@ class TemplateHelperTests(unittest.TestCase):
self.assertIn("async function api(path, options = {})", APP.CONFIG_HTML)
self.assertIn("async function api(path, options = {})", APP.WATCHLIST_HTML)
def test_pages_include_favicon_link(self):
expected = '<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">'
self.assertIn(expected, APP.INDEX_HTML)
self.assertIn(expected, APP.QUEUE_HTML)
self.assertIn(expected, APP.CONFIG_HTML)
self.assertIn(expected, APP.WATCHLIST_HTML)
def test_config_page_places_dependencies_before_settings_grid(self):
self.assertIn('<section class="deps" id="deps"></section>', APP.CONFIG_HTML)
self.assertIn('<div class="settings-grid">', APP.CONFIG_HTML)
+1
View File
@@ -11,6 +11,7 @@ WATCHLIST_HTML = r"""<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ani-cli web - watchlist</title>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">
<style>
:root {
color-scheme: dark;