imcp 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/dist/cli/commands/list-installed.d.ts +2 -0
- package/dist/cli/commands/list-installed.js +2 -0
- package/dist/cli/commands/list-installed.js.map +1 -0
- package/dist/cli/commands/update.js +1 -1
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/tui/components/ClientSelector.js +1 -1
- package/dist/cli/tui/components/ClientSelector.js.map +1 -1
- package/dist/core/installers/clients/BaseClientInstaller.js +1 -1
- package/dist/core/installers/clients/BaseClientInstaller.js.map +1 -1
- package/dist/core/installers/clients/ClientInstallerFactory.js +1 -1
- package/dist/core/installers/clients/ClientInstallerFactory.js.map +1 -1
- package/dist/core/installers/clients/CopilotCLIInstaller.d.ts +18 -0
- package/dist/core/installers/clients/CopilotCLIInstaller.js +2 -0
- package/dist/core/installers/clients/CopilotCLIInstaller.js.map +1 -0
- package/dist/core/installers/clients/ExtensionInstaller.d.ts +1 -0
- package/dist/core/installers/clients/ExtensionInstaller.js +1 -1
- package/dist/core/installers/clients/ExtensionInstaller.js.map +1 -1
- package/dist/core/installers/requirements/NpmInstaller.js +1 -1
- package/dist/core/installers/requirements/NpmInstaller.js.map +1 -1
- package/dist/core/metadatas/constants.d.ts +4 -0
- package/dist/core/metadatas/constants.js +1 -1
- package/dist/core/metadatas/constants.js.map +1 -1
- package/dist/utils/versionUtils.js +1 -1
- package/dist/utils/versionUtils.js.map +1 -1
- package/dist/web/public/js/api.js.map +1 -1
- package/dist/web/public/js/detailsWidget.js.map +1 -1
- package/dist/web/public/js/flights/flights.js.map +1 -1
- package/dist/web/public/js/modal/index.js.map +1 -1
- package/dist/web/public/js/modal/installModal.js.map +1 -1
- package/dist/web/public/js/modal/installation.js.map +1 -1
- package/dist/web/public/js/modal/loadingModal.js.map +1 -1
- package/dist/web/public/js/modal/modalSetup.js.map +1 -1
- package/dist/web/public/js/modal/modalUtils.js.map +1 -1
- package/dist/web/public/js/modal/versionUtils.js.map +1 -1
- package/dist/web/public/js/modal.js.map +1 -1
- package/dist/web/public/js/notifications.js.map +1 -1
- package/dist/web/public/js/onboard/formProcessor.js.map +1 -1
- package/dist/web/public/js/onboard/index.js.map +1 -1
- package/dist/web/public/js/onboard/publishHandler.js.map +1 -1
- package/dist/web/public/js/onboard/state.js.map +1 -1
- package/dist/web/public/js/onboard/templates.js.map +1 -1
- package/dist/web/public/js/onboard/uiHandlers.js.map +1 -1
- package/dist/web/public/js/onboard/validationHandlers.js.map +1 -1
- package/dist/web/public/js/serverCategoryDetails.js.map +1 -1
- package/dist/web/public/js/serverCategoryList.js.map +1 -1
- package/dist/web/public/js/settings.js.map +1 -1
- package/package.json +1 -1
- package/dist/core/installers/clients/CodexInstaller.d.ts +0 -45
- package/dist/core/installers/clients/CodexInstaller.js +0 -2
- package/dist/core/installers/clients/CodexInstaller.js.map +0 -1
- package/dist/utils/tomlUtils.d.ts +0 -17
- package/dist/utils/tomlUtils.js +0 -2
- package/dist/utils/tomlUtils.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sources":[],"mappings":"","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["serverTemplate","envVariableTemplate","serverRequirementTemplate","state","setServerCounter","getServerCounter","setEnvCounter","getEnvCounter","deleteEnvCounter","clearEnvCountersForTab","setServerRequirementCounter","getServerRequirementCounter","deleteServerRequirementCounter","clearServerRequirementCountersForTab","getFormData","populateForm","resetOnboardFormDynamicContent","showToast","reindexElements","container","oldIndex","newIndex","selectors","forEach","selector","idBase","namePattern","element","querySelector","id","getAttribute","newName","replace","RegExp","setAttribute","reindexServers","serversListId","serversList","document","getElementById","serverItems","querySelectorAll","newEnvCounters","Map","newServerRequirementCounters","serverItem","newServerIndex","oldServerIndex","parseInt","dataset","index","titleElement","originalTextContent","textContent","baseTitle","suffix","includes","innerHTML","headerToggle","oldContentId","newContentId","onclickAttr","onkeydownAttr","titleForAria","contentRegion","updatedOnclick","envVarsContainer","envCounter","envItem","newEnvIndex","oldEnvIndex","envIndex","set","reqsContainer","reqCounter","reqItem","newReqIndex","oldReqIndex","reqIndex","count","serverIdx","length","addServer","isContextGenerallyReadOnly","serverData","children","actualReadOnlyForThisServer","systemTags","adhoc","originalServerNamesForFormPopulation","name","has","insertAdjacentHTML","JSON","stringify","originalName","setupServerMode","setupReadOnlyState","contentId","iconSelector","contentElement","iconElement","classList","remove","add","item","currentIndex","parentElement","contains","toggleSectionContent","setTimeout","scrollIntoView","behavior","block","serverIndex","isReadOnly","modeSelect","value","mode","addEventListener","renderInstallationConfig","installation","isEffectivelyReadOnly","serverDataFromPopulation","isAdhocForTitle","parse","e","adhocSpanHTML","outerHTML","el","closest","disabled","btn","style","display","removeAttribute","initialModeValue","installationData","envVarsBlock","disabledAttr","readOnlyClasses","generateSSETemplate","generateStdioTemplate","isLocalCheckbox","startingCommandContainer","startingCommandInput","event","target","checked","isLocalInitialChecked","startingCommand","startingCommandInitialValue","isLocalCheckboxDisabled","isLocalCheckboxChecked","startingCommandInputDisabled","startingCommandContainerDisplay","startingCommandReadOnlyClasses","url","command","args","join","removeServer","serverIndexToRemove","serverListContainer","addEnvVariable","isServerEffectivelyReadOnly","removeEnvVariable","addServerRequirement","console","log","toggleServerAliasField","toggleServerRegistryConfig","removeServerRequirement","requirementItem","typeSelect","aliasField","toggle","select","type","config","toggleElement","isHiddenAfterToggle","toString","toggleViewMode","isJsonView","currentFormId","currentServersListId","baseCategoryData","isExistingCategoryContext","elements","createCategory","createServer","jsonEditor","textarea","activeForm","viewModeToggle","actionButtons","json","main","isViewAlreadyActive","handleJsonView","mcpServers","Set","map","s","filter","handleFormView","error","message","feedConfig","getFeedConfiguration","readOnly","togglePanels","toggleButtons","jsonData","newData","merged","requirements","buttons","async","saveJsonData","activeTab","currentSelectedCategoryData","prepareConfiguration","saveConfiguration","editorData","data","isUpdate","isUpdateOperation","mergeConfigurations","categoryName","urlParams","URLSearchParams","window","location","search","get","base","editor","existingServerNames","newServers","concat","existingReqs","r","version","req","key","isNewServerReq","some","dependencies","push","response","fetch","method","headers","body","categoryData","ok","Error","status","copyJsonToClipboard","navigator","clipboard","writeText","err","duplicateServer","serverIndexToDuplicate","formId","formElement","baseCategoryDataForFormExtraction","disabledFields","wasDisabled","currentFullFeedConfig","serverToDuplicateData","isContextGenerallyReadOnlyForAddServer","isExistingCategoryReadOnly","newServerItem","populateServerManually","serverItemElement","description","schemas","repository","dispatchEvent","Event","commandInput","argsInput","urlInput","env","Object","entries","envConfig","envVarIndex","envVarItem","Default","Required","Description","serverReqsContainer","order","alias","registryTypeSelect","registryType","registry","githubRelease","assetsName","assetName","artifacts","registryName","registryUrl","local","localPath","fn"],"sources":["0"],"sourcesContent":["import {\n serverTemplate,\n envVariableTemplate,\n serverRequirementTemplate\n} from './templates.js';\n\nimport {\n state,\n setServerCounter,\n getServerCounter, // Added\n setEnvCounter,\n getEnvCounter,\n deleteEnvCounter,\n clearEnvCountersForTab, // Added\n setServerRequirementCounter,\n getServerRequirementCounter,\n deleteServerRequirementCounter,\n clearServerRequirementCountersForTab // Added\n} from './state.js';\nimport { getFormData, populateForm, resetOnboardFormDynamicContent } from './formProcessor.js';\nimport { showToast } from '../notifications.js';\n\nfunction reindexElements(container, oldIndex, newIndex, selectors) {\n selectors.forEach(({ selector, idBase, namePattern }) => {\n const element = container.querySelector(selector);\n if (!element) return;\n\n if (idBase) {\n element.id = `${idBase}${newIndex}`;\n }\n\n if (namePattern && element.getAttribute('name')) {\n const oldName = element.getAttribute('name');\n const newName = oldName.replace(\n new RegExp(namePattern.replace('INDEX', oldIndex)),\n `$1${newIndex}$2`\n );\n element.setAttribute('name', newName);\n }\n });\n}\n\nfunction reindexServers(serversListId = 'serversList') {\n const serversList = document.getElementById(serversListId);\n if (!serversList) return;\n\n const serverItems = serversList.querySelectorAll('.server-item');\n const newEnvCounters = new Map();\n const newServerRequirementCounters = new Map();\n\n serverItems.forEach((serverItem, newServerIndex) => {\n const oldServerIndex = parseInt(serverItem.dataset.index, 10);\n serverItem.dataset.index = newServerIndex;\n\n const titleElement = serverItem.querySelector('h3');\n if (titleElement) {\n const originalTextContent = titleElement.textContent || '';\n const baseTitle = `MCP Server #${newServerIndex + 1}`;\n let suffix = '';\n if (originalTextContent.includes('(Adhoc - Editable)')) {\n suffix = ' <span class=\"text-sm text-blue-600 ml-1\">(Adhoc - Editable)</span>';\n } else if (originalTextContent.includes('(Read-only)')) {\n suffix = ' (Read-only)';\n }\n // Use innerHTML to preserve the span if it's an adhoc server\n titleElement.innerHTML = `${baseTitle}${suffix}`;\n }\n\n // Update onclick handlers\n // Ensure the server header toggle onclick is correctly re-indexed for its ID parameters\n const headerToggle = serverItem.querySelector(`#${serversListId}-server-header-${oldServerIndex}`);\n if (headerToggle) {\n headerToggle.id = `${serversListId}-server-header-${newServerIndex}`;\n const oldContentId = `${serversListId}-server-content-${oldServerIndex}`;\n const newContentId = `${serversListId}-server-content-${newServerIndex}`;\n let onclickAttr = headerToggle.getAttribute('onclick');\n if (onclickAttr) {\n onclickAttr = onclickAttr.replace(new RegExp(oldContentId.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g'), newContentId);\n headerToggle.setAttribute('onclick', onclickAttr);\n }\n let onkeydownAttr = headerToggle.getAttribute('onkeydown');\n if (onkeydownAttr) {\n onkeydownAttr = onkeydownAttr.replace(new RegExp(oldContentId.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g'), newContentId);\n headerToggle.setAttribute('onkeydown', onkeydownAttr);\n }\n headerToggle.setAttribute('aria-controls', newContentId);\n }\n // Update title ID for aria-labelledby\n const titleForAria = serverItem.querySelector(`#${serversListId}-server-title-${oldServerIndex}`);\n if (titleForAria) {\n titleForAria.id = `${serversListId}-server-title-${newServerIndex}`;\n }\n const contentRegion = serverItem.querySelector(`#${serversListId}-server-content-${oldServerIndex}`);\n if (contentRegion) {\n contentRegion.setAttribute('aria-labelledby', `${serversListId}-server-title-${newServerIndex}`);\n }\n\n\n serverItem.querySelectorAll('[onclick]').forEach(element => {\n const onclickAttr = element.getAttribute('onclick');\n if (!onclickAttr) return;\n\n // Skip the header toggle as it's handled above to be more precise with ID replacement\n if (element.id === `${serversListId}-server-header-${newServerIndex}`) return;\n\n const updatedOnclick = onclickAttr\n .replace(new RegExp(`\\\\((\\\\s*)${oldServerIndex}(\\\\s*[,\\\\)])`, 'g'), `($1${newServerIndex}$2`)\n // More specific replacement for content IDs to avoid accidental replacements\n .replace(new RegExp(`${serversListId}-server-deps-content-${oldServerIndex}`.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g'), `${serversListId}-server-deps-content-${newServerIndex}`)\n .replace(new RegExp(`${serversListId}-installation-content-${oldServerIndex}`.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g'), `${serversListId}-installation-content-${newServerIndex}`)\n .replace(new RegExp(`${serversListId}-env-vars-content-${oldServerIndex}`.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g'), `${serversListId}-env-vars-content-${newServerIndex}`);\n\n element.setAttribute('onclick', updatedOnclick);\n });\n\n // Update IDs and names\n reindexElements(serverItem, oldServerIndex, newServerIndex, [\n { selector: `#${serversListId}-server-content-${oldServerIndex}`, idBase: `${serversListId}-server-content-` },\n { selector: `#${serversListId}-server-deps-content-${oldServerIndex}`, idBase: `${serversListId}-server-deps-content-` },\n { selector: `#${serversListId}-installation-content-${oldServerIndex}`, idBase: `${serversListId}-installation-content-` },\n { selector: `#${serversListId}-env-vars-content-${oldServerIndex}`, idBase: `${serversListId}-env-vars-content-` },\n { selector: `#schema-path-${oldServerIndex}`, idBase: 'schema-path-' },\n { selector: `#envVarsContainer_${oldServerIndex}`, idBase: 'envVarsContainer_' }\n ]);\n\n // Reindex env variables\n const envVarsContainer = serverItem.querySelector(`#envVarsContainer_${newServerIndex}`);\n let envCounter = 0;\n if (envVarsContainer) {\n envVarsContainer.querySelectorAll('.env-var-item').forEach((envItem, newEnvIndex) => {\n const oldEnvIndex = parseInt(envItem.dataset.envIndex, 10);\n envItem.dataset.envIndex = newEnvIndex;\n reindexElements(envItem, oldEnvIndex, newEnvIndex, [{\n selector: '[name]',\n namePattern: `(servers\\\\[${newServerIndex}\\\\]\\\\.installation\\\\.env\\\\[)\\\\d+(\\\\]\\\\..+)`\n }]);\n envCounter++;\n });\n }\n newEnvCounters.set(newServerIndex, envCounter);\n\n // Reindex server requirements\n const reqsContainer = serverItem.querySelector(`#server-requirements-list-${newServerIndex}`);\n let reqCounter = 0;\n if (reqsContainer) {\n reqsContainer.querySelectorAll('.server-requirement-item').forEach((reqItem, newReqIndex) => {\n const oldReqIndex = parseInt(reqItem.dataset.reqIndex, 10);\n reqItem.dataset.reqIndex = newReqIndex;\n reindexElements(reqItem, oldReqIndex, newReqIndex, [{\n selector: '[name]',\n namePattern: `(servers\\\\[${newServerIndex}\\\\]\\\\.requirements\\\\[)\\\\d+(\\\\]\\\\..+)`\n }]);\n reqCounter++;\n });\n }\n newServerRequirementCounters.set(newServerIndex, reqCounter);\n });\n\n // Update state for the specific tab\n clearEnvCountersForTab(serversListId);\n newEnvCounters.forEach((count, serverIdx) => setEnvCounter(serversListId, serverIdx, count));\n\n clearServerRequirementCountersForTab(serversListId);\n newServerRequirementCounters.forEach((count, serverIdx) => setServerRequirementCounter(serversListId, serverIdx, count));\n\n setServerCounter(serversListId, serverItems.length);\n}\n\nexport function addServer(serversListId = 'serversList', isContextGenerallyReadOnly = false, serverData = null) {\n const container = document.getElementById(serversListId);\n if (!container) return -1;\n\n // Initialize counters for this tab if it's the first server being added to this specific list\n if (container.children.length === 0) {\n setServerCounter(serversListId, 0);\n clearEnvCountersForTab(serversListId);\n clearServerRequirementCountersForTab(serversListId);\n }\n\n const newServerIndex = getServerCounter(serversListId);\n let actualReadOnlyForThisServer;\n const isServerAdhoc = serverData?.systemTags?.adhoc === 'true';\n\n if (isServerAdhoc) {\n // Adhoc servers are always editable, regardless of the general context.\n actualReadOnlyForThisServer = false;\n } else if (!isContextGenerallyReadOnly) {\n // Context is not generally read-only (e.g., creating a brand new category,\n // or user clicked \"Add Server\" button when no specific read-only category context applies).\n actualReadOnlyForThisServer = false;\n } else {\n // Context IS generally read-only AND the server is NOT adhoc.\n // Determine read-only status based on whether it's an original server or a newly added one.\n if (state.originalServerNamesForFormPopulation) {\n // We are in the process of toggling from JSON view back to Form view for an existing category.\n // state.originalServerNamesForFormPopulation contains names of servers that were part of the category *before* any UI/JSON edits.\n if (serverData && serverData.name && state.originalServerNamesForFormPopulation.has(serverData.name)) {\n // This serverData (from JSON) corresponds to one of the original servers. It should be read-only.\n actualReadOnlyForThisServer = true;\n } else {\n // This serverData (from JSON) is new (wasn't in originalServerNamesForFormPopulation). It should be editable.\n actualReadOnlyForThisServer = false;\n }\n } else {\n // We are NOT toggling from JSON view. This is either:\n // 1. Initial population of an existing category's servers (serverData will be provided).\n // 2. User clicked the \"Add Server\" button while an existing category context is active (serverData will be null).\n if (serverData) {\n // Case 1: Initial population of an existing server from category data. Should be read-only (unless it was adhoc, handled above).\n actualReadOnlyForThisServer = true;\n } else {\n // Case 2: User clicked \"Add Server\" button. The new server item should be editable.\n actualReadOnlyForThisServer = false;\n }\n }\n }\n // `isContextGenerallyReadOnly`: Influences initial template state for some UI elements (e.g., button visibility).\n // `actualReadOnlyForThisServer`: Determines if fields should be disabled and is passed to setupReadOnlyState.\n // `serverData`: Used by the template for initial adhoc title span, and passed to setupReadOnlyState for more checks.\n container.insertAdjacentHTML('beforeend', serverTemplate(newServerIndex, isContextGenerallyReadOnly, serverData, serversListId));\n\n setEnvCounter(serversListId, newServerIndex, 0);\n setServerRequirementCounter(serversListId, newServerIndex, 0);\n setServerCounter(serversListId, newServerIndex + 1);\n\n const serverItem = container.querySelector(`.server-item[data-index=\"${newServerIndex}\"]`);\n if (serverItem) {\n // Handle systemTags dataset\n if (serverData && serverData.systemTags) {\n serverItem.dataset.systemTags = JSON.stringify(serverData.systemTags);\n } else if (serverData && !serverData.systemTags) {\n delete serverItem.dataset.systemTags;\n } else if (!serverData) {\n delete serverItem.dataset.systemTags;\n }\n\n // Handle originalName dataset\n if (serverData && serverData.name) {\n serverItem.dataset.originalName = serverData.name;\n } else {\n // If it's a new server (serverData is null) or serverData has no name,\n // ensure no stale originalName if the DOM element was somehow reused.\n delete serverItem.dataset.originalName;\n }\n\n setupServerMode(serverItem, newServerIndex, serversListId, actualReadOnlyForThisServer, serverData);\n // Pass serverData to setupReadOnlyState so it can accurately determine adhoc status for title.\n setupReadOnlyState(serverItem, actualReadOnlyForThisServer, serverData, serversListId, newServerIndex);\n\n // Default expand Package Dependencies, Startup Configuration, and Environment Variables sections\n const sections = [\n { contentId: `${serversListId}-server-deps-content-${newServerIndex}`, iconSelector: `#${serversListId}-deps-header-${newServerIndex} i` },\n { contentId: `${serversListId}-installation-content-${newServerIndex}`, iconSelector: `#${serversListId}-startup-header-${newServerIndex} i` },\n { contentId: `${serversListId}-env-vars-content-${newServerIndex}`, iconSelector: `#${serversListId}-envars-header-${newServerIndex} i` }\n ];\n\n sections.forEach(({ contentId, iconSelector }) => {\n const contentElement = document.getElementById(contentId);\n const iconElement = serverItem.querySelector(iconSelector);\n if (contentElement && iconElement) {\n contentElement.classList.remove('hidden');\n iconElement.classList.remove('bxs-chevron-down');\n iconElement.classList.add('bxs-chevron-up');\n }\n });\n\n // New logic: Focus on the newly added server and collapse others,\n // only if it's not an existing read-only server being populated.\n if (!actualReadOnlyForThisServer) {\n const allServerItems = container.querySelectorAll('.server-item');\n allServerItems.forEach(item => {\n const currentIndex = parseInt(item.dataset.index, 10);\n // Ensure item is a direct child of the container to avoid issues if querySelectorAll picks up nested items.\n if (item.parentElement !== container) return;\n\n const contentId = `${serversListId}-server-content-${currentIndex}`;\n const iconElement = item.querySelector('.server-header-toggle i.toggle-icon');\n const contentElement = document.getElementById(contentId);\n\n if (contentElement && iconElement) {\n if (currentIndex === newServerIndex) {\n // Expand the new server if it's collapsed\n if (contentElement.classList.contains('hidden')) {\n toggleSectionContent(contentId, iconElement);\n }\n // Scroll to the new server\n // Use a slight delay to ensure rendering is complete for smooth scroll\n setTimeout(() => {\n item.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n }, 0);\n } else {\n // Collapse other servers if they are expanded\n if (!contentElement.classList.contains('hidden')) {\n toggleSectionContent(contentId, iconElement);\n }\n }\n }\n });\n }\n }\n\n return newServerIndex;\n}\n\nfunction setupServerMode(serverItem, serverIndex, serversListId, isReadOnly, serverData) {\n const modeSelect = serverItem.querySelector(`select[name=\"servers[${serverIndex}].mode\"]`);\n if (!modeSelect) return;\n\n if (isReadOnly && serverData) {\n modeSelect.value = serverData.mode || 'stdio';\n }\n\n modeSelect.addEventListener('change', () => renderInstallationConfig(serverIndex, serversListId));\n renderInstallationConfig(serverIndex, serversListId, modeSelect.value, isReadOnly, serverData?.installation);\n}\n\n// `isEffectivelyReadOnly` determines if the fields within this server item should be disabled.\n// `serverDataFromPopulation` is the original server data object passed during population, used to check initial adhoc status.\nfunction setupReadOnlyState(serverItem, isEffectivelyReadOnly, serverDataFromPopulation, serversListId, serverIndex) {\n const titleElement = serverItem.querySelector(`#${serversListId}-server-title-${serverIndex}`);\n const baseTitle = `MCP Server #${serverIndex + 1}`;\n let isAdhocForTitle = serverDataFromPopulation?.systemTags?.adhoc === 'true';\n\n // Double-check adhoc status from dataset if serverDataFromPopulation doesn't indicate it\n // (e.g. if it became adhoc after JSON edit and re-population)\n if (!isAdhocForTitle && serverItem.dataset.systemTags) {\n try {\n const tags = JSON.parse(serverItem.dataset.systemTags);\n if (tags.adhoc === \"true\") {\n isAdhocForTitle = true;\n }\n } catch (e) { /* ignore parsing error for title determination */ }\n }\n\n if (isEffectivelyReadOnly) {\n if (titleElement) {\n if (!isAdhocForTitle) {\n titleElement.innerHTML = `${baseTitle} (Read-only)`;\n } else {\n // For adhoc servers, the template is responsible for the \"(Adhoc - Editable)\" span.\n // If it's somehow missing and it's adhoc, ensure it's present.\n // This situation should be rare if template logic is correct.\n if (!titleElement.querySelector('span.text-blue-600')) {\n titleElement.innerHTML = `${baseTitle} <span class=\"text-sm text-blue-600 ml-1\">(Adhoc - Editable)</span>`;\n } else {\n // Ensure base title is correct if adhoc span is already there\n const adhocSpanHTML = titleElement.querySelector('span.text-blue-600').outerHTML;\n titleElement.innerHTML = `${baseTitle} ${adhocSpanHTML}`;\n }\n }\n }\n // Disable all input fields within this specific server item\n serverItem.querySelectorAll('input, select, textarea').forEach(el => {\n // Check if the element is part of a sub-item (like env var or requirement)\n // These sub-items have their own read-only logic handled by their add functions.\n // We only want to disable the main server fields here.\n if (!el.closest('.env-var-item') && !el.closest('.server-requirement-item')) {\n el.disabled = true;\n el.classList.add('bg-gray-100', 'cursor-not-allowed', 'opacity-70');\n }\n });\n\n // Hide action buttons (Add Dependency, Add Env Var, Remove Server) for this server if it's read-only,\n // but ensure the \"Duplicate Server\" button remains visible.\n serverItem.querySelectorAll('.action-button-in-server').forEach(btn => {\n if (!btn.classList.contains('duplicate-mcp-server-button')) {\n // This is NOT the duplicate button, so hide it if the server is read-only.\n btn.style.display = 'none';\n btn.classList.add('hidden');\n } else {\n // This IS the duplicate button. Ensure it's visible.\n // (It should be visible by default as it doesn't have conditional hide classes from the template)\n btn.style.display = 'flex'; // Or its appropriate display style\n btn.classList.remove('hidden');\n btn.disabled = false; // Ensure it's enabled\n }\n });\n\n // Expand server content and all key sections\n const sections = [\n { contentId: `${serversListId}-server-content-${serverIndex}`, iconSelector: '.server-header-toggle i.toggle-icon' },\n { contentId: `${serversListId}-server-deps-content-${serverIndex}`, iconSelector: `#${serversListId}-deps-header-${serverIndex} i` },\n { contentId: `${serversListId}-installation-content-${serverIndex}`, iconSelector: `#${serversListId}-startup-header-${serverIndex} i` },\n { contentId: `${serversListId}-env-vars-content-${serverIndex}`, iconSelector: `#${serversListId}-envars-header-${serverIndex} i` }\n ];\n\n sections.forEach(({ contentId, iconSelector }) => {\n const contentElement = document.getElementById(contentId);\n const iconElement = serverItem.querySelector(iconSelector);\n if (contentElement && iconElement) {\n contentElement.classList.remove('hidden');\n iconElement.classList.remove('bxs-chevron-down');\n iconElement.classList.add('bxs-chevron-up');\n }\n });\n } else { // Server is effectively editable\n if (titleElement) {\n // Server is editable. Template handles the adhoc span.\n // We just ensure the base title is correct.\n // If adhoc, the template adds the span. If not adhoc, it's just the base title.\n if (isAdhocForTitle) {\n // Check if the adhoc span is already there from the template.\n // If not (e.g. server became adhoc after initial render and this is a re-evaluation), add it.\n if (!titleElement.querySelector('span.text-blue-600')) {\n titleElement.innerHTML = `${baseTitle} <span class=\"text-sm text-blue-600 ml-1\">(Adhoc - Editable)</span>`;\n } else {\n // Ensure base title is correct if adhoc span is already there\n const adhocSpanHTML = titleElement.querySelector('span.text-blue-600').outerHTML;\n titleElement.innerHTML = `${baseTitle} ${adhocSpanHTML}`;\n }\n } else {\n // Editable and not adhoc, just the base title.\n titleElement.innerHTML = baseTitle;\n }\n }\n setTimeout(() => {\n // Enable main server fields\n serverItem.querySelectorAll('input, select, textarea').forEach(el => {\n if (!el.closest('.env-var-item') && !el.closest('.server-requirement-item')) {\n el.disabled = false;\n el.removeAttribute('readonly'); // Ensure readonly is also removed\n el.classList.remove('bg-gray-100', 'cursor-not-allowed', 'opacity-70');\n }\n });\n\n // Show ALL action buttons for this server (Add Dependency, Add Env Var, Remove Server)\n // as the server is determined to be effectively editable.\n serverItem.querySelectorAll('.action-button-in-server').forEach(btn => {\n btn.style.display = 'flex'; // Or 'inline-flex' or whatever its default visible display is\n btn.disabled = false;\n btn.classList.remove('hidden', 'opacity-50', 'cursor-not-allowed');\n });\n }, 0);\n }\n}\n\nexport function renderInstallationConfig(serverIndex, serversListId = 'serversList', initialModeValue = null, isReadOnly = false, installationData = null) {\n const serverItem = document.querySelector(`#${serversListId} .server-item[data-index=\"${serverIndex}\"]`);\n if (!serverItem) return;\n\n const modeSelect = serverItem.querySelector(`select[name=\"servers[${serverIndex}].mode\"]`);\n const mode = initialModeValue || (modeSelect ? modeSelect.value : 'stdio');\n const container = serverItem.querySelector(`#installation-config-${serverIndex}`);\n const envVarsBlock = serverItem.querySelector(`#env-vars-block-${serverIndex}`);\n\n if (!container) return;\n\n const disabledAttr = isReadOnly ? 'disabled' : '';\n const readOnlyClasses = isReadOnly ? 'bg-gray-100 cursor-not-allowed opacity-70' : '';\n\n container.innerHTML = mode === 'sse' || mode === 'streamable-http' ?\n generateSSETemplate(serverIndex, disabledAttr, readOnlyClasses, installationData) :\n generateStdioTemplate(serverIndex, disabledAttr, readOnlyClasses, installationData);\n\n if (mode === 'sse' || mode === 'streamable-http') {\n const isLocalCheckbox = serverItem.querySelector(`#isLocalCheckbox-${serverIndex}`);\n const startingCommandContainer = serverItem.querySelector(`#startingCommandContainer-${serverIndex}`);\n const startingCommandInput = serverItem.querySelector(`#startingCommandInput-${serverIndex}`);\n\n if (isLocalCheckbox && startingCommandContainer && startingCommandInput) {\n isLocalCheckbox.addEventListener('change', (event) => {\n const isChecked = event.target.checked;\n if (isChecked) {\n startingCommandContainer.style.display = '';\n if (!isReadOnly) { // Only enable if the parent context isn't read-only\n startingCommandInput.disabled = false;\n startingCommandInput.classList.remove('bg-gray-100', 'cursor-not-allowed', 'opacity-70');\n }\n } else {\n startingCommandContainer.style.display = 'none';\n startingCommandInput.disabled = true;\n startingCommandInput.classList.add('bg-gray-100', 'cursor-not-allowed', 'opacity-70');\n // Clear the value when hiding if desired, or leave it.\n // startingCommandInput.value = ''; // Optional: clear value\n }\n });\n }\n }\n\n if (envVarsBlock) {\n envVarsBlock.style.display = mode === 'sse' || mode === 'streamable-http' ? 'none' : '';\n }\n}\n\nfunction generateSSETemplate(serverIndex, disabledAttr, readOnlyClasses, installationData) {\n const isReadOnly = disabledAttr.includes('disabled');\n const isLocalInitialChecked = !!(installationData?.startingCommand);\n const startingCommandInitialValue = installationData?.startingCommand || '';\n\n // Checkbox is disabled if the whole section is read-only.\n const isLocalCheckboxDisabled = isReadOnly ? 'disabled' : '';\n const isLocalCheckboxChecked = isLocalInitialChecked ? 'checked' : '';\n\n // Starting command input is disabled if section is read-only OR if 'Is Local' is not checked.\n const startingCommandInputDisabled = (isReadOnly || !isLocalInitialChecked) ? 'disabled' : '';\n const startingCommandContainerDisplay = isLocalInitialChecked ? '' : 'display: none;';\n const startingCommandReadOnlyClasses = (isReadOnly || !isLocalInitialChecked) ? readOnlyClasses : (isReadOnly ? readOnlyClasses : '');\n\n\n return `\n <div class=\"space-y-4\">\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\" for=\"server-url-${serverIndex}\">Server URL*</label>\n <input type=\"text\" id=\"server-url-${serverIndex}\" name=\"servers[${serverIndex}].installation.url\" required ${disabledAttr}\n class=\"w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${readOnlyClasses}\"\n placeholder=\"e.g., http://localhost:8000 or https://remote-server.com/api\"\n value=\"${installationData?.url || ''}\">\n </div>\n\n <div class=\"flex items-center\">\n <input type=\"checkbox\" id=\"isLocalCheckbox-${serverIndex}\" name=\"servers[${serverIndex}].installation.isLocal\" ${isLocalCheckboxDisabled} ${isLocalCheckboxChecked}\n class=\"h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 ${isReadOnly ? 'cursor-not-allowed opacity-70' : ''}\">\n <label for=\"isLocalCheckbox-${serverIndex}\" class=\"ml-2 block text-sm text-gray-900 ${isReadOnly ? 'cursor-not-allowed opacity-70' : ''}\">\n Is Local Server (requires starting command)\n </label>\n </div>\n\n <div id=\"startingCommandContainer-${serverIndex}\" style=\"${startingCommandContainerDisplay}\">\n <label class=\"block text-sm font-medium text-gray-700 mb-1\" for=\"startingCommandInput-${serverIndex}\">Starting Command</label>\n <input type=\"text\" id=\"startingCommandInput-${serverIndex}\" name=\"servers[${serverIndex}].installation.startingCommand\"\n ${startingCommandInputDisabled}\n class=\"w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${startingCommandReadOnlyClasses}\"\n placeholder=\"e.g., python -m my_server --port 8000\"\n value=\"${startingCommandInitialValue}\">\n <p class=\"mt-1 text-xs text-gray-500\">Command to run if the local server is not already running. Will be ignored if 'Is Local Server' is unchecked.</p>\n </div>\n </div>\n `;\n}\n\nfunction generateStdioTemplate(serverIndex, disabledAttr, readOnlyClasses, installationData) {\n return `\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Command*</label>\n <input type=\"text\" name=\"servers[${serverIndex}].installation.command\" required ${disabledAttr}\n class=\"w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${readOnlyClasses}\"\n placeholder=\"e.g., node, python3\"\n value=\"${installationData?.command || ''}\">\n </div>\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Arguments (comma-separated)</label>\n <input type=\"text\" name=\"servers[${serverIndex}].installation.args\" ${disabledAttr}\n class=\"w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${readOnlyClasses}\"\n placeholder=\"arg1, path/to/script.js, --flag\"\n value=\"${installationData?.args ? installationData.args.join(', ') : ''}\">\n </div>\n </div>\n `;\n}\n\nexport function removeServer(serverIndexToRemove, serversListId = 'serversList') {\n const serverListContainer = document.getElementById(serversListId);\n if (!serverListContainer) return;\n\n const item = serverListContainer.querySelector(`.server-item[data-index=\"${serverIndexToRemove}\"]`);\n if (item) {\n item.remove();\n deleteEnvCounter(serversListId, serverIndexToRemove);\n deleteServerRequirementCounter(serversListId, serverIndexToRemove);\n reindexServers(serversListId);\n }\n}\n\n// `isServerEffectivelyReadOnly` is passed to determine if the new env var item itself should be read-only.\n// This would be true if the parent server is non-adhoc and in a read-only category.\nexport function addEnvVariable(serverIndex, serversListId = 'serversList', isServerEffectivelyReadOnly = false) {\n const container = document.querySelector(`#${serversListId} .server-item[data-index=\"${serverIndex}\"] #envVarsContainer_${serverIndex}`);\n if (!container) return -1;\n\n const envIndex = getEnvCounter(serversListId, serverIndex);\n // Pass `isServerEffectivelyReadOnly` to the template for the new env var item.\n container.insertAdjacentHTML('beforeend', envVariableTemplate(serverIndex, envIndex, isServerEffectivelyReadOnly, serversListId));\n setEnvCounter(serversListId, serverIndex, envIndex + 1);\n\n // The template itself handles disabling fields if isServerEffectivelyReadOnly is true.\n // No need for additional logic here to disable fields of the newly added item.\n\n return envIndex;\n}\n\nexport function removeEnvVariable(serverIndex, envIndex, serversListId = 'serversList') {\n const item = document.querySelector(`#${serversListId} .server-item[data-index=\"${serverIndex}\"] .env-var-item[data-env-index=\"${envIndex}\"]`);\n if (item) item.remove();\n}\n\n// `isServerEffectivelyReadOnly` is passed to determine if the new requirement item itself should be read-only.\nexport function addServerRequirement(serverIndex, serversListId = 'serversList', isServerEffectivelyReadOnly = false) {\n console.log(`[addServerRequirement] ServerIndex: ${serverIndex}, isServerEffectivelyReadOnly received: ${isServerEffectivelyReadOnly}`); // DEBUG\n const container = document.querySelector(`#${serversListId} .server-item[data-index=\"${serverIndex}\"] #server-requirements-list-${serverIndex}`);\n if (!container) return -1;\n\n const reqIndex = getServerRequirementCounter(serversListId, serverIndex);\n // Pass `isServerEffectivelyReadOnly` to the template for the new requirement item.\n container.insertAdjacentHTML('beforeend', serverRequirementTemplate(serverIndex, reqIndex, isServerEffectivelyReadOnly, serversListId));\n setServerRequirementCounter(serversListId, serverIndex, reqIndex + 1);\n\n // The template itself handles disabling fields if isServerEffectivelyReadOnly is true.\n\n toggleServerAliasField(serverIndex, reqIndex, serversListId);\n toggleServerRegistryConfig(serverIndex, reqIndex, serversListId);\n return reqIndex;\n}\n\nexport function removeServerRequirement(serverIndex, reqIndex, serversListId = 'serversList') {\n const item = document.querySelector(`#${serversListId} .server-item[data-index=\"${serverIndex}\"] .server-requirement-item[data-req-index=\"${reqIndex}\"]`);\n if (item) item.remove();\n}\n\nexport function toggleServerAliasField(serverIndex, reqIndex, serversListId = 'serversList') {\n const requirementItem = document.querySelector(`#${serversListId} .server-item[data-index=\"${serverIndex}\"] .server-requirement-item[data-req-index=\"${reqIndex}\"]`);\n if (!requirementItem) return;\n\n const typeSelect = requirementItem.querySelector(`select[name=\"servers[${serverIndex}].requirements[${reqIndex}].type\"]`);\n const aliasField = requirementItem.querySelector(`#server-alias-field-${serverIndex}-${reqIndex}`);\n\n if (typeSelect && aliasField) {\n aliasField.classList.toggle('hidden', typeSelect.value !== 'command');\n }\n}\n\nexport function toggleServerRegistryConfig(serverIndex, reqIndex, serversListId = 'serversList') {\n const requirementItem = document.querySelector(`#${serversListId} .server-item[data-index=\"${serverIndex}\"] .server-requirement-item[data-req-index=\"${reqIndex}\"]`);\n if (!requirementItem) return;\n\n const select = requirementItem.querySelector(`select[name=\"servers[${serverIndex}].requirements[${reqIndex}].registryType\"]`);\n if (!select) return;\n\n ['github', 'artifacts'].forEach(type => {\n const config = requirementItem.querySelector(`#server-${type}-config-${serverIndex}-${reqIndex}`);\n if (config) {\n config.classList.toggle('hidden', select.value !== type);\n }\n });\n}\n\nexport function toggleSectionContent(contentId, iconElement, toggleElement = null) {\n const contentElement = document.getElementById(contentId);\n if (!contentElement) return;\n\n toggleElement = toggleElement || (iconElement?.parentElement || null);\n const isHiddenAfterToggle = contentElement.classList.toggle('hidden');\n\n if (iconElement) {\n iconElement.classList.toggle('bxs-chevron-up', !isHiddenAfterToggle);\n iconElement.classList.toggle('bxs-chevron-down', isHiddenAfterToggle);\n }\n\n if (toggleElement) {\n toggleElement.setAttribute('aria-expanded', (!isHiddenAfterToggle).toString());\n }\n}\n\nexport function toggleViewMode(isJsonView, currentFormId, currentServersListId, baseCategoryData = null, isExistingCategoryContext = false) {\n const elements = {\n createCategory: document.getElementById('panel-create-category'),\n createServer: document.getElementById('panel-create-server'),\n jsonEditor: document.getElementById('panel-json-editor'),\n textarea: document.getElementById('jsonEditorTextarea'),\n activeForm: document.getElementById(currentFormId),\n viewModeToggle: document.getElementById('viewModeToggle'),\n actionButtons: {\n json: document.getElementById('jsonEditorActionsContainer'),\n main: document.querySelector(`#${currentFormId} ~ .flex.justify-end.space-x-4.pt-6.border-t`)\n }\n };\n\n if (isViewAlreadyActive(isJsonView, elements, currentFormId)) return;\n\n // Clear any previous temporary state\n if (state.originalServerNamesForFormPopulation) {\n state.originalServerNamesForFormPopulation = null;\n }\n\n try {\n if (isJsonView) {\n handleJsonView(elements, currentFormId, currentServersListId, baseCategoryData, isExistingCategoryContext);\n } else { // Switching to Form View\n // If switching to form view for 'create-server' tab in an existing category context,\n // prepare the set of original server names.\n if (isExistingCategoryContext && baseCategoryData && baseCategoryData.mcpServers && currentFormId === 'onboardServerForm') {\n state.originalServerNamesForFormPopulation = new Set(\n baseCategoryData.mcpServers.map(s => s.name).filter(name => name) // Ensure name exists\n );\n }\n handleFormView(elements, currentFormId, currentServersListId, baseCategoryData, isExistingCategoryContext);\n // Clean up the temporary state after populateForm has finished using it.\n if (state.originalServerNamesForFormPopulation) {\n state.originalServerNamesForFormPopulation = null;\n }\n }\n } catch (error) {\n console.error('Error in view mode toggle:', error);\n showToast(`Error: ${error.message}`, 'error');\n if (elements.viewModeToggle) elements.viewModeToggle.checked = isJsonView; // Revert toggle on error\n // Ensure cleanup on error too\n if (state.originalServerNamesForFormPopulation) {\n state.originalServerNamesForFormPopulation = null;\n }\n }\n}\n\nfunction isViewAlreadyActive(isJsonView, elements, currentFormId) {\n if (isJsonView && elements.jsonEditor && !elements.jsonEditor.classList.contains('hidden')) {\n return true;\n }\n\n if (!isJsonView && elements.jsonEditor?.classList.contains('hidden')) {\n if (currentFormId === 'onboardForm' && elements.createCategory && !elements.createCategory.classList.contains('hidden')) {\n return true;\n }\n if (currentFormId === 'onboardServerForm' && elements.createServer && !elements.createServer.classList.contains('hidden')) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction handleJsonView(elements, currentFormId, currentServersListId, baseCategoryData, isExistingCategoryContext) {\n const feedConfig = getFeedConfiguration(elements.activeForm, baseCategoryData, isExistingCategoryContext);\n\n elements.textarea.value = JSON.stringify(feedConfig, null, 2);\n elements.textarea.readOnly = isExistingCategoryContext && baseCategoryData?.mcpServers?.length > 0;\n\n if (elements.textarea.readOnly) {\n elements.textarea.classList.add('bg-gray-100', 'cursor-not-allowed');\n showToast('JSON view is read-only for existing servers in this category.', 'info');\n } else {\n elements.textarea.classList.remove('bg-gray-100', 'cursor-not-allowed');\n }\n\n togglePanels(elements, true);\n toggleButtons(elements.actionButtons, true);\n}\n\nfunction handleFormView(elements, currentFormId, currentServersListId, baseCategoryData, isExistingCategoryContext) {\n const jsonData = JSON.parse(elements.textarea.value);\n // Explicitly clear the server list container before repopulating to prevent UI duplication\n const serverListContainer = document.getElementById(currentServersListId);\n if (serverListContainer) {\n serverListContainer.innerHTML = ''; // Clear existing UI server items\n }\n // Reset counters and other dynamic state\n resetOnboardFormDynamicContent(currentFormId, currentServersListId);\n populateForm(jsonData, currentFormId, isExistingCategoryContext && baseCategoryData?.mcpServers?.length > 0, currentServersListId);\n\n togglePanels(elements, false, currentFormId);\n toggleButtons(elements.actionButtons, false);\n}\n\nfunction getFeedConfiguration(activeForm, baseCategoryData, isExistingCategoryContext) {\n if (!isExistingCategoryContext || !baseCategoryData) {\n // If not in an existing category context, or no base data, just get current form data.\n return activeForm ? getFormData(activeForm) : {};\n }\n\n // In an existing category context, get the data from the current form.\n // `true` for forExistingCategoryTab ensures getFormData processes only server-related fields\n // and correctly handles adhoc/new servers.\n const newData = getFormData(activeForm, true, baseCategoryData);\n\n // Start with a deep clone of the original category data (for name, description, etc.)\n const merged = JSON.parse(JSON.stringify(baseCategoryData));\n\n // Replace mcpServers with the current state from the form (newData).\n // This ensures deletions and modifications in the form are accurately reflected.\n // newData.mcpServers will include original read-only servers (if any),\n // modified adhoc servers, and newly added servers, all with their current state.\n merged.mcpServers = newData.mcpServers || [];\n\n // Requirements should also be derived from the current state of servers in the form.\n // formDataToFeedConfiguration (which produces newData) calculates global requirements\n // based on the servers it finds. So, newData.requirements should be the correct set.\n merged.requirements = newData.requirements || [];\n\n return merged;\n}\n\nfunction togglePanels(elements, isJsonView, currentFormId = null) {\n // First ensure all panels are hidden\n elements.createCategory?.classList.add('hidden');\n elements.createServer?.classList.add('hidden');\n elements.jsonEditor?.classList.add('hidden');\n\n // Then show only the appropriate panel\n if (isJsonView) {\n elements.jsonEditor?.classList.remove('hidden');\n } else if (currentFormId) {\n // Show only the panel corresponding to the current form\n if (currentFormId === 'onboardForm') {\n elements.createCategory?.classList.remove('hidden');\n } else if (currentFormId === 'onboardServerForm') {\n elements.createServer?.classList.remove('hidden');\n }\n }\n}\n\nfunction toggleButtons(buttons, isJsonView) {\n buttons.main?.classList.toggle('hidden', isJsonView);\n buttons.json?.classList.toggle('hidden', isJsonView);\n}\n\nexport async function saveJsonData(activeTab, currentSelectedCategoryData = null) {\n const textarea = document.getElementById('jsonEditorTextarea');\n if (textarea.readOnly) {\n showToast('Cannot save, JSON editor is in read-only mode for existing servers.', 'warning');\n return;\n }\n\n try {\n const editorData = JSON.parse(textarea.value);\n const config = prepareConfiguration(activeTab, editorData, currentSelectedCategoryData);\n if (!config) return;\n\n await saveConfiguration(config);\n } catch (error) {\n console.error('Error saving JSON data:', error);\n showToast(`Error saving JSON data: ${error.message}`, 'error');\n }\n}\n\nfunction prepareConfiguration(activeTab, editorData, currentSelectedCategoryData) {\n if (activeTab === 'create-category') {\n if (!editorData.name) {\n showToast('Category Name is required in JSON.', 'error');\n return null;\n }\n return {\n data: editorData,\n isUpdate: isUpdateOperation(editorData.name)\n };\n }\n\n if (activeTab === 'create-server') {\n if (!currentSelectedCategoryData?.name) {\n showToast('No existing category context for saving JSON.', 'error');\n return null;\n }\n return {\n data: mergeConfigurations(currentSelectedCategoryData, editorData),\n isUpdate: true\n };\n }\n\n showToast('Invalid tab context for saving JSON.', 'error');\n return null;\n}\n\nfunction isUpdateOperation(categoryName) {\n const urlParams = new URLSearchParams(window.location.search);\n return urlParams.get('action') === 'edit' && urlParams.get('category') === categoryName;\n}\n\nfunction mergeConfigurations(base, editor) {\n const merged = JSON.parse(JSON.stringify(base));\n const existingServerNames = new Set(merged.mcpServers?.map(s => s.name) || []);\n\n const newServers = (editor.mcpServers || []).filter(s => !existingServerNames.has(s.name));\n merged.mcpServers = (merged.mcpServers || []).concat(newServers);\n\n const existingReqs = new Set(merged.requirements?.map(r => `${r.type}|${r.name}|${r.version}`) || []);\n (editor.requirements || []).forEach(req => {\n const key = `${req.type}|${req.name}|${req.version}`;\n const isNewServerReq = newServers.some(s =>\n s.dependencies?.requirements?.some(r =>\n r.name === req.name && r.version === req.version\n )\n );\n\n if (!existingReqs.has(key) && isNewServerReq) {\n merged.requirements = merged.requirements || [];\n merged.requirements.push(req);\n }\n });\n\n return merged;\n}\n\nasync function saveConfiguration({ data, isUpdate }) {\n const response = await fetch('/api/categories/onboard', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ categoryData: data, isUpdate })\n });\n\n if (!response.ok) {\n const error = await response.json();\n throw new Error(error.error || `HTTP error! status: ${response.status}`);\n }\n\n showToast('JSON data submitted successfully!', 'success');\n}\n\nexport async function copyJsonToClipboard() {\n const textarea = document.getElementById('jsonEditorTextarea');\n if (!textarea) {\n showToast('JSON content not found.', 'error');\n return;\n }\n\n try {\n await navigator.clipboard.writeText(textarea.value);\n showToast('JSON copied to clipboard!', 'success');\n } catch (err) {\n console.error('Failed to copy JSON:', err);\n showToast('Failed to copy JSON. See console for details.', 'error');\n }\n}\n\n/**\n * Duplicates an MCP server item in the form.\n * @param {number} serverIndexToDuplicate - The index of the server to duplicate.\n * @param {string} serversListId - The ID of the servers list container (e.g., 'serversList', 'existingCategoryServersList').\n */\nexport function duplicateServer(serverIndexToDuplicate, serversListId) {\n const formId = serversListId === 'serversList' ? 'onboardForm' : 'onboardServerForm';\n const formElement = document.getElementById(formId);\n if (!formElement) {\n showToast(`Form with ID ${formId} not found. Cannot duplicate server.`, 'error');\n return;\n }\n\n // Determine if we are in the \"Create Server in Existing Category\" tab context\n const isExistingCategoryContext = serversListId === 'existingCategoryServersList';\n let baseCategoryDataForFormExtraction = null;\n if (isExistingCategoryContext) {\n // If in existing category context, getFormData might need the base category data\n // This depends on how getFormData is structured to handle this tab.\n // Assuming window.currentSelectedCategoryData holds the loaded category.\n baseCategoryDataForFormExtraction = window.currentSelectedCategoryData || null;\n }\n\n const serverItemToDuplicate = formElement.querySelector(`.server-item[data-index=\"${serverIndexToDuplicate}\"]`);\n if (!serverItemToDuplicate) {\n showToast('Could not find the server item to duplicate in the DOM.', 'error');\n return;\n }\n\n // Track and temporarily enable ALL disabled fields in the form to ensure we get complete data\n const disabledFields = [];\n formElement.querySelectorAll('input, select, textarea').forEach(el => {\n if (el.disabled) {\n disabledFields.push({element: el, wasDisabled: true});\n el.disabled = false;\n }\n });\n\n // Get all current server data from the form\n const currentFullFeedConfig = getFormData(formElement, isExistingCategoryContext, baseCategoryDataForFormExtraction);\n\n // Restore original disabled state for all fields\n disabledFields.forEach(({element, wasDisabled}) => {\n element.disabled = wasDisabled;\n });\n\n if (!currentFullFeedConfig || !currentFullFeedConfig.mcpServers ||\n currentFullFeedConfig.mcpServers.length <= serverIndexToDuplicate) {\n showToast('Could not retrieve data for the server to duplicate after attempting to enable fields.', 'error');\n return;\n }\n\n // 2. Get the specific server data to duplicate (deep copy)\n let serverToDuplicateData = JSON.parse(JSON.stringify(currentFullFeedConfig.mcpServers[serverIndexToDuplicate]));\n\n // 3. IMPORTANT: Remove systemTags from the duplicated data.\n // The duplicated server should be treated as a new, fully editable server.\n if (serverToDuplicateData.systemTags) {\n delete serverToDuplicateData.systemTags;\n }\n // Also, ensure its name is distinct if needed, or clear it to force user input.\n // For now, we'll copy the name but it will be editable.\n // Consider adding a suffix like \"-copy\" to the name if automatic distinct names are desired.\n // serverToDuplicateData.name = serverToDuplicateData.name ? `${serverToDuplicateData.name}-copy` : 'duplicated-server';\n\n\n // 4. Determine if the general context for adding a new server is read-only\n // This is primarily for the 'Edit Existing Category' tab.\n // 'Create New Category' tab is never read-only in this sense.\n let isContextGenerallyReadOnlyForAddServer = false;\n if (isExistingCategoryContext) {\n isContextGenerallyReadOnlyForAddServer = window.isExistingCategoryReadOnly || false;\n }\n\n // 5. Add a new server item. Pass `null` for serverData to ensure it's treated as a new item.\n // The `isContextGenerallyReadOnlyForAddServer` helps `addServer` decide initial template state.\n const newServerIndex = addServer(serversListId, isContextGenerallyReadOnlyForAddServer, null);\n\n if (newServerIndex === -1) {\n showToast('Failed to add a new server item for duplication.', 'error');\n return;\n }\n\n // 6. Populate the new server item with the copied data.\n // The new server should be fully editable.\n // We need to call populateForm or a similar function for just this new server.\n // populateForm expects a full FeedConfiguration. We'll construct a minimal one.\n const tempFeedConfigForPopulation = {\n mcpServers: [serverToDuplicateData] // Contains only the server data to populate\n };\n\n // Call populateForm, but ensure it targets the *new* server index and makes it editable.\n // The populateForm function needs to be aware it's populating a *specific, new* server.\n // The last `true` tells populateForm to make this specific server editable, overriding general read-only context.\n // This might require adjustments in populateForm or a dedicated populateSingleServerForm function.\n // For now, assuming populateForm can handle this if we pass a single server and target the new index.\n // We pass `false` for `renderServersAsReadOnly` to ensure the duplicated server is editable.\n // And we pass `true` for a hypothetical `forceEditableForSingleServer` if populateForm supported it.\n // Let's simplify: populateForm will populate based on the data. addServer already set it up as editable.\n // We need to ensure that `populateForm` correctly populates the server at `newServerIndex`.\n // The `populateForm` function in formProcessor.js needs to be able to populate a *specific* server\n // if we pass only one server in `mcpServers`.\n\n // Get the newly added server item\n const serversList = document.getElementById(serversListId);\n const newServerItem = serversList.querySelector(`.server-item[data-index=\"${newServerIndex}\"]`);\n\n if (newServerItem) {\n // Directly populate fields for the new server.\n // This is a simplified version of what populateForm does for a single server.\n // This avoids needing to modify populateForm extensively for this specific use case.\n populateServerManually(newServerItem, newServerIndex, serverToDuplicateData, serversListId);\n showToast(`Server #${serverIndexToDuplicate + 1} duplicated to Server #${newServerIndex + 1}.`, 'success');\n } else {\n showToast('Failed to find the new server item after duplication.', 'error');\n }\n}\nwindow.duplicateServer = duplicateServer;\n\n/**\n * Manually populates a single server item's form fields with provided data.\n * This is a helper for the duplicateServer functionality.\n * @param {HTMLElement} serverItemElement - The HTML element of the server item.\n * @param {number} serverIndex - The index of the server item.\n * @param {object} serverData - The data object for the server.\n * @param {string} serversListId - The ID of the servers list.\n */\nfunction populateServerManually(serverItemElement, serverIndex, serverData, serversListId) {\n // Populate basic fields\n serverItemElement.querySelector(`input[name=\"servers[${serverIndex}].name\"]`).value = serverData.name || '';\n serverItemElement.querySelector(`textarea[name=\"servers[${serverIndex}].description\"]`).value = serverData.description || '';\n serverItemElement.querySelector(`select[name=\"servers[${serverIndex}].mode\"]`).value = serverData.mode || 'stdio';\n serverItemElement.querySelector(`input[name=\"servers[${serverIndex}].schemas\"]`).value = serverData.schemas || '';\n serverItemElement.querySelector(`input[name=\"servers[${serverIndex}].repository\"]`).value = serverData.repository || '';\n\n // Trigger change on mode to render correct installation config\n const modeSelect = serverItemElement.querySelector(`select[name=\"servers[${serverIndex}].mode\"]`);\n if (modeSelect) {\n modeSelect.dispatchEvent(new Event('change')); // This will call renderInstallationConfig\n }\n\n // Populate installation config (command/args for stdio, url for sse)\n // renderInstallationConfig should have been called by the modeSelect change event.\n // Now, set the values based on serverData.installation\n if (serverData.mode === 'stdio' && serverData.installation) {\n const commandInput = serverItemElement.querySelector(`input[name=\"servers[${serverIndex}].installation.command\"]`);\n if (commandInput) commandInput.value = serverData.installation.command || '';\n const argsInput = serverItemElement.querySelector(`input[name=\"servers[${serverIndex}].installation.args\"]`);\n if (argsInput) argsInput.value = serverData.installation.args ? serverData.installation.args.join(', ') : '';\n } else if ((serverData.mode === 'sse' || serverData.mode === 'streamable-http') && serverData.installation) {\n const urlInput = serverItemElement.querySelector(`input[name=\"servers[${serverIndex}].installation.url\"]`);\n if (urlInput) urlInput.value = serverData.installation.url || '';\n }\n\n // Populate environment variables\n const envVarsContainer = serverItemElement.querySelector(`#envVarsContainer_${serverIndex}`);\n if (envVarsContainer && serverData.installation && serverData.installation.env) {\n Object.entries(serverData.installation.env).forEach(([name, envConfig]) => {\n const envVarIndex = addEnvVariable(serverIndex, serversListId, false); // Add as editable\n const envVarItem = envVarsContainer.querySelector(`.env-var-item[data-env-index=\"${envVarIndex}\"]`);\n if (envVarItem) {\n envVarItem.querySelector(`input[name=\"servers[${serverIndex}].installation.env[${envVarIndex}].name\"]`).value = name;\n envVarItem.querySelector(`input[name=\"servers[${serverIndex}].installation.env[${envVarIndex}].default\"]`).value = envConfig.Default || '';\n envVarItem.querySelector(`input[name=\"servers[${serverIndex}].installation.env[${envVarIndex}].required\"]`).checked = envConfig.Required || false;\n envVarItem.querySelector(`textarea[name=\"servers[${serverIndex}].installation.env[${envVarIndex}].description\"]`).value = envConfig.Description || '';\n }\n });\n }\n\n // Populate server requirements (dependencies)\n const serverReqsContainer = serverItemElement.querySelector(`#server-requirements-list-${serverIndex}`);\n if (serverReqsContainer && serverData.dependencies && serverData.dependencies.requirements) {\n serverData.dependencies.requirements.forEach(req => {\n const reqIndex = addServerRequirement(serverIndex, serversListId, false); // Add as editable\n const reqItem = serverReqsContainer.querySelector(`.server-requirement-item[data-req-index=\"${reqIndex}\"]`);\n if (reqItem) {\n reqItem.querySelector(`input[name=\"servers[${serverIndex}].requirements[${reqIndex}].name\"]`).value = req.name || '';\n reqItem.querySelector(`select[name=\"servers[${serverIndex}].requirements[${reqIndex}].type\"]`).value = req.type || '';\n reqItem.querySelector(`input[name=\"servers[${serverIndex}].requirements[${reqIndex}].version\"]`).value = req.version || '';\n reqItem.querySelector(`input[name=\"servers[${serverIndex}].requirements[${reqIndex}].order\"]`).value = req.order || '';\n reqItem.querySelector(`input[name=\"servers[${serverIndex}].requirements[${reqIndex}].alias\"]`).value = req.alias || '';\n\n const typeSelect = reqItem.querySelector(`select[name=\"servers[${serverIndex}].requirements[${reqIndex}].type\"]`);\n if (typeSelect) typeSelect.dispatchEvent(new Event('change')); // To show/hide alias\n\n const registryTypeSelect = reqItem.querySelector(`select[name=\"servers[${serverIndex}].requirements[${reqIndex}].registryType\"]`);\n if (registryTypeSelect) {\n registryTypeSelect.value = req.registryType || 'public';\n registryTypeSelect.dispatchEvent(new Event('change')); // To show/hide specific registry configs\n\n if (req.registry) {\n if (req.registryType === 'github' && req.registry.githubRelease) {\n reqItem.querySelector(`input[name=\"servers[${serverIndex}].requirements[${reqIndex}].registry.githubRelease.repository\"]`).value = req.registry.githubRelease.repository || '';\n reqItem.querySelector(`input[name=\"servers[${serverIndex}].requirements[${reqIndex}].registry.githubRelease.assetsName\"]`).value = req.registry.githubRelease.assetsName || '';\n reqItem.querySelector(`input[name=\"servers[${serverIndex}].requirements[${reqIndex}].registry.githubRelease.assetName\"]`).value = req.registry.githubRelease.assetName || '';\n } else if (req.registryType === 'artifacts' && req.registry.artifacts) {\n reqItem.querySelector(`input[name=\"servers[${serverIndex}].requirements[${reqIndex}].registry.artifacts.registryName\"]`).value = req.registry.artifacts.registryName || '';\n reqItem.querySelector(`input[name=\"servers[${serverIndex}].requirements[${reqIndex}].registry.artifacts.registryUrl\"]`).value = req.registry.artifacts.registryUrl || '';\n } else if (req.registryType === 'local' && req.registry.local) {\n reqItem.querySelector(`input[name=\"servers[${serverIndex}].requirements[${reqIndex}].registry.local.localPath\"]`).value = req.registry.local.localPath || '';\n reqItem.querySelector(`input[name=\"servers[${serverIndex}].requirements[${reqIndex}].registry.local.assetName\"]`).value = req.registry.local.assetName || '';\n }\n }\n }\n }\n });\n }\n // Ensure the duplicated server is fully editable by calling setupReadOnlyState with false\n setupReadOnlyState(serverItemElement, false, null, serversListId, serverIndex);\n}\nObject.entries({\n addServer,\n removeServer,\n addEnvVariable,\n removeEnvVariable,\n addServerRequirement,\n removeServerRequirement,\n toggleServerAliasField,\n toggleServerRegistryConfig,\n renderInstallationConfig,\n toggleSectionContent,\n copyJsonToClipboard\n}).forEach(([name, fn]) => {\n window[name] = fn;\n});\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sources":[],"mappings":"","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["showToast","getFormData","toggleSectionContent","showValidationMessage","element","message","isError","existingMessage","nextElementSibling","classList","contains","remove","messageDiv","document","createElement","className","textContent","insertAdjacentElement","add","validateFormFields","form","activeTab","querySelectorAll","forEach","el","hasErrors","errors","Array","from","field","value","trim","push","name","nameInput","querySelector","test","URL","e","isValid","POLLING_INTERVAL","pollingIntervalId","updateOperationDisplay","data","contentElement","status","validationStatus","prInfo","errorMessage","operationErrorMessage","operationType","steps","upperStatus","toUpperCase","isInProgressOverall","htmlContent","isArray","length","progressListId","id","step","index","stepTimestamp","Date","timestamp","toLocaleString","stepStatusIcon","stepTextColor","isLastStep","stepName","serverName","hasDetailedItems","serverResults","serverResult","icon","serverMessage","key","item","replace","url","innerHTML","ensureProgressToggleListener","statusContentElement","dataset","progressToggleListenerAttached","addEventListener","event","toggleHeader","target","closest","targetId","iconElement","async","pollOperationStatus","categoryName","contentId","validateBtnId","publishBtnId","operationInitiator","getElementById","validateButton","publishButton","updateButtonStates","isCompleted","disabled","response","fetch","encodeURIComponent","result","json","success","currentStatus","String","error","console","handleValidation","currentSelectedCategoryData","panelId","formId","validateButtonId","publishButtonId","getElementIdsByTab","onboardForm","formData","mcpServers","clearInterval","forExistingCategoryTab","newServersData","finalFeedConfiguration","JSON","parse","stringify","requirements","ensureStatusPanelVisible","method","headers","body","categoryData","forExistingCategory","categoryNameForPolling","operationTypeForPolling","initialStatus","upperInitialStatus","setInterval","isCreateCategoryTab","statusPanel","statusContent","headerIcon","warn"],"sources":["0"],"sourcesContent":["// src/web/public/js/onboard/validationHandlers.js\nimport { showToast } from '../notifications.js';\nimport { getFormData } from './formProcessor.js';\nimport { toggleSectionContent } from './uiHandlers.js'; // Assuming this is correctly in uiHandlers.js and exported\n\n/**\n * Shows validation message under an input field.\n * @param {HTMLElement} element - The input element to show validation message for.\n * @param {string} message - The validation message to display.\n * @param {boolean} isError - Whether this is an error message (true) or success message (false).\n */\nexport function showValidationMessage(element, message, isError = true) {\n // Remove any existing validation message\n const existingMessage = element.nextElementSibling;\n if (existingMessage && existingMessage.classList.contains('validation-message')) {\n existingMessage.remove();\n }\n\n // Create new validation message element\n const messageDiv = document.createElement('div');\n messageDiv.className = `validation-message text-xs mt-1 ${isError ? 'text-red-500' : 'text-green-500'}`;\n messageDiv.textContent = message;\n\n // Insert the message after the input element\n element.insertAdjacentElement('afterend', messageDiv);\n\n // Add visual feedback to the input field\n if (isError) {\n element.classList.add('border-red-500');\n element.classList.remove('border-green-500');\n } else {\n element.classList.add('border-green-500');\n element.classList.remove('border-red-500');\n }\n}\n\n/**\n * Common form field validation used by both validate and publish handlers\n * @param {HTMLFormElement} form - The form to validate\n * @param {string} activeTab - The active tab ('create-category' or 'create-server')\n * @returns {{isValid: boolean, errors: string[]}} Validation result with error messages\n */\nexport function validateFormFields(form, activeTab) {\n // Clear any existing validation messages\n form.querySelectorAll('.validation-message').forEach(el => el.remove());\n\n let hasErrors = false;\n const errors = [];\n\n // Check for empty required fields and show validation messages\n const emptyRequiredFields = Array.from(form.querySelectorAll('[required]'));\n emptyRequiredFields.forEach(field => {\n if (!field.value.trim()) {\n showValidationMessage(field, 'This field is required', true);\n hasErrors = true;\n errors.push(`${field.name || 'Field'} is required`);\n }\n });\n\n // Check category name format if this is the create category tab\n if (activeTab === 'create-category') {\n const nameInput = form.querySelector('input[name=\"name\"]');\n if (nameInput && nameInput.value.trim()) {\n if (!/^[a-zA-Z0-9-_]+$/.test(nameInput.value.trim())) {\n showValidationMessage(nameInput, 'Only alphanumeric characters, hyphens, and underscores allowed', true);\n hasErrors = true;\n errors.push('Category name must only contain alphanumeric characters, hyphens, and underscores');\n }\n }\n }\n\n // Check for other validation errors\n const envNameFields = form.querySelectorAll('input[name$=\".env\\\\[\\\\].name\"]');\n envNameFields.forEach(field => {\n const value = field.value.trim();\n if (value && !/^[A-Z_][A-Z0-9_]*$/.test(value)) {\n showValidationMessage(field, 'Must be uppercase with only letters, numbers, and underscores', true);\n hasErrors = true;\n errors.push('Environment variable names must be uppercase with only letters, numbers, and underscores');\n }\n });\n\n const urlFields = form.querySelectorAll('input[name$=\".url\"]');\n urlFields.forEach(field => {\n const value = field.value.trim();\n if (value) {\n try {\n new URL(value);\n } catch (e) {\n showValidationMessage(field, 'Invalid URL format', true);\n hasErrors = true;\n errors.push('Invalid URL format');\n }\n }\n });\n\n return {\n isValid: !hasErrors,\n errors\n };\n}\n\nconst POLLING_INTERVAL = 3000; // 3 seconds\nlet pollingIntervalId = null;\n\n/**\n * Updates the operation status display.\n * @param {object} data - The data object from the API response.\n * @param {HTMLElement} contentElement - The HTML element to update.\n */\nexport function updateOperationDisplay(data, contentElement) { // Renamed from updateValidationDisplay\n const { status, message, validationStatus, prInfo, errorMessage: operationErrorMessage, operationType, steps } = data; // Added steps\n const upperStatus = typeof status === 'string' ? status.toUpperCase() : ''; // Overall status\n const isInProgressOverall = upperStatus === 'PENDING' || upperStatus === 'VALIDATING' || upperStatus === 'PRCREATING' || upperStatus === 'IN_PROGRESS' || upperStatus === 'PUBLISHING';\n\n let htmlContent = `<p><strong>Overall Status:</strong> <span class=\"${upperStatus === 'COMPLETED' || upperStatus === 'SUCCEEDED' ? 'text-green-600' : (upperStatus === 'FAILED' ? 'text-red-600' : 'text-yellow-600')}\">${status} ${isInProgressOverall ? \"<i class='bx bx-loader-alt bx-spin ml-2'></i>\" : \"\"}</span></p>`;\n if (operationType) htmlContent += `<p><strong>Operation Type:</strong> ${operationType}</p>`;\n // Show message if provided, especially useful for direct success/failure from API.\n if (message) htmlContent += `<p><strong>Message:</strong> ${message}</p>`;\n if (operationErrorMessage) htmlContent += `<p class=\"text-red-500\"><strong>Overall Error:</strong> ${operationErrorMessage}</p>`;\n\n // Display Steps\n if (Array.isArray(steps) && steps.length > 0) {\n const progressListId = `progress-steps-list-${contentElement.id}`; // Unique ID for the steps list\n // Ensure the toggle icon reflects the initial state (expanded, so 'bx-chevron-up')\n // The UL itself won't have 'hidden' class initially.\n htmlContent += `\n <div class=\"progress-section-container mt-4\">\n <h4 class=\"font-semibold mb-2 text-gray-700 cursor-pointer flex items-center progress-section-toggle\" data-target-id=\"${progressListId}\">\n Progress\n <i class='bx bx-chevron-up toggle-icon-progress ml-2 text-gray-500'></i>\n </h4>\n <ul id=\"${progressListId}\" class=\"list-none space-y-2 border-l-2 border-blue-500 pl-4 py-2\">`; // Added py-2 for padding\n steps.forEach((step, index) => {\n const stepTimestamp = new Date(step.timestamp).toLocaleString();\n let stepStatusIcon = '';\n let stepTextColor = 'text-gray-700'; // Default text color\n\n const isLastStep = index === steps.length - 1;\n\n if (step.errorMessage || step.status === 'failed') {\n stepStatusIcon = \"<i class='bx bx-x-circle text-red-500 mr-2'></i>\";\n stepTextColor = 'text-red-600';\n } else if (isLastStep && isInProgressOverall) {\n // Current active step if overall process is still running\n stepStatusIcon = \"<i class='bx bx-loader-alt bx-spin text-blue-500 mr-2'></i>\";\n stepTextColor = 'text-blue-700';\n } else {\n // Completed, non-failed step\n stepStatusIcon = \"<i class='bx bx-check-circle text-green-500 mr-2'></i>\";\n stepTextColor = 'text-gray-700'; // Keep default gray for completed steps, icon is green\n }\n \n htmlContent += `<li class=\"text-sm ${stepTextColor}\">`;\n htmlContent += `${stepStatusIcon}<strong>${step.stepName}</strong>`;\n if (step.serverName) htmlContent += ` (Server: ${step.serverName})`;\n htmlContent += `<span class=\"text-xs text-gray-500 ml-2\">- ${stepTimestamp}</span>`;\n if (step.errorMessage) {\n htmlContent += `<p class=\"text-xs text-red-400 pl-6\">Error: ${step.errorMessage}</p>`;\n }\n htmlContent += `</li>`;\n });\n htmlContent += '</ul>';\n }\n\n if (validationStatus) {\n htmlContent += '<h4 class=\"font-semibold mt-4 mb-2 text-gray-700\">Detailed Validation Results:</h4>';\n htmlContent += '<ul class=\"list-disc list-inside space-y-1\">';\n let hasDetailedItems = false;\n // Check for serverResults array first\n if (Array.isArray(validationStatus.serverResults)) {\n validationStatus.serverResults.forEach(serverResult => {\n if (serverResult && typeof serverResult.serverName === 'string' && typeof serverResult.isValid === 'boolean') {\n hasDetailedItems = true;\n const icon = serverResult.isValid ? \"<i class='bx bx-check-circle text-green-500'></i>\" : \"<i class='bx bx-x-circle text-red-500'></i>\";\n let serverMessage = serverResult.message ? `: ${serverResult.message}` : (serverResult.isValid ? ': Valid' : ': Invalid');\n htmlContent += `<li>${icon} <strong>Server \"${serverResult.serverName}\"</strong>${serverMessage}</li>`;\n }\n });\n }\n\n // Fallback for other keys in validationStatus or if serverResults is not the primary detail\n for (const key in validationStatus) {\n if (key === 'serverResults') continue; // Already processed\n\n const item = validationStatus[key];\n // Handle simple {isValid, message} structure directly under validationStatus\n if (key === 'isValid' && typeof item === 'boolean' && typeof validationStatus.message === 'string' && !hasDetailedItems) {\n hasDetailedItems = true;\n const icon = item ? \"<i class='bx bx-check-circle text-green-500'></i>\" : \"<i class='bx bx-x-circle text-red-500'></i>\";\n htmlContent += `<li>${icon} <strong>Overall Validation:</strong> ${validationStatus.message}</li>`;\n break; // Assume this is the main summary if serverResults wasn't present or detailed\n } else if (typeof item === 'object' && item !== null && 'isValid' in item && 'message' in item) {\n // Handle other named detailed items\n hasDetailedItems = true;\n const icon = item.isValid ? \"<i class='bx bx-check-circle text-green-500'></i>\" : \"<i class='bx bx-x-circle text-red-500'></i>\";\n htmlContent += `<li>${icon} <strong>${key.replace(/([A-Z])/g, ' $1').trim()}:</strong> ${item.message}</li>`;\n }\n }\n\n if (!hasDetailedItems && validationStatus.isValid === true && validationStatus.message) {\n // Catch-all for a simple success message if no other details were parsed\n hasDetailedItems = true;\n const icon = \"<i class='bx bx-check-circle text-green-500'></i>\";\n htmlContent += `<li>${icon} <strong>Overall:</strong> ${validationStatus.message}</li>`;\n } else if (!hasDetailedItems) {\n htmlContent += '<li>No detailed validation information available or format is unexpected.</li>';\n }\n htmlContent += '</ul>';\n }\n\n if (prInfo && prInfo.url) {\n htmlContent += `<p class=\"mt-3\"><strong>PR Info:</strong> <a href=\"${prInfo.url}\" target=\"_blank\" class=\"text-blue-600 hover:underline\">${prInfo.url}</a></p>`;\n }\n contentElement.innerHTML = htmlContent;\n // After updating content, ensure the toggle listener is active for the new/updated progress section\n if (Array.isArray(steps) && steps.length > 0) {\n ensureProgressToggleListener(contentElement);\n }\n}\n\n/**\n * Ensures that a click listener is attached to the statusContentElement\n * to handle toggling the visibility of the progress steps list.\n * This listener is attached only once.\n * @param {HTMLElement} statusContentElement - The parent element where progress is displayed.\n */\nexport function ensureProgressToggleListener(statusContentElement) {\n if (statusContentElement && !statusContentElement.dataset.progressToggleListenerAttached) {\n statusContentElement.addEventListener('click', (event) => {\n const toggleHeader = event.target.closest('.progress-section-toggle');\n if (toggleHeader) {\n const targetId = toggleHeader.dataset.targetId;\n const iconElement = toggleHeader.querySelector('.toggle-icon-progress');\n // Assuming toggleSectionContent is imported from uiHandlers.js and is in scope\n if (targetId && iconElement && typeof toggleSectionContent === 'function') {\n toggleSectionContent(targetId, iconElement);\n }\n }\n });\n statusContentElement.dataset.progressToggleListenerAttached = 'true';\n }\n}\n\n\n/**\n * Polls the server for operation status.\n * @param {string} categoryName - The name of the category.\n * @param {string} contentId - The ID of the status content element.\n * @param {string} validateBtnId - The ID of the validate button.\n * @param {string} publishBtnId - The ID of the publish button.\n * @param {string} operationInitiator - 'validate' or 'publish' to restore correct button text.\n * @param {string} operationType - The type of operation to poll for (e.g., 'VALIDATION_ONLY', 'FULL_ONBOARDING').\n * @returns {Promise<boolean>} - Promise resolving to true if polling should continue, false otherwise.\n */\nexport async function pollOperationStatus(categoryName, contentId, validateBtnId, publishBtnId, operationInitiator, operationType) {\n const statusContentElement = document.getElementById(contentId);\n ensureProgressToggleListener(statusContentElement); // Ensure listener is active during polling updates\n\n const validateButton = document.getElementById(validateBtnId);\n const publishButton = document.getElementById(publishBtnId);\n\n // Helper function to update button states based on operation status\n const updateButtonStates = (isCompleted = false) => {\n if (validateButton && publishButton) {\n if (isCompleted) {\n // On completion, enable both buttons and restore their original state\n validateButton.disabled = false;\n validateButton.innerHTML = \"<i class='bx bx-check-shield mr-2'></i>Validate\";\n validateButton.classList.remove('opacity-50');\n publishButton.disabled = false;\n publishButton.innerHTML = \"<i class='bx bx-cloud-upload mr-2'></i>Publish\";\n publishButton.classList.remove('opacity-50');\n } else {\n // During operation, handle buttons based on which operation is running\n if (operationInitiator === 'validate') {\n validateButton.disabled = true;\n validateButton.innerHTML = \"<i class='bx bx-loader-alt bx-spin mr-2'></i>Validating...\";\n publishButton.disabled = true;\n publishButton.innerHTML = \"<i class='bx bx-cloud-upload mr-2'></i>Publish\";\n publishButton.classList.add('opacity-50');\n } else if (operationInitiator === 'publish') {\n publishButton.disabled = true;\n publishButton.innerHTML = \"<i class='bx bx-loader-alt bx-spin mr-2'></i>Publishing...\";\n validateButton.disabled = true;\n validateButton.innerHTML = \"<i class='bx bx-check-shield mr-2'></i>Validate\";\n validateButton.classList.add('opacity-50');\n }\n }\n }\n };\n\n try {\n // Add operationType as a query parameter\n const response = await fetch(`/api/categories/${categoryName}/onboard/status?operationType=${encodeURIComponent(operationType)}`);\n const result = await response.json();\n\n if (result.success && result.data) {\n updateOperationDisplay(result.data, statusContentElement);\n\n const currentStatus = result.data.status ? String(result.data.status).toUpperCase() : ''; // Ensure uppercase for comparison\n // Use string literals for status comparison, include 'succeeded'\n if (currentStatus === 'COMPLETED' || currentStatus === 'FAILED' || currentStatus === 'SUCCEEDED') {\n updateButtonStates(true);\n return false; // Stop polling\n } else {\n // Operation is still in progress\n updateButtonStates(false);\n return true; // Continue polling\n }\n } else {\n statusContentElement.innerHTML = `<p class=\"text-red-500\">Error polling status: ${result.error || 'Unknown error'}</p>`;\n updateButtonStates(true);\n return false; // Stop polling\n }\n } catch (error) {\n console.error('Error polling operation status:', error);\n statusContentElement.innerHTML = `<p class=\"text-red-500\">An error occurred while polling status. Please check the console.</p>`;\n updateButtonStates(true);\n return false; // Stop polling\n }\n}\n\n/**\n * Handles the validation process for the onboarding form.\n * It initiates validation, displays status, and polls for updates.\n * @param {Event} event - The click event.\n * @param {string} activeTab - Identifier for the active tab ('create-category' or 'create-server').\n * @param {object|null} currentSelectedCategoryData - Data for the currently selected category if activeTab is 'create-server'.\n */\nexport async function handleValidation(event, activeTab, currentSelectedCategoryData = null) {\n const { panelId, contentId, formId, validateButtonId, publishButtonId } = getElementIdsByTab(activeTab);\n\n const statusContentElement = document.getElementById(contentId);\n ensureProgressToggleListener(statusContentElement); // Ensure listener is active for initial display\n\n const onboardForm = document.getElementById(formId);\n const validateButton = document.getElementById(validateButtonId);\n const publishButton = document.getElementById(publishButtonId);\n\n if (!onboardForm || !statusContentElement || !validateButton || !publishButton) {\n console.error('Required form or panel elements not found for validation.');\n showToast('Required form or panel elements not found for validation.', 'error');\n return;\n }\n\n const validationResult = validateFormFields(onboardForm, activeTab);\n if (!validationResult.isValid) {\n showToast('Please fix all validation errors before proceeding.', 'error');\n validateButton.disabled = false;\n validateButton.innerHTML = \"<i class='bx bx-check-shield mr-2'></i>Validate\";\n return;\n }\n\n // Additional validation for new category form\n if (activeTab === 'create-category') {\n const formData = getFormData(onboardForm, false);\n if (!formData.mcpServers || formData.mcpServers.length === 0) {\n showToast('At least one MCP server must be configured for a new category.', 'error');\n return;\n }\n }\n\n // Disable buttons and show appropriate states\n validateButton.disabled = true;\n validateButton.innerHTML = \"<i class='bx bx-loader-alt bx-spin mr-2'></i>Validating...\";\n publishButton.disabled = true;\n publishButton.innerHTML = \"<i class='bx bx-cloud-upload mr-2'></i>Publish\";\n publishButton.classList.add('opacity-50'); // Add gray state to inactive button\n\n if (pollingIntervalId) {\n clearInterval(pollingIntervalId); // Clear any existing polling\n pollingIntervalId = null;\n }\n\n const forExistingCategoryTab = activeTab === 'create-server';\n const newServersData = getFormData(onboardForm, forExistingCategoryTab, currentSelectedCategoryData);\n let finalFeedConfiguration;\n\n if (forExistingCategoryTab && currentSelectedCategoryData) {\n finalFeedConfiguration = JSON.parse(JSON.stringify(currentSelectedCategoryData)); // Deep clone\n\n // Replace MCP Servers and Requirements with the current state from the form (newServersData).\n // newServersData, generated by getFormData, already contains the correct, complete list\n // of servers (including original, adhoc, and newly added ones) and their derived global requirements.\n finalFeedConfiguration.mcpServers = newServersData.mcpServers || [];\n finalFeedConfiguration.requirements = newServersData.requirements || [];\n } else {\n finalFeedConfiguration = newServersData;\n }\n\n statusContentElement.innerHTML = '<p class=\"text-gray-500\">Initiating validation...</p>';\n ensureStatusPanelVisible(panelId, contentId);\n\n\n try {\n const response = await fetch('/api/categories/onboard/validate', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n categoryData: finalFeedConfiguration,\n forExistingCategory: forExistingCategoryTab\n }),\n });\n\n const result = await response.json();\n\n if (result.success && result.data) {\n updateOperationDisplay(result.data, statusContentElement);\n // The onboardingId from the response is the combined ID (categoryName_operationType).\n // We need the categoryName for polling, which we already have as a parameter.\n // The operationType is also passed to pollOperationStatus.\n const categoryNameForPolling = finalFeedConfiguration.name; // Use the original category name\n const operationTypeForPolling = 'VALIDATION_ONLY'; // Validation always initiates 'VALIDATION_ONLY'\n const initialStatus = result.data.status;\n\n // Use string literals for status comparison, include 'succeeded'\n const upperInitialStatus = typeof initialStatus === 'string' ? initialStatus.toUpperCase() : '';\n if (categoryNameForPolling && upperInitialStatus !== 'COMPLETED' && upperInitialStatus !== 'FAILED' && upperInitialStatus !== 'SUCCEEDED') {\n pollingIntervalId = setInterval(async () => {\n const shouldContinue = await pollOperationStatus(categoryNameForPolling, contentId, validateButtonId, publishButtonId, 'validate', operationTypeForPolling);\n if (!shouldContinue) {\n clearInterval(pollingIntervalId);\n pollingIntervalId = null;\n }\n }, POLLING_INTERVAL);\n } else {\n validateButton.disabled = false;\n validateButton.innerHTML = \"<i class='bx bx-check-shield mr-2'></i>Validate\";\n publishButton.disabled = false;\n publishButton.classList.remove('opacity-50'); // Remove gray state\n }\n } else {\n statusContentElement.innerHTML = `<p class=\"text-red-500\">Validation request failed: ${result.error || 'Unknown error'}</p>`;\n validateButton.disabled = false;\n validateButton.innerHTML = \"<i class='bx bx-check-shield mr-2'></i>Validate\";\n publishButton.disabled = false;\n publishButton.classList.remove('opacity-50'); // Remove gray state on error\n }\n } catch (error) {\n console.error('Error during validation:', error);\n statusContentElement.innerHTML = `<p class=\"text-red-500\">An error occurred while initiating validation. Please check the console.</p>`;\n validateButton.disabled = false;\n validateButton.innerHTML = \"<i class='bx bx-check-shield mr-2'></i>Validate\";\n publishButton.disabled = false;\n publishButton.classList.remove('opacity-50'); // Remove gray state on error\n }\n}\n\n/**\n * Gets common element IDs based on the active tab.\n * @param {string} activeTab - Identifier for the active tab.\n * @returns {object} Object containing element IDs.\n */\nexport function getElementIdsByTab(activeTab) {\n const isCreateCategoryTab = activeTab === 'create-category';\n return {\n panelId: isCreateCategoryTab ? 'validationStatusPanelNewCategory' : 'validationStatusPanelExistingCategory',\n contentId: isCreateCategoryTab ? 'validationStatusContentNewCategory' : 'validationStatusContentExistingCategoryTab',\n formId: isCreateCategoryTab ? 'onboardForm' : 'onboardServerForm',\n validateButtonId: isCreateCategoryTab ? 'validateButtonNewCategory' : 'validateButtonExistingCategory',\n publishButtonId: isCreateCategoryTab ? 'publishButtonNewCategory' : 'publishButtonExistingCategory',\n };\n}\n\n/**\n * Ensures the status panel is visible and expanded.\n * @param {string} panelId - The ID of the status panel.\n * @param {string} contentId - The ID of the status content area.\n */\nexport function ensureStatusPanelVisible(panelId, contentId) {\n const statusPanel = document.getElementById(panelId);\n const statusContent = document.getElementById(contentId);\n\n if (!statusPanel || !statusContent) return;\n\n statusPanel.classList.remove('hidden');\n if (statusContent.classList.contains('hidden')) {\n const headerIcon = statusPanel.querySelector('.toggle-icon');\n if (headerIcon && typeof toggleSectionContent === 'function') {\n toggleSectionContent(contentId, headerIcon);\n } else if (headerIcon) {\n console.warn('toggleSectionContent function not available for status panel.');\n statusContent.classList.remove('hidden');\n headerIcon.classList.remove('bx-chevron-down');\n headerIcon.classList.add('bx-chevron-up');\n } else {\n statusContent.classList.remove('hidden');\n }\n }\n}"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["allServerCategoriesData","fetchServerCategories"],"sources":["0"],"sourcesContent":["import { allServerCategoriesData, fetchServerCategories } from './api.js';\r\nimport { showInstallModal } from './modal.js';\r\nimport { showToast, showConfirm } from './notifications.js';\r\nimport { DetailsWidget } from './detailsWidget.js';\r\n\r\nconst REFRESH_INTERVAL = 2000; // 2 seconds\r\nlet refreshTimer = null;\r\nlet activeDetailsWidget = null;\r\nconst MAX_RETRIES = 3;\r\nconst RETRY_DELAY = 1000;\r\n\r\n// Start refresh timer for installation status\r\nfunction startRefreshTimer(serverName) {\r\n if (refreshTimer) {\r\n clearInterval(refreshTimer);\r\n }\r\n\r\n refreshTimer = setInterval(async () => {\r\n try {\r\n const server = allServerCategoriesData.find(s => s.name === serverName);\r\n if (!server?.installationStatus?.serversStatus) return;\r\n\r\n const hasPendingInstallation = Object.values(server.installationStatus.serversStatus).some(serverStatus =>\r\n Object.values(serverStatus.installedStatus || {}).some(status =>\r\n status.status === 'pending' || status.status === 'in-progress'\r\n )\r\n );\r\n\r\n if (hasPendingInstallation) {\r\n await fetchServerCategories();\r\n await showServerDetails(serverName);\r\n } else {\r\n clearInterval(refreshTimer);\r\n }\r\n } catch (error) {\r\n console.error('Error in refresh timer:', error);\r\n clearInterval(refreshTimer);\r\n }\r\n }, REFRESH_INTERVAL);\r\n}\r\n\r\n// Show server details with retry mechanism\r\nasync function showServerDetails(serverName, retryCount = 0) {\r\n console.log(\"Showing details for:\", serverName);\r\n try {\r\n localStorage.setItem('lastSelectedCategory', serverName);\r\n\r\n // If server data is not available, attempt to fetch it\r\n if (allServerCategoriesData.length === 0) {\r\n await fetchServerCategories();\r\n }\r\n\r\n const server = allServerCategoriesData.find(s => s.name === serverName);\r\n const detailsDiv = document.getElementById('serverCategoryDetails');\r\n\r\n if (!server) {\r\n if (retryCount < MAX_RETRIES) {\r\n console.log(`Server data not found, retrying (${retryCount + 1}/${MAX_RETRIES})...`);\r\n await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));\r\n await fetchServerCategories(); // Refresh the data\r\n return showServerDetails(serverName, retryCount + 1);\r\n }\r\n throw new Error('Server data not found after retries');\r\n }\r\n\r\n let pullRequestHtml = '';\r\n if (server.feedConfiguration?.PullRequest) {\r\n pullRequestHtml = `\r\n <p class=\"mb-1 text-sm\">\r\n <span class=\"font-semibold\">Pull Request:</span>\r\n <a href=\"${server.feedConfiguration.PullRequest}\" target=\"_blank\" class=\"text-blue-600 hover:underline\">${server.feedConfiguration.PullRequest}</a>\r\n </p>`;\r\n }\r\n\r\n detailsDiv.innerHTML = `\r\n <h3 class=\"text-xl font-semibold mb-2 text-gray-800\">${server.displayName || server.name}</h3>\r\n <p class=\"mb-1\"><span class=\"font-semibold\">Description:</span> ${server.description || 'N/A'}</p>\r\n ${pullRequestHtml}\r\n <div id=\"toolMcpsList\" class=\"mt-4\">\r\n <div class=\"animate-pulse flex space-x-4\">\r\n <div class=\"flex-1 space-y-4 py-1\">\r\n <div class=\"h-4 bg-gray-200 rounded w-3/4\"></div>\r\n <div class=\"space-y-2\">\r\n <div class=\"h-4 bg-gray-200 rounded\"></div>\r\n <div class=\"h-4 bg-gray-200 rounded w-5/6\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n `;\r\n\r\n const serversListHtml = await renderServersList(server);\r\n const toolMcpsList = document.getElementById('toolMcpsList');\r\n if (!toolMcpsList) {\r\n throw new Error('toolMcpsList element not found');\r\n }\r\n\r\n toolMcpsList.innerHTML = serversListHtml;\r\n\r\n // Setup server items click handlers\r\n const serverItems = document.querySelectorAll('.server-item-content');\r\n console.log(`Found ${serverItems.length} server items`);\r\n\r\n serverItems.forEach(item => {\r\n const mcpServerName = item.dataset.serverName;\r\n console.log(`Setting up click handler for server: ${mcpServerName}`);\r\n\r\n const mcpServer = server.feedConfiguration?.mcpServers?.find(\r\n s => s.name === mcpServerName\r\n );\r\n\r\n if (mcpServer) {\r\n const detailsWidget = new DetailsWidget(item);\r\n\r\n // Initially set description\r\n detailsWidget.setContent(mcpServer.description);\r\n\r\n // Fetch and display schema when expanded\r\n const fetchSchema = async () => {\r\n try {\r\n const response = await fetch(`/api/categories/${server.name}/servers/${mcpServerName}/schema`);\r\n if (!response.ok) return;\r\n\r\n const result = await response.json();\r\n if (result.success && result.data) {\r\n detailsWidget.setContent(result.data);\r\n }\r\n } catch (error) {\r\n console.error('Error fetching schema:', error);\r\n // Silently fail - don't show error in UI\r\n }\r\n };\r\n\r\n item.addEventListener('click', async (event) => {\r\n if (!event.target.closest('button')) {\r\n if (detailsWidget === activeDetailsWidget) {\r\n // Only toggle if clicking the currently active item\r\n detailsWidget.toggle();\r\n if (!detailsWidget.isVisible()) {\r\n activeDetailsWidget = null;\r\n }\r\n } else {\r\n // Expand the clicked item without collapsing others\r\n detailsWidget.expand();\r\n activeDetailsWidget = detailsWidget;\r\n await fetchSchema();\r\n }\r\n }\r\n });\r\n }\r\n });\r\n\r\n startRefreshTimer(serverName);\r\n } catch (error) {\r\n console.error('Error in showServerDetails:', error);\r\n const detailsDiv = document.getElementById('serverCategoryDetails');\r\n if (detailsDiv) {\r\n detailsDiv.innerHTML = `<p class=\"text-red-500\">Error loading server details: ${error.message}</p>`;\r\n }\r\n showToast(`Error loading server details: ${error.message}`, 'error');\r\n }\r\n}\r\n\r\n/**\r\n * Checks if any defined requirement for an MCP server has a pending update.\r\n * An update is pending if a requirement has an `availableUpdate` or if its installed version\r\n * differs from the version defined in the feed.\r\n * @param {object} mcpServer - The MCP server configuration object from the feed.\r\n * @param {object} requirementsStatus - The status of requirements for this server, typically from `installationStatus.requirementsStatus`.\r\n * @returns {{ needsUpdate: boolean, updateMessage: string|null }} - True and message if update is available, else false/null.\r\n */\r\nfunction checkNeedsRequirementUpdate(mcpServer, requirementsStatus) {\r\n const definedReqs = mcpServer?.dependencies?.requirements || [];\r\n if (definedReqs.length === 0) {\r\n return false; // No requirements defined in the feed for this server.\r\n }\r\n\r\n const currentReqsStatus = requirementsStatus || {};\r\n\r\n for (const defReq of definedReqs) {\r\n const alias = defReq.alias || defReq.name;\r\n if (!alias) continue;\r\n\r\n const statusEntry = currentReqsStatus[alias];\r\n\r\n if (statusEntry) {\r\n // Check 1: Explicit 'availableUpdate' information\r\n if (statusEntry.availableUpdate && statusEntry.availableUpdate.version) {\r\n return {needsUpdate: true, updateMessage: `${statusEntry.name}: ${statusEntry.availableUpdate.message}` || null};\r\n }\r\n }\r\n }\r\n return { needsUpdate: false, updateMessage: null };\r\n}\r\n\r\nasync function renderServersList(serverCategory) {\r\n try {\r\n const targetResponse = await fetch('/api/targets');\r\n const targetData = await targetResponse.json();\r\n const availableTargets = targetData.success ? targetData.data : [];\r\n\r\n if (!serverCategory.feedConfiguration?.mcpServers) {\r\n return '<p class=\"text-gray-500\">No MCP Servers found for this server.</p>';\r\n }\r\n\r\n const mcpServers = serverCategory.feedConfiguration.mcpServers;\r\n if (mcpServers.length === 0) {\r\n return '<p class=\"text-gray-500\">No MCP Servers found for this server.</p>';\r\n }\r\n\r\n let toolsHtml = `\r\n <div class=\"flex justify-between items-center mb-4\">\r\n <h2 class=\"text-lg font-semibold text-gray-600\">MCP Servers</h2>\r\n </div>`;\r\n\r\n mcpServers.forEach(mcpServer => {\r\n const isInstalled = mcpServer.installed;\r\n const envRequirements = mcpServer.installation?.['env:'] || mcpServer.installation?.env || {};\r\n const hasEnvRequirements = Object.keys(envRequirements).length > 0;\r\n\r\n toolsHtml += `\r\n <div class=\"server-item-content\" data-server-name=\"${mcpServer.name}\">\r\n <div class=\"server-item-info\" style=\"width: 100%; box-sizing: border-box;\">\r\n <div class=\"server-item-header\">\r\n <div class=\"flex items-center flex-wrap\">\r\n <h5 class=\"font-semibold text-gray-800 mr-2\">${mcpServer.displayName || mcpServer.name}</h5>\r\n ${mcpServer.systemTags && Object.keys(mcpServer.systemTags).length > 0 ? `\r\n <div class=\"flex flex-wrap gap-1 items-center\">\r\n ${Object.entries(mcpServer.systemTags).map(([key, value]) => `<span class=\"text-xs bg-purple-100 text-purple-700 px-2 py-0.5 rounded-full\">${key}: ${value}</span>`).join('')}\r\n </div>\r\n ` : ''}\r\n ${mcpServer.repository || serverCategory.feedConfiguration?.repository ? (() => {\r\n const repoUrl = mcpServer.repository || serverCategory.feedConfiguration?.repository;\r\n const isGithub = repoUrl.toLowerCase().includes('github.com');\r\n return `\r\n <a href=\"${repoUrl}\" target=\"_blank\" class=\"ml-2 flex items-center\">\r\n <span class=\"text-xs px-2 py-1 bg-gray-100 rounded-md flex items-center text-gray-700 hover:bg-gray-200 transition-colors duration-200\">\r\n ${isGithub ? `\r\n <svg class=\"w-4 h-4 mr-1\" fill=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path fill-rule=\"evenodd\" d=\"M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z\" clip-rule=\"evenodd\"/>\r\n </svg>\r\n GitHub` : `\r\n <svg class=\"w-4 h-4 mr-1\" fill=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path d=\"M21.721 12.752a9.711 9.711 0 00-.945-5.003 1.743 1.743 0 01-1.339-1.647c0-.522.236-1.01.62-1.341a9.707 9.707 0 00-3.62-2.087 1.744 1.744 0 01-2.113-1.334A9.721 9.721 0 0012 1.016 9.721 9.721 0 009.676 1.3 1.744 1.744 0 017.562 2.634a9.707 9.707 0 00-3.62 2.087 1.744 1.744 0 00-.048 2.32 9.711 9.711 0 00-.945 5.003 9.712 9.712 0 00.945 5.003 1.744 1.744 0 01.048 2.32 9.707 9.707 0 003.62 2.087 1.744 1.744 0 012.114 1.334A9.721 9.721 0 0012 22.982a9.721 9.721 0 002.324-.284 1.744 1.744 0 012.114-1.334 9.707 9.707 0 003.62-2.087 1.744 1.744 0 01.048-2.32 9.711 9.711 0 00.945-5.003z\"/>\r\n </svg>\r\n Website`}\r\n </span>\r\n </a>`\r\n })() : ''}\r\n </div>\r\n <p class=\"text-sm text-gray-600 mb-1 mt-1\">${mcpServer.description || 'No description'}</p>\r\n </div>\r\n <div class=\"flex flex-col mt-3\">\r\n <span class=\"text-xs font-semibold mb-2\">Client Status:</span>\r\n <div class=\"flex flex-wrap gap-2\">\r\n ${availableTargets.map(client => {\r\n const status = (serverCategory.installationStatus?.serversStatus[mcpServer.name]?.installedStatus || {})[client];\r\n const isInstalled = status?.status === 'completed';\r\n return `\r\n <span class=\"text-xs flex items-center ${isInstalled ? 'text-green-600 bg-green-50' : 'text-gray-600 bg-gray-50'} px-3 py-1 rounded-full shadow\">\r\n <svg class=\"w-3 h-3 mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\">\r\n ${isInstalled\r\n ? '<path fill-rule=\"evenodd\" d=\"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z\" clip-rule=\"evenodd\"></path>'\r\n : '<path d=\"M7 3.5A1.5 1.5 0 018.5 2h3.879a1.5 1.5 0 011.06.44l3.122 3.12A1.5 1.5 0 0117 6.622V12.5a1.5 1.5 0 01-1.5 1.5h-1v-3.379a1.5 1.5 0 00-.44-1.06L10.94 6.44A1.5 1.5 0 009.879 6H7V3.5z M6 6h2.879A1.5 1.5 0 0110 7.5v1.379a1.5 1.5 0 01-.44 1.06L6.44 13.06A1.5 1.5 0 015.379 13H4.5A1.5 1.5 0 013 11.5V7.5A1.5 1.5 0 014.5 6H6z\"></path>'\r\n }\r\n </svg>\r\n ${client}\r\n </span>\r\n `;\r\n }).join('')}\r\n </div>\r\n </div>\r\n <div class=\"action-buttons\">\r\n ${(() => {\r\n const serverInstallationStatus = serverCategory.installationStatus;\r\n const requirementsStatus = serverInstallationStatus?.requirementsStatus;\r\n // dependencies is a simple array of names\r\n const { needsUpdate, updateMessage } = checkNeedsRequirementUpdate(mcpServer, requirementsStatus);\r\n\r\n if (isInstalled) {\r\n return `\r\n <button onclick=\"event.stopPropagation(); window.uninstallTools('${serverCategory.name}', ['${mcpServer.name}'])\"\r\n class=\"bg-red-500 hover:bg-red-700 text-white text-sm font-bold py-2 px-4 rounded-full shadow-sm ease-in-out\">\r\n Uninstall\r\n </button>`;\r\n } else {\r\n let buttonText = 'Setup';\r\n let titleAttr = '';\r\n if (needsUpdate) {\r\n buttonText = 'Update';\r\n if (updateMessage) {\r\n titleAttr = ` title=\"${updateMessage.replace(/\"/g, '\"')}\"`;\r\n }\r\n }\r\n return `\r\n <button onclick=\"event.stopPropagation(); window.showInstallModal('${serverCategory.name}', '${mcpServer.name}')\"\r\n class=\"${needsUpdate ? 'btn-update' : 'bg-blue-500 hover:bg-blue-700'} text-white text-xs font-bold py-2 px-4 rounded-full shadow-sm ease-in-out\"${titleAttr}>\r\n ${buttonText}\r\n </button>`;\r\n }\r\n })()}\r\n </div>\r\n ${hasEnvRequirements ? `<div class=\"mt-3 text-xs text-blue-600\">\r\n <svg class=\"w-3 h-3 inline mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z\" clip-rule=\"evenodd\"></path>\r\n </svg>\r\n Requires environment configuration\r\n </div>` : ''}\r\n </div>\r\n </div>\r\n `;\r\n });\r\n\r\n return toolsHtml;\r\n } catch (error) {\r\n console.error('Error in renderServersList:', error);\r\n throw error;\r\n }\r\n}\r\n\r\nwindow.uninstallTools = async function (serverName, toolList) {\r\n try {\r\n const confirmed = await showConfirm(`Are you sure you want to uninstall ${toolList.length} tool(s)?`);\r\n if (!confirmed) return;\r\n\r\n const response = await fetch(`/api/categories/${serverName}/uninstall`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ toolList })\r\n });\r\n\r\n if (!response.ok) {\r\n const errorData = await response.text();\r\n throw new Error(`Uninstallation failed: ${errorData || response.statusText}`);\r\n }\r\n\r\n const result = await response.json();\r\n if (!result.success) {\r\n throw new Error(result.error || 'Uninstallation failed');\r\n }\r\n\r\n showToast('Tools uninstalled successfully!', 'success');\r\n await fetchServerCategories();\r\n await showServerDetails(serverName);\r\n } catch (error) {\r\n console.error('Error uninstalling tools:', error);\r\n showToast(`Error uninstalling tools: ${error.message}`, 'error');\r\n }\r\n}\r\n\r\nwindow.showServerDetails = async function (serverName) {\r\n try {\r\n await showServerDetails(serverName);\r\n } catch (error) {\r\n console.error('Error in window.showServerDetails:', error);\r\n const detailsDiv = document.getElementById('serverCategoryDetails');\r\n if (detailsDiv) {\r\n detailsDiv.innerHTML = `<p class=\"text-red-500\">Error loading server details: ${error.message}</p>`;\r\n }\r\n showToast(`Error loading server details: ${error.message}`, 'error');\r\n }\r\n};\r\n\r\nexport { showServerDetails };\r\n"],"mappings":"OAASA,wBAAyBC,0BAA6B,kB","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["allServerCategoriesData","fetchServerCategories","showInstallModal","showToast","showConfirm","DetailsWidget","REFRESH_INTERVAL","refreshTimer","activeDetailsWidget","MAX_RETRIES","RETRY_DELAY","startRefreshTimer","serverName","clearInterval","setInterval","async","server","find","s","name","installationStatus","serversStatus","Object","values","some","serverStatus","installedStatus","status","showServerDetails","error","console","retryCount","log","localStorage","setItem","length","detailsDiv","document","getElementById","Promise","resolve","setTimeout","Error","pullRequestHtml","feedConfiguration","PullRequest","innerHTML","displayName","description","serversListHtml","renderServersList","toolMcpsList","serverItems","querySelectorAll","forEach","item","mcpServerName","dataset","mcpServer","mcpServers","detailsWidget","setContent","fetchSchema","response","fetch","ok","result","json","success","data","addEventListener","event","target","closest","toggle","isVisible","expand","message","checkNeedsRequirementUpdate","requirementsStatus","definedReqs","dependencies","requirements","currentReqsStatus","defReq","alias","statusEntry","availableUpdate","version","needsUpdate","updateMessage","serverCategory","targetResponse","targetData","availableTargets","toolsHtml","isInstalled","installed","envRequirements","installation","env","hasEnvRequirements","keys","systemTags","entries","map","key","value","join","repository","repoUrl","isGithub","toLowerCase","includes","client","serverInstallationStatus","buttonText","titleAttr","replace","window","uninstallTools","toolList","method","headers","body","JSON","stringify","errorData","text","statusText"],"sources":["0"],"sourcesContent":["import { allServerCategoriesData, fetchServerCategories } from './api.js';\r\nimport { showInstallModal } from './modal.js';\r\nimport { showToast, showConfirm } from './notifications.js';\r\nimport { DetailsWidget } from './detailsWidget.js';\r\n\r\nconst REFRESH_INTERVAL = 2000; // 2 seconds\r\nlet refreshTimer = null;\r\nlet activeDetailsWidget = null;\r\nconst MAX_RETRIES = 3;\r\nconst RETRY_DELAY = 1000;\r\n\r\n// Start refresh timer for installation status\r\nfunction startRefreshTimer(serverName) {\r\n if (refreshTimer) {\r\n clearInterval(refreshTimer);\r\n }\r\n\r\n refreshTimer = setInterval(async () => {\r\n try {\r\n const server = allServerCategoriesData.find(s => s.name === serverName);\r\n if (!server?.installationStatus?.serversStatus) return;\r\n\r\n const hasPendingInstallation = Object.values(server.installationStatus.serversStatus).some(serverStatus =>\r\n Object.values(serverStatus.installedStatus || {}).some(status =>\r\n status.status === 'pending' || status.status === 'in-progress'\r\n )\r\n );\r\n\r\n if (hasPendingInstallation) {\r\n await fetchServerCategories();\r\n await showServerDetails(serverName);\r\n } else {\r\n clearInterval(refreshTimer);\r\n }\r\n } catch (error) {\r\n console.error('Error in refresh timer:', error);\r\n clearInterval(refreshTimer);\r\n }\r\n }, REFRESH_INTERVAL);\r\n}\r\n\r\n// Show server details with retry mechanism\r\nasync function showServerDetails(serverName, retryCount = 0) {\r\n console.log(\"Showing details for:\", serverName);\r\n try {\r\n localStorage.setItem('lastSelectedCategory', serverName);\r\n\r\n // If server data is not available, attempt to fetch it\r\n if (allServerCategoriesData.length === 0) {\r\n await fetchServerCategories();\r\n }\r\n\r\n const server = allServerCategoriesData.find(s => s.name === serverName);\r\n const detailsDiv = document.getElementById('serverCategoryDetails');\r\n\r\n if (!server) {\r\n if (retryCount < MAX_RETRIES) {\r\n console.log(`Server data not found, retrying (${retryCount + 1}/${MAX_RETRIES})...`);\r\n await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));\r\n await fetchServerCategories(); // Refresh the data\r\n return showServerDetails(serverName, retryCount + 1);\r\n }\r\n throw new Error('Server data not found after retries');\r\n }\r\n\r\n let pullRequestHtml = '';\r\n if (server.feedConfiguration?.PullRequest) {\r\n pullRequestHtml = `\r\n <p class=\"mb-1 text-sm\">\r\n <span class=\"font-semibold\">Pull Request:</span>\r\n <a href=\"${server.feedConfiguration.PullRequest}\" target=\"_blank\" class=\"text-blue-600 hover:underline\">${server.feedConfiguration.PullRequest}</a>\r\n </p>`;\r\n }\r\n\r\n detailsDiv.innerHTML = `\r\n <h3 class=\"text-xl font-semibold mb-2 text-gray-800\">${server.displayName || server.name}</h3>\r\n <p class=\"mb-1\"><span class=\"font-semibold\">Description:</span> ${server.description || 'N/A'}</p>\r\n ${pullRequestHtml}\r\n <div id=\"toolMcpsList\" class=\"mt-4\">\r\n <div class=\"animate-pulse flex space-x-4\">\r\n <div class=\"flex-1 space-y-4 py-1\">\r\n <div class=\"h-4 bg-gray-200 rounded w-3/4\"></div>\r\n <div class=\"space-y-2\">\r\n <div class=\"h-4 bg-gray-200 rounded\"></div>\r\n <div class=\"h-4 bg-gray-200 rounded w-5/6\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n `;\r\n\r\n const serversListHtml = await renderServersList(server);\r\n const toolMcpsList = document.getElementById('toolMcpsList');\r\n if (!toolMcpsList) {\r\n throw new Error('toolMcpsList element not found');\r\n }\r\n\r\n toolMcpsList.innerHTML = serversListHtml;\r\n\r\n // Setup server items click handlers\r\n const serverItems = document.querySelectorAll('.server-item-content');\r\n console.log(`Found ${serverItems.length} server items`);\r\n\r\n serverItems.forEach(item => {\r\n const mcpServerName = item.dataset.serverName;\r\n console.log(`Setting up click handler for server: ${mcpServerName}`);\r\n\r\n const mcpServer = server.feedConfiguration?.mcpServers?.find(\r\n s => s.name === mcpServerName\r\n );\r\n\r\n if (mcpServer) {\r\n const detailsWidget = new DetailsWidget(item);\r\n\r\n // Initially set description\r\n detailsWidget.setContent(mcpServer.description);\r\n\r\n // Fetch and display schema when expanded\r\n const fetchSchema = async () => {\r\n try {\r\n const response = await fetch(`/api/categories/${server.name}/servers/${mcpServerName}/schema`);\r\n if (!response.ok) return;\r\n\r\n const result = await response.json();\r\n if (result.success && result.data) {\r\n detailsWidget.setContent(result.data);\r\n }\r\n } catch (error) {\r\n console.error('Error fetching schema:', error);\r\n // Silently fail - don't show error in UI\r\n }\r\n };\r\n\r\n item.addEventListener('click', async (event) => {\r\n if (!event.target.closest('button')) {\r\n if (detailsWidget === activeDetailsWidget) {\r\n // Only toggle if clicking the currently active item\r\n detailsWidget.toggle();\r\n if (!detailsWidget.isVisible()) {\r\n activeDetailsWidget = null;\r\n }\r\n } else {\r\n // Expand the clicked item without collapsing others\r\n detailsWidget.expand();\r\n activeDetailsWidget = detailsWidget;\r\n await fetchSchema();\r\n }\r\n }\r\n });\r\n }\r\n });\r\n\r\n startRefreshTimer(serverName);\r\n } catch (error) {\r\n console.error('Error in showServerDetails:', error);\r\n const detailsDiv = document.getElementById('serverCategoryDetails');\r\n if (detailsDiv) {\r\n detailsDiv.innerHTML = `<p class=\"text-red-500\">Error loading server details: ${error.message}</p>`;\r\n }\r\n showToast(`Error loading server details: ${error.message}`, 'error');\r\n }\r\n}\r\n\r\n/**\r\n * Checks if any defined requirement for an MCP server has a pending update.\r\n * An update is pending if a requirement has an `availableUpdate` or if its installed version\r\n * differs from the version defined in the feed.\r\n * @param {object} mcpServer - The MCP server configuration object from the feed.\r\n * @param {object} requirementsStatus - The status of requirements for this server, typically from `installationStatus.requirementsStatus`.\r\n * @returns {{ needsUpdate: boolean, updateMessage: string|null }} - True and message if update is available, else false/null.\r\n */\r\nfunction checkNeedsRequirementUpdate(mcpServer, requirementsStatus) {\r\n const definedReqs = mcpServer?.dependencies?.requirements || [];\r\n if (definedReqs.length === 0) {\r\n return false; // No requirements defined in the feed for this server.\r\n }\r\n\r\n const currentReqsStatus = requirementsStatus || {};\r\n\r\n for (const defReq of definedReqs) {\r\n const alias = defReq.alias || defReq.name;\r\n if (!alias) continue;\r\n\r\n const statusEntry = currentReqsStatus[alias];\r\n\r\n if (statusEntry) {\r\n // Check 1: Explicit 'availableUpdate' information\r\n if (statusEntry.availableUpdate && statusEntry.availableUpdate.version) {\r\n return {needsUpdate: true, updateMessage: `${statusEntry.name}: ${statusEntry.availableUpdate.message}` || null};\r\n }\r\n }\r\n }\r\n return { needsUpdate: false, updateMessage: null };\r\n}\r\n\r\nasync function renderServersList(serverCategory) {\r\n try {\r\n const targetResponse = await fetch('/api/targets');\r\n const targetData = await targetResponse.json();\r\n const availableTargets = targetData.success ? targetData.data : [];\r\n\r\n if (!serverCategory.feedConfiguration?.mcpServers) {\r\n return '<p class=\"text-gray-500\">No MCP Servers found for this server.</p>';\r\n }\r\n\r\n const mcpServers = serverCategory.feedConfiguration.mcpServers;\r\n if (mcpServers.length === 0) {\r\n return '<p class=\"text-gray-500\">No MCP Servers found for this server.</p>';\r\n }\r\n\r\n let toolsHtml = `\r\n <div class=\"flex justify-between items-center mb-4\">\r\n <h2 class=\"text-lg font-semibold text-gray-600\">MCP Servers</h2>\r\n </div>`;\r\n\r\n mcpServers.forEach(mcpServer => {\r\n const isInstalled = mcpServer.installed;\r\n const envRequirements = mcpServer.installation?.['env:'] || mcpServer.installation?.env || {};\r\n const hasEnvRequirements = Object.keys(envRequirements).length > 0;\r\n\r\n toolsHtml += `\r\n <div class=\"server-item-content\" data-server-name=\"${mcpServer.name}\">\r\n <div class=\"server-item-info\" style=\"width: 100%; box-sizing: border-box;\">\r\n <div class=\"server-item-header\">\r\n <div class=\"flex items-center flex-wrap\">\r\n <h5 class=\"font-semibold text-gray-800 mr-2\">${mcpServer.displayName || mcpServer.name}</h5>\r\n ${mcpServer.systemTags && Object.keys(mcpServer.systemTags).length > 0 ? `\r\n <div class=\"flex flex-wrap gap-1 items-center\">\r\n ${Object.entries(mcpServer.systemTags).map(([key, value]) => `<span class=\"text-xs bg-purple-100 text-purple-700 px-2 py-0.5 rounded-full\">${key}: ${value}</span>`).join('')}\r\n </div>\r\n ` : ''}\r\n ${mcpServer.repository || serverCategory.feedConfiguration?.repository ? (() => {\r\n const repoUrl = mcpServer.repository || serverCategory.feedConfiguration?.repository;\r\n const isGithub = repoUrl.toLowerCase().includes('github.com');\r\n return `\r\n <a href=\"${repoUrl}\" target=\"_blank\" class=\"ml-2 flex items-center\">\r\n <span class=\"text-xs px-2 py-1 bg-gray-100 rounded-md flex items-center text-gray-700 hover:bg-gray-200 transition-colors duration-200\">\r\n ${isGithub ? `\r\n <svg class=\"w-4 h-4 mr-1\" fill=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path fill-rule=\"evenodd\" d=\"M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z\" clip-rule=\"evenodd\"/>\r\n </svg>\r\n GitHub` : `\r\n <svg class=\"w-4 h-4 mr-1\" fill=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path d=\"M21.721 12.752a9.711 9.711 0 00-.945-5.003 1.743 1.743 0 01-1.339-1.647c0-.522.236-1.01.62-1.341a9.707 9.707 0 00-3.62-2.087 1.744 1.744 0 01-2.113-1.334A9.721 9.721 0 0012 1.016 9.721 9.721 0 009.676 1.3 1.744 1.744 0 017.562 2.634a9.707 9.707 0 00-3.62 2.087 1.744 1.744 0 00-.048 2.32 9.711 9.711 0 00-.945 5.003 9.712 9.712 0 00.945 5.003 1.744 1.744 0 01.048 2.32 9.707 9.707 0 003.62 2.087 1.744 1.744 0 012.114 1.334A9.721 9.721 0 0012 22.982a9.721 9.721 0 002.324-.284 1.744 1.744 0 012.114-1.334 9.707 9.707 0 003.62-2.087 1.744 1.744 0 01.048-2.32 9.711 9.711 0 00.945-5.003z\"/>\r\n </svg>\r\n Website`}\r\n </span>\r\n </a>`\r\n })() : ''}\r\n </div>\r\n <p class=\"text-sm text-gray-600 mb-1 mt-1\">${mcpServer.description || 'No description'}</p>\r\n </div>\r\n <div class=\"flex flex-col mt-3\">\r\n <span class=\"text-xs font-semibold mb-2\">Client Status:</span>\r\n <div class=\"flex flex-wrap gap-2\">\r\n ${availableTargets.map(client => {\r\n const status = (serverCategory.installationStatus?.serversStatus[mcpServer.name]?.installedStatus || {})[client];\r\n const isInstalled = status?.status === 'completed';\r\n return `\r\n <span class=\"text-xs flex items-center ${isInstalled ? 'text-green-600 bg-green-50' : 'text-gray-600 bg-gray-50'} px-3 py-1 rounded-full shadow\">\r\n <svg class=\"w-3 h-3 mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\">\r\n ${isInstalled\r\n ? '<path fill-rule=\"evenodd\" d=\"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z\" clip-rule=\"evenodd\"></path>'\r\n : '<path d=\"M7 3.5A1.5 1.5 0 018.5 2h3.879a1.5 1.5 0 011.06.44l3.122 3.12A1.5 1.5 0 0117 6.622V12.5a1.5 1.5 0 01-1.5 1.5h-1v-3.379a1.5 1.5 0 00-.44-1.06L10.94 6.44A1.5 1.5 0 009.879 6H7V3.5z M6 6h2.879A1.5 1.5 0 0110 7.5v1.379a1.5 1.5 0 01-.44 1.06L6.44 13.06A1.5 1.5 0 015.379 13H4.5A1.5 1.5 0 013 11.5V7.5A1.5 1.5 0 014.5 6H6z\"></path>'\r\n }\r\n </svg>\r\n ${client}\r\n </span>\r\n `;\r\n }).join('')}\r\n </div>\r\n </div>\r\n <div class=\"action-buttons\">\r\n ${(() => {\r\n const serverInstallationStatus = serverCategory.installationStatus;\r\n const requirementsStatus = serverInstallationStatus?.requirementsStatus;\r\n // dependencies is a simple array of names\r\n const { needsUpdate, updateMessage } = checkNeedsRequirementUpdate(mcpServer, requirementsStatus);\r\n\r\n if (isInstalled) {\r\n return `\r\n <button onclick=\"event.stopPropagation(); window.uninstallTools('${serverCategory.name}', ['${mcpServer.name}'])\"\r\n class=\"bg-red-500 hover:bg-red-700 text-white text-sm font-bold py-2 px-4 rounded-full shadow-sm ease-in-out\">\r\n Uninstall\r\n </button>`;\r\n } else {\r\n let buttonText = 'Setup';\r\n let titleAttr = '';\r\n if (needsUpdate) {\r\n buttonText = 'Update';\r\n if (updateMessage) {\r\n titleAttr = ` title=\"${updateMessage.replace(/\"/g, '\"')}\"`;\r\n }\r\n }\r\n return `\r\n <button onclick=\"event.stopPropagation(); window.showInstallModal('${serverCategory.name}', '${mcpServer.name}')\"\r\n class=\"${needsUpdate ? 'btn-update' : 'bg-blue-500 hover:bg-blue-700'} text-white text-xs font-bold py-2 px-4 rounded-full shadow-sm ease-in-out\"${titleAttr}>\r\n ${buttonText}\r\n </button>`;\r\n }\r\n })()}\r\n </div>\r\n ${hasEnvRequirements ? `<div class=\"mt-3 text-xs text-blue-600\">\r\n <svg class=\"w-3 h-3 inline mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z\" clip-rule=\"evenodd\"></path>\r\n </svg>\r\n Requires environment configuration\r\n </div>` : ''}\r\n </div>\r\n </div>\r\n `;\r\n });\r\n\r\n return toolsHtml;\r\n } catch (error) {\r\n console.error('Error in renderServersList:', error);\r\n throw error;\r\n }\r\n}\r\n\r\nwindow.uninstallTools = async function (serverName, toolList) {\r\n try {\r\n const confirmed = await showConfirm(`Are you sure you want to uninstall ${toolList.length} tool(s)?`);\r\n if (!confirmed) return;\r\n\r\n const response = await fetch(`/api/categories/${serverName}/uninstall`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ toolList })\r\n });\r\n\r\n if (!response.ok) {\r\n const errorData = await response.text();\r\n throw new Error(`Uninstallation failed: ${errorData || response.statusText}`);\r\n }\r\n\r\n const result = await response.json();\r\n if (!result.success) {\r\n throw new Error(result.error || 'Uninstallation failed');\r\n }\r\n\r\n showToast('Tools uninstalled successfully!', 'success');\r\n await fetchServerCategories();\r\n await showServerDetails(serverName);\r\n } catch (error) {\r\n console.error('Error uninstalling tools:', error);\r\n showToast(`Error uninstalling tools: ${error.message}`, 'error');\r\n }\r\n}\r\n\r\nwindow.showServerDetails = async function (serverName) {\r\n try {\r\n await showServerDetails(serverName);\r\n } catch (error) {\r\n console.error('Error in window.showServerDetails:', error);\r\n const detailsDiv = document.getElementById('serverCategoryDetails');\r\n if (detailsDiv) {\r\n detailsDiv.innerHTML = `<p class=\"text-red-500\">Error loading server details: ${error.message}</p>`;\r\n }\r\n showToast(`Error loading server details: ${error.message}`, 'error');\r\n }\r\n};\r\n\r\nexport { showServerDetails };\r\n"],"mappings":"OAASA,wBAAyBC,0BAA6B,kBACtDC,qBAAwB,oBACxBC,UAAWC,gBAAmB,4BAC9BC,kBAAqB,qBAE9B,MAAMC,iBAAmB,IACzB,IAAIC,aAAe,KACfC,oBAAsB,KAC1B,MAAMC,YAAc,EACdC,YAAc,IAGpB,SAASC,kBAAkBC,GACnBL,cACAM,cAAcN,cAGlBA,aAAeO,aAAYC,UACvB,IACI,MAAMC,EAAShB,wBAAwBiB,MAAKC,GAAKA,EAAEC,OAASP,IAC5D,IAAKI,GAAQI,oBAAoBC,cAAe,OAEjBC,OAAOC,OAAOP,EAAOI,mBAAmBC,eAAeG,MAAKC,GACvFH,OAAOC,OAAOE,EAAaC,iBAAmB,CAAC,GAAGF,MAAKG,GACjC,YAAlBA,EAAOA,QAA0C,gBAAlBA,EAAOA,kBAKpC1B,8BACA2B,kBAAkBhB,IAExBC,cAAcN,aAEtB,CAAE,MAAOsB,GACLC,QAAQD,MAAM,0BAA2BA,GACzChB,cAAcN,aAClB,IAhCiB,IAkCzB,CAGAQ,eAAea,kBAAkBhB,EAAYmB,EAAa,GACtDD,QAAQE,IAAI,uBAAwBpB,GACpC,IACIqB,aAAaC,QAAQ,uBAAwBtB,GAGN,IAAnCZ,wBAAwBmC,cAClBlC,wBAGV,MAAMe,EAAShB,wBAAwBiB,MAAKC,GAAKA,EAAEC,OAASP,IACtDwB,EAAaC,SAASC,eAAe,yBAE3C,IAAKtB,EAAQ,CACT,GAAIe,EAhDI,EAoDJ,OAHAD,QAAQE,IAAI,oCAAoCD,EAAa,iBACvD,IAAIQ,SAAQC,GAAWC,WAAWD,EAjDpC,aAkDEvC,wBACC2B,kBAAkBhB,EAAYmB,EAAa,GAEtD,MAAM,IAAIW,MAAM,sCACpB,CAEA,IAAIC,EAAkB,GAClB3B,EAAO4B,mBAAmBC,cAC1BF,EAAkB,sIAGH3B,EAAO4B,kBAAkBC,sEAAsE7B,EAAO4B,kBAAkBC,qCAI3IT,EAAWU,UAAY,sEACoC9B,EAAO+B,aAAe/B,EAAOG,0FAClBH,EAAOgC,aAAe,0BACtFL,ijBAcN,MAAMM,QAAwBC,kBAAkBlC,GAC1CmC,EAAed,SAASC,eAAe,gBAC7C,IAAKa,EACD,MAAM,IAAIT,MAAM,kCAGpBS,EAAaL,UAAYG,EAGzB,MAAMG,EAAcf,SAASgB,iBAAiB,wBAC9CvB,QAAQE,IAAI,SAASoB,EAAYjB,uBAEjCiB,EAAYE,SAAQC,IAChB,MAAMC,EAAgBD,EAAKE,QAAQ7C,WACnCkB,QAAQE,IAAI,wCAAwCwB,KAEpD,MAAME,EAAY1C,EAAO4B,mBAAmBe,YAAY1C,MACpDC,GAAKA,EAAEC,OAASqC,IAGpB,GAAIE,EAAW,CACX,MAAME,EAAgB,IAAIvD,cAAckD,GAGxCK,EAAcC,WAAWH,EAAUV,aAGnC,MAAMc,YAAc/C,UAChB,IACI,MAAMgD,QAAiBC,MAAM,mBAAmBhD,EAAOG,gBAAgBqC,YACvE,IAAKO,EAASE,GAAI,OAElB,MAAMC,QAAeH,EAASI,OAC1BD,EAAOE,SAAWF,EAAOG,MACzBT,EAAcC,WAAWK,EAAOG,KAExC,CAAE,MAAOxC,GACLC,QAAQD,MAAM,yBAA0BA,EAE5C,GAGJ0B,EAAKe,iBAAiB,SAASvD,MAAOwD,IAC7BA,EAAMC,OAAOC,QAAQ,YAClBb,IAAkBpD,qBAElBoD,EAAcc,SACTd,EAAce,cACfnE,oBAAsB,QAI1BoD,EAAcgB,SACdpE,oBAAsBoD,QAChBE,eAEd,GAER,KAGJnD,kBAAkBC,EACtB,CAAE,MAAOiB,GACLC,QAAQD,MAAM,8BAA+BA,GAC7C,MAAMO,EAAaC,SAASC,eAAe,yBACvCF,IACAA,EAAWU,UAAY,yDAAyDjB,EAAMgD,eAE1F1E,UAAU,iCAAiC0B,EAAMgD,UAAW,QAChE,CACJ,CAUA,SAASC,4BAA4BpB,EAAWqB,GAC5C,MAAMC,EAActB,GAAWuB,cAAcC,cAAgB,GAC7D,GAA2B,IAAvBF,EAAY7C,OACZ,OAAO,EAGX,MAAMgD,EAAoBJ,GAAsB,CAAC,EAEjD,IAAK,MAAMK,KAAUJ,EAAa,CAC9B,MAAMK,EAAQD,EAAOC,OAASD,EAAOjE,KACrC,IAAKkE,EAAO,SAEZ,MAAMC,EAAcH,EAAkBE,GAEtC,GAAIC,GAEIA,EAAYC,iBAAmBD,EAAYC,gBAAgBC,QAC3D,MAAO,CAACC,aAAa,EAAMC,cAAe,GAAGJ,EAAYnE,SAASmE,EAAYC,gBAAgBV,WAAa,KAGvH,CACA,MAAO,CAAEY,aAAa,EAAOC,cAAe,KAChD,CAEA3E,eAAemC,kBAAkByC,GAC7B,IACI,MAAMC,QAAuB5B,MAAM,gBAC7B6B,QAAmBD,EAAezB,OAClC2B,EAAmBD,EAAWzB,QAAUyB,EAAWxB,KAAO,GAEhE,IAAKsB,EAAe/C,mBAAmBe,WACnC,MAAO,qEAGX,MAAMA,EAAagC,EAAe/C,kBAAkBe,WACpD,GAA0B,IAAtBA,EAAWxB,OACX,MAAO,qEAGX,IAAI4D,EAAY,2KAuGhB,OAlGApC,EAAWL,SAAQI,IACf,MAAMsC,EAActC,EAAUuC,UACxBC,EAAkBxC,EAAUyC,eAAe,SAAWzC,EAAUyC,cAAcC,KAAO,CAAC,EACtFC,EAAqB/E,OAAOgF,KAAKJ,GAAiB/D,OAAS,EAEjE4D,GAAa,wEAC4CrC,EAAUvC,0TAIAuC,EAAUX,aAAeW,EAAUvC,8CAChFuC,EAAU6C,YAAcjF,OAAOgF,KAAK5C,EAAU6C,YAAYpE,OAAS,EAAI,0HAEnEb,OAAOkF,QAAQ9C,EAAU6C,YAAYE,KAAI,EAAEC,EAAKC,KAAW,gFAAgFD,MAAQC,aAAgBC,KAAK,gFAE1K,uCACFlD,EAAUmD,YAAclB,EAAe/C,mBAAmBiE,WAAa,MACrF,MAAMC,EAAUpD,EAAUmD,YAAclB,EAAe/C,mBAAmBiE,WACpEE,EAAWD,EAAQE,cAAcC,SAAS,cAChD,MAAO,kDACoBH,qRAEDC,EAAW,ojCAIH,k+BAOrC,EAjBwF,GAiBlF,kHAEkDrD,EAAUV,aAAe,+SAKhE8C,EAAiBW,KAAIS,IACnC,MAAMvF,GAAUgE,EAAevE,oBAAoBC,cAAcqC,EAAUvC,OAAOO,iBAAmB,CAAC,GAAGwF,GACnGlB,EAAiC,cAAnBrE,GAAQA,OAC5B,MAAO,oFACsDqE,EAAc,6BAA+B,kQAE5EA,EACpB,+KACA,qbAGgBkB,0FAET,IAClBN,KAAK,8JAIM,MACE,MAAMO,EAA2BxB,EAAevE,mBAC1C2D,EAAqBoC,GAA0BpC,oBAE/CU,YAAEA,EAAWC,cAAEA,GAAkBZ,4BAA4BpB,EAAWqB,GAE9E,GAAIiB,EACA,MAAO,0GAC4DL,EAAexE,YAAYuC,EAAUvC,yQAIrG,CACH,IAAIiG,EAAa,QACbC,EAAY,GAOhB,OANI5B,IACA2B,EAAa,SACT1B,IACA2B,EAAY,WAAW3B,EAAc4B,QAAQ,KAAM,UAGpD,4GAC8D3B,EAAexE,WAAWuC,EAAUvC,+DACxFsE,EAAc,aAAe,6GAA6G4B,gDACpJD,mDAEX,CACH,EA3BC,+DA6BJf,EAAqB,qhBAKb,sEAGrB,IAGEN,CACX,CAAE,MAAOlE,GAEL,MADAC,QAAQD,MAAM,8BAA+BA,GACvCA,CACV,CACJ,CAEA0F,OAAOC,eAAiBzG,eAAgBH,EAAY6G,GAChD,IAEI,UADwBrH,YAAY,sCAAsCqH,EAAStF,mBACnE,OAEhB,MAAM4B,QAAiBC,MAAM,mBAAmBpD,cAAwB,CACpE8G,OAAQ,OACRC,QAAS,CAAE,eAAgB,oBAC3BC,KAAMC,KAAKC,UAAU,CAAEL,eAG3B,IAAK1D,EAASE,GAAI,CACd,MAAM8D,QAAkBhE,EAASiE,OACjC,MAAM,IAAItF,MAAM,0BAA0BqF,GAAahE,EAASkE,aACpE,CAEA,MAAM/D,QAAeH,EAASI,OAC9B,IAAKD,EAAOE,QACR,MAAM,IAAI1B,MAAMwB,EAAOrC,OAAS,yBAGpC1B,UAAU,kCAAmC,iBACvCF,8BACA2B,kBAAkBhB,EAC5B,CAAE,MAAOiB,GACLC,QAAQD,MAAM,4BAA6BA,GAC3C1B,UAAU,6BAA6B0B,EAAMgD,UAAW,QAC5D,CACJ,EAEA0C,OAAO3F,kBAAoBb,eAAgBH,GACvC,UACUgB,kBAAkBhB,EAC5B,CAAE,MAAOiB,GACLC,QAAQD,MAAM,qCAAsCA,GACpD,MAAMO,EAAaC,SAASC,eAAe,yBACvCF,IACAA,EAAWU,UAAY,yDAAyDjB,EAAMgD,eAE1F1E,UAAU,iCAAiC0B,EAAMgD,UAAW,QAChE,CACJ,SAESjD","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["allServerCategoriesData","fetchServerCategories"],"sources":["0"],"sourcesContent":["import { allServerCategoriesData, fetchServerCategories } from './api.js';\r\nimport { showServerDetails } from './serverCategoryDetails.js'; // Still needed for initial load and direct calls\r\nimport { showToast } from './notifications.js';\r\nimport { buildUrlWithFlights } from './flights/flights.js';\r\n\r\n// Store state for categories\r\nlet pinnedCategories = {};\r\n\r\n// Wait for data to be loaded\r\nasync function waitForData() {\r\n if (allServerCategoriesData && allServerCategoriesData.length > 0) {\r\n return true;\r\n }\r\n await fetchServerCategories();\r\n return allServerCategoriesData && allServerCategoriesData.length > 0;\r\n}\r\n\r\n// Load pinned state from localStorage\r\nfunction loadPinnedState() {\r\n const saved = localStorage.getItem('pinnedCategories');\r\n if (saved) {\r\n try {\r\n pinnedCategories = JSON.parse(saved);\r\n } catch (e) {\r\n console.error('Error parsing pinned categories from localStorage:', e);\r\n pinnedCategories = {};\r\n }\r\n }\r\n}\r\n\r\n// Save pinned state to localStorage\r\nfunction savePinnedState() {\r\n localStorage.setItem('pinnedCategories', JSON.stringify(pinnedCategories));\r\n}\r\n\r\n// Toggle pin status for a category\r\nfunction togglePinCategory(categoryName, event) {\r\n // Stop propagation to prevent navigation\r\n event.stopPropagation();\r\n \r\n // Toggle pin status\r\n if (pinnedCategories[categoryName]) {\r\n delete pinnedCategories[categoryName];\r\n } else {\r\n pinnedCategories[categoryName] = true;\r\n }\r\n \r\n // Save to localStorage\r\n savePinnedState();\r\n \r\n // Find the server item element and add a temporary animation class\r\n const serverItem = event.target.closest('.server-item');\r\n if (serverItem) {\r\n serverItem.classList.add('pin-animation');\r\n setTimeout(() => {\r\n serverItem.classList.remove('pin-animation');\r\n }, 300); // Animation duration\r\n }\r\n \r\n // Re-render list with updated pin status\r\n if (allServerCategoriesData && allServerCategoriesData.length > 0) {\r\n renderServerCategoryList(allServerCategoriesData);\r\n }\r\n}\r\n\r\n// Function to show the last selected category on page load\r\nasync function loadLastSelectedCategory() {\r\n const lastSelected = localStorage.getItem('lastSelectedCategory');\r\n if (lastSelected && allServerCategoriesData) {\r\n // Check if the category still exists\r\n const categoryExists = allServerCategoriesData.some(server => server.name === lastSelected);\r\n if (categoryExists) {\r\n await showServerDetails(lastSelected);\r\n }\r\n }\r\n}\r\n\r\n// Function to render server list (used by fetchServerCategories and search)\r\nfunction renderServerCategoryList(servers) {\r\n const serverCategoryList = document.getElementById('serverCategoryList');\r\n\r\n if (servers.length === 0) {\r\n serverCategoryList.innerHTML = '<p class=\"text-gray-500\">No servers found matching search.</p>';\r\n document.getElementById('serverCategoryDetails').innerHTML = '<p>Select a server from the list to see details.</p>'; // Clear details\r\n return;\r\n }\r\n \r\n // Load pinned state\r\n loadPinnedState();\r\n \r\n // Create a copy of the servers array to avoid modifying the original\r\n const sortedServers = [...servers];\r\n \r\n // Sort servers with pinned ones at the top\r\n sortedServers.sort((a, b) => {\r\n const isPinnedA = pinnedCategories[a.name] === true;\r\n const isPinnedB = pinnedCategories[b.name] === true;\r\n \r\n if (isPinnedA && !isPinnedB) return -1;\r\n if (!isPinnedA && isPinnedB) return 1;\r\n return 0; // Keep original order for items with same pin status\r\n });\r\n\r\n serverCategoryList.innerHTML = sortedServers.map(server => {\r\n let statusHtml = '';\r\n\r\n // Add tool status summary if available\r\n if (server.installationStatus && server.installationStatus.serversStatus) {\r\n const totalTools = Object.keys(server.installationStatus.serversStatus).length;\r\n const installedTools = Object.values(server.installationStatus.serversStatus)\r\n .filter(serverStatus =>\r\n Object.values(serverStatus.installedStatus || {})\r\n .some(opStatus => opStatus.status === 'completed' && opStatus.type === 'install')\r\n ).length;\r\n\r\n if (totalTools > 0) {\r\n let colorClass, icon, statusText;\r\n\r\n if (installedTools === totalTools) {\r\n // All tools installed\r\n colorClass = \"text-green-600 bg-green-50\";\r\n icon = '<svg class=\"w-5 h-5 mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\" clip-rule=\"evenodd\"></path></svg>';\r\n statusText = \"Fully Configured\";\r\n } else if (installedTools > 0) {\r\n // Some tools installed\r\n colorClass = \"text-green-600 bg-green-50\";\r\n icon = '<svg class=\"w-5 h-5 mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\" clip-rule=\"evenodd\"></path></svg>';\r\n statusText = \"Partial Configured\";\r\n } else {\r\n // No tools installed\r\n colorClass = \"text-yellow-600 bg-orange-50\";\r\n icon = '<svg class=\"w-5 h-5 mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" d=\"M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM10 13a1 1 0 110-2 1 1 0 010 2zm-1.75-5.75a.75.75 0 00-1.5 0v3a.75.75 0 001.5 0v-3z\" clip-rule=\"evenodd\" /></svg>';\r\n statusText = \"Not Configured\";\r\n }\r\n\r\n statusHtml = `<span class=\"${colorClass} inline-flex items-center px-2 py-1 rounded-full text-xs\" style=\"width: fit-content; max-width: 100%;\">\r\n ${icon}<span class=\"truncate\">${statusText} (${installedTools}/${totalTools})</span>\r\n </span>`;\r\n }\r\n }\r\n\r\n let systemTagsHtml = '';\r\n if (server.feedConfiguration?.systemTags && Object.keys(server.feedConfiguration.systemTags).length > 0) {\r\n systemTagsHtml += '<div class=\"flex flex-wrap gap-1 ml-2\">'; // Added ml-2 for spacing\r\n for (const [key, value] of Object.entries(server.feedConfiguration.systemTags)) {\r\n systemTagsHtml += `<span class=\"text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full\">${key}: ${value}</span>`;\r\n }\r\n systemTagsHtml += '</div>';\r\n }\r\n // Check if this server is pinned\r\n const isPinned = pinnedCategories[server.name] === true;\r\n const pinnedClass = isPinned ? 'pinned' : '';\r\n \r\n // Pin/unpin button with appropriate icon\r\n const pinIcon = isPinned \r\n ? '<i class=\"bx bxs-pin\"></i>' // Solid pin icon for pinned items\r\n : '<i class=\"bx bx-pin\"></i>'; // Outline pin icon for unpinned items\r\n\r\n return `\r\n <div class=\"server-item border border-gray-200 p-3 rounded hover:bg-gray-50 cursor-pointer transition duration-150 ease-in-out ${pinnedClass}\"\r\n data-server-name=\"${server.name}\" onclick=\"navigateToCategory('${server.name}')\">\r\n <div class=\"flex justify-between items-center\">\r\n <h3 class=\"font-semibold text-gray-800\">${server.displayName || server.name}</h3>\r\n <div class=\"flex items-center\">\r\n <div class=\"pin-button ${pinnedClass}\" onclick=\"togglePinCategoryItem('${server.name}', event)\" title=\"${isPinned ? 'Unpin' : 'Pin'} this category\">\r\n ${pinIcon}\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"text-sm text-gray-500 flex items-center mt-1\">\r\n ${statusHtml}\r\n ${systemTagsHtml}\r\n </div>\r\n </div>\r\n `;\r\n }).join('');\r\n\r\n // After rendering, try to load the last selected category\r\n loadLastSelectedCategory();\r\n}\r\n\r\n// Setup search functionality\r\nfunction setupSearch() {\r\n const searchBox = document.getElementById('searchBox');\r\n \r\n // Load pinned state on page load\r\n loadPinnedState();\r\n\r\n searchBox.addEventListener('input', async function () {\r\n const searchTerm = this.value.toLowerCase();\r\n\r\n try {\r\n // Ensure data is loaded\r\n if (!(await waitForData())) {\r\n showToast('Error: Unable to load server data', 'error');\r\n return;\r\n }\r\n\r\n // Filter the servers list based on search\r\n const filteredServers = allServerCategoriesData.filter(server => {\r\n // Check category name/display name\r\n const categoryMatch = (server.displayName || server.name).toLowerCase().includes(searchTerm);\r\n\r\n // Check description\r\n const descriptionMatch = (server.description || '').toLowerCase().includes(searchTerm);\r\n\r\n // Check MCP server names from feedConfiguration\r\n const mcpServerMatch = server.feedConfiguration?.mcpServers?.some(mcpServer =>\r\n (mcpServer.displayName || mcpServer.name).toLowerCase().includes(searchTerm)\r\n ) || false;\r\n\r\n // Check installed server names from installationStatus\r\n const installedServerMatch = server.installationStatus?.serversStatus &&\r\n Object.keys(server.installationStatus.serversStatus)\r\n .some(serverName => serverName.toLowerCase().includes(searchTerm));\r\n\r\n return categoryMatch || descriptionMatch || mcpServerMatch || installedServerMatch;\r\n });\r\n\r\n renderServerCategoryList(filteredServers);\r\n } catch (error) {\r\n console.error('Error in search:', error);\r\n showToast('Error performing search', 'error');\r\n }\r\n });\r\n}\r\n\r\nfunction navigateToCategory(categoryName) {\r\n // This function will now handle the navigation, ensuring flight params are preserved.\r\n // It constructs the new URL with the selected category and existing flight params.\r\n window.location.href = buildUrlWithFlights('index.html', { category: categoryName });\r\n}\r\n\r\n// Make navigateToCategory globally accessible if it's directly used in HTML onclick\r\n// Alternatively, attach event listeners dynamically after rendering the list.\r\n// For simplicity with current structure, we'll make it global.\r\nwindow.navigateToCategory = navigateToCategory;\r\nwindow.togglePinCategoryItem = togglePinCategory;\r\n\r\n// Export functions\r\nexport { renderServerCategoryList, setupSearch, loadLastSelectedCategory, togglePinCategory };"],"mappings":"OAASA,wBAAyBC,0BAA6B,kB","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["allServerCategoriesData","fetchServerCategories","showServerDetails","showToast","buildUrlWithFlights","pinnedCategories","async","waitForData","length","loadPinnedState","saved","localStorage","getItem","JSON","parse","e","console","error","savePinnedState","setItem","stringify","togglePinCategory","categoryName","event","stopPropagation","serverItem","target","closest","classList","add","setTimeout","remove","renderServerCategoryList","loadLastSelectedCategory","lastSelected","some","server","name","servers","serverCategoryList","document","getElementById","innerHTML","sortedServers","sort","a","b","isPinnedA","isPinnedB","map","statusHtml","installationStatus","serversStatus","totalTools","Object","keys","installedTools","values","filter","serverStatus","installedStatus","opStatus","status","type","colorClass","icon","statusText","systemTagsHtml","feedConfiguration","systemTags","key","value","entries","isPinned","pinnedClass","pinIcon","displayName","join","setupSearch","searchBox","addEventListener","searchTerm","this","toLowerCase","categoryMatch","includes","descriptionMatch","description","mcpServerMatch","mcpServers","mcpServer","installedServerMatch","serverName","navigateToCategory","window","location","href","category","togglePinCategoryItem"],"sources":["0"],"sourcesContent":["import { allServerCategoriesData, fetchServerCategories } from './api.js';\r\nimport { showServerDetails } from './serverCategoryDetails.js'; // Still needed for initial load and direct calls\r\nimport { showToast } from './notifications.js';\r\nimport { buildUrlWithFlights } from './flights/flights.js';\r\n\r\n// Store state for categories\r\nlet pinnedCategories = {};\r\n\r\n// Wait for data to be loaded\r\nasync function waitForData() {\r\n if (allServerCategoriesData && allServerCategoriesData.length > 0) {\r\n return true;\r\n }\r\n await fetchServerCategories();\r\n return allServerCategoriesData && allServerCategoriesData.length > 0;\r\n}\r\n\r\n// Load pinned state from localStorage\r\nfunction loadPinnedState() {\r\n const saved = localStorage.getItem('pinnedCategories');\r\n if (saved) {\r\n try {\r\n pinnedCategories = JSON.parse(saved);\r\n } catch (e) {\r\n console.error('Error parsing pinned categories from localStorage:', e);\r\n pinnedCategories = {};\r\n }\r\n }\r\n}\r\n\r\n// Save pinned state to localStorage\r\nfunction savePinnedState() {\r\n localStorage.setItem('pinnedCategories', JSON.stringify(pinnedCategories));\r\n}\r\n\r\n// Toggle pin status for a category\r\nfunction togglePinCategory(categoryName, event) {\r\n // Stop propagation to prevent navigation\r\n event.stopPropagation();\r\n \r\n // Toggle pin status\r\n if (pinnedCategories[categoryName]) {\r\n delete pinnedCategories[categoryName];\r\n } else {\r\n pinnedCategories[categoryName] = true;\r\n }\r\n \r\n // Save to localStorage\r\n savePinnedState();\r\n \r\n // Find the server item element and add a temporary animation class\r\n const serverItem = event.target.closest('.server-item');\r\n if (serverItem) {\r\n serverItem.classList.add('pin-animation');\r\n setTimeout(() => {\r\n serverItem.classList.remove('pin-animation');\r\n }, 300); // Animation duration\r\n }\r\n \r\n // Re-render list with updated pin status\r\n if (allServerCategoriesData && allServerCategoriesData.length > 0) {\r\n renderServerCategoryList(allServerCategoriesData);\r\n }\r\n}\r\n\r\n// Function to show the last selected category on page load\r\nasync function loadLastSelectedCategory() {\r\n const lastSelected = localStorage.getItem('lastSelectedCategory');\r\n if (lastSelected && allServerCategoriesData) {\r\n // Check if the category still exists\r\n const categoryExists = allServerCategoriesData.some(server => server.name === lastSelected);\r\n if (categoryExists) {\r\n await showServerDetails(lastSelected);\r\n }\r\n }\r\n}\r\n\r\n// Function to render server list (used by fetchServerCategories and search)\r\nfunction renderServerCategoryList(servers) {\r\n const serverCategoryList = document.getElementById('serverCategoryList');\r\n\r\n if (servers.length === 0) {\r\n serverCategoryList.innerHTML = '<p class=\"text-gray-500\">No servers found matching search.</p>';\r\n document.getElementById('serverCategoryDetails').innerHTML = '<p>Select a server from the list to see details.</p>'; // Clear details\r\n return;\r\n }\r\n \r\n // Load pinned state\r\n loadPinnedState();\r\n \r\n // Create a copy of the servers array to avoid modifying the original\r\n const sortedServers = [...servers];\r\n \r\n // Sort servers with pinned ones at the top\r\n sortedServers.sort((a, b) => {\r\n const isPinnedA = pinnedCategories[a.name] === true;\r\n const isPinnedB = pinnedCategories[b.name] === true;\r\n \r\n if (isPinnedA && !isPinnedB) return -1;\r\n if (!isPinnedA && isPinnedB) return 1;\r\n return 0; // Keep original order for items with same pin status\r\n });\r\n\r\n serverCategoryList.innerHTML = sortedServers.map(server => {\r\n let statusHtml = '';\r\n\r\n // Add tool status summary if available\r\n if (server.installationStatus && server.installationStatus.serversStatus) {\r\n const totalTools = Object.keys(server.installationStatus.serversStatus).length;\r\n const installedTools = Object.values(server.installationStatus.serversStatus)\r\n .filter(serverStatus =>\r\n Object.values(serverStatus.installedStatus || {})\r\n .some(opStatus => opStatus.status === 'completed' && opStatus.type === 'install')\r\n ).length;\r\n\r\n if (totalTools > 0) {\r\n let colorClass, icon, statusText;\r\n\r\n if (installedTools === totalTools) {\r\n // All tools installed\r\n colorClass = \"text-green-600 bg-green-50\";\r\n icon = '<svg class=\"w-5 h-5 mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\" clip-rule=\"evenodd\"></path></svg>';\r\n statusText = \"Fully Configured\";\r\n } else if (installedTools > 0) {\r\n // Some tools installed\r\n colorClass = \"text-green-600 bg-green-50\";\r\n icon = '<svg class=\"w-5 h-5 mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\" clip-rule=\"evenodd\"></path></svg>';\r\n statusText = \"Partial Configured\";\r\n } else {\r\n // No tools installed\r\n colorClass = \"text-yellow-600 bg-orange-50\";\r\n icon = '<svg class=\"w-5 h-5 mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" d=\"M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM10 13a1 1 0 110-2 1 1 0 010 2zm-1.75-5.75a.75.75 0 00-1.5 0v3a.75.75 0 001.5 0v-3z\" clip-rule=\"evenodd\" /></svg>';\r\n statusText = \"Not Configured\";\r\n }\r\n\r\n statusHtml = `<span class=\"${colorClass} inline-flex items-center px-2 py-1 rounded-full text-xs\" style=\"width: fit-content; max-width: 100%;\">\r\n ${icon}<span class=\"truncate\">${statusText} (${installedTools}/${totalTools})</span>\r\n </span>`;\r\n }\r\n }\r\n\r\n let systemTagsHtml = '';\r\n if (server.feedConfiguration?.systemTags && Object.keys(server.feedConfiguration.systemTags).length > 0) {\r\n systemTagsHtml += '<div class=\"flex flex-wrap gap-1 ml-2\">'; // Added ml-2 for spacing\r\n for (const [key, value] of Object.entries(server.feedConfiguration.systemTags)) {\r\n systemTagsHtml += `<span class=\"text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full\">${key}: ${value}</span>`;\r\n }\r\n systemTagsHtml += '</div>';\r\n }\r\n // Check if this server is pinned\r\n const isPinned = pinnedCategories[server.name] === true;\r\n const pinnedClass = isPinned ? 'pinned' : '';\r\n \r\n // Pin/unpin button with appropriate icon\r\n const pinIcon = isPinned \r\n ? '<i class=\"bx bxs-pin\"></i>' // Solid pin icon for pinned items\r\n : '<i class=\"bx bx-pin\"></i>'; // Outline pin icon for unpinned items\r\n\r\n return `\r\n <div class=\"server-item border border-gray-200 p-3 rounded hover:bg-gray-50 cursor-pointer transition duration-150 ease-in-out ${pinnedClass}\"\r\n data-server-name=\"${server.name}\" onclick=\"navigateToCategory('${server.name}')\">\r\n <div class=\"flex justify-between items-center\">\r\n <h3 class=\"font-semibold text-gray-800\">${server.displayName || server.name}</h3>\r\n <div class=\"flex items-center\">\r\n <div class=\"pin-button ${pinnedClass}\" onclick=\"togglePinCategoryItem('${server.name}', event)\" title=\"${isPinned ? 'Unpin' : 'Pin'} this category\">\r\n ${pinIcon}\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"text-sm text-gray-500 flex items-center mt-1\">\r\n ${statusHtml}\r\n ${systemTagsHtml}\r\n </div>\r\n </div>\r\n `;\r\n }).join('');\r\n\r\n // After rendering, try to load the last selected category\r\n loadLastSelectedCategory();\r\n}\r\n\r\n// Setup search functionality\r\nfunction setupSearch() {\r\n const searchBox = document.getElementById('searchBox');\r\n \r\n // Load pinned state on page load\r\n loadPinnedState();\r\n\r\n searchBox.addEventListener('input', async function () {\r\n const searchTerm = this.value.toLowerCase();\r\n\r\n try {\r\n // Ensure data is loaded\r\n if (!(await waitForData())) {\r\n showToast('Error: Unable to load server data', 'error');\r\n return;\r\n }\r\n\r\n // Filter the servers list based on search\r\n const filteredServers = allServerCategoriesData.filter(server => {\r\n // Check category name/display name\r\n const categoryMatch = (server.displayName || server.name).toLowerCase().includes(searchTerm);\r\n\r\n // Check description\r\n const descriptionMatch = (server.description || '').toLowerCase().includes(searchTerm);\r\n\r\n // Check MCP server names from feedConfiguration\r\n const mcpServerMatch = server.feedConfiguration?.mcpServers?.some(mcpServer =>\r\n (mcpServer.displayName || mcpServer.name).toLowerCase().includes(searchTerm)\r\n ) || false;\r\n\r\n // Check installed server names from installationStatus\r\n const installedServerMatch = server.installationStatus?.serversStatus &&\r\n Object.keys(server.installationStatus.serversStatus)\r\n .some(serverName => serverName.toLowerCase().includes(searchTerm));\r\n\r\n return categoryMatch || descriptionMatch || mcpServerMatch || installedServerMatch;\r\n });\r\n\r\n renderServerCategoryList(filteredServers);\r\n } catch (error) {\r\n console.error('Error in search:', error);\r\n showToast('Error performing search', 'error');\r\n }\r\n });\r\n}\r\n\r\nfunction navigateToCategory(categoryName) {\r\n // This function will now handle the navigation, ensuring flight params are preserved.\r\n // It constructs the new URL with the selected category and existing flight params.\r\n window.location.href = buildUrlWithFlights('index.html', { category: categoryName });\r\n}\r\n\r\n// Make navigateToCategory globally accessible if it's directly used in HTML onclick\r\n// Alternatively, attach event listeners dynamically after rendering the list.\r\n// For simplicity with current structure, we'll make it global.\r\nwindow.navigateToCategory = navigateToCategory;\r\nwindow.togglePinCategoryItem = togglePinCategory;\r\n\r\n// Export functions\r\nexport { renderServerCategoryList, setupSearch, loadLastSelectedCategory, togglePinCategory };"],"mappings":"OAASA,wBAAyBC,0BAA6B,kBACtDC,sBAAyB,oCACzBC,cAAiB,4BACjBC,wBAA2B,uBAGpC,IAAIC,iBAAmB,CAAC,EAGxBC,eAAeC,cACX,SAAIP,yBAA2BA,wBAAwBQ,OAAS,WAG1DP,wBACCD,yBAA2BA,wBAAwBQ,OAAS,EACvE,CAGA,SAASC,kBACL,MAAMC,EAAQC,aAAaC,QAAQ,oBACnC,GAAIF,EACA,IACIL,iBAAmBQ,KAAKC,MAAMJ,EAClC,CAAE,MAAOK,GACLC,QAAQC,MAAM,qDAAsDF,GACpEV,iBAAmB,CAAC,CACxB,CAER,CAGA,SAASa,kBACLP,aAAaQ,QAAQ,mBAAoBN,KAAKO,UAAUf,kBAC5D,CAGA,SAASgB,kBAAkBC,EAAcC,GAErCA,EAAMC,kBAGFnB,iBAAiBiB,UACVjB,iBAAiBiB,GAExBjB,iBAAiBiB,IAAgB,EAIrCJ,kBAGA,MAAMO,EAAaF,EAAMG,OAAOC,QAAQ,gBACpCF,IACAA,EAAWG,UAAUC,IAAI,iBACzBC,YAAW,KACPL,EAAWG,UAAUG,OAAO,gBAAgB,GAC7C,MAIH/B,yBAA2BA,wBAAwBQ,OAAS,GAC5DwB,yBAAyBhC,wBAEjC,CAGAM,eAAe2B,2BACX,MAAMC,EAAevB,aAAaC,QAAQ,wBAC1C,GAAIsB,GAAgBlC,wBAAyB,CAElBA,wBAAwBmC,MAAKC,GAAUA,EAAOC,OAASH,WAEpEhC,kBAAkBgC,EAEhC,CACJ,CAGA,SAASF,yBAAyBM,GAC9B,MAAMC,EAAqBC,SAASC,eAAe,sBAEnD,GAAuB,IAAnBH,EAAQ9B,OAGR,OAFA+B,EAAmBG,UAAY,sEAC/BF,SAASC,eAAe,yBAAyBC,UAAY,wDAKjEjC,kBAGA,MAAMkC,EAAgB,IAAIL,GAG1BK,EAAcC,MAAK,CAACC,EAAGC,KACnB,MAAMC,GAAyC,IAA7B1C,iBAAiBwC,EAAER,MAC/BW,GAAyC,IAA7B3C,iBAAiByC,EAAET,MAErC,OAAIU,IAAcC,GAAmB,GAChCD,GAAaC,EAAkB,EAC7B,CAAC,IAGZT,EAAmBG,UAAYC,EAAcM,KAAIb,IAC7C,IAAIc,EAAa,GAGjB,GAAId,EAAOe,oBAAsBf,EAAOe,mBAAmBC,cAAe,CACtE,MAAMC,EAAaC,OAAOC,KAAKnB,EAAOe,mBAAmBC,eAAe5C,OAClEgD,EAAiBF,OAAOG,OAAOrB,EAAOe,mBAAmBC,eAC1DM,QAAOC,GACJL,OAAOG,OAAOE,EAAaC,iBAAmB,CAAC,GAC1CzB,MAAK0B,GAAgC,cAApBA,EAASC,QAA4C,YAAlBD,EAASE,SACpEvD,OAEN,GAAI6C,EAAa,EAAG,CAChB,IAAIW,EAAYC,EAAMC,EAElBV,IAAmBH,GAEnBW,EAAa,6BACbC,EAAO,6SACPC,EAAa,oBACNV,EAAiB,GAExBQ,EAAa,6BACbC,EAAO,6SACPC,EAAa,uBAGbF,EAAa,+BACbC,EAAO,uXACPC,EAAa,kBAGjBhB,EAAa,gBAAgBc,iIACvBC,2BAA8BC,MAAeV,KAAkBH,oCAEzE,CACJ,CAEA,IAAIc,EAAiB,GACrB,GAAI/B,EAAOgC,mBAAmBC,YAAcf,OAAOC,KAAKnB,EAAOgC,kBAAkBC,YAAY7D,OAAS,EAAG,CACrG2D,GAAkB,0CAClB,IAAK,MAAOG,EAAKC,KAAUjB,OAAOkB,QAAQpC,EAAOgC,kBAAkBC,YAC/DF,GAAkB,4EAA4EG,MAAQC,WAE1GJ,GAAkB,QACtB,CAEA,MAAMM,GAA6C,IAAlCpE,iBAAiB+B,EAAOC,MACnCqC,EAAcD,EAAW,SAAW,GAGpCE,EAAUF,EACV,6BACA,4BAEN,MAAO,4IAC0HC,sCACxGtC,EAAOC,sCAAsCD,EAAOC,kIAE3BD,EAAOwC,aAAexC,EAAOC,0GAE1CqC,sCAAgDtC,EAAOC,yBAAyBoC,EAAW,QAAU,kDACxHE,sKAKRzB,sBACAiB,iDAGT,IACFU,KAAK,IAGR5C,0BACJ,CAGA,SAAS6C,cACL,MAAMC,EAAYvC,SAASC,eAAe,aAG1ChC,kBAEAsE,EAAUC,iBAAiB,SAAS1E,iBAChC,MAAM2E,EAAaC,KAAKX,MAAMY,cAE9B,IAEI,UAAY5E,cAER,YADAJ,UAAU,oCAAqC,SAyBnD6B,yBApBwBhC,wBAAwB0D,QAAOtB,IAEnD,MAAMgD,GAAiBhD,EAAOwC,aAAexC,EAAOC,MAAM8C,cAAcE,SAASJ,GAG3EK,GAAoBlD,EAAOmD,aAAe,IAAIJ,cAAcE,SAASJ,GAGrEO,EAAiBpD,EAAOgC,mBAAmBqB,YAAYtD,MAAKuD,IAC7DA,EAAUd,aAAec,EAAUrD,MAAM8C,cAAcE,SAASJ,OAChE,EAGCU,EAAuBvD,EAAOe,oBAAoBC,eACpDE,OAAOC,KAAKnB,EAAOe,mBAAmBC,eACjCjB,MAAKyD,GAAcA,EAAWT,cAAcE,SAASJ,KAE9D,OAAOG,GAAiBE,GAAoBE,GAAkBG,CAAoB,IAI1F,CAAE,MAAO1E,GACLD,QAAQC,MAAM,mBAAoBA,GAClCd,UAAU,0BAA2B,QACzC,CACJ,GACJ,CAEA,SAAS0F,mBAAmBvE,GAGxBwE,OAAOC,SAASC,KAAO5F,oBAAoB,aAAc,CAAE6F,SAAU3E,GACzE,CAKAwE,OAAOD,mBAAqBA,mBAC5BC,OAAOI,sBAAwB7E,yBAGtBW,yBAA0B8C,YAAa7C,yBAA0BZ","ignoreList":[]}
|