From 8d83728212554fae616f381cb776616ed9c92d41 Mon Sep 17 00:00:00 2001 From: MaciejZiel Date: Tue, 13 Oct 2026 21:54:36 +0200 Subject: [PATCH 1/5] Add a queue-bars favicon on the shared dark tile --- src/pgqueue/admin/static/favicon.svg | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/pgqueue/admin/static/favicon.svg 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 @@ + + + + + + From 92636c7d260c05e13c3a50fd2560c172ecaa8d5c Mon Sep 17 00:00:00 2001 From: MaciejZiel Date: Tue, 13 Oct 2026 21:54:36 +0200 Subject: [PATCH 2/5] Add ICO, 32px PNG and Apple touch icon fallbacks --- src/pgqueue/admin/static/apple-touch-icon.png | Bin 0 -> 2304 bytes src/pgqueue/admin/static/favicon-32x32.png | Bin 0 -> 635 bytes src/pgqueue/admin/static/favicon.ico | Bin 0 -> 2014 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/pgqueue/admin/static/apple-touch-icon.png create mode 100644 src/pgqueue/admin/static/favicon-32x32.png create mode 100644 src/pgqueue/admin/static/favicon.ico 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 0000000000000000000000000000000000000000..74a8fee493d92e240599d615eec2ebe2b5e586bd GIT binary patch literal 2304 zcmchZ`#TegAIB#OImm4am3%d~IVH+m_r4IyOZ zGG>u-$x#^*v&gWftXzsQcAP)p{Cb|}_5Qry&*#_A^Ld^(*3|_rdsyu-005A6K)~Gh z{n_7=Is1i+4I%8{q0_h#-~?ZZjr zM{F$f-dM4ag_=X>$_#HQUKui&+IkVAA_H~)KDg!-UD<@MP?ZCPDJW{I!M)pBuCY)9 zf9ObAR|R&FJED0#wDw4ljXR9vyZMSV(R>F#*P>kf-fzu`&Hi26o^J*ZM>aGpHb^8E z(EqP;f1*&C2C@DjP?cVaT{NE6(X)*&o@mrh=#*LlClBE1W!kp7dDDv(Qtq&?VTWJ2 zwTBtoH6G>UL;lmWUlcDWoo<~_ws=RV? z^!mfcsOsKG&|H={PE}sC)GZDjAEuS~K~{enSzE47BntD@tPi|9m*;n(i~Jbn74L;( z^Uh6aPpFT1n4IOUZ=|rhQv!7C<6eKA+xuF2y{6B*4PejN@#8k)F{b07qa|@PK ze2SvAY%uSjCpyq6U;F*gn=PvWTs@D-wzjY~ph@e>AUz7V*^dfL+VL?xqoGYW^(x9J z`rN?8R(afob|?u)lX>#7kg*;odcnR~>S2}A7D=Ke{SDdcVRMQQH%8yZ7e{Y@e+A3+*x zwazeom@gk}zP8iM_WC~6pYhCc{2ApYi`jPk82VAv+qssq22HCFpcEXtr6#;>Dy)#JFbc{aS;vmaaYcjZ9s-F!1*o6cIrqU z7z?SPquf2TAr9ss1vp~LjABlDs2^`f5DJ}>vmI=L+oz3{mk^)Lvf;f|zC$&}yg?!I zNDgxAb-eon?sPfK($*ZLJTSM2t^p}`8Ln9TxXLuGf+)5OO2*Ycdt)|}7)J5wj_VpP zVtwVTZ>X9D-GYl(cSn(G6Tkh8Od@PJ-_ZHsy6Fb*^Pt&BbVcklYDp&|?q(_c(qpUfZ?JuG nzrKh>V??@OKOFkIz=#9vj^>*@@jC(A-vAu!U0_YNq3M4CvL9eU literal 0 HcmV?d00001 diff --git a/src/pgqueue/admin/static/favicon-32x32.png b/src/pgqueue/admin/static/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..4621e5e053ba5c1938703153e273ee248821d9d3 GIT binary patch literal 635 zcmV->0)+jEP)S1*yC?|&#vOOYS@d&Q-g%#weZKR~`<-VV1xF|@Q$Il9 zIhd#s0=F4s3Opc$NHc~J2o?uJi=LYNeK^hzMwg=%MM^kE6EAUw2;iR~tdCetBo72! zD!u+B1pbGvDi8o9LbFWk?bdptgUbm@ULRC)3ElOz5a*vW<8X=knsfX`9IPY-4ev4-4ZxwsDzTq2Ef_D7g3R3u<3{ zg&*`cf5$-U57;QE9sJ_X<9ge9nVTceI0{I_+u#@43IE0D)hlcRTtT$z z0U~Oxr$e}`#W6qD1!1L6Cm<+JEHtOJp`M(}Z1=XFgj*bUAXgi1t}WsJx2LW}I3B`W zZ5S?zpz7f(*Xu8y4J|W`nW1*q>wUGMJcM(jozPdN;g<7j!)%q#vjDd?bgXnP@oYn< zO6OTXPMss6q1;VV}o#k^pcW+Mmy0Wo8wjA~$UW6uTavr9U}E!jBY zjG-J&23U literal 0 HcmV?d00001 diff --git a/src/pgqueue/admin/static/favicon.ico b/src/pgqueue/admin/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e104f308f67fde7e8ff57980f7f8a09885cffec6 GIT binary patch literal 2014 zcmah|cTm%b68@zSdI?>M^w6Z86c2B}Xb40fod`m(P$HoODIz_9G>>u$k*1VHkO)dS z1wkoNBOnqEfk>4S5D^d}5K!Qu&YO9EyqUK%yZi0#?Edl1w*UYFAb^MnaA=CaMGycO z|3HerYCISK$cH_B{a^Jw1ORpq{g$1Lr68XS-(gn}V})`!%s(!O@W2kE0{h?!0D#mm zD5O*P{U_P2dQf{2x_QWjt|I0nPE}MX0^YB7V~}8E=*v|mG)dTl7>42~Tb0VL@C=5? ztT?rXE8{lqe6Z||-)%ivBU8i?gUNe6+sCIP*hN5IP63>f_%T5d!Alu5bJcQv`YCEP z(<{J8y&avUA^9*r_wj=&DB2YKs!pjS0Tt_?(>4pWLZH{%99ene+13D*3}PaiPqf4l zHW6X{g5oWpxPWJpfte$YzxxO-g^`7X-0{MV`_WK_e-nGx1!*D+$G>j#A}Evx(gN5o zhjiX<=P2(;S1-+xETxYY^0|97pJ&Zp&VMdtGJ@n`5HqIOklYmQCBS^tr5f94KAs@~ z`M#rhl|eoUC&7EcmtB2b6T2?OE*edTJU0h*&-jT#nsN^gc>^#OHmIu8mlOU6yNZWn z#h>hkL`HrE0I<$4b|-Ud{ltRA>E>&>xaHvuf_kuh7<^ptYdlHThP( zwIaK6mv+;%VCWzc8uLNC{B>hY7l-r-%RKtnkUzZ@IlPvGg+ z_AT}_cEcIe=(eyf^MvqIZs^PyA%eq(%{HTSzr6+$^;M8XGV8WwXRL_UM-HU={35~_2;ws5z);fE_flP9Ge${BI>diQ9Q+G`)tVF++oY1Iu z+&lDTJ7=t;(G3Zs=nFrd;_={JezOtTQrWS*)5qHUjtLVqQVw4ws?m>MoBQ^zE~_V&y3eHTnyH&GXV8u ztneQo7;qVx`VS}J5o59e4?ouZ{izebyA=qGbX3MFY+%@F+_zOEHw=VH(fqH3ET&GK zn0ZuXDssosedhHt08SZSv0KXFfn4OTBW^-$5zx#XL>n=EgdgY54w|lj%+2l6TxAr_HDp z#|B8VaS z+wg43)AH%N(&|6phgkgNf8bX2bc5&|bSMwadkj~AClF!S>uSAFtT%y}oOEsoz4ebo zrgWumyP4f3aDS>juhH>X$Bv>F9+kPP5&nvQ4lgt%KEo+zyLew;_nYkt$=vhzbUMA) z;dwtg1a_&U|N5&!Tr`bnac3R?nq0PzWl<=kRT%W&` zs01MlqiMp=#s$!H`ReMfc!9&A?F-jdDem9iUCk!_K!OUN(vtpISKAh>l##q$CvFwUbtw)v$?u%WTGwCq I|NF=P4cTX!a{vGU literal 0 HcmV?d00001 From 81c9add4bcc799c08d8cd5cda6e9d5682c278c6d Mon Sep 17 00:00:00 2001 From: MaciejZiel Date: Tue, 13 Oct 2026 21:55:02 +0200 Subject: [PATCH 3/5] Link the icon set and theme-color tags, answer /favicon.ico from the app --- src/pgqueue/admin/api.py | 17 ++++++++++++++++- src/pgqueue/admin/templates/dashboard.html | 7 ++++++- tests/test_admin_api.py | 17 +++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) 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/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 @@ pgqueue ยท live dashboard - + + + + + + diff --git a/tests/test_admin_api.py b/tests/test_admin_api.py index 7464868..5143608 100644 --- a/tests/test_admin_api.py +++ b/tests/test_admin_api.py @@ -157,3 +157,20 @@ async def test_dashboard_page_and_assets_are_served(client: Client) -> None: script = await client.get("/static/dashboard.js") assert script.status == 200 assert "X-PgQueue-Action" in script.text # the dashboard sends the CSRF header + + +async def test_icons_are_served_with_image_types(client: Client) -> None: + page = await client.get("/") + for name in ("favicon.ico", "favicon.svg", "favicon-32x32.png", "apple-touch-icon.png"): + assert f"static/{name}" in page.text + for path, ctype in ( + ("/favicon.ico", "image/x-icon"), + ("/static/favicon.svg", "image/svg+xml"), + ("/static/favicon-32x32.png", "image/png"), + ("/static/apple-touch-icon.png", "image/png"), + ): + icon = await client.get(path) + assert icon.status == 200, path + assert icon.headers["content-type"].split(";")[0] == ctype + assert icon.body + assert (await client.get("/favicon.ico")).headers["cache-control"] == "public, max-age=86400" From f30f4481286508e5ea6b5d820164bba475f1f54f Mon Sep 17 00:00:00 2001 From: MaciejZiel Date: Tue, 13 Oct 2026 21:55:02 +0200 Subject: [PATCH 4/5] Serve /favicon.ico and the touch icon at the host root from nginx --- deploy/termux/nginx-location.conf | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 { From 10ede643ae82f347e331ba65a821af4becd9cee0 Mon Sep 17 00:00:00 2001 From: MaciejZiel Date: Tue, 13 Oct 2026 21:55:07 +0200 Subject: [PATCH 5/5] Note the favicon set in the changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) 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