Add website favicon support
This commit is contained in:
@@ -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,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
|
||||
|
||||
|
||||
@@ -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
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
@@ -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":
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user