Add website favicon support
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
# Changelog
|
# 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
|
## 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.
|
- 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.
|
Local web UI for a system-wide `ani-cli` install.
|
||||||
|
|
||||||
Current version: `0.48.1`
|
Current version: `0.48.2`
|
||||||
|
|
||||||
## What it does
|
## 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
|
- 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
|
- Move fully completed libraries into Jellyfin TV or movie folders with a tracked background handoff job
|
||||||
- Send optional Discord webhook notifications
|
- Send optional Discord webhook notifications
|
||||||
|
- Serve a checked-in `favicon.png` as the site favicon
|
||||||
|
|
||||||
## Pages
|
## Pages
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ CONFIG_HTML = r"""<!doctype html>
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>ani-cli web - config</title>
|
<title>ani-cli web - config</title>
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
color-scheme: dark;
|
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,
|
CLIENT_DISCONNECT_ERRORS,
|
||||||
MAX_JSON_BODY_BYTES,
|
MAX_JSON_BODY_BYTES,
|
||||||
MODE_CHOICES,
|
MODE_CHOICES,
|
||||||
|
PROJECT_ROOT,
|
||||||
VERSION,
|
VERSION,
|
||||||
client_address_is_local,
|
client_address_is_local,
|
||||||
configured_remote_path_roots,
|
configured_remote_path_roots,
|
||||||
@@ -46,6 +47,8 @@ from app_support import (
|
|||||||
validate_discord_webhook_url,
|
validate_discord_webhook_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FAVICON_PATH = PROJECT_ROOT / "favicon.png"
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class HandlerContext:
|
class HandlerContext:
|
||||||
add_to_watchlist: object
|
add_to_watchlist: object
|
||||||
@@ -382,6 +385,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{APP_NAME} sign in</title>
|
<title>{APP_NAME} sign in</title>
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">
|
||||||
<style>
|
<style>
|
||||||
:root {{
|
:root {{
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
@@ -545,6 +549,11 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.ensure_client_access()
|
self.ensure_client_access()
|
||||||
if parsed.path == "/":
|
if parsed.path == "/":
|
||||||
self.html(Handler._context(self).index_html)
|
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":
|
elif parsed.path == "/queue":
|
||||||
self.html(Handler._context(self).queue_html)
|
self.html(Handler._context(self).queue_html)
|
||||||
elif parsed.path == "/config":
|
elif parsed.path == "/config":
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ QUEUE_HTML = r"""<!doctype html>
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>ani-cli web queue</title>
|
<title>ani-cli web queue</title>
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ INDEX_HTML = r"""<!doctype html>
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>ani-cli web</title>
|
<title>ani-cli web</title>
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|||||||
+15
@@ -176,6 +176,14 @@ class NetworkGuardTests(unittest.TestCase):
|
|||||||
self.assertIn("Username", body)
|
self.assertIn("Username", body)
|
||||||
self.assertIn("Password", 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):
|
def test_remote_login_page_escapes_hidden_next_value(self):
|
||||||
handler = DummyHandler('/?next="><script>alert(1)</script>')
|
handler = DummyHandler('/?next="><script>alert(1)</script>')
|
||||||
handler.client_address = ("192.168.1.25", 8421)
|
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.CONFIG_HTML)
|
||||||
self.assertIn("async function api(path, options = {})", APP.WATCHLIST_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):
|
def test_config_page_places_dependencies_before_settings_grid(self):
|
||||||
self.assertIn('<section class="deps" id="deps"></section>', APP.CONFIG_HTML)
|
self.assertIn('<section class="deps" id="deps"></section>', APP.CONFIG_HTML)
|
||||||
self.assertIn('<div class="settings-grid">', 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 charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>ani-cli web - watchlist</title>
|
<title>ani-cli web - watchlist</title>
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|||||||
Reference in New Issue
Block a user