From c62fdd38723a6dc23590a0c0701a51f7adfcb8ec Mon Sep 17 00:00:00 2001 From: rakdutta Date: Thu, 20 Nov 2025 11:44:50 +0530 Subject: [PATCH 01/18] add mcp server, change layout Signed-off-by: rakdutta --- mcpgateway/templates/admin.html | 99 ++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/mcpgateway/templates/admin.html b/mcpgateway/templates/admin.html index 5a6806545..71933b329 100644 --- a/mcpgateway/templates/admin.html +++ b/mcpgateway/templates/admin.html @@ -9042,6 +9042,101 @@

+
+
+ + +
+ {% for gateway in gateways %} + + {% endfor %} + + + + +
+
+ + + +
+ +

+ Choose an MCP server, then select the tools, resources, and prompts to configure your virtual server. +

+ + +
+ + +
+
@@ -9101,7 +9196,8 @@

aria-live="polite" >

- + +
@@ -9211,6 +9307,7 @@

>

+
From 129a7443e6f0cbe46d95636d835abe19fb175429 Mon Sep 17 00:00:00 2001 From: rakdutta Date: Thu, 20 Nov 2025 12:15:12 +0530 Subject: [PATCH 02/18] mcp filter Signed-off-by: rakdutta --- mcpgateway/static/admin.js | 143 ++++++++++++++++++++++++++++++------- 1 file changed, 116 insertions(+), 27 deletions(-) diff --git a/mcpgateway/static/admin.js b/mcpgateway/static/admin.js index f9422c7db..ad6915430 100644 --- a/mcpgateway/static/admin.js +++ b/mcpgateway/static/admin.js @@ -5501,8 +5501,20 @@ async function editServer(serverId) { } openModal("server-edit-modal"); + // Initialize the select handlers for gateways, resources and prompts in the edit modal + // so that gateway changes will trigger filtering of associated items while editing. + if (document.getElementById("associatedEditGateways")) { + initGatewaySelect( + "associatedEditGateways", + "selectedGatewayPills", + "selectedGatewayWarning", + 12, + "selectAllEditGatewayBtn", + "clearAllEditGatewayBtn", + "searchEditGateways", + ); + } - // Initialize the select handlers for resources and prompts in the edit modal initResourceSelect( "edit-server-resources", "selectedEditResourcesPills", @@ -8100,12 +8112,30 @@ function initGatewaySelect( * @returns {string[]} Array of selected gateway IDs */ function getSelectedGatewayIds() { - const container = document.getElementById("associatedGateways"); - console.log("[Gateway Selection DEBUG] Container found:", !!container); + // Prefer the gateway selection belonging to the currently active form. + // If the edit-server modal is open, use the edit modal's gateway container + // (`associatedEditGateways`). Otherwise use the create form container + // (`associatedGateways`). This allows the same filtering logic to work + // for both Add and Edit flows. + let container = document.getElementById("associatedGateways"); + const editContainer = document.getElementById("associatedEditGateways"); + + const editModal = document.getElementById("server-edit-modal"); + const isEditModalOpen = editModal && !editModal.classList.contains("hidden"); + + if (isEditModalOpen && editContainer) { + container = editContainer; + } else if (editContainer && editContainer.offsetParent !== null && !container) { + // If edit container is visible (e.g. modal rendered) and associatedGateways + // not present, prefer edit container. + container = editContainer; + } + + console.log("[Gateway Selection DEBUG] Container used:", container ? container.id : null); if (!container) { console.warn( - "[Gateway Selection DEBUG] associatedGateways container not found", + "[Gateway Selection DEBUG] No gateway container found (associatedGateways or associatedEditGateways)", ); return []; } @@ -8182,19 +8212,33 @@ function reloadAssociatedItems() { selectedGatewayIds, ); + // Determine whether to reload the 'create server' containers (associated*) + // or the 'edit server' containers (edit-server-*). Prefer the edit + // containers when the edit modal is open or the edit-gateway selector + // exists and is visible. + const editModal = document.getElementById("server-edit-modal"); + const isEditModalOpen = editModal && !editModal.classList.contains("hidden"); + const editGateways = document.getElementById("associatedEditGateways"); + + const useEditContainers = isEditModalOpen || (editGateways && editGateways.offsetParent !== null); + + const toolsContainerId = useEditContainers ? "edit-server-tools" : "associatedTools"; + const resourcesContainerId = useEditContainers ? "edit-server-resources" : "associatedResources"; + const promptsContainerId = useEditContainers ? "edit-server-prompts" : "associatedPrompts"; + // Reload tools - const toolsContainer = document.getElementById("associatedTools"); + const toolsContainer = document.getElementById(toolsContainerId); if (toolsContainer) { const toolsUrl = gatewayIdParam ? `${window.ROOT_PATH}/admin/tools/partial?page=1&per_page=50&render=selector&gateway_id=${encodeURIComponent(gatewayIdParam)}` : `${window.ROOT_PATH}/admin/tools/partial?page=1&per_page=50&render=selector`; - console.log("[Filter Update DEBUG] Tools URL:", toolsUrl); + console.log("[Filter Update DEBUG] Tools URL:", toolsUrl, "-> target:", `#${toolsContainerId}`); - // Use HTMX to reload the content + // Use HTMX to reload the content into the chosen container if (window.htmx) { htmx.ajax("GET", toolsUrl, { - target: "#associatedTools", + target: `#${toolsContainerId}`, swap: "innerHTML", }) .then(() => { @@ -8202,13 +8246,18 @@ function reloadAssociatedItems() { "[Filter Update DEBUG] Tools reloaded successfully", ); // Re-initialize the tool select after content is loaded + const pillsId = useEditContainers ? "selectedEditToolsPills" : "selectedToolsPills"; + const warnId = useEditContainers ? "selectedEditToolsWarning" : "selectedToolsWarning"; + const selectBtn = useEditContainers ? "selectAllEditToolsBtn" : "selectAllToolsBtn"; + const clearBtn = useEditContainers ? "clearAllEditToolsBtn" : "clearAllToolsBtn"; + initToolSelect( - "associatedTools", - "selectedToolsPills", - "selectedToolsWarning", + toolsContainerId, + pillsId, + warnId, 6, - "selectAllToolsBtn", - "clearAllToolsBtn", + selectBtn, + clearBtn, ); }) .catch((err) => { @@ -8223,11 +8272,11 @@ function reloadAssociatedItems() { ); } } else { - console.warn("[Filter Update DEBUG] Tools container not found"); + console.warn("[Filter Update DEBUG] Tools container not found ->", toolsContainerId); } // Reload resources - use fetch directly to avoid HTMX race conditions - const resourcesContainer = document.getElementById("associatedResources"); + const resourcesContainer = document.getElementById(resourcesContainerId); if (resourcesContainer) { const resourcesUrl = gatewayIdParam ? `${window.ROOT_PATH}/admin/resources/partial?page=1&per_page=50&render=selector&gateway_id=${encodeURIComponent(gatewayIdParam)}` @@ -8311,14 +8360,49 @@ function reloadAssociatedItems() { } // Re-initialize the resource select after content is loaded + const resPills = useEditContainers ? "selectedEditResourcesPills" : "selectedResourcesPills"; + const resWarn = useEditContainers ? "selectedEditResourcesWarning" : "selectedResourcesWarning"; + const resSelectBtn = useEditContainers ? "selectAllEditResourcesBtn" : "selectAllResourcesBtn"; + const resClearBtn = useEditContainers ? "clearAllEditResourcesBtn" : "clearAllResourcesBtn"; + initResourceSelect( - "associatedResources", - "selectedResourcesPills", - "selectedResourcesWarning", + resourcesContainerId, + resPills, + resWarn, 6, - "selectAllResourcesBtn", - "clearAllResourcesBtn", + resSelectBtn, + resClearBtn, ); + // Re-apply server-associated resource selections so selections + // persist across gateway-filtered reloads. The resources partial + // replaces checkbox inputs; use the container's + // `data-server-resources` attribute (set when opening edit modal) + // to restore checked state. + try { + const dataAttr = resourcesContainer.getAttribute( + "data-server-resources", + ); + if (dataAttr) { + const associated = JSON.parse(dataAttr); + if (Array.isArray(associated) && associated.length > 0) { + const resourceCheckboxes = resourcesContainer.querySelectorAll( + 'input[type="checkbox"][name="associatedResources"]', + ); + resourceCheckboxes.forEach((cb) => { + const val = parseInt(cb.value); + if (!Number.isNaN(val) && associated.includes(val)) { + cb.checked = true; + } + }); + + // Trigger change so pills and counts update + const event = new Event("change", { bubbles: true }); + resourcesContainer.dispatchEvent(event); + } + } + } catch (e) { + console.warn("Error restoring associated resources:", e); + } console.log( "[Filter Update DEBUG] Resources reloaded successfully via fetch", ); @@ -8334,7 +8418,7 @@ function reloadAssociatedItems() { } // Reload prompts - const promptsContainer = document.getElementById("associatedPrompts"); + const promptsContainer = document.getElementById(promptsContainerId); if (promptsContainer) { const promptsUrl = gatewayIdParam ? `${window.ROOT_PATH}/admin/prompts/partial?page=1&per_page=50&render=selector&gateway_id=${encodeURIComponent(gatewayIdParam)}` @@ -8342,17 +8426,22 @@ function reloadAssociatedItems() { if (window.htmx) { htmx.ajax("GET", promptsUrl, { - target: "#associatedPrompts", + target: `#${promptsContainerId}`, swap: "innerHTML", }).then(() => { // Re-initialize the prompt select after content is loaded + const pPills = useEditContainers ? "selectedEditPromptsPills" : "selectedPromptsPills"; + const pWarn = useEditContainers ? "selectedEditPromptsWarning" : "selectedPromptsWarning"; + const pSelectBtn = useEditContainers ? "selectAllEditPromptsBtn" : "selectAllPromptsBtn"; + const pClearBtn = useEditContainers ? "clearAllEditPromptsBtn" : "clearAllPromptsBtn"; + initPromptSelect( - "associatedPrompts", - "selectedPromptsPills", - "selectedPromptsWarning", + promptsContainerId, + pPills, + pWarn, 6, - "selectAllPromptsBtn", - "clearAllPromptsBtn", + pSelectBtn, + pClearBtn, ); }); } From 745f5e726b0e07f6724410ce7df2036cb3dd5f92 Mon Sep 17 00:00:00 2001 From: rakdutta Date: Thu, 20 Nov 2025 12:30:09 +0530 Subject: [PATCH 03/18] persistance Signed-off-by: rakdutta --- mcpgateway/static/admin.js | 64 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/mcpgateway/static/admin.js b/mcpgateway/static/admin.js index ad6915430..bb1eafcd6 100644 --- a/mcpgateway/static/admin.js +++ b/mcpgateway/static/admin.js @@ -7498,6 +7498,38 @@ function initResourceSelect( } } + // If we're in the edit-server-resources container, maintain the + // `data-server-resources` attribute so user selections persist + // across gateway-filtered reloads. + else if (selectId === "edit-server-resources") { + try { + let serverResources = []; + const dataAttr = container.getAttribute("data-server-resources"); + if (dataAttr) { + try { + serverResources = JSON.parse(dataAttr); + } catch (e) { + console.error("Error parsing data-server-resources:", e); + } + } + + const idVal = parseInt(e.target.value); + if (!Number.isNaN(idVal)) { + if (e.target.checked) { + if (!serverResources.includes(idVal)) { + serverResources.push(idVal); + } + } else { + serverResources = serverResources.filter((x) => x !== idVal); + } + + container.setAttribute("data-server-resources", JSON.stringify(serverResources)); + } + } catch (err) { + console.error("Error updating data-server-resources:", err); + } + } + update(); } }); @@ -7751,6 +7783,38 @@ function initPromptSelect( } } + // If we're in the edit-server-prompts container, maintain the + // `data-server-prompts` attribute so user selections persist + // across gateway-filtered reloads. + else if (selectId === "edit-server-prompts") { + try { + let serverPrompts = []; + const dataAttr = container.getAttribute("data-server-prompts"); + if (dataAttr) { + try { + serverPrompts = JSON.parse(dataAttr); + } catch (e) { + console.error("Error parsing data-server-prompts:", e); + } + } + + const idVal = parseInt(e.target.value); + if (!Number.isNaN(idVal)) { + if (e.target.checked) { + if (!serverPrompts.includes(idVal)) { + serverPrompts.push(idVal); + } + } else { + serverPrompts = serverPrompts.filter((x) => x !== idVal); + } + + container.setAttribute("data-server-prompts", JSON.stringify(serverPrompts)); + } + } catch (err) { + console.error("Error updating data-server-prompts:", err); + } + } + update(); } }); From e2126c0c4ef310cb3e81983157d17d8bf3b8b95f Mon Sep 17 00:00:00 2001 From: rakdutta Date: Thu, 20 Nov 2025 15:55:29 +0530 Subject: [PATCH 04/18] search for tools Signed-off-by: rakdutta --- mcpgateway/static/admin.js | 233 ++++++++++++++++++++++++++++++++ mcpgateway/templates/admin.html | 13 ++ 2 files changed, 246 insertions(+) diff --git a/mcpgateway/static/admin.js b/mcpgateway/static/admin.js index bb1eafcd6..81302ea20 100644 --- a/mcpgateway/static/admin.js +++ b/mcpgateway/static/admin.js @@ -12397,6 +12397,46 @@ function setupSelectorSearch() { }); } + // Edit-server tools search (server-side, mirror of searchTools) + const searchEditTools = safeGetElement("searchEditTools", true); + if (searchEditTools) { + let editSearchTimeout; + searchEditTools.addEventListener("input", function () { + const searchTerm = this.value; + if (editSearchTimeout) { + clearTimeout(editSearchTimeout); + } + editSearchTimeout = setTimeout(() => { + serverSideEditToolSearch(searchTerm); + }, 300); + }); + + // If HTMX swaps/paginates the edit tools container, re-run server-side search + const editToolsContainer = document.getElementById("edit-server-tools"); + if (editToolsContainer) { + editToolsContainer.addEventListener("htmx:afterSwap", function () { + try { + const current = searchEditTools.value || ""; + if (current && current.trim() !== "") { + serverSideEditToolSearch(current); + } else { + // No active search — ensure the selector is initialized + initToolSelect( + "edit-server-tools", + "selectedEditToolsPills", + "selectedEditToolsWarning", + 6, + "selectAllEditToolsBtn", + "clearAllEditToolsBtn", + ); + } + } catch (err) { + console.error("Error handling edit-tools afterSwap:", err); + } + }); + } + } + // Prompts search (server-side) const searchPrompts = safeGetElement("searchPrompts", true); if (searchPrompts) { @@ -19506,6 +19546,199 @@ async function serverSidePromptSearch(searchTerm) { } } +/** + * Perform server-side search for tools in the edit-server selector and update the list + */ +async function serverSideEditToolSearch(searchTerm) { + const container = document.getElementById("edit-server-tools"); + const noResultsMessage = safeGetElement("noEditToolsMessage", true); + const searchQuerySpan = safeGetElement("searchQueryEditTools", true); + + if (!container) { + console.error("edit-server-tools container not found"); + return; + } + + // Show loading state + container.innerHTML = ` +
+ + + + +

Searching tools...

+
+ `; + + if (searchTerm.trim() === "") { + // If search term is empty, reload the default tool selector partial + try { + const response = await fetch( + `${window.ROOT_PATH}/admin/tools/partial?page=1&per_page=50&render=selector`, + ); + if (response.ok) { + const html = await response.text(); + container.innerHTML = html; + + // Hide no results message + if (noResultsMessage) { + noResultsMessage.style.display = "none"; + } + + // Update tool mapping + updateToolMapping(container); + + // Restore checked state for any tools already associated with the server + try { + const dataAttr = container.getAttribute("data-server-tools"); + if (dataAttr) { + const serverTools = JSON.parse(dataAttr); + if (Array.isArray(serverTools) && serverTools.length > 0) { + const checkboxes = container.querySelectorAll( + 'input[name="associatedTools"]', + ); + checkboxes.forEach((cb) => { + const toolName = cb.getAttribute("data-tool-name") || (window.toolMapping && window.toolMapping[cb.value]); + if (toolName && serverTools.includes(toolName)) { + cb.checked = true; + } + }); + + // Trigger update so pills/counts refresh + const firstCb = container.querySelector('input[type="checkbox"]'); + if (firstCb) { + firstCb.dispatchEvent(new Event("change", { bubbles: true })); + } + } + } + } catch (e) { + console.error("Error restoring edit-server tools checked state:", e); + } + + // Re-initialize the selector logic for the edit container + initToolSelect( + "edit-server-tools", + "selectedEditToolsPills", + "selectedEditToolsWarning", + 6, + "selectAllEditToolsBtn", + "clearAllEditToolsBtn", + ); + } else { + container.innerHTML = + '
Failed to load tools
'; + } + } catch (error) { + console.error("Error loading tools:", error); + container.innerHTML = + '
Error loading tools
'; + } + return; + } + + try { + // Call the search API + const response = await fetch( + `${window.ROOT_PATH}/admin/tools/search?q=${encodeURIComponent(searchTerm)}&limit=100`, + ); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + + if (data.tools && data.tools.length > 0) { + // Create HTML for search results + let searchResultsHtml = ""; + data.tools.forEach((tool) => { + const displayName = + tool.display_name || tool.custom_name || tool.name || tool.id; + + searchResultsHtml += ` + + `; + }); + + container.innerHTML = searchResultsHtml; + + // Update mapping + updateToolMapping(container); + + // Restore checked state for any tools already associated with the server + try { + const dataAttr = container.getAttribute("data-server-tools"); + if (dataAttr) { + const serverTools = JSON.parse(dataAttr); + if (Array.isArray(serverTools) && serverTools.length > 0) { + const checkboxes = container.querySelectorAll( + 'input[name="associatedTools"]', + ); + checkboxes.forEach((cb) => { + const toolName = cb.getAttribute("data-tool-name") || (window.toolMapping && window.toolMapping[cb.value]); + if (toolName && serverTools.includes(toolName)) { + cb.checked = true; + } + }); + + // Trigger update so pills/counts refresh + const firstCb = container.querySelector('input[type="checkbox"]'); + if (firstCb) { + firstCb.dispatchEvent(new Event("change", { bubbles: true })); + } + } + } + } catch (e) { + console.error("Error restoring edit-server tools checked state:", e); + } + + // Initialize selector behavior + initToolSelect( + "edit-server-tools", + "selectedEditToolsPills", + "selectedEditToolsWarning", + 6, + "selectAllEditToolsBtn", + "clearAllEditToolsBtn", + ); + + // Hide no results message + if (noResultsMessage) { + noResultsMessage.style.display = "none"; + } + } else { + // Show no results message + container.innerHTML = ""; + if (noResultsMessage) { + if (searchQuerySpan) { + searchQuerySpan.textContent = searchTerm; + } + noResultsMessage.style.display = "block"; + } + } + } catch (error) { + console.error("Error searching tools:", error); + container.innerHTML = + '
Error searching tools
'; + + if (noResultsMessage) { + noResultsMessage.style.display = "none"; + } + } +} + // Add CSS for streaming indicator animation const style = document.createElement("style"); style.textContent = ` diff --git a/mcpgateway/templates/admin.html b/mcpgateway/templates/admin.html index 71933b329..24d03671e 100644 --- a/mcpgateway/templates/admin.html +++ b/mcpgateway/templates/admin.html @@ -9146,6 +9146,12 @@

> Associated Tools +
Loading tools...

+
+
+
- -
+
+
@@ -9097,7 +9097,7 @@

class="text-gray-700 dark:text-gray-300" style="display: none" > - No MCP server found containing "" + No MCP server found containing ""

@@ -9126,13 +9126,13 @@

@@ -9210,6 +9210,8 @@

>

+ +
Date: Fri, 21 Nov 2025 15:05:24 +0530 Subject: [PATCH 15/18] js Signed-off-by: rakdutta --- mcpgateway/static/admin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mcpgateway/static/admin.js b/mcpgateway/static/admin.js index 8d0722f17..d910c4d79 100644 --- a/mcpgateway/static/admin.js +++ b/mcpgateway/static/admin.js @@ -5506,8 +5506,8 @@ async function editServer(serverId) { if (document.getElementById("associatedEditGateways")) { initGatewaySelect( "associatedEditGateways", - "selectedGatewayPills", - "selectedGatewayWarning", + "selectedEditGatewayPills", + "selectedEditGatewayWarning", 12, "selectAllEditGatewayBtn", "clearAllEditGatewayBtn", From ac8818e468f635a90028fb5d99672b43434be37b Mon Sep 17 00:00:00 2001 From: rakdutta Date: Fri, 21 Nov 2025 20:58:06 +0530 Subject: [PATCH 16/18] black Signed-off-by: rakdutta --- mcpgateway/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mcpgateway/admin.py b/mcpgateway/admin.py index 687996d29..bff61268b 100644 --- a/mcpgateway/admin.py +++ b/mcpgateway/admin.py @@ -5815,6 +5815,7 @@ async def admin_search_resources( return {"resources": resources, "count": len(resources)} + @admin_router.get("/prompts/search", response_class=JSONResponse) async def admin_search_prompts( q: str = Query("", description="Search query"), From 9a126b5385c9bbae099518e460b3eb49073b5781 Mon Sep 17 00:00:00 2001 From: rakdutta Date: Fri, 21 Nov 2025 21:46:39 +0530 Subject: [PATCH 17/18] lint js Signed-off-by: rakdutta --- mcpgateway/static/admin.js | 565 ++++++++++++++++++++++++------------- 1 file changed, 365 insertions(+), 200 deletions(-) diff --git a/mcpgateway/static/admin.js b/mcpgateway/static/admin.js index d910c4d79..c644e37ae 100644 --- a/mcpgateway/static/admin.js +++ b/mcpgateway/static/admin.js @@ -5541,9 +5541,8 @@ async function editServer(serverId) { // Set associated items after modal is opened setTimeout(() => { // Set associated tools checkboxes (scope to edit modal container only) - const editToolContainer = document.getElementById( - "edit-server-tools", - ); + const editToolContainer = + document.getElementById("edit-server-tools"); const toolCheckboxes = editToolContainer ? editToolContainer.querySelectorAll( 'input[name="associatedTools"]', @@ -5592,9 +5591,7 @@ async function editServer(serverId) { ? editPromptContainer.querySelectorAll( 'input[name="associatedPrompts"]', ) - : document.querySelectorAll( - 'input[name="associatedPrompts"]', - ); + : document.querySelectorAll('input[name="associatedPrompts"]'); promptCheckboxes.forEach((checkbox) => { const checkboxValue = parseInt(checkbox.value); @@ -7528,12 +7525,17 @@ function initResourceSelect( else if (selectId === "edit-server-resources") { try { let serverResources = []; - const dataAttr = container.getAttribute("data-server-resources"); + const dataAttr = container.getAttribute( + "data-server-resources", + ); if (dataAttr) { try { serverResources = JSON.parse(dataAttr); } catch (e) { - console.error("Error parsing data-server-resources:", e); + console.error( + "Error parsing data-server-resources:", + e, + ); } } @@ -7544,13 +7546,21 @@ function initResourceSelect( serverResources.push(idVal); } } else { - serverResources = serverResources.filter((x) => x !== idVal); + serverResources = serverResources.filter( + (x) => x !== idVal, + ); } - container.setAttribute("data-server-resources", JSON.stringify(serverResources)); + container.setAttribute( + "data-server-resources", + JSON.stringify(serverResources), + ); } } catch (err) { - console.error("Error updating data-server-resources:", err); + console.error( + "Error updating data-server-resources:", + err, + ); } } @@ -7813,12 +7823,17 @@ function initPromptSelect( else if (selectId === "edit-server-prompts") { try { let serverPrompts = []; - const dataAttr = container.getAttribute("data-server-prompts"); + const dataAttr = container.getAttribute( + "data-server-prompts", + ); if (dataAttr) { try { serverPrompts = JSON.parse(dataAttr); } catch (e) { - console.error("Error parsing data-server-prompts:", e); + console.error( + "Error parsing data-server-prompts:", + e, + ); } } @@ -7829,13 +7844,21 @@ function initPromptSelect( serverPrompts.push(idVal); } } else { - serverPrompts = serverPrompts.filter((x) => x !== idVal); + serverPrompts = serverPrompts.filter( + (x) => x !== idVal, + ); } - container.setAttribute("data-server-prompts", JSON.stringify(serverPrompts)); + container.setAttribute( + "data-server-prompts", + JSON.stringify(serverPrompts), + ); } } catch (err) { - console.error("Error updating data-server-prompts:", err); + console.error( + "Error updating data-server-prompts:", + err, + ); } } @@ -8135,7 +8158,10 @@ function initGatewaySelect( // Log gateway_id when checkbox is clicked // Normalize the special null-gateway checkbox to the literal string "null" let gatewayId = e.target.value; - if (e.target.dataset && e.target.dataset.gatewayNull === "true") { + if ( + e.target.dataset && + e.target.dataset.gatewayNull === "true" + ) { gatewayId = "null"; } const gatewayName = @@ -8165,15 +8191,15 @@ function initGatewaySelect( try { let allIds = JSON.parse(allIdsInput.value); - if (e.target.checked) { - // Add the ID if it's not already there - if (!allIds.includes(gatewayId)) { - allIds.push(gatewayId); - } - } else { - // Remove the ID from the array - allIds = allIds.filter((id) => id !== gatewayId); + if (e.target.checked) { + // Add the ID if it's not already there + if (!allIds.includes(gatewayId)) { + allIds.push(gatewayId); } + } else { + // Remove the ID from the array + allIds = allIds.filter((id) => id !== gatewayId); + } // Update the hidden field allIdsInput.value = JSON.stringify(allIds); @@ -8213,17 +8239,25 @@ function getSelectedGatewayIds() { const editContainer = document.getElementById("associatedEditGateways"); const editModal = document.getElementById("server-edit-modal"); - const isEditModalOpen = editModal && !editModal.classList.contains("hidden"); + const isEditModalOpen = + editModal && !editModal.classList.contains("hidden"); if (isEditModalOpen && editContainer) { container = editContainer; - } else if (editContainer && editContainer.offsetParent !== null && !container) { + } else if ( + editContainer && + editContainer.offsetParent !== null && + !container + ) { // If edit container is visible (e.g. modal rendered) and associatedGateways // not present, prefer edit container. container = editContainer; } - console.log("[Gateway Selection DEBUG] Container used:", container ? container.id : null); + console.log( + "[Gateway Selection DEBUG] Container used:", + container ? container.id : null, + ); if (!container) { console.warn( @@ -8309,14 +8343,22 @@ function reloadAssociatedItems() { // containers when the edit modal is open or the edit-gateway selector // exists and is visible. const editModal = document.getElementById("server-edit-modal"); - const isEditModalOpen = editModal && !editModal.classList.contains("hidden"); + const isEditModalOpen = + editModal && !editModal.classList.contains("hidden"); const editGateways = document.getElementById("associatedEditGateways"); - const useEditContainers = isEditModalOpen || (editGateways && editGateways.offsetParent !== null); + const useEditContainers = + isEditModalOpen || (editGateways && editGateways.offsetParent !== null); - const toolsContainerId = useEditContainers ? "edit-server-tools" : "associatedTools"; - const resourcesContainerId = useEditContainers ? "edit-server-resources" : "associatedResources"; - const promptsContainerId = useEditContainers ? "edit-server-prompts" : "associatedPrompts"; + const toolsContainerId = useEditContainers + ? "edit-server-tools" + : "associatedTools"; + const resourcesContainerId = useEditContainers + ? "edit-server-resources" + : "associatedResources"; + const promptsContainerId = useEditContainers + ? "edit-server-prompts" + : "associatedPrompts"; // Reload tools const toolsContainer = document.getElementById(toolsContainerId); @@ -8325,7 +8367,12 @@ function reloadAssociatedItems() { ? `${window.ROOT_PATH}/admin/tools/partial?page=1&per_page=50&render=selector&gateway_id=${encodeURIComponent(gatewayIdParam)}` : `${window.ROOT_PATH}/admin/tools/partial?page=1&per_page=50&render=selector`; - console.log("[Filter Update DEBUG] Tools URL:", toolsUrl, "-> target:", `#${toolsContainerId}`); + console.log( + "[Filter Update DEBUG] Tools URL:", + toolsUrl, + "-> target:", + `#${toolsContainerId}`, + ); // Use HTMX to reload the content into the chosen container if (window.htmx) { @@ -8338,10 +8385,18 @@ function reloadAssociatedItems() { "[Filter Update DEBUG] Tools reloaded successfully", ); // Re-initialize the tool select after content is loaded - const pillsId = useEditContainers ? "selectedEditToolsPills" : "selectedToolsPills"; - const warnId = useEditContainers ? "selectedEditToolsWarning" : "selectedToolsWarning"; - const selectBtn = useEditContainers ? "selectAllEditToolsBtn" : "selectAllToolsBtn"; - const clearBtn = useEditContainers ? "clearAllEditToolsBtn" : "clearAllToolsBtn"; + const pillsId = useEditContainers + ? "selectedEditToolsPills" + : "selectedToolsPills"; + const warnId = useEditContainers + ? "selectedEditToolsWarning" + : "selectedToolsWarning"; + const selectBtn = useEditContainers + ? "selectAllEditToolsBtn" + : "selectAllToolsBtn"; + const clearBtn = useEditContainers + ? "clearAllEditToolsBtn" + : "clearAllToolsBtn"; initToolSelect( toolsContainerId, @@ -8364,7 +8419,10 @@ function reloadAssociatedItems() { ); } } else { - console.warn("[Filter Update DEBUG] Tools container not found ->", toolsContainerId); + console.warn( + "[Filter Update DEBUG] Tools container not found ->", + toolsContainerId, + ); } // Reload resources - use fetch directly to avoid HTMX race conditions @@ -8452,10 +8510,18 @@ function reloadAssociatedItems() { } // Re-initialize the resource select after content is loaded - const resPills = useEditContainers ? "selectedEditResourcesPills" : "selectedResourcesPills"; - const resWarn = useEditContainers ? "selectedEditResourcesWarning" : "selectedResourcesWarning"; - const resSelectBtn = useEditContainers ? "selectAllEditResourcesBtn" : "selectAllResourcesBtn"; - const resClearBtn = useEditContainers ? "clearAllEditResourcesBtn" : "clearAllResourcesBtn"; + const resPills = useEditContainers + ? "selectedEditResourcesPills" + : "selectedResourcesPills"; + const resWarn = useEditContainers + ? "selectedEditResourcesWarning" + : "selectedResourcesWarning"; + const resSelectBtn = useEditContainers + ? "selectAllEditResourcesBtn" + : "selectAllResourcesBtn"; + const resClearBtn = useEditContainers + ? "clearAllEditResourcesBtn" + : "clearAllResourcesBtn"; initResourceSelect( resourcesContainerId, @@ -8476,19 +8542,28 @@ function reloadAssociatedItems() { ); if (dataAttr) { const associated = JSON.parse(dataAttr); - if (Array.isArray(associated) && associated.length > 0) { - const resourceCheckboxes = resourcesContainer.querySelectorAll( - 'input[type="checkbox"][name="associatedResources"]', - ); + if ( + Array.isArray(associated) && + associated.length > 0 + ) { + const resourceCheckboxes = + resourcesContainer.querySelectorAll( + 'input[type="checkbox"][name="associatedResources"]', + ); resourceCheckboxes.forEach((cb) => { const val = parseInt(cb.value); - if (!Number.isNaN(val) && associated.includes(val)) { + if ( + !Number.isNaN(val) && + associated.includes(val) + ) { cb.checked = true; } }); // Trigger change so pills and counts update - const event = new Event("change", { bubbles: true }); + const event = new Event("change", { + bubbles: true, + }); resourcesContainer.dispatchEvent(event); } } @@ -8522,10 +8597,18 @@ function reloadAssociatedItems() { swap: "innerHTML", }).then(() => { // Re-initialize the prompt select after content is loaded - const pPills = useEditContainers ? "selectedEditPromptsPills" : "selectedPromptsPills"; - const pWarn = useEditContainers ? "selectedEditPromptsWarning" : "selectedPromptsWarning"; - const pSelectBtn = useEditContainers ? "selectAllEditPromptsBtn" : "selectAllPromptsBtn"; - const pClearBtn = useEditContainers ? "clearAllEditPromptsBtn" : "clearAllPromptsBtn"; + const pPills = useEditContainers + ? "selectedEditPromptsPills" + : "selectedPromptsPills"; + const pWarn = useEditContainers + ? "selectedEditPromptsWarning" + : "selectedPromptsWarning"; + const pSelectBtn = useEditContainers + ? "selectAllEditPromptsBtn" + : "selectAllPromptsBtn"; + const pClearBtn = useEditContainers + ? "clearAllEditPromptsBtn" + : "clearAllPromptsBtn"; initPromptSelect( promptsContainerId, @@ -12411,7 +12494,6 @@ function setupSelectorSearch() { }); } - // Edit-server tools search (server-side, mirror of searchTools) const searchEditTools = safeGetElement("searchEditTools", true); if (searchEditTools) { @@ -12482,28 +12564,36 @@ function setupSelectorSearch() { }); // If HTMX swaps/paginates the edit prompts container, re-run server-side search - const editPromptsContainer = document.getElementById("edit-server-prompts"); + const editPromptsContainer = document.getElementById( + "edit-server-prompts", + ); if (editPromptsContainer) { - editPromptsContainer.addEventListener("htmx:afterSwap", function () { - try { - const current = searchEditPrompts.value || ""; - if (current && current.trim() !== "") { - serverSideEditPromptsSearch(current); - } else { - // No active search — ensure the selector is initialized - initPromptSelect( - "edit-server-prompts", - "selectedEditPromptsPills", - "selectedEditPromptsWarning", - 6, - "selectAllEditPromptsBtn", - "clearAllEditPromptsBtn", + editPromptsContainer.addEventListener( + "htmx:afterSwap", + function () { + try { + const current = searchEditPrompts.value || ""; + if (current && current.trim() !== "") { + serverSideEditPromptsSearch(current); + } else { + // No active search — ensure the selector is initialized + initPromptSelect( + "edit-server-prompts", + "selectedEditPromptsPills", + "selectedEditPromptsWarning", + 6, + "selectAllEditPromptsBtn", + "clearAllEditPromptsBtn", + ); + } + } catch (err) { + console.error( + "Error handling edit-prompts afterSwap:", + err, ); } - } catch (err) { - console.error("Error handling edit-prompts afterSwap:", err); - } - }); + }, + ); } } @@ -12537,87 +12627,36 @@ function setupSelectorSearch() { }); // If HTMX swaps/paginates the edit resources container, re-run server-side search - const editResourcesContainer = document.getElementById("edit-server-resources"); + const editResourcesContainer = document.getElementById( + "edit-server-resources", + ); if (editResourcesContainer) { - editResourcesContainer.addEventListener("htmx:afterSwap", function () { - try { - const current = searchEditResources.value || ""; - if (current && current.trim() !== "") { - serverSideEditResourcesSearch(current); - } else { - // No active search — ensure the selector is initialized - initResourceSelect( - "edit-server-resources", - "selectedEditResourcesPills", - "selectedEditResourcesWarning", - 6, - "selectAllEditResourcesBtn", - "clearAllEditResourcesBtn", + editResourcesContainer.addEventListener( + "htmx:afterSwap", + function () { + try { + const current = searchEditResources.value || ""; + if (current && current.trim() !== "") { + serverSideEditResourcesSearch(current); + } else { + // No active search — ensure the selector is initialized + initResourceSelect( + "edit-server-resources", + "selectedEditResourcesPills", + "selectedEditResourcesWarning", + 6, + "selectAllEditResourcesBtn", + "clearAllEditResourcesBtn", + ); + } + } catch (err) { + console.error( + "Error handling edit-resources afterSwap:", + err, ); } - } catch (err) { - console.error("Error handling edit-resources afterSwap:", err); - } - }); - } - } -} - -/** - * Generic function to filter items in multi-select dropdowns with no results message - */ -function filterSelectorItems( - searchText, - containerSelector, - itemSelector, - noResultsId, - searchQueryId, -) { - const container = document.querySelector(containerSelector); - if (!container) { - return; - } - - const items = container.querySelectorAll(itemSelector); - const search = searchText.toLowerCase().trim(); - let hasVisibleItems = false; - - items.forEach((item) => { - let textContent = ""; - - // Get text from all text nodes within the item - const textElements = item.querySelectorAll( - "span, .text-xs, .font-medium", - ); - textElements.forEach((el) => { - textContent += " " + el.textContent; - }); - - // Also get direct text content - textContent += " " + item.textContent; - - if (search === "" || textContent.toLowerCase().includes(search)) { - item.style.display = ""; - hasVisibleItems = true; - } else { - item.style.display = "none"; - } - }); - - // Handle no results message - const noResultsMessage = safeGetElement(noResultsId, true); - const searchQuerySpan = safeGetElement(searchQueryId, true); - - if (search !== "" && !hasVisibleItems) { - if (noResultsMessage) { - noResultsMessage.style.display = "block"; - } - if (searchQuerySpan) { - searchQuerySpan.textContent = searchText; - } - } else { - if (noResultsMessage) { - noResultsMessage.style.display = "none"; + }, + ); } } } @@ -19539,7 +19578,9 @@ function updatePromptMapping(container) { window.promptMapping = {}; } - const checkboxes = container.querySelectorAll('input[name="associatedPrompts"]'); + const checkboxes = container.querySelectorAll( + 'input[name="associatedPrompts"]', + ); checkboxes.forEach((checkbox) => { const promptId = checkbox.value; const promptName = @@ -19560,7 +19601,9 @@ function updateResourceMapping(container) { window.resourceMapping = {}; } - const checkboxes = container.querySelectorAll('input[name="associatedResources"]'); + const checkboxes = container.querySelectorAll( + 'input[name="associatedResources"]', + ); checkboxes.forEach((checkbox) => { const resourceId = checkbox.value; const resourceName = @@ -19573,7 +19616,6 @@ function updateResourceMapping(container) { }); } - /** * Perform server-side search for prompts and update the prompt list */ @@ -19868,32 +19910,52 @@ async function serverSideEditToolSearch(searchTerm) { // Restore checked state for any tools already associated with the server try { - const dataAttr = container.getAttribute("data-server-tools"); + const dataAttr = + container.getAttribute("data-server-tools"); if (dataAttr) { const serverTools = JSON.parse(dataAttr); - if (Array.isArray(serverTools) && serverTools.length > 0) { + if ( + Array.isArray(serverTools) && + serverTools.length > 0 + ) { // Normalize serverTools to a set of strings for robust comparison - const serverToolSet = new Set(serverTools.map((s) => String(s))); + const serverToolSet = new Set( + serverTools.map((s) => String(s)), + ); const checkboxes = container.querySelectorAll( 'input[name="associatedTools"]', ); checkboxes.forEach((cb) => { const toolId = cb.value; - const toolName = cb.getAttribute("data-tool-name") || (window.toolMapping && window.toolMapping[cb.value]); - if (serverToolSet.has(toolId) || (toolName && serverToolSet.has(String(toolName)))) { + const toolName = + cb.getAttribute("data-tool-name") || + (window.toolMapping && + window.toolMapping[cb.value]); + if ( + serverToolSet.has(toolId) || + (toolName && + serverToolSet.has(String(toolName))) + ) { cb.checked = true; } }); // Trigger update so pills/counts refresh - const firstCb = container.querySelector('input[type="checkbox"]'); + const firstCb = container.querySelector( + 'input[type="checkbox"]', + ); if (firstCb) { - firstCb.dispatchEvent(new Event("change", { bubbles: true })); + firstCb.dispatchEvent( + new Event("change", { bubbles: true }), + ); } } } } catch (e) { - console.error("Error restoring edit-server tools checked state:", e); + console.error( + "Error restoring edit-server tools checked state:", + e, + ); } // Re-initialize the selector logic for the edit container @@ -19934,7 +19996,10 @@ async function serverSideEditToolSearch(searchTerm) { let searchResultsHtml = ""; data.tools.forEach((tool) => { const displayName = - tool.display_name || tool.custom_name || tool.name || tool.id; + tool.display_name || + tool.custom_name || + tool.name || + tool.id; searchResultsHtml += `
tag for the mt-6 wrapper that was breaking the form structure and causing lint errors. Signed-off-by: Mihai Criveti --- mcpgateway/templates/admin.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mcpgateway/templates/admin.html b/mcpgateway/templates/admin.html index 05b1038c0..3fe94e430 100644 --- a/mcpgateway/templates/admin.html +++ b/mcpgateway/templates/admin.html @@ -9210,7 +9210,6 @@

>

-
>
- + +