diff --git a/mcpgateway/static/admin.js b/mcpgateway/static/admin.js index ce80ba66f..3794bdb85 100644 --- a/mcpgateway/static/admin.js +++ b/mcpgateway/static/admin.js @@ -121,6 +121,35 @@ document.addEventListener("DOMContentLoaded", function () { setTimeout(initializeCACertUpload, 100); }); } + + // Initialize search functionality for all entity types + initializeSearchInputs(); + + // Re-initialize search inputs when HTMX content loads + document.body.addEventListener("htmx:afterSwap", function (event) { + setTimeout(() => { + initializeSearchInputs(); + }, 200); + }); + + // Also listen for htmx:load event + document.body.addEventListener("htmx:load", function (event) { + setTimeout(() => { + initializeSearchInputs(); + }, 200); + }); + + // Initialize search when switching tabs + document.addEventListener("click", function (event) { + if ( + event.target.matches('[onclick*="showTab"]') || + event.target.closest('[onclick*="showTab"]') + ) { + setTimeout(() => { + initializeSearchInputs(); + }, 300); + } + }); }); /** * ==================================================================== @@ -14484,13 +14513,28 @@ function filterServerTable(searchText) { rows.forEach((row) => { let textContent = ""; - // Get text from all cells in the row + // Get text from all searchable cells (exclude only Actions column) + // Table columns: Icon(0), S.No.(1), UUID(2), Name(3), Description(4), Tools(5), Resources(6), Prompts(7), Tags(8), Owner(9), Team(10), Visibility(11), Actions(12) const cells = row.querySelectorAll("td"); - cells.forEach((cell) => { - textContent += " " + cell.textContent; + // Search all columns except Icon and Actions columns + const searchableColumnIndices = []; + for (let i = 1; i < cells.length - 1; i++) { + searchableColumnIndices.push(i); + } + + searchableColumnIndices.forEach((index) => { + if (cells[index]) { + // Clean the text content and make it searchable + const cellText = cells[index].textContent + .replace(/\s+/g, " ") + .trim(); + textContent += " " + cellText; + } }); - if (search === "" || textContent.toLowerCase().includes(search)) { + const isMatch = + search === "" || textContent.toLowerCase().includes(search); + if (isMatch) { row.style.display = ""; } else { row.style.display = "none"; @@ -14504,6 +14548,564 @@ function filterServerTable(searchText) { // Make server search function available globally window.filterServerTable = filterServerTable; +/** + * Filter Tools table based on search text + */ +function filterToolsTable(searchText) { + try { + const tbody = document.querySelector("#tools-table-body"); + if (!tbody) { + console.warn("Tools table body not found"); + return; + } + + const rows = tbody.querySelectorAll("tr"); + const search = searchText.toLowerCase().trim(); + + rows.forEach((row) => { + let textContent = ""; + + // Get text from searchable cells (exclude S.No. and Actions columns) + // Tools columns: S.No.(0), Gateway Name(1), Name(2), URL(3), Type(4), Request Type(5), Description(6), Annotations(7), Tags(8), Owner(9), Team(10), Visibility(11), Status(12), Actions(13) + const cells = row.querySelectorAll("td"); + const searchableColumns = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; // Exclude S.No. and Actions + + searchableColumns.forEach((index) => { + if (cells[index]) { + // Clean the text content and make it searchable + const cellText = cells[index].textContent + .replace(/\s+/g, " ") + .trim(); + textContent += " " + cellText; + } + }); + + const isMatch = + search === "" || textContent.toLowerCase().includes(search); + if (isMatch) { + row.style.display = ""; + } else { + row.style.display = "none"; + } + }); + } catch (error) { + console.error("Error filtering tools table:", error); + } +} + +/** + * Filter Resources table based on search text + */ +function filterResourcesTable(searchText) { + try { + const tbody = document.querySelector("#resources-table-body"); + if (!tbody) { + console.warn("Resources table body not found"); + return; + } + + const rows = tbody.querySelectorAll("tr"); + const search = searchText.toLowerCase().trim(); + + rows.forEach((row) => { + let textContent = ""; + + // Get text from searchable cells (exclude Actions column) + // Resources columns: ID(0), URI(1), Name(2), Description(3), MIME Type(4), Tags(5), Owner(6), Team(7), Visibility(8), Status(9), Actions(10) + const cells = row.querySelectorAll("td"); + const searchableColumns = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; // All except Actions + + searchableColumns.forEach((index) => { + if (cells[index]) { + textContent += " " + cells[index].textContent; + } + }); + + if (search === "" || textContent.toLowerCase().includes(search)) { + row.style.display = ""; + } else { + row.style.display = "none"; + } + }); + } catch (error) { + console.error("Error filtering resources table:", error); + } +} + +/** + * Filter Prompts table based on search text + */ +function filterPromptsTable(searchText) { + try { + const tbody = document.querySelector("#prompts-table-body"); + if (!tbody) { + console.warn("Prompts table body not found"); + return; + } + + const rows = tbody.querySelectorAll("tr"); + const search = searchText.toLowerCase().trim(); + + rows.forEach((row) => { + let textContent = ""; + + // Get text from searchable cells (exclude Actions column) + // Prompts columns: S.No.(0), Name(1), Description(2), Tags(3), Owner(4), Team(5), Visibility(6), Status(7), Actions(8) + const cells = row.querySelectorAll("td"); + const searchableColumns = [0, 1, 2, 3, 4, 5, 6, 7]; // All except Actions + + searchableColumns.forEach((index) => { + if (cells[index]) { + textContent += " " + cells[index].textContent; + } + }); + + if (search === "" || textContent.toLowerCase().includes(search)) { + row.style.display = ""; + } else { + row.style.display = "none"; + } + }); + } catch (error) { + console.error("Error filtering prompts table:", error); + } +} + +/** + * Filter A2A Agents table based on search text + */ +function filterA2AAgentsTable(searchText) { + try { + const tbody = document.querySelector("#a2a-agents-panel tbody"); + if (!tbody) { + console.warn("A2A Agents table body not found"); + return; + } + + const rows = tbody.querySelectorAll("tr"); + const search = searchText.toLowerCase().trim(); + + rows.forEach((row) => { + let textContent = ""; + + // Get text from searchable cells (exclude ID and Actions columns) + // A2A Agents columns: ID(0), Name(1), Description(2), Endpoint(3), Tags(4), Type(5), Status(6), Reachability(7), Owner(8), Team(9), Visibility(10), Actions(11) + const cells = row.querySelectorAll("td"); + const searchableColumns = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // Exclude ID and Actions + + searchableColumns.forEach((index) => { + if (cells[index]) { + textContent += " " + cells[index].textContent; + } + }); + + if (search === "" || textContent.toLowerCase().includes(search)) { + row.style.display = ""; + } else { + row.style.display = "none"; + } + }); + } catch (error) { + console.error("Error filtering A2A agents table:", error); + } +} + +/** + * Filter MCP Servers (Gateways) table based on search text + */ +function filterGatewaysTable(searchText) { + try { + console.log("๐ Starting MCP Servers search for:", searchText); + + // Find the MCP servers table - use multiple strategies + let table = null; + + // Strategy 1: Direct selector for gateways panel + const gatewaysPanel = document.querySelector("#gateways-panel"); + if (gatewaysPanel) { + table = gatewaysPanel.querySelector("table"); + console.log("โ Found table in gateways panel"); + } + + // Strategy 2: Look for table in currently visible tab + if (!table) { + const visiblePanel = document.querySelector( + ".tab-panel:not(.hidden)", + ); + if (visiblePanel) { + table = visiblePanel.querySelector("table"); + console.log("โ Found table in visible panel"); + } + } + + // Strategy 3: Just look for any table with MCP server structure + if (!table) { + const allTables = document.querySelectorAll("table"); + for (const t of allTables) { + const headers = t.querySelectorAll("thead th"); + if (headers.length >= 8) { + // Check for MCP server specific headers + const headerTexts = Array.from(headers).map((h) => + h.textContent.toLowerCase().trim(), + ); + if ( + headerTexts.includes("name") && + headerTexts.includes("url") && + headerTexts.includes("status") + ) { + table = t; + console.log("โ Found MCP table by header matching"); + break; + } + } + } + } + + if (!table) { + console.warn("โ No MCP servers table found"); + return; + } + + const tbody = table.querySelector("tbody"); + if (!tbody) { + console.warn("โ No tbody found"); + return; + } + + const rows = tbody.querySelectorAll("tr"); + if (rows.length === 0) { + console.warn("โ No rows found"); + return; + } + + const search = searchText.toLowerCase().trim(); + console.log(`๐ Searching ${rows.length} rows for: "${search}"`); + + let visibleCount = 0; + + rows.forEach((row, index) => { + const cells = row.querySelectorAll("td"); + + if (cells.length === 0) { + return; + } + + // Combine text from all cells except the last one (Actions column) + let searchContent = ""; + for (let i = 0; i < cells.length - 1; i++) { + if (cells[i]) { + const cellText = cells[i].textContent.trim(); + searchContent += " " + cellText; + } + } + + const fullText = searchContent.trim().toLowerCase(); + const shouldShow = search === "" || fullText.includes(search); + + // Debug first few rows + if (index < 3) { + console.log( + `Row ${index + 1}: "${fullText.substring(0, 50)}..." -> Match: ${shouldShow}`, + ); + } + + // Show/hide the row + if (shouldShow) { + row.style.display = ""; + row.style.visibility = "visible"; + visibleCount++; + } else { + row.style.display = "none"; + row.style.visibility = "hidden"; + } + }); + + console.log( + `โ Search complete: ${visibleCount}/${rows.length} rows visible`, + ); + } catch (error) { + console.error("โ Error in filterGatewaysTable:", error); + } +} + +// Make filter functions available globally +window.filterServerTable = filterServerTable; +window.filterToolsTable = filterToolsTable; +window.filterResourcesTable = filterResourcesTable; +window.filterPromptsTable = filterPromptsTable; +window.filterA2AAgentsTable = filterA2AAgentsTable; +window.filterGatewaysTable = filterGatewaysTable; + +// Add a test function for debugging +window.testGatewaySearch = function (searchTerm = "Cou") { + console.log("๐งช Testing gateway search with:", searchTerm); + console.log("Available tables:", document.querySelectorAll("table").length); + + // Test the search input exists + const searchInput = document.getElementById("gateways-search-input"); + console.log("Search input found:", !!searchInput); + + if (searchInput) { + searchInput.value = searchTerm; + console.log("Set search input value to:", searchInput.value); + } + + filterGatewaysTable(searchTerm); +}; + +// Simple fallback search function +window.simpleGatewaySearch = function (searchTerm) { + console.log("๐ง Simple gateway search for:", searchTerm); + + // Find any table in the current tab/page + const tables = document.querySelectorAll("table"); + console.log("Found tables:", tables.length); + + tables.forEach((table, tableIndex) => { + const tbody = table.querySelector("tbody"); + if (!tbody) { + return; + } + + const rows = tbody.querySelectorAll("tr"); + console.log(`Table ${tableIndex}: ${rows.length} rows`); + + if (rows.length > 0) { + // Check if this looks like the MCP servers table + const firstRow = rows[0]; + const cells = firstRow.querySelectorAll("td"); + + if (cells.length >= 8) { + // MCP servers table should have many columns + console.log( + `Table ${tableIndex} looks like MCP servers table with ${cells.length} columns`, + ); + + const search = searchTerm.toLowerCase().trim(); + let visibleCount = 0; + + rows.forEach((row) => { + const cells = row.querySelectorAll("td"); + let rowText = ""; + + // Get text from all cells except last (Actions) + for (let i = 0; i < cells.length - 1; i++) { + rowText += " " + cells[i].textContent.trim(); + } + + const shouldShow = + search === "" || rowText.toLowerCase().includes(search); + + if (shouldShow) { + row.style.display = ""; + visibleCount++; + } else { + row.style.display = "none"; + } + }); + + console.log( + `โ Simple search complete: ${visibleCount}/${rows.length} rows visible`, + ); + // Found the table, stop searching + } + } + }); +}; + +// Add initialization test function +window.testSearchInit = function () { + console.log("๐งช Testing search initialization..."); + initializeSearchInputs(); +}; + +/** + * Clear search functionality for different entity types + */ +function clearSearch(entityType) { + try { + if (entityType === "catalog") { + const searchInput = document.getElementById("catalog-search-input"); + if (searchInput) { + searchInput.value = ""; + filterServerTable(""); // Clear the filter + } + } else if (entityType === "tools") { + const searchInput = document.getElementById("tools-search-input"); + if (searchInput) { + searchInput.value = ""; + filterToolsTable(""); // Clear the filter + } + } else if (entityType === "resources") { + const searchInput = document.getElementById( + "resources-search-input", + ); + if (searchInput) { + searchInput.value = ""; + filterResourcesTable(""); // Clear the filter + } + } else if (entityType === "prompts") { + const searchInput = document.getElementById("prompts-search-input"); + if (searchInput) { + searchInput.value = ""; + filterPromptsTable(""); // Clear the filter + } + } else if (entityType === "a2a-agents") { + const searchInput = document.getElementById( + "a2a-agents-search-input", + ); + if (searchInput) { + searchInput.value = ""; + filterA2AAgentsTable(""); // Clear the filter + } + } else if (entityType === "gateways") { + const searchInput = document.getElementById( + "gateways-search-input", + ); + if (searchInput) { + searchInput.value = ""; + filterGatewaysTable(""); // Clear the filter + } + } else if (entityType === "gateways") { + const searchInput = document.getElementById( + "gateways-search-input", + ); + if (searchInput) { + searchInput.value = ""; + filterGatewaysTable(""); // Clear the filter + } + } + } catch (error) { + console.error("Error clearing search:", error); + } +} + +// Make clearSearch function available globally +window.clearSearch = clearSearch; + +/** + * Initialize search inputs for all entity types + * This function also handles re-initialization after HTMX content loads + */ +function initializeSearchInputs() { + console.log("๐ Initializing search inputs..."); + + // Remove existing event listeners to prevent duplicates + const searchInputs = [ + "catalog-search-input", + "gateways-search-input", + "tools-search-input", + "resources-search-input", + "prompts-search-input", + "a2a-agents-search-input", + ]; + + searchInputs.forEach((inputId) => { + const input = document.getElementById(inputId); + if (input) { + // Clone the input to remove all event listeners, then replace it + const newInput = input.cloneNode(true); + input.parentNode.replaceChild(newInput, input); + } + }); + + // Get fresh references to all search inputs after cloning + + // Virtual Servers search + const catalogSearchInput = document.getElementById("catalog-search-input"); + if (catalogSearchInput) { + catalogSearchInput.addEventListener("input", function () { + filterServerTable(this.value); + }); + console.log("โ Virtual Servers search initialized"); + } + + // MCP Servers (Gateways) search + const gatewaysSearchInput = document.getElementById( + "gateways-search-input", + ); + if (gatewaysSearchInput) { + console.log("โ Found MCP Servers search input"); + + // Use addEventListener instead of direct assignment + gatewaysSearchInput.addEventListener("input", function (e) { + const searchValue = e.target.value; + console.log("๐ MCP Servers search triggered:", searchValue); + filterGatewaysTable(searchValue); + }); + + // Add keyup as backup + gatewaysSearchInput.addEventListener("keyup", function (e) { + const searchValue = e.target.value; + filterGatewaysTable(searchValue); + }); + + // Add change as backup + gatewaysSearchInput.addEventListener("change", function (e) { + const searchValue = e.target.value; + filterGatewaysTable(searchValue); + }); + + console.log("โ MCP Servers search events attached"); + + // Test the function works + filterGatewaysTable(""); + } else { + console.error("โ MCP Servers search input not found!"); + + // Debug available inputs + const allInputs = document.querySelectorAll('input[type="text"]'); + console.log( + "Available text inputs:", + Array.from(allInputs).map((input) => ({ + id: input.id, + placeholder: input.placeholder, + className: input.className, + })), + ); + } + + // Tools search + const toolsSearchInput = document.getElementById("tools-search-input"); + if (toolsSearchInput) { + toolsSearchInput.addEventListener("input", function () { + filterToolsTable(this.value); + }); + console.log("โ Tools search initialized"); + } + + // Resources search + const resourcesSearchInput = document.getElementById( + "resources-search-input", + ); + if (resourcesSearchInput) { + resourcesSearchInput.addEventListener("input", function () { + filterResourcesTable(this.value); + }); + console.log("โ Resources search initialized"); + } + + // Prompts search + const promptsSearchInput = document.getElementById("prompts-search-input"); + if (promptsSearchInput) { + promptsSearchInput.addEventListener("input", function () { + filterPromptsTable(this.value); + }); + console.log("โ Prompts search initialized"); + } + + // A2A Agents search + const agentsSearchInput = document.getElementById( + "a2a-agents-search-input", + ); + if (agentsSearchInput) { + agentsSearchInput.addEventListener("input", function () { + filterA2AAgentsTable(this.value); + }); + console.log("โ A2A Agents search initialized"); + } +} + function handleAuthTypeChange() { const authType = this.value; @@ -23065,3 +23667,87 @@ function updateEntityActionButtons(cell, type, id, isEnabled) { `; } } + +// CRITICAL DEBUG AND FIX FOR MCP SERVERS SEARCH +console.log("๐ง LOADING MCP SERVERS SEARCH DEBUG FUNCTIONS..."); + +// Emergency fix function for MCP Servers search +window.emergencyFixMCPSearch = function () { + console.log("๐จ EMERGENCY FIX: Attempting to fix MCP Servers search..."); + + // Find the search input + const searchInput = document.getElementById("gateways-search-input"); + if (!searchInput) { + console.error("โ Cannot find gateways-search-input element"); + return false; + } + + console.log("โ Found search input:", searchInput); + + // Remove all existing event listeners by cloning + const newSearchInput = searchInput.cloneNode(true); + searchInput.parentNode.replaceChild(newSearchInput, searchInput); + + // Add fresh event listener + const finalSearchInput = document.getElementById("gateways-search-input"); + finalSearchInput.addEventListener("input", function (e) { + console.log("๐ EMERGENCY SEARCH EVENT:", e.target.value); + filterGatewaysTable(e.target.value); + }); + + console.log( + "โ Emergency fix applied - test by typing in MCP Servers search box", + ); + return true; +}; + +// Manual test function +window.testMCPSearchManually = function (searchTerm = "github") { + console.log("๐งช MANUAL TEST: Testing MCP search with:", searchTerm); + filterGatewaysTable(searchTerm); +}; + +// Debug current state function +window.debugMCPSearchState = function () { + console.log("๐ DEBUGGING MCP SEARCH STATE:"); + + const searchInput = document.getElementById("gateways-search-input"); + console.log("Search input:", searchInput); + console.log( + "Search input value:", + searchInput ? searchInput.value : "NOT FOUND", + ); + + const panel = document.getElementById("gateways-panel"); + console.log("Gateways panel:", panel); + + const table = panel ? panel.querySelector("table") : null; + console.log("Table in panel:", table); + + const rows = table ? table.querySelectorAll("tbody tr") : []; + console.log("Rows found:", rows.length); + + if (rows.length > 0) { + console.log("First row content:", rows[0].textContent); + } + + return { + searchInput: !!searchInput, + panel: !!panel, + table: !!table, + rowCount: rows.length, + }; +}; + +// Auto-fix on page load +setTimeout(function () { + console.log("๐ AUTO-FIX: Attempting to fix MCP search after page load..."); + if (window.emergencyFixMCPSearch) { + window.emergencyFixMCPSearch(); + } +}, 1000); + +console.log("๐ง MCP SERVERS SEARCH DEBUG FUNCTIONS LOADED!"); +console.log("๐ก Use: window.emergencyFixMCPSearch() to fix search"); +console.log("๐ก Use: window.testMCPSearchManually('github') to test search"); +console.log("๐ก Use: window.debugMCPSearchState() to check current state"); diff --git a/mcpgateway/templates/admin.html b/mcpgateway/templates/admin.html index e9a13d0b1..7805403d5 100644 --- a/mcpgateway/templates/admin.html +++ b/mcpgateway/templates/admin.html @@ -1703,28 +1703,37 @@