Skip to content

Commit 0fb33dc

Browse files
authored
URLs and reverse proxy (#110)
* Release 0.14.1
1 parent dbcbfe3 commit 0fb33dc

8 files changed

Lines changed: 127 additions & 86 deletions

File tree

asyncz/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from .monkay import create_monkay
44

5-
__version__ = "0.14.0"
5+
__version__ = "0.14.1"
66

77
if TYPE_CHECKING:
88
from .conf import settings

asyncz/contrib/dashboard/config.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import secrets
22
from dataclasses import dataclass
3-
from typing import Literal, cast
3+
from typing import Literal
4+
5+
from lilya.requests import Request
46

57
try:
68
from lilya.middleware import DefineMiddleware
@@ -95,13 +97,37 @@ def session_middleware(self) -> DefineMiddleware:
9597
)
9698

9799

98-
def get_effective_prefix() -> str:
99-
"""Compute an absolute dashboard base path, combining ASGI root_path and the
100-
configured dashboard URL prefix.
100+
def _normalize_prefix(value: str | None) -> str:
101+
"""Ensure a leading slash and remove trailing slash (except for root)."""
102+
if not value:
103+
return "/"
104+
v = value.strip()
105+
if not v.startswith("/"):
106+
v = "/" + v
107+
return v if v == "/" else v.rstrip("/")
108+
101109

102-
Guarantees:
103-
- Always starts with '/'
104-
- No trailing slash (except when the result is exactly '/')
105-
- Never double-appends the configured prefix if it's already in root_path
110+
def get_effective_prefix(request: Request | None = None) -> str:
111+
"""Compute the effective base URL prefix for the dashboard.
112+
113+
- If *request* is **None**, return the configured dashboard prefix exactly as before
114+
(leading '/', no trailing '/', except when it is '/'). This preserves
115+
backward compatibility with tests that called the old zero-arg function.
116+
- If *request* is provided, combine ASGI mount ``root_path`` (if any) with the
117+
configured prefix, avoiding double-prefixing and double slashes.
106118
"""
107-
return cast(str, monkay.settings.dashboard_config.dashboard_url_prefix)
119+
configured_prefix = _normalize_prefix(
120+
getattr(monkay.settings.dashboard_config, "dashboard_url_prefix", "/")
121+
)
122+
123+
# Prefer the configured prefix when it's meaningful (not '/')
124+
if configured_prefix != "/":
125+
return configured_prefix
126+
127+
# Only when configured is '/' do we consider the mount root
128+
if request is not None:
129+
scope = getattr(request, "scope", {}) or {}
130+
mount_prefix = _normalize_prefix(scope.get("root_path") or "/")
131+
return mount_prefix
132+
133+
return "/"

asyncz/contrib/dashboard/mixins.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
def default_context(request: Request) -> dict:
1212
context = {}
13-
effective_prefix = get_effective_prefix()
13+
effective_prefix = get_effective_prefix(request)
1414
context.update(
1515
{
1616
"title": monkay.settings.dashboard_config.title,

asyncz/contrib/dashboard/templates/tasks/_table.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676

7777
<td class="px-4 py-3">
7878
<div class="flex flex-wrap gap-2 justify-end">
79-
<form hx-post="{{ url_prefix }}/tasks/{{ t.id }}/run" hx-target="#tasks-table" hx-swap="innerHTML">
79+
<form hx-post="{{ t.id }}/run" hx-target="#tasks-table" hx-swap="innerHTML">
8080
<button class="inline-flex items-center gap-1 rounded-lg bg-[#f06824] px-3 py-1.5 text-sm font-medium text-white hover:bg-[#e35d1e]">
8181
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-white/90" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
8282
<path stroke-linecap="round" stroke-linejoin="round" d="M8 5v14l11-7z" />
@@ -86,7 +86,7 @@
8686
</form>
8787

8888
{% if t.next_run_time %}
89-
<form hx-post="{{ url_prefix }}/tasks/{{ t.id }}/pause" hx-target="#tasks-table" hx-swap="innerHTML">
89+
<form hx-post="{{ t.id }}/pause" hx-target="#tasks-table" hx-swap="innerHTML">
9090
<button class="inline-flex items-center gap-1 rounded-lg border border-yellow-300 bg-yellow-50 px-3 py-1.5 text-sm font-medium text-yellow-800 hover:bg-yellow-100">
9191
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-yellow-700" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
9292
<path stroke-linecap="round" stroke-linejoin="round" d="M10 9v6M14 9v6" />
@@ -95,7 +95,7 @@
9595
</button>
9696
</form>
9797
{% else %}
98-
<form hx-post="{{ url_prefix }}/tasks/{{ t.id }}/resume" hx-target="#tasks-table" hx-swap="innerHTML">
98+
<form hx-post="{{ t.id }}/resume" hx-target="#tasks-table" hx-swap="innerHTML">
9999
<button class="inline-flex items-center gap-1 rounded-lg border border-green-300 bg-green-50 px-3 py-1.5 text-sm font-medium text-green-800 hover:bg-green-100">
100100
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-green-700" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
101101
<path stroke-linecap="round" stroke-linejoin="round" d="M8 5v14l11-7z" />
@@ -105,7 +105,7 @@
105105
</form>
106106
{% endif %}
107107

108-
<form hx-post="{{ url_prefix }}/tasks/{{ t.id }}/remove" hx-target="#tasks-table" hx-swap="innerHTML"
108+
<form hx-post="{{ t.id }}/remove" hx-target="#tasks-table" hx-swap="innerHTML"
109109
onsubmit="return confirm('Remove task?')">
110110
<button class="inline-flex items-center gap-1 rounded-lg border border-red-300 bg-red-50 px-3 py-1.5 text-sm font-medium text-red-800 hover:bg-red-100">
111111
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-red-700" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">

asyncz/contrib/dashboard/templates/tasks/tasks.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ <h2 class="text-lg font-semibold leading-tight">Tasks</h2>
8787
</form>
8888
<div class="w-[64rem] max-w-[90vw] p-5">
8989
<h3 class="text-lg font-semibold mb-3">Add task</h3>
90-
<form hx-post="{{ url_prefix }}/tasks/create" hx-target="#tasks-table" hx-swap="outerHTML" onsubmit="document.getElementById('add-modal').close()">
90+
<form hx-post="create" hx-target="#tasks-table" hx-swap="outerHTML" onsubmit="document.getElementById('add-modal').close()">
9191
<label class="block text-sm text-gray-600 mb-1">Callable (pkg.mod:func)</label>
9292
<input name="callable_path" required placeholder="myapp.jobs:ping"
9393
class="w-full rounded-lg border border-gray-200 px-3 py-2 mb-3" />

docs/en/docs/release-notes.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ hide:
55

66
# Release Notes
77

8+
## 0.14.1
9+
10+
### Fixed
11+
12+
- Duplicate dashboard URL prefixing (`/dashboard/tasks/dashboard`) when deployed behind Nginx or under an ASGI mount.
13+
- Nested HTMX table container causing duplicate `#tasks-table` and incorrect `hx-get` paths.
14+
15+
### Changed
16+
17+
- `get_effective_prefix()` now prefers the configured `dashboard_url_prefix` and falls back to `root_path` only when configured as `/`.
18+
- All HTMX and action URLs in the dashboard are now relative to the current path for reverse-proxy compatibility.
19+
- Updated templates to remove hardcoded `/dashboard` from links and actions.
20+
- AsyncZ Dashboard is now fully **reverse-proxy ready** (works with `X-Forwarded-Prefix` and ASGI mounts).
21+
822
## 0.14.0
923

1024
### Added

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ testing = [
8383
"mypy>=0.982,<2.0.0",
8484
"ravyn>=0.1.0",
8585
"starlette",
86-
"lilya[standard]>=0.22.11",
86+
"lilya[standard]>=0.22.15",
8787
"sqlalchemy",
8888
"httpx",
8989
"pytz>=2022.6",
@@ -104,7 +104,7 @@ docs = [
104104
"typing_extensions>=3.10.0",
105105
]
106106

107-
dashboard = ["lilya[standard]>=0.22.11"]
107+
dashboard = ["lilya[standard]>=0.22.15"]
108108

109109
[tool.hatch.envs.default.scripts]
110110
clean_pyc = "find . -type f -name \"*.pyc\" -delete"

0 commit comments

Comments
 (0)