Skip to content

fix: Admin UI pagination proxy compatibility and fix for count accuracy#2846

Merged
crivetimihai merged 5 commits intomainfrom
fix-x-data
Feb 11, 2026
Merged

fix: Admin UI pagination proxy compatibility and fix for count accuracy#2846
crivetimihai merged 5 commits intomainfrom
fix-x-data

Conversation

@madhav165
Copy link
Copy Markdown
Collaborator

@madhav165 madhav165 commented Feb 11, 2026

🐛 Bug-fix PR

Fixes #2845


Summary

  • Fix Alpine.js x-data attribute corruption when a reverse proxy rewrites URLs in the response body by moving base_url from inline JavaScript to a data-* attribute
  • Use request.scope["root_path"] instead of hardcoded settings.app_root_path for HTMX partial endpoint URLs so proxy path rewriting is respected
  • Adjust pagination total_items when DB rows fail Pydantic conversion so "Showing X of Y" counts match actual displayed items
  • Clamp page to total_pages in offset_paginate to avoid empty results when a page number exceeds available pages
  • Hide page navigation buttons when totalPages is 0 and show "No items found" for empty result sets
  • Fix 6 timezone-sensitive unit tests that used datetime.now() (local time) instead of UTC-based naive datetimes

Changes

mcpgateway/templates/pagination_controls.html

  • Move {{ base_url }} from inside the x-data JavaScript expression to a data-base-url HTML attribute on the outer <div>. The Alpine init() method reads it via this.baseUrl = $el.dataset.baseUrl. This prevents proxy URL rewriting from corrupting the x-data="..." attribute boundary.
  • Replace new URL('{{ base_url }}', ...) in loadPage() with new URL(this.baseUrl, ...).
  • Wrap page navigation buttons in <template x-if="totalPages > 0"> so they are hidden for empty results.
  • Show "No items found" when totalItems === 0 instead of "Showing 1 - 0 of 0 items".
  • Minor formatting/whitespace normalization (single-quote to double-quote in <script> block).

mcpgateway/admin.py

  • Add _adjust_pagination_for_conversion_failures() helper that decrements pagination.total_items by the number of rows that fail DB-to-Pydantic conversion.
  • Apply this helper in all 6 admin partial endpoints: servers, tools, prompts, gateways, resources, a2a agents.
  • Replace settings.app_root_path with request.scope.get("root_path", "") when constructing base_url in all partial endpoints (servers, tools, prompts, gateways, resources, a2a agents, users).

mcpgateway/utils/pagination.py

  • Clamp page to total_pages when total_pages > 0 to prevent out-of-range offsets returning empty results.

tests/unit/mcpgateway/utils/test_pagination.py

  • Add 3 tests for page clamping: page beyond total, zero items, and exact last page.

tests/unit/mcpgateway/services/test_token_storage_service.py

  • Fix test_is_token_expired_naive_datetime: use datetime.now(timezone.utc).replace(tzinfo=None) instead of datetime.now() to create a naive datetime that represents UTC.

tests/unit/mcpgateway/routers/test_log_search.py

  • Fix test_aggregate_custom_windows_adds_timezone_to_naive_earliest_log: same datetime.now() to UTC-based naive datetime fix.

tests/unit/mcpgateway/test_db.py

  • Fix 4 tests (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): monkeypatched utc_now lambdas now return datetime.now(timezone.utc).replace(tzinfo=None, microsecond=0) instead of datetime.now().replace(microsecond=0).

uv.lock

  • Add pytest-instafail dev dependency.

🧪 Verification

Check Command Status
Lint suite make lint pass
Unit tests make test pass

📐 MCP Compliance (if relevant)

  • Matches current MCP spec
  • No breaking change to MCP clients

✅ Checklist

  • Code formatted (make black isort pre-commit)
  • No secrets/credentials committed

@madhav165 madhav165 added bug Something isn't working ica ICA related issues labels Feb 11, 2026
Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>
Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>
Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>
@madhav165 madhav165 changed the title fix: Admin UI pagination proxy compatibility, count accuracy, and timezone test fixes fix: Admin UI pagination proxy compatibility and fix for count accuracy Feb 11, 2026
@crivetimihai
Copy link
Copy Markdown
Member

Review

Rebased onto main (clean, no conflicts). All 15 new/modified tests pass.

Changes look good

  • Proxy compatibility: Moving base_url from inline JS in x-data to a data-base-url HTML attribute is the correct approach to prevent reverse proxy URL rewriting from corrupting Alpine.js initialization.
  • Dynamic root_path: Using request.scope.get("root_path", "") instead of settings.app_root_path is the proper ASGI mechanism — consistently applied across all 7 partial endpoints.
  • Pagination accuracy: _adjust_pagination_for_conversion_failures helper and page clamping logic are correct. Empty-state UX ("No items found", hidden nav buttons) is a good improvement.
  • Timezone test fixes: datetime.now(timezone.utc).replace(tzinfo=None) is the correct pattern for UTC-based naive datetimes — fixes flaky tests in non-UTC environments.
  • Security: No issues. The data-base-url attribute approach is actually more secure than the previous JS-in-attribute context (avoids double-context escaping risks). root_path is ASGI-server controlled, not user-input.
  • Performance: No impact.

Minor issues (non-blocking)

  1. _adjust_pagination_for_conversion_failures only adjusts total_items, not total_pages/has_next/has_prev. Page navigation could show extra pages if many items fail conversion. Acceptable since conversion failures are rare edge cases.

  2. Test name mismatch: test_offset_paginate_page_stays_1_when_no_items asserts page == 5, not page == 1. The test logic is correct (no clamping when total_pages=0), but the name is misleading.

  3. Remaining settings.app_root_path uses in admin.py (out of scope for this PR, but worth a follow-up):

    • Lines 2799, 2920: get_overview_partial() and admin_login_required()
    • Line 6838: admin_tool_ops_partial() pagination base_url
    • Lines 12011, 12084: export functions
    • Line 13909: HTMX hx-post attribute in Python string
  4. admin_tool_ops_partial() (line ~6838) has no try/except in its conversion loop (uses a bare list comprehension), unlike the other 6 endpoints updated in this PR. Could crash on corrupt data.

- Recompute total_pages, has_next, has_prev, and page in
  _adjust_pagination_for_conversion_failures to avoid stale UI state
- Fix test_auth_cache_import_error: patch the already-imported function
  reference instead of sys.modules (module-level import was already
  resolved)
- Fix test_redis_available_cache_hit: patch get_redis_client in the
  consuming module, not the source module
- Rename misleading test to test_offset_paginate_page_not_clamped_when_no_items
- Add tests for derived pagination field recomputation

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
- Remove page clamping from _adjust_pagination_for_conversion_failures
  since data was already fetched for the original page; clamping would
  make metadata claim a different page than what is displayed
- Use request.scope root_path for tool-ops partial endpoint base_url
  instead of settings.app_root_path, consistent with all other partials

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
@crivetimihai
Copy link
Copy Markdown
Member

Additional fixes pushed

Two follow-up commits added after review:

Commit 4: fix: complete pagination adjustment and fix broken mock targets

Pagination adjustment_adjust_pagination_for_conversion_failures previously only mutated total_items, leaving total_pages, has_next, and has_prev stale. Now recomputes all derived fields so the UI stays internally consistent.

Broken test mocks (pre-existing on main, not from this PR):

  • test_auth_cache_import_error — patched sys.modules["mcpgateway.cache.auth_cache"] but get_auth_cache was already imported at module level (line 33 of team_management_service.py). sys.modules patching only blocks future imports, not already-resolved references. Fixed to patch mcpgateway.services.team_management_service.get_auth_cache directly.
  • test_redis_available_cache_hit — patched mcpgateway.utils.redis_client.get_redis_client but the code uses from ... import get_redis_client, so the local reference is unaffected. Fixed to patch mcpgateway.services.team_management_service.get_redis_client.

Test improvements:

  • Renamed test_offset_paginate_page_stays_1_when_no_itemstest_offset_paginate_page_not_clamped_when_no_items (test asserts page == 5, not page == 1)
  • Added assertions for derived pagination fields (total_pages, has_next, has_prev)
  • Added 2 boundary tests: test_recomputes_has_next_on_boundary and test_page_not_clamped_when_total_pages_shrinks

Commit 5: fix: do not clamp page after conversion and use root_path for tool-ops

Page clamping removed from _adjust_pagination_for_conversion_failures — Data is already fetched for the original page before conversion happens. Clamping page afterward would make metadata claim a different page than what's displayed (e.g., metadata says "page 4" but showing data from offset 80 which is page 5). total_items, total_pages, has_next, has_prev are still recomputed.

tool-ops endpointadmin_tool_ops_partial() (line 6843) still used settings.app_root_path for its pagination base_url. Changed to request.scope.get('root_path', '') for consistency with all other partial endpoints.

All 867 affected tests pass.

@crivetimihai crivetimihai merged commit 054d2b3 into main Feb 11, 2026
52 checks passed
@crivetimihai crivetimihai deleted the fix-x-data branch February 11, 2026 16:54
ja8zyjits pushed a commit that referenced this pull request Feb 13, 2026
…cy (#2846)

* fix baseUrl

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>

* fix x-data base-url

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>

* add new tests

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>

* fix: complete pagination adjustment and fix broken mock targets

- Recompute total_pages, has_next, has_prev, and page in
  _adjust_pagination_for_conversion_failures to avoid stale UI state
- Fix test_auth_cache_import_error: patch the already-imported function
  reference instead of sys.modules (module-level import was already
  resolved)
- Fix test_redis_available_cache_hit: patch get_redis_client in the
  consuming module, not the source module
- Rename misleading test to test_offset_paginate_page_not_clamped_when_no_items
- Add tests for derived pagination field recomputation

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: do not clamp page after conversion and use root_path for tool-ops

- Remove page clamping from _adjust_pagination_for_conversion_failures
  since data was already fetched for the original page; clamping would
  make metadata claim a different page than what is displayed
- Use request.scope root_path for tool-ops partial endpoint base_url
  instead of settings.app_root_path, consistent with all other partials

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

---------

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
vishu-bh pushed a commit that referenced this pull request Feb 18, 2026
…cy (#2846)

* fix baseUrl

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>

* fix x-data base-url

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>

* add new tests

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>

* fix: complete pagination adjustment and fix broken mock targets

- Recompute total_pages, has_next, has_prev, and page in
  _adjust_pagination_for_conversion_failures to avoid stale UI state
- Fix test_auth_cache_import_error: patch the already-imported function
  reference instead of sys.modules (module-level import was already
  resolved)
- Fix test_redis_available_cache_hit: patch get_redis_client in the
  consuming module, not the source module
- Rename misleading test to test_offset_paginate_page_not_clamped_when_no_items
- Add tests for derived pagination field recomputation

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: do not clamp page after conversion and use root_path for tool-ops

- Remove page clamping from _adjust_pagination_for_conversion_failures
  since data was already fetched for the original page; clamping would
  make metadata claim a different page than what is displayed
- Use request.scope root_path for tool-ops partial endpoint base_url
  instead of settings.app_root_path, consistent with all other partials

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

---------

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
Signed-off-by: Vishu Bhatnagar <vishu.bhatnagar@ibm.com>
kcostell06 pushed a commit to kcostell06/mcp-context-forge that referenced this pull request Feb 24, 2026
…cy (IBM#2846)

* fix baseUrl

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>

* fix x-data base-url

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>

* add new tests

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>

* fix: complete pagination adjustment and fix broken mock targets

- Recompute total_pages, has_next, has_prev, and page in
  _adjust_pagination_for_conversion_failures to avoid stale UI state
- Fix test_auth_cache_import_error: patch the already-imported function
  reference instead of sys.modules (module-level import was already
  resolved)
- Fix test_redis_available_cache_hit: patch get_redis_client in the
  consuming module, not the source module
- Rename misleading test to test_offset_paginate_page_not_clamped_when_no_items
- Add tests for derived pagination field recomputation

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: do not clamp page after conversion and use root_path for tool-ops

- Remove page clamping from _adjust_pagination_for_conversion_failures
  since data was already fetched for the original page; clamping would
  make metadata claim a different page than what is displayed
- Use request.scope root_path for tool-ops partial endpoint base_url
  instead of settings.app_root_path, consistent with all other partials

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

---------

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
suciu-daniel added a commit that referenced this pull request Feb 25, 2026
Fixes #2851

When items fail Pydantic validation during admin list views, the pagination
'Showing X - Y of Z' display was incorrect. The server adjusted total_items (Z)
but the X-Y range was computed client-side using requested items, not actual
rendered count.

Changes:
- Add optional page_items field to PaginationMeta schema
- Update _adjust_pagination_for_conversion_failures() to set page_items
- Update 6 admin partial endpoints to pass rendered item count
- Update pagination_controls.html to use page_items when available
- Update tests to match new function signature

Example: 20 items requested, 2 fail conversion
- Before: 'Showing 1 - 20 of 98 items' (18 rows displayed)
- After: 'Showing 1 - 18 of 98 items' (18 rows displayed)

Builds on PR #2846 which introduced _adjust_pagination_for_conversion_failures()

Signed-off-by: SuciuDaniel <Daniel.Vasile.Suciu@ibm.com>
@crivetimihai crivetimihai self-assigned this Feb 26, 2026
crivetimihai pushed a commit that referenced this pull request Mar 10, 2026
Fixes #2851

When items fail Pydantic validation during admin list views, the pagination
'Showing X - Y of Z' display was incorrect. The server adjusted total_items (Z)
but the X-Y range was computed client-side using requested items, not actual
rendered count.

Changes:
- Add optional page_items field to PaginationMeta schema
- Update _adjust_pagination_for_conversion_failures() to set page_items
- Update 6 admin partial endpoints to pass rendered item count
- Update pagination_controls.html to use page_items when available
- Update tests to match new function signature

Example: 20 items requested, 2 fail conversion
- Before: 'Showing 1 - 20 of 98 items' (18 rows displayed)
- After: 'Showing 1 - 18 of 98 items' (18 rows displayed)

Builds on PR #2846 which introduced _adjust_pagination_for_conversion_failures()

Signed-off-by: SuciuDaniel <Daniel.Vasile.Suciu@ibm.com>
crivetimihai pushed a commit that referenced this pull request Mar 10, 2026
…3238)

Fixes #2851

When items fail Pydantic validation during admin list views, the pagination
'Showing X - Y of Z' display was incorrect. The server adjusted total_items (Z)
but the X-Y range was computed client-side using requested items, not actual
rendered count.

Changes:
- Add optional page_items field to PaginationMeta schema
- Update _adjust_pagination_for_conversion_failures() to set page_items
- Update 6 admin partial endpoints to pass rendered item count
- Update pagination_controls.html to use page_items when available
- Update tests to match new function signature

Example: 20 items requested, 2 fail conversion
- Before: 'Showing 1 - 20 of 98 items' (18 rows displayed)
- After: 'Showing 1 - 18 of 98 items' (18 rows displayed)

Builds on PR #2846 which introduced _adjust_pagination_for_conversion_failures()

Signed-off-by: SuciuDaniel <Daniel.Vasile.Suciu@ibm.com>
MohanLaksh pushed a commit that referenced this pull request Mar 12, 2026
…3238)

Fixes #2851

When items fail Pydantic validation during admin list views, the pagination
'Showing X - Y of Z' display was incorrect. The server adjusted total_items (Z)
but the X-Y range was computed client-side using requested items, not actual
rendered count.

Changes:
- Add optional page_items field to PaginationMeta schema
- Update _adjust_pagination_for_conversion_failures() to set page_items
- Update 6 admin partial endpoints to pass rendered item count
- Update pagination_controls.html to use page_items when available
- Update tests to match new function signature

Example: 20 items requested, 2 fail conversion
- Before: 'Showing 1 - 20 of 98 items' (18 rows displayed)
- After: 'Showing 1 - 18 of 98 items' (18 rows displayed)

Builds on PR #2846 which introduced _adjust_pagination_for_conversion_failures()

Signed-off-by: SuciuDaniel <Daniel.Vasile.Suciu@ibm.com>
Yosiefeyob pushed a commit that referenced this pull request Mar 13, 2026
…3238)

Fixes #2851

When items fail Pydantic validation during admin list views, the pagination
'Showing X - Y of Z' display was incorrect. The server adjusted total_items (Z)
but the X-Y range was computed client-side using requested items, not actual
rendered count.

Changes:
- Add optional page_items field to PaginationMeta schema
- Update _adjust_pagination_for_conversion_failures() to set page_items
- Update 6 admin partial endpoints to pass rendered item count
- Update pagination_controls.html to use page_items when available
- Update tests to match new function signature

Example: 20 items requested, 2 fail conversion
- Before: 'Showing 1 - 20 of 98 items' (18 rows displayed)
- After: 'Showing 1 - 18 of 98 items' (18 rows displayed)

Builds on PR #2846 which introduced _adjust_pagination_for_conversion_failures()

Signed-off-by: SuciuDaniel <Daniel.Vasile.Suciu@ibm.com>
Signed-off-by: Yosief Eyob <yosiefogbazion@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ica ICA related issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants