diff --git a/CHANGELOG.md b/CHANGELOG.md index af527d6..824d017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Dashboard favicon set (SVG, ICO, 32 px PNG, Apple touch icon) and `theme-color` tags; the admin app + answers `/favicon.ico`. - Dashboard rebuilt: live updates over server-sent events (polling fallback), throughput per second, pickup and run latency percentiles, queue depth history, workers with heartbeats and leases, running jobs with lease countdowns, a live job feed and a diff --git a/deploy/termux/nginx-location.conf b/deploy/termux/nginx-location.conf index d129fe5..75a7383 100644 --- a/deploy/termux/nginx-location.conf +++ b/deploy/termux/nginx-location.conf @@ -10,6 +10,16 @@ location /pgqueue/static/ { add_header Cache-Control "public"; } +# Browsers ask for /favicon.ico (and iOS for the touch icon) at the host root, without reading the page. +location = /favicon.ico { + alias /data/data/com.termux/files/home/pgqueue/src/pgqueue/admin/static/favicon.ico; + expires 1d; +} +location = /apple-touch-icon.png { + alias /data/data/com.termux/files/home/pgqueue/src/pgqueue/admin/static/apple-touch-icon.png; + expires 1d; +} + # The live event stream: long-lived, unbuffered, capped per visitor and in total. # Refused viewers get a 503 and the dashboard falls back to polling. location = /pgqueue/api/stream { diff --git a/src/pgqueue/admin/api.py b/src/pgqueue/admin/api.py index 9c61ee0..36aa7db 100644 --- a/src/pgqueue/admin/api.py +++ b/src/pgqueue/admin/api.py @@ -10,7 +10,13 @@ from typing import Annotated from fastapi import APIRouter, Depends, FastAPI, HTTPException, Query, Request, status -from fastapi.responses import HTMLResponse, PlainTextResponse, Response, StreamingResponse +from fastapi.responses import ( + FileResponse, + HTMLResponse, + PlainTextResponse, + Response, + StreamingResponse, +) from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates @@ -235,6 +241,15 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]: app.include_router(actions) app.mount("/static", StaticFiles(directory=HERE / "static"), name="static") + @app.get("/favicon.ico", include_in_schema=False) + async def favicon() -> FileResponse: + # Browsers request this on their own, without credentials and before reading the page. + return FileResponse( + HERE / "static" / "favicon.ico", + media_type="image/x-icon", + headers={"Cache-Control": "public, max-age=86400"}, + ) + @app.get("/", include_in_schema=False, dependencies=[Depends(_authenticate)]) async def dashboard(request: Request) -> HTMLResponse: return templates.TemplateResponse( diff --git a/src/pgqueue/admin/static/apple-touch-icon.png b/src/pgqueue/admin/static/apple-touch-icon.png new file mode 100644 index 0000000..74a8fee Binary files /dev/null and b/src/pgqueue/admin/static/apple-touch-icon.png differ diff --git a/src/pgqueue/admin/static/favicon-32x32.png b/src/pgqueue/admin/static/favicon-32x32.png new file mode 100644 index 0000000..4621e5e Binary files /dev/null and b/src/pgqueue/admin/static/favicon-32x32.png differ diff --git a/src/pgqueue/admin/static/favicon.ico b/src/pgqueue/admin/static/favicon.ico new file mode 100644 index 0000000..e104f30 Binary files /dev/null and b/src/pgqueue/admin/static/favicon.ico differ diff --git a/src/pgqueue/admin/static/favicon.svg b/src/pgqueue/admin/static/favicon.svg new file mode 100644 index 0000000..380852b --- /dev/null +++ b/src/pgqueue/admin/static/favicon.svg @@ -0,0 +1,6 @@ + diff --git a/src/pgqueue/admin/templates/dashboard.html b/src/pgqueue/admin/templates/dashboard.html index 19e64f3..d07964c 100644 --- a/src/pgqueue/admin/templates/dashboard.html +++ b/src/pgqueue/admin/templates/dashboard.html @@ -5,7 +5,12 @@