Skip to content

2108 enable pagination#2131

Merged
crivetimihai merged 3 commits intomainfrom
2108-enable-pagination
Jan 20, 2026
Merged

2108 enable pagination#2131
crivetimihai merged 3 commits intomainfrom
2108-enable-pagination

Conversation

@gcgoncalves
Copy link
Copy Markdown
Collaborator

🐛 Bug-fix PR

📌 Summary
Admin UI pagination was not functioning correctly - page size dropdown had no effect, page state was not preserved in URLs, and
the "Show Inactive" toggle was not synchronized with pagination requests. This prevented users from effectively browsing large
datasets and sharing filtered views.

🔁 Reproduction Steps

  1. Navigate to Admin UI → Servers (or any other table)
  2. Change page size dropdown from 10 to 50
  3. Bug: Table still loads only 20 items (hardcoded value)
  4. Navigate to page 2
  5. Bug: URL doesn't update with page state
  6. Refresh browser
  7. Bug: Returns to page 1 instead of page 2
  8. Toggle "Show Inactive" checkbox
  9. Navigate to next page
  10. Bug: Inactive items still shown (toggle state not sent to backend)

🐞 Root Cause

  1. Hardcoded pagination: Initial table loads used hx-get attributes with hardcoded page=1&per_page=20, ignoring URL querystring parameters
  2. No URL state management: pagination_controls.html didn't read from or write to browser URL querystring
  3. Disconnected toggle: "Show Inactive" checkbox state was not synchronized with pagination requests
  4. Template escaping: Jinja2 auto-escaping converted Alpine.js operators (>, <, &&) to HTML entities (>, <, &&), breaking Alpine.js logic

💡 Fix Description
Frontend-only changes (no backend modifications required):

  1. URL State Management (admin.html):
  • Added getPaginationParams() helper to read page_number (default: 1) and page_size (default: 10) from URL
  • Added buildTableUrl() helper to construct table URLs with querystring values
  1. Table Initialization:
  • Updated all 6 tables (servers, tools, gateways, resources, prompts, a2a-agents) to use buildTableUrl() for initial loads
  • Tables now respect both URL parameters and "Show Inactive" checkbox state
  1. Pagination Controls (pagination_controls.html):
  • Added init() to read page_size from URL on component initialization
  • Modified loadPage() to update browser URL with page_number and page_size
  • Dynamically reads "Show Inactive" checkbox state and includes in backend requests
  1. Template Fixes:
  • Wrapped {% include 'pagination_controls.html' %} with {% autoescape false %} in all 6 partial templates
  • Prevents Jinja2 from breaking Alpine.js template syntax

🧪 Verification

Check Command Status
Lint suite make lint
Unit tests make test
Coverage ≥ 90 % make coverage
Manual regression no longer fails Tested all 6 tables

@gcgoncalves
Copy link
Copy Markdown
Collaborator Author

gcgoncalves commented Jan 16, 2026

BUILT ON TOP OF #2112, NEEDS REBASE BEFORE MERGING

EDIT: Rebased from main, ignore message above.

@gcgoncalves gcgoncalves force-pushed the 2108-enable-pagination branch 3 times, most recently from 1d0e7ea to e4f8c43 Compare January 19, 2026 10:18
@gcgoncalves gcgoncalves marked this pull request as ready for review January 19, 2026 10:29
@crivetimihai crivetimihai self-assigned this Jan 20, 2026
@crivetimihai crivetimihai added this to the Release 1.0.0-BETA-2 milestone Jan 20, 2026
- Add helper functions to read/write namespaced pagination params per table
  (e.g., servers_page, servers_size, servers_inactive)
- Update all 6 admin tables to use namespaced params for initial load
- Enhance pagination controls to sync browser URL on navigation
- Include include_inactive filter state in URL for full shareability
- Sync checkbox state from URL on page load (shareable links work correctly)
- Update URL when Show Inactive checkbox is toggled
- Fix checkbox ID mapping for -table-body targets
- Fix Jinja2 auto-escaping in partial templates for Alpine.js compatibility

URL parameter naming:
- Each table has its own namespace: {table}_page, {table}_size, {table}_inactive
- Example: ?servers_page=2&servers_size=50&servers_inactive=true
- URL state takes precedence over checkbox state for proper link sharing

Signed-off-by: Gabriel Costa <gabrielcg@proton.me>
@crivetimihai crivetimihai force-pushed the 2108-enable-pagination branch from e4f8c43 to a8ebaee Compare January 20, 2026 03:35
@crivetimihai
Copy link
Copy Markdown
Member

Changes Made During Review

This PR has been significantly reworked to fix several issues discovered during review.

Original Issues Found

The original PR had critical bugs:

  • Missing toggleInactiveItems() function - Called but never defined
  • Wrong endpoint paths - Changed /state to /toggle but backend uses /state
  • Incomplete pagination_controls.html - PR description mentioned URL state management but file wasn't modified

Fixed Implementation

1. Namespaced URL Parameters

Each table now has its own URL namespace for pagination state:

  • {table}_page - Current page number
  • {table}_size - Items per page
  • {table}_inactive - Show inactive filter state

Example: ?servers_page=2&servers_size=50&servers_inactive=true&tools_page=1&tools_size=25

2. Shareable Links Now Work

  • URL state takes precedence over checkbox state on page load
  • Checkbox state syncs from URL via syncCheckboxFromUrl()
  • Toggling checkbox updates URL via updateInactiveUrlState()

3. Files Changed (148 additions, 10 deletions)

File Changes
admin.html Added pagination helpers, checkbox sync functions, updated all 6 table inits and checkboxes
pagination_controls.html Added table_name support, namespaced URL read/write, fixed -table-body checkbox mapping
6 partial templates Added table_name variable, {% autoescape false %} for Alpine.js

4. Key Functions Added

// Read namespaced params from URL
getPaginationParams(tableName)  {page, perPage, includeInactive}

// Build table URL with namespaced params (URL state takes precedence)
buildTableUrl(tableName, baseUrl, additionalParams)

// Sync checkbox from URL on page load
syncCheckboxFromUrl(tableName, checkboxId)

// Update URL when checkbox toggled
updateInactiveUrlState(tableName, checked)

Testing

  • make lint-web passes with no errors
  • All 6 admin tables (servers, tools, resources, prompts, gateways, agents) support:
    • Independent pagination state per table
    • Shareable filtered views via URL
    • Checkbox state preserved on refresh

Inline pagination_controls.html includes in admin.html were missing
the table_name variable, causing Alpine.js to initialize them with
empty tableName. This resulted in updateBrowserUrl() returning early
and not writing the correct namespaced URL params for each table.

Added table_name to all four inline includes:
- servers: table_name = 'servers'
- tools: table_name = 'tools'
- gateways: table_name = 'gateways'
- agents: table_name = 'agents'

Also wrapped includes with autoescape false for Alpine.js compatibility.

Closes #2108

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

Latest Fix: Inline Pagination Includes Missing table_name

Root Cause

The inline pagination_controls.html includes in admin.html were missing the table_name variable. When Alpine.js initialized these controls, they had an empty tableName, causing:

  1. updateBrowserUrl() to return early without writing URL params
  2. All tables appeared to share the same URL params (first one to load "won")

Fix Applied

Added table_name to all four inline pagination includes:

  • servers -> {% set table_name = 'servers' %}
  • tools -> {% set table_name = 'tools' %}
  • gateways -> {% set table_name = 'gateways' %}
  • agents -> {% set table_name = 'agents' %}

Also wrapped includes with {% autoescape false %} for Alpine.js compatibility.

Expected Behavior After Fix

Each table now maintains its own URL state:

  • ?servers_page=1&servers_size=10&servers_inactive=false
  • ?tools_page=2&tools_size=25&tools_inactive=true
  • etc.

Multiple tables can coexist in the URL, making links shareable with full pagination state preserved.

Testing

  • make lint-web passes with no errors

1. admin.js: Update global include_inactive handler to support namespaced
   params with legacy fallback. Removed the change handler that wrote
   legacy include_inactive (conflicts with namespaced params).

2. admin.html: updateInactiveUrlState now resets *_page to 1 when
   toggling inactive checkbox, preventing stale page numbers in URL.

3. Add table_name to templates that were missing it:
   - users_partial.html: table_name = 'users'
   - teams_partial.html: table_name = 'teams'
   - tools_with_pagination.html: table_name = 'tools'
   - metrics_top_performers_partial.html: table_name = 'metrics_' + entity_type

All templates now wrapped with autoescape false for Alpine.js compatibility.

Closes #2108

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

ChatGPT Review Findings - All Fixed

Finding 1 (Medium): Global include_inactive in admin.js

Status: FIXED

The admin.js code at lines 17191-17221 was:

  1. Setting ALL checkboxes to the same state from legacy include_inactive param
  2. Writing legacy include_inactive on change (conflicting with namespaced params)

Fix:

  • Updated initialization to use a checkbox-to-table mapping that prefers namespaced params (e.g., servers_inactive) with fallback to legacy include_inactive for backwards compatibility
  • Removed the change handler that wrote legacy params (since updateInactiveUrlState() in admin.html now handles this with namespaced params)

Finding 2 (Medium): Stale page number when toggling inactive

Status: FIXED

When toggling "Show Inactive", HTMX reloads page 1, but updateInactiveUrlState() wasn't resetting the *_page param. This caused URL to show stale page numbers.

Fix:
updateInactiveUrlState() now also sets *_page = 1 when the checkbox is toggled:

browserUrl.searchParams.set(prefix + 'page', '1');  // Reset to page 1 when filter changes

Finding 3 (Low): Other templates missing table_name

Status: FIXED

Added table_name to all templates that were missing it:

  • users_partial.html: table_name = 'users'
  • teams_partial.html: table_name = 'teams'
  • tools_with_pagination.html: table_name = 'tools'
  • metrics_top_performers_partial.html: table_name = 'metrics_' + entity_type

All includes wrapped with {% autoescape false %} for Alpine.js compatibility.

ChatGPT's Question

Should legacy include_inactive remain supported?

Answer: Yes, for backwards compatibility. Old bookmarked URLs with ?include_inactive=true will still work - the code now prefers namespaced params but falls back to legacy if namespaced ones don't exist.

Testing

  • make lint-web passes with no errors

@crivetimihai
Copy link
Copy Markdown
Member

@gcgoncalves please retest.

@crivetimihai crivetimihai merged commit 2cf21e9 into main Jan 20, 2026
50 checks passed
@crivetimihai crivetimihai deleted the 2108-enable-pagination branch January 20, 2026 04:15
kcostell06 pushed a commit to kcostell06/mcp-context-forge that referenced this pull request Feb 24, 2026
* Fix pagination URL state management with namespaced params

- Add helper functions to read/write namespaced pagination params per table
  (e.g., servers_page, servers_size, servers_inactive)
- Update all 6 admin tables to use namespaced params for initial load
- Enhance pagination controls to sync browser URL on navigation
- Include include_inactive filter state in URL for full shareability
- Sync checkbox state from URL on page load (shareable links work correctly)
- Update URL when Show Inactive checkbox is toggled
- Fix checkbox ID mapping for -table-body targets
- Fix Jinja2 auto-escaping in partial templates for Alpine.js compatibility

URL parameter naming:
- Each table has its own namespace: {table}_page, {table}_size, {table}_inactive
- Example: ?servers_page=2&servers_size=50&servers_inactive=true
- URL state takes precedence over checkbox state for proper link sharing

Signed-off-by: Gabriel Costa <gabrielcg@proton.me>

* fix: add table_name to inline pagination includes

Inline pagination_controls.html includes in admin.html were missing
the table_name variable, causing Alpine.js to initialize them with
empty tableName. This resulted in updateBrowserUrl() returning early
and not writing the correct namespaced URL params for each table.

Added table_name to all four inline includes:
- servers: table_name = 'servers'
- tools: table_name = 'tools'
- gateways: table_name = 'gateways'
- agents: table_name = 'agents'

Also wrapped includes with autoescape false for Alpine.js compatibility.

Closes IBM#2108

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

* fix: address ChatGPT review findings for pagination

1. admin.js: Update global include_inactive handler to support namespaced
   params with legacy fallback. Removed the change handler that wrote
   legacy include_inactive (conflicts with namespaced params).

2. admin.html: updateInactiveUrlState now resets *_page to 1 when
   toggling inactive checkbox, preventing stale page numbers in URL.

3. Add table_name to templates that were missing it:
   - users_partial.html: table_name = 'users'
   - teams_partial.html: table_name = 'teams'
   - tools_with_pagination.html: table_name = 'tools'
   - metrics_top_performers_partial.html: table_name = 'metrics_' + entity_type

All templates now wrapped with autoescape false for Alpine.js compatibility.

Closes IBM#2108

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

---------

Signed-off-by: Gabriel Costa <gabrielcg@proton.me>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants