Skip to content

Commit c7c1c94

Browse files
authored
Fix pagination bugs in servers page (#1408)
* Fix select all in add server Signed-off-by: Madhav Kandukuri <madhav165@gmail.com> * Add pagination in edit server Signed-off-by: Madhav Kandukuri <madhav165@gmail.com> * lint-web fixes Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>
1 parent a3b5ccc commit c7c1c94

File tree

2 files changed

+277
-83
lines changed

2 files changed

+277
-83
lines changed

mcpgateway/static/admin.js

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5103,8 +5103,49 @@ async function editServer(serverId) {
51035103
);
51045104
}
51055105

5106+
// Set associated resources data attribute on the container
5107+
const editResourcesContainer = document.getElementById(
5108+
"edit-server-resources",
5109+
);
5110+
if (editResourcesContainer && server.associatedResources) {
5111+
editResourcesContainer.setAttribute(
5112+
"data-server-resources",
5113+
JSON.stringify(server.associatedResources),
5114+
);
5115+
}
5116+
5117+
// Set associated prompts data attribute on the container
5118+
const editPromptsContainer = document.getElementById(
5119+
"edit-server-prompts",
5120+
);
5121+
if (editPromptsContainer && server.associatedPrompts) {
5122+
editPromptsContainer.setAttribute(
5123+
"data-server-prompts",
5124+
JSON.stringify(server.associatedPrompts),
5125+
);
5126+
}
5127+
51065128
openModal("server-edit-modal");
51075129

5130+
// Initialize the select handlers for resources and prompts in the edit modal
5131+
initResourceSelect(
5132+
"edit-server-resources",
5133+
"selectedEditResourcesPills",
5134+
"selectedEditResourcesWarning",
5135+
6,
5136+
"selectAllEditResourcesBtn",
5137+
"clearAllEditResourcesBtn",
5138+
);
5139+
5140+
initPromptSelect(
5141+
"edit-server-prompts",
5142+
"selectedEditPromptsPills",
5143+
"selectedEditPromptsWarning",
5144+
6,
5145+
"selectAllEditPromptsBtn",
5146+
"clearAllEditPromptsBtn",
5147+
);
5148+
51085149
// Use multiple approaches to ensure checkboxes get set
51095150
setEditServerAssociations(server);
51105151
setTimeout(() => setEditServerAssociations(server), 100);
@@ -5452,6 +5493,213 @@ if (window.htmx && !window._toolsHtmxHandlerAttached) {
54525493
});
54535494
}
54545495

5496+
// Set up HTMX handler for auto-checking newly loaded resources when Select All is active
5497+
if (window.htmx && !window._resourcesHtmxHandlerAttached) {
5498+
window._resourcesHtmxHandlerAttached = true;
5499+
5500+
window.htmx.on("htmx:afterSettle", function (evt) {
5501+
// Only handle resource pagination requests
5502+
if (
5503+
evt.detail.pathInfo &&
5504+
evt.detail.pathInfo.requestPath &&
5505+
evt.detail.pathInfo.requestPath.includes("/admin/resources/partial")
5506+
) {
5507+
setTimeout(() => {
5508+
// Find the container
5509+
let container = null;
5510+
const target = evt.detail.target;
5511+
5512+
if (target && target.id === "edit-server-resources") {
5513+
container = target;
5514+
} else if (target && target.id === "associatedResources") {
5515+
container = target;
5516+
} else if (target) {
5517+
container =
5518+
target.closest("#associatedResources") ||
5519+
target.closest("#edit-server-resources");
5520+
}
5521+
5522+
if (!container) {
5523+
const editModal =
5524+
document.getElementById("server-edit-modal");
5525+
const isEditModalOpen =
5526+
editModal && !editModal.classList.contains("hidden");
5527+
5528+
if (isEditModalOpen) {
5529+
container = document.getElementById(
5530+
"edit-server-resources",
5531+
);
5532+
} else {
5533+
container = document.getElementById(
5534+
"associatedResources",
5535+
);
5536+
}
5537+
}
5538+
5539+
if (container) {
5540+
const newCheckboxes = container.querySelectorAll(
5541+
"input[data-auto-check=true]",
5542+
);
5543+
5544+
const selectAllInput = container.querySelector(
5545+
'input[name="selectAllResources"]',
5546+
);
5547+
5548+
// Check if Select All is active
5549+
if (selectAllInput && selectAllInput.value === "true") {
5550+
newCheckboxes.forEach((cb) => {
5551+
cb.checked = true;
5552+
cb.removeAttribute("data-auto-check");
5553+
});
5554+
5555+
if (newCheckboxes.length > 0) {
5556+
const event = new Event("change", {
5557+
bubbles: true,
5558+
});
5559+
container.dispatchEvent(event);
5560+
}
5561+
}
5562+
5563+
// Also check for edit mode: pre-select items based on server's associated resources
5564+
const dataAttr = container.getAttribute(
5565+
"data-server-resources",
5566+
);
5567+
if (dataAttr) {
5568+
try {
5569+
const associatedResourceIds = JSON.parse(dataAttr);
5570+
newCheckboxes.forEach((cb) => {
5571+
const checkboxValue = parseInt(cb.value);
5572+
if (
5573+
associatedResourceIds.includes(
5574+
checkboxValue,
5575+
)
5576+
) {
5577+
cb.checked = true;
5578+
}
5579+
cb.removeAttribute("data-auto-check");
5580+
});
5581+
5582+
if (newCheckboxes.length > 0) {
5583+
const event = new Event("change", {
5584+
bubbles: true,
5585+
});
5586+
container.dispatchEvent(event);
5587+
}
5588+
} catch (e) {
5589+
console.error(
5590+
"Error parsing data-server-resources:",
5591+
e,
5592+
);
5593+
}
5594+
}
5595+
}
5596+
}, 10);
5597+
}
5598+
});
5599+
}
5600+
5601+
// Set up HTMX handler for auto-checking newly loaded prompts when Select All is active
5602+
if (window.htmx && !window._promptsHtmxHandlerAttached) {
5603+
window._promptsHtmxHandlerAttached = true;
5604+
5605+
window.htmx.on("htmx:afterSettle", function (evt) {
5606+
// Only handle prompt pagination requests
5607+
if (
5608+
evt.detail.pathInfo &&
5609+
evt.detail.pathInfo.requestPath &&
5610+
evt.detail.pathInfo.requestPath.includes("/admin/prompts/partial")
5611+
) {
5612+
setTimeout(() => {
5613+
// Find the container
5614+
let container = null;
5615+
const target = evt.detail.target;
5616+
5617+
if (target && target.id === "edit-server-prompts") {
5618+
container = target;
5619+
} else if (target && target.id === "associatedPrompts") {
5620+
container = target;
5621+
} else if (target) {
5622+
container =
5623+
target.closest("#associatedPrompts") ||
5624+
target.closest("#edit-server-prompts");
5625+
}
5626+
5627+
if (!container) {
5628+
const editModal =
5629+
document.getElementById("server-edit-modal");
5630+
const isEditModalOpen =
5631+
editModal && !editModal.classList.contains("hidden");
5632+
5633+
if (isEditModalOpen) {
5634+
container = document.getElementById(
5635+
"edit-server-prompts",
5636+
);
5637+
} else {
5638+
container =
5639+
document.getElementById("associatedPrompts");
5640+
}
5641+
}
5642+
5643+
if (container) {
5644+
const newCheckboxes = container.querySelectorAll(
5645+
"input[data-auto-check=true]",
5646+
);
5647+
5648+
const selectAllInput = container.querySelector(
5649+
'input[name="selectAllPrompts"]',
5650+
);
5651+
5652+
// Check if Select All is active
5653+
if (selectAllInput && selectAllInput.value === "true") {
5654+
newCheckboxes.forEach((cb) => {
5655+
cb.checked = true;
5656+
cb.removeAttribute("data-auto-check");
5657+
});
5658+
5659+
if (newCheckboxes.length > 0) {
5660+
const event = new Event("change", {
5661+
bubbles: true,
5662+
});
5663+
container.dispatchEvent(event);
5664+
}
5665+
}
5666+
5667+
// Also check for edit mode: pre-select items based on server's associated prompts
5668+
const dataAttr = container.getAttribute(
5669+
"data-server-prompts",
5670+
);
5671+
if (dataAttr) {
5672+
try {
5673+
const associatedPromptIds = JSON.parse(dataAttr);
5674+
newCheckboxes.forEach((cb) => {
5675+
const checkboxValue = parseInt(cb.value);
5676+
if (
5677+
associatedPromptIds.includes(checkboxValue)
5678+
) {
5679+
cb.checked = true;
5680+
}
5681+
cb.removeAttribute("data-auto-check");
5682+
});
5683+
5684+
if (newCheckboxes.length > 0) {
5685+
const event = new Event("change", {
5686+
bubbles: true,
5687+
});
5688+
container.dispatchEvent(event);
5689+
}
5690+
} catch (e) {
5691+
console.error(
5692+
"Error parsing data-server-prompts:",
5693+
e,
5694+
);
5695+
}
5696+
}
5697+
}
5698+
}, 10);
5699+
}
5700+
});
5701+
}
5702+
54555703
// ===================================================================
54565704
// ENHANCED TAB HANDLING with Better Error Management
54575705
// ===================================================================

mcpgateway/templates/admin.html

Lines changed: 29 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -8876,20 +8876,19 @@ <h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">
88768876
<div
88778877
id="edit-server-resources"
88788878
class="max-h-60 overflow-y-auto rounded-md border border-gray-600 shadow-sm p-3 bg-gray-50 dark:bg-gray-900"
8879+
hx-get="{{ root_path }}/admin/resources/partial?page=1&per_page=50&render=selector"
8880+
hx-trigger="load"
8881+
hx-swap="innerHTML"
8882+
hx-on::after-swap="initResourceSelect('edit-server-resources', 'selectedEditResourcesPills', 'selectedEditResourcesWarning', 6, 'selectAllEditResourcesBtn', 'clearAllEditResourcesBtn')"
88798883
>
8880-
{% for resource in resources %}
8881-
<label
8882-
class="flex items-center space-x-3 text-gray-700 dark:text-gray-300 mb-2 cursor-pointer hover:bg-blue-50 dark:hover:bg-blue-900 rounded-md p-1"
8883-
>
8884-
<input
8885-
type="checkbox"
8886-
name="associatedResources"
8887-
value="{{ resource.id }}"
8888-
class="resource-checkbox form-checkbox h-5 w-5 text-blue-600 dark:bg-gray-800 dark:border-gray-600"
8889-
/>
8890-
<span class="select-none">{{ resource.name }}</span>
8891-
</label>
8892-
{% endfor %}
8884+
<!-- Resources will be loaded via HTMX -->
8885+
<div class="text-center py-4 text-gray-500 dark:text-gray-400">
8886+
<svg class="animate-spin h-5 w-5 text-indigo-600 dark:text-indigo-400 inline-block" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
8887+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
8888+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
8889+
</svg>
8890+
<span class="ml-2">Loading resources...</span>
8891+
</div>
88938892
</div>
88948893
<div class="flex justify-end gap-3 mt-3">
88958894
<button
@@ -8931,20 +8930,19 @@ <h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">
89318930
<div
89328931
id="edit-server-prompts"
89338932
class="max-h-60 overflow-y-auto rounded-md border border-gray-600 shadow-sm p-3 bg-gray-50 dark:bg-gray-900"
8933+
hx-get="{{ root_path }}/admin/prompts/partial?page=1&per_page=50&render=selector"
8934+
hx-trigger="load"
8935+
hx-swap="innerHTML"
8936+
hx-on::after-swap="initPromptSelect('edit-server-prompts', 'selectedEditPromptsPills', 'selectedEditPromptsWarning', 6, 'selectAllEditPromptsBtn', 'clearAllEditPromptsBtn')"
89348937
>
8935-
{% for prompt in prompts %}
8936-
<label
8937-
class="flex items-center space-x-3 text-gray-700 dark:text-gray-300 mb-2 cursor-pointer hover:bg-purple-50 dark:hover:bg-purple-900 rounded-md p-1"
8938-
>
8939-
<input
8940-
type="checkbox"
8941-
name="associatedPrompts"
8942-
value="{{ prompt.id }}"
8943-
class="prompt-checkbox form-checkbox h-5 w-5 text-purple-600 dark:bg-gray-800 dark:border-gray-600"
8944-
/>
8945-
<span class="select-none">{{ prompt.name }}</span>
8946-
</label>
8947-
{% endfor %}
8938+
<!-- Prompts will be loaded via HTMX -->
8939+
<div class="text-center py-4 text-gray-500 dark:text-gray-400">
8940+
<svg class="animate-spin h-5 w-5 text-indigo-600 dark:text-indigo-400 inline-block" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
8941+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
8942+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
8943+
</svg>
8944+
<span class="ml-2">Loading prompts...</span>
8945+
</div>
89488946
</div>
89498947
<div class="flex justify-end gap-3 mt-3">
89508948
<button
@@ -12639,63 +12637,11 @@ <h3 class="text-lg font-medium text-gray-900 dark:text-white mb-4">
1263912637
}
1264012638
});
1264112639

12642-
// Select All / Clear All handlers for create server form
12643-
document.addEventListener('DOMContentLoaded', function() {
12644-
// Resources Select All / Clear All for create form
12645-
const selectAllResourcesBtn = document.getElementById('selectAllResourcesBtn');
12646-
const clearAllResourcesBtn = document.getElementById('clearAllResourcesBtn');
12647-
if (selectAllResourcesBtn && clearAllResourcesBtn) {
12648-
selectAllResourcesBtn.addEventListener('click', function() {
12649-
const checkboxes = document.querySelectorAll('#associatedResources input[type="checkbox"][name="associatedResources"]');
12650-
checkboxes.forEach(checkbox => checkbox.checked = true);
12651-
});
12652-
clearAllResourcesBtn.addEventListener('click', function() {
12653-
const checkboxes = document.querySelectorAll('#associatedResources input[type="checkbox"][name="associatedResources"]');
12654-
checkboxes.forEach(checkbox => checkbox.checked = false);
12655-
});
12656-
}
12657-
12658-
// Prompts Select All / Clear All for create form
12659-
const selectAllPromptsBtn = document.getElementById('selectAllPromptsBtn');
12660-
const clearAllPromptsBtn = document.getElementById('clearAllPromptsBtn');
12661-
if (selectAllPromptsBtn && clearAllPromptsBtn) {
12662-
selectAllPromptsBtn.addEventListener('click', function() {
12663-
const checkboxes = document.querySelectorAll('#associatedPrompts input[type="checkbox"][name="associatedPrompts"]');
12664-
checkboxes.forEach(checkbox => checkbox.checked = true);
12665-
});
12666-
clearAllPromptsBtn.addEventListener('click', function() {
12667-
const checkboxes = document.querySelectorAll('#associatedPrompts input[type="checkbox"][name="associatedPrompts"]');
12668-
checkboxes.forEach(checkbox => checkbox.checked = false);
12669-
});
12670-
}
12671-
12672-
// Select All / Clear All handlers for edit server form (should already exist but let's add them)
12673-
const selectAllEditResourcesBtn = document.getElementById('selectAllEditResourcesBtn');
12674-
const clearAllEditResourcesBtn = document.getElementById('clearAllEditResourcesBtn');
12675-
if (selectAllEditResourcesBtn && clearAllEditResourcesBtn) {
12676-
selectAllEditResourcesBtn.addEventListener('click', function() {
12677-
const checkboxes = document.querySelectorAll('#edit-server-resources input[type="checkbox"][name="associatedResources"]');
12678-
checkboxes.forEach(checkbox => checkbox.checked = true);
12679-
});
12680-
clearAllEditResourcesBtn.addEventListener('click', function() {
12681-
const checkboxes = document.querySelectorAll('#edit-server-resources input[type="checkbox"][name="associatedResources"]');
12682-
checkboxes.forEach(checkbox => checkbox.checked = false);
12683-
});
12684-
}
12685-
12686-
const selectAllEditPromptsBtn = document.getElementById('selectAllEditPromptsBtn');
12687-
const clearAllEditPromptsBtn = document.getElementById('clearAllEditPromptsBtn');
12688-
if (selectAllEditPromptsBtn && clearAllEditPromptsBtn) {
12689-
selectAllEditPromptsBtn.addEventListener('click', function() {
12690-
const checkboxes = document.querySelectorAll('#edit-server-prompts input[type="checkbox"][name="associatedPrompts"]');
12691-
checkboxes.forEach(checkbox => checkbox.checked = true);
12692-
});
12693-
clearAllEditPromptsBtn.addEventListener('click', function() {
12694-
const checkboxes = document.querySelectorAll('#edit-server-prompts input[type="checkbox"][name="associatedPrompts"]');
12695-
checkboxes.forEach(checkbox => checkbox.checked = false);
12696-
});
12697-
}
12698-
});
12640+
// NOTE: Select All / Clear All handlers for Resources and Prompts are now handled
12641+
// by initResourceSelect() and initPromptSelect() functions in admin.js.
12642+
// Those functions properly fetch ALL IDs from the server and maintain state,
12643+
// so items that load via pagination are also included in the selection.
12644+
// The handlers are attached via HTMX's hx-on::after-swap attribute.
1269912645

1270012646
// Download sample JSON for bulk import
1270112647
function downloadSampleJSON() {

0 commit comments

Comments
 (0)