Skip to content

[BUG]: Admin UI pagination breaks behind reverse proxies and shows incorrect counts #2845

@madhav165

Description

@madhav165

Problem

The Admin UI pagination controls break in two distinct scenarios:

1. Reverse proxy corrupts Alpine.js x-data attribute

When MCP Gateway runs behind a reverse proxy (e.g., nginx, Traefik, Cloudflare) that rewrites URLs in response bodies, the pagination controls fail completely. The x-data attribute contains ~130 lines of JavaScript including new URL('{{ base_url }}', ...). When a proxy rewrites URLs and changes ' to ", it terminates the x-data="..." attribute mid-expression, killing the entire Alpine.js scope.

Impact: All pagination navigation (page buttons, page size selector, keyboard shortcuts) stops working. The UI appears frozen with no error visible to the user.

2. base_url uses hardcoded settings.app_root_path instead of request scope

All admin partial endpoints construct base_url using settings.app_root_path, which does not reflect the actual root path seen by the ASGI server when deployed behind a proxy with path stripping/rewriting. This causes HTMX requests to target incorrect URLs.

3. Pagination "Showing X of Y" count is wrong when DB rows fail Pydantic conversion

When database rows fail to convert to Pydantic models (e.g., due to corrupted data, schema drift, or encoding issues), they are silently skipped. However, the pagination metadata still counts them in total_items, resulting in messages like "Showing 1-18 of 20 items" when only 18 items are actually displayed.

4. Requesting a page beyond total pages returns empty results

When page exceeds total_pages (e.g., after filtering reduces total items), offset_paginate computes an out-of-range offset, returning zero results instead of clamping to the last valid page.

5. Navigation buttons render when there are zero pages

The page navigation buttons (first, prev, page numbers, next, last) render even when totalPages is 0, showing a confusing disabled nav bar for empty result sets.

6. Tests use datetime.now() instead of UTC, fail in non-UTC timezones

Six unit tests create naive datetimes via datetime.now() (local time) to test timezone-mismatch handling. The production code correctly treats naive datetimes as UTC via replace(tzinfo=timezone.utc). On any non-UTC system, datetime.now() returns a local timestamp that differs from UTC, causing all six tests to fail.

Affected tests:

  • test_is_token_expired_naive_datetime
  • test_aggregate_custom_windows_adds_timezone_to_naive_earliest_log
  • test_email_team_invitation_is_expired_handles_timezone_mismatch
  • test_email_team_join_request_is_expired_handles_timezone_mismatch_now_naive
  • test_pending_user_approval_is_expired_handles_timezone_mismatch_now_naive
  • test_sso_auth_session_is_expired_handles_timezone_mismatch_now_naive

Environment

  • Any deployment behind a reverse proxy that rewrites response body URLs
  • Any system with a non-UTC local timezone (e.g., IST, EST, CET)
  • Any admin page where DB-to-Pydantic conversion can fail for individual rows

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingicaICA related issuestriageIssues / Features awaiting triage

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions