mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-28 01:32:17 +00:00
fix(admin-ui): emit nested routes as <dir>/index.html (#28106)
Some checks failed
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests: Security / security (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / schema-migration (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled
Some checks failed
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests: Security / security (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / schema-migration (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled
Linear and other OAuth providers redirect the user back to /ui/mcp/oauth/callback?code=...&state=... after the consent step. The packaged Next.js static export only produced /ui/mcp/oauth/callback.html, so FastAPI's StaticFiles served a 404 on the extensionless URL and the OAuth handshake never completed. The Dockerfile.non_root build step tried to paper over this at image-build time with `for html_file in *.html; do ...`, but that shell glob does not recurse, so nested routes like mcp/oauth/callback.html were left stranded next to an empty mcp/oauth/callback/ directory containing only Next.js metadata. The runtime restructure step in proxy_server.py was then skipped because the .litellm_ui_ready marker had already been dropped. Set trailingSlash: true in the dashboard's Next.js config so the export emits every nested route as <dir>/index.html natively. The Dockerfile loop is now a no-op for the bundled UI and has been removed; the .litellm_ui_ready marker is still written so the proxy keeps skipping the redundant Python restructure step at startup. Stacks on top of the static export regeneration in the parent branch.
This commit is contained in:
parent
8757b86a09
commit
7e0dced0bd
3 changed files with 45 additions and 13 deletions
|
|
@ -54,22 +54,10 @@ COPY . .
|
|||
# Set non-root flag for build time consistency
|
||||
ENV LITELLM_NON_ROOT=true
|
||||
|
||||
# Stage the pre-built Admin UI from the checked-in Next.js static export.
|
||||
# _experimental/out/ is regenerated as part of the release runbook.
|
||||
# Restructure extensionless routes (foo.html -> foo/index.html) to match the layout
|
||||
# proxy_server.py expects, and drop a readiness marker.
|
||||
RUN mkdir -p /var/lib/litellm/ui /var/lib/litellm/assets && \
|
||||
cp -r /app/litellm/proxy/_experimental/out/. /var/lib/litellm/ui/ && \
|
||||
cp /app/litellm/proxy/logo.jpg /var/lib/litellm/assets/logo.jpg && \
|
||||
( cd /var/lib/litellm/ui && \
|
||||
for html_file in *.html; do \
|
||||
if [ "$html_file" != "index.html" ] && [ -f "$html_file" ]; then \
|
||||
folder_name="${html_file%.html}" && \
|
||||
mkdir -p "$folder_name" && \
|
||||
mv "$html_file" "$folder_name/index.html"; \
|
||||
fi; \
|
||||
done && \
|
||||
touch .litellm_ui_ready )
|
||||
touch /var/lib/litellm/ui/.litellm_ui_ready
|
||||
|
||||
RUN --mount=type=cache,target=/app/.cache/uv,id=litellm-uv-cache \
|
||||
if [ "$PROXY_EXTRAS_SOURCE" = "published" ]; then \
|
||||
|
|
|
|||
|
|
@ -604,6 +604,49 @@ def test_ui_extensionless_route_requires_restructure(tmp_path):
|
|||
assert "login" in response.text
|
||||
|
||||
|
||||
def test_admin_ui_export_serves_nested_extensionless_routes():
|
||||
out_dir = (
|
||||
Path(litellm.__file__).parent / "proxy" / "_experimental" / "out"
|
||||
)
|
||||
assert out_dir.is_dir(), f"missing UI export at {out_dir}"
|
||||
|
||||
nested_html_offenders = [
|
||||
path.relative_to(out_dir).as_posix()
|
||||
for path in out_dir.rglob("*.html")
|
||||
if path.parent != out_dir
|
||||
and path.name != "index.html"
|
||||
and "_next" not in path.parts
|
||||
and "litellm-asset-prefix" not in path.parts
|
||||
]
|
||||
assert not nested_html_offenders, (
|
||||
"Nested routes must be named index.html. Offenders: "
|
||||
f"{nested_html_offenders}"
|
||||
)
|
||||
|
||||
callback_index = out_dir / "mcp" / "oauth" / "callback" / "index.html"
|
||||
assert callback_index.is_file(), (
|
||||
f"MCP OAuth callback page must exist at {callback_index}; "
|
||||
"without it /ui/mcp/oauth/callback 404s after Linear redirects back."
|
||||
)
|
||||
|
||||
fastapi_app = FastAPI()
|
||||
fastapi_app.mount(
|
||||
"/ui", StaticFiles(directory=str(out_dir), html=True), name="ui"
|
||||
)
|
||||
client = TestClient(fastapi_app)
|
||||
|
||||
redirect = client.get(
|
||||
"/ui/mcp/oauth/callback?code=abc&state=xyz",
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert redirect.status_code == 307
|
||||
assert redirect.headers["location"].endswith("/ui/mcp/oauth/callback/?code=abc&state=xyz")
|
||||
|
||||
landed = client.get("/ui/mcp/oauth/callback?code=abc&state=xyz")
|
||||
assert landed.status_code == 200
|
||||
assert "<html" in landed.text.lower()
|
||||
|
||||
|
||||
def test_restructure_always_happens(monkeypatch):
|
||||
"""
|
||||
Test that restructuring logic always executes regardless of LITELLM_NON_ROOT setting.
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const nextConfig = {
|
|||
},
|
||||
basePath: "",
|
||||
assetPrefix: "/litellm-asset-prefix",
|
||||
trailingSlash: true,
|
||||
turbopack: {
|
||||
// Must be absolute; "." is no longer allowed
|
||||
root: __dirname,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue