imcp 0.0.13 → 0.0.15
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/dist/core/ConfigurationProvider.d.ts +1 -0
- package/dist/core/ConfigurationProvider.js +15 -0
- package/dist/core/InstallationService.js +2 -7
- package/dist/core/MCPManager.d.ts +11 -2
- package/dist/core/MCPManager.js +24 -1
- package/dist/core/RequirementService.js +2 -8
- package/dist/core/installers/clients/BaseClientInstaller.d.ts +51 -0
- package/dist/core/installers/clients/BaseClientInstaller.js +160 -0
- package/dist/core/installers/clients/ClientInstaller.d.ts +16 -8
- package/dist/core/installers/clients/ClientInstaller.js +77 -504
- package/dist/core/installers/clients/ClientInstallerFactory.d.ts +19 -0
- package/dist/core/installers/clients/ClientInstallerFactory.js +41 -0
- package/dist/core/installers/clients/ClineInstaller.d.ts +18 -0
- package/dist/core/installers/clients/ClineInstaller.js +124 -0
- package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +34 -0
- package/dist/core/installers/clients/GithubCopilotInstaller.js +162 -0
- package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +15 -0
- package/dist/core/installers/clients/MSRooCodeInstaller.js +122 -0
- package/dist/core/installers/requirements/BaseInstaller.d.ts +11 -34
- package/dist/core/installers/requirements/BaseInstaller.js +5 -116
- package/dist/core/installers/requirements/CommandInstaller.d.ts +6 -1
- package/dist/core/installers/requirements/CommandInstaller.js +7 -0
- package/dist/core/installers/requirements/GeneralInstaller.d.ts +6 -1
- package/dist/core/installers/requirements/GeneralInstaller.js +9 -4
- package/dist/core/installers/requirements/NpmInstaller.d.ts +46 -7
- package/dist/core/installers/requirements/NpmInstaller.js +150 -58
- package/dist/core/installers/requirements/PipInstaller.d.ts +9 -0
- package/dist/core/installers/requirements/PipInstaller.js +66 -28
- package/dist/core/onboard/FeedOnboardService.d.ts +72 -0
- package/dist/core/onboard/FeedOnboardService.js +312 -0
- package/dist/core/onboard/OnboardProcessor.d.ts +79 -0
- package/dist/core/onboard/OnboardProcessor.js +290 -0
- package/dist/core/onboard/OnboardStatus.d.ts +49 -0
- package/dist/core/onboard/OnboardStatus.js +10 -0
- package/dist/core/onboard/OnboardStatusManager.d.ts +57 -0
- package/dist/core/onboard/OnboardStatusManager.js +176 -0
- package/dist/core/types.d.ts +6 -6
- package/dist/core/validators/FeedValidator.d.ts +20 -0
- package/dist/core/validators/FeedValidator.js +80 -0
- package/dist/core/validators/IServerValidator.d.ts +19 -0
- package/dist/core/validators/IServerValidator.js +2 -0
- package/dist/core/validators/SSEServerValidator.d.ts +15 -0
- package/dist/core/validators/SSEServerValidator.js +39 -0
- package/dist/core/validators/ServerValidatorFactory.d.ts +24 -0
- package/dist/core/validators/ServerValidatorFactory.js +45 -0
- package/dist/core/validators/StdioServerValidator.d.ts +46 -0
- package/dist/core/validators/StdioServerValidator.js +229 -0
- package/dist/services/InstallRequestValidator.d.ts +1 -1
- package/dist/services/ServerService.d.ts +9 -6
- package/dist/services/ServerService.js +18 -7
- package/dist/utils/adoUtils.d.ts +29 -0
- package/dist/utils/adoUtils.js +252 -0
- package/dist/utils/clientUtils.d.ts +0 -7
- package/dist/utils/clientUtils.js +0 -42
- package/dist/utils/githubUtils.d.ts +10 -0
- package/dist/utils/githubUtils.js +22 -0
- package/dist/utils/macroExpressionUtils.d.ts +38 -0
- package/dist/utils/macroExpressionUtils.js +116 -0
- package/dist/utils/osUtils.d.ts +4 -20
- package/dist/utils/osUtils.js +78 -23
- package/dist/web/contract/serverContract.d.ts +66 -0
- package/dist/web/contract/serverContract.js +2 -0
- package/dist/web/public/css/notifications.css +48 -17
- package/dist/web/public/css/onboard.css +107 -0
- package/dist/web/public/index.html +90 -18
- package/dist/web/public/js/api.js +3 -6
- package/dist/web/public/js/flights/flights.js +127 -0
- package/dist/web/public/js/modal/index.js +58 -0
- package/dist/web/public/js/modal/installHandler.js +227 -0
- package/dist/web/public/js/modal/installModal.js +163 -0
- package/dist/web/public/js/modal/installation.js +281 -0
- package/dist/web/public/js/modal/loadingModal.js +52 -0
- package/dist/web/public/js/modal/loadingUI.js +74 -0
- package/dist/web/public/js/modal/messageQueue.js +112 -0
- package/dist/web/public/js/modal/modalSetup.js +513 -0
- package/dist/web/public/js/modal/modalUI.js +214 -0
- package/dist/web/public/js/modal/modalUtils.js +49 -0
- package/dist/web/public/js/modal/version.js +20 -0
- package/dist/web/public/js/modal/versionUtils.js +20 -0
- package/dist/web/public/js/modal.js +25 -1041
- package/dist/web/public/js/notifications.js +66 -27
- package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
- package/dist/web/public/js/onboard/formProcessor.js +864 -0
- package/dist/web/public/js/onboard/index.js +374 -0
- package/dist/web/public/js/onboard/publishHandler.js +132 -0
- package/dist/web/public/js/onboard/state.js +76 -0
- package/dist/web/public/js/onboard/templates.js +343 -0
- package/dist/web/public/js/onboard/uiHandlers.js +758 -0
- package/dist/web/public/js/onboard/validationHandlers.js +378 -0
- package/dist/web/public/js/serverCategoryDetails.js +43 -17
- package/dist/web/public/js/serverCategoryList.js +15 -2
- package/dist/web/public/onboard.html +296 -0
- package/dist/web/public/styles.css +91 -1
- package/dist/web/server.d.ts +0 -10
- package/dist/web/server.js +131 -22
- package/package.json +2 -2
- package/src/core/ConfigurationProvider.ts +15 -0
- package/src/core/InstallationService.ts +2 -7
- package/src/core/MCPManager.ts +26 -1
- package/src/core/RequirementService.ts +2 -9
- package/src/core/installers/clients/BaseClientInstaller.ts +196 -0
- package/src/core/installers/clients/ClientInstaller.ts +97 -589
- package/src/core/installers/clients/ClientInstallerFactory.ts +46 -0
- package/src/core/installers/clients/ClineInstaller.ts +135 -0
- package/src/core/installers/clients/GithubCopilotInstaller.ts +179 -0
- package/src/core/installers/clients/MSRooCodeInstaller.ts +133 -0
- package/src/core/installers/requirements/BaseInstaller.ts +13 -136
- package/src/core/installers/requirements/CommandInstaller.ts +9 -1
- package/src/core/installers/requirements/GeneralInstaller.ts +11 -4
- package/src/core/installers/requirements/NpmInstaller.ts +178 -61
- package/src/core/installers/requirements/PipInstaller.ts +68 -29
- package/src/core/onboard/FeedOnboardService.ts +346 -0
- package/src/core/onboard/OnboardProcessor.ts +305 -0
- package/src/core/onboard/OnboardStatus.ts +55 -0
- package/src/core/onboard/OnboardStatusManager.ts +188 -0
- package/src/core/types.ts +6 -6
- package/src/core/validators/FeedValidator.ts +79 -0
- package/src/core/validators/IServerValidator.ts +21 -0
- package/src/core/validators/SSEServerValidator.ts +43 -0
- package/src/core/validators/ServerValidatorFactory.ts +51 -0
- package/src/core/validators/StdioServerValidator.ts +259 -0
- package/src/services/InstallRequestValidator.ts +1 -1
- package/src/services/ServerService.ts +22 -7
- package/src/utils/adoUtils.ts +291 -0
- package/src/utils/clientUtils.ts +0 -44
- package/src/utils/githubUtils.ts +24 -0
- package/src/utils/macroExpressionUtils.ts +121 -0
- package/src/utils/osUtils.ts +89 -24
- package/src/web/contract/serverContract.ts +74 -0
- package/src/web/public/css/notifications.css +48 -17
- package/src/web/public/css/onboard.css +107 -0
- package/src/web/public/index.html +90 -18
- package/src/web/public/js/api.js +3 -6
- package/src/web/public/js/flights/flights.js +127 -0
- package/src/web/public/js/modal/index.js +58 -0
- package/src/web/public/js/modal/installModal.js +163 -0
- package/src/web/public/js/modal/installation.js +281 -0
- package/src/web/public/js/modal/loadingModal.js +52 -0
- package/src/web/public/js/modal/messageQueue.js +112 -0
- package/src/web/public/js/modal/modalSetup.js +513 -0
- package/src/web/public/js/modal/modalUtils.js +49 -0
- package/src/web/public/js/modal/versionUtils.js +20 -0
- package/src/web/public/js/modal.js +25 -1041
- package/src/web/public/js/notifications.js +66 -27
- package/src/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
- package/src/web/public/js/onboard/formProcessor.js +864 -0
- package/src/web/public/js/onboard/index.js +374 -0
- package/src/web/public/js/onboard/publishHandler.js +132 -0
- package/src/web/public/js/onboard/state.js +76 -0
- package/src/web/public/js/onboard/templates.js +343 -0
- package/src/web/public/js/onboard/uiHandlers.js +758 -0
- package/src/web/public/js/onboard/validationHandlers.js +378 -0
- package/src/web/public/js/serverCategoryDetails.js +43 -17
- package/src/web/public/js/serverCategoryList.js +15 -2
- package/src/web/public/onboard.html +296 -0
- package/src/web/public/styles.css +91 -1
- package/src/web/server.ts +167 -58
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
import {
|
|
2
|
+
serverTemplate,
|
|
3
|
+
envVariableTemplate,
|
|
4
|
+
serverRequirementTemplate
|
|
5
|
+
} from './templates.js';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
state,
|
|
9
|
+
setServerCounter,
|
|
10
|
+
getServerCounter, // Added
|
|
11
|
+
setEnvCounter,
|
|
12
|
+
getEnvCounter,
|
|
13
|
+
deleteEnvCounter,
|
|
14
|
+
clearEnvCountersForTab, // Added
|
|
15
|
+
setServerRequirementCounter,
|
|
16
|
+
getServerRequirementCounter,
|
|
17
|
+
deleteServerRequirementCounter,
|
|
18
|
+
clearServerRequirementCountersForTab // Added
|
|
19
|
+
} from './state.js';
|
|
20
|
+
import { getFormData, populateForm, resetOnboardFormDynamicContent } from './formProcessor.js';
|
|
21
|
+
import { showToast } from '../notifications.js';
|
|
22
|
+
|
|
23
|
+
function reindexElements(container, oldIndex, newIndex, selectors) {
|
|
24
|
+
selectors.forEach(({ selector, idBase, namePattern }) => {
|
|
25
|
+
const element = container.querySelector(selector);
|
|
26
|
+
if (!element) return;
|
|
27
|
+
|
|
28
|
+
if (idBase) {
|
|
29
|
+
element.id = `${idBase}${newIndex}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (namePattern && element.getAttribute('name')) {
|
|
33
|
+
const oldName = element.getAttribute('name');
|
|
34
|
+
const newName = oldName.replace(
|
|
35
|
+
new RegExp(namePattern.replace('INDEX', oldIndex)),
|
|
36
|
+
`$1${newIndex}$2`
|
|
37
|
+
);
|
|
38
|
+
element.setAttribute('name', newName);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function reindexServers(serversListId = 'serversList') {
|
|
44
|
+
const serversList = document.getElementById(serversListId);
|
|
45
|
+
if (!serversList) return;
|
|
46
|
+
|
|
47
|
+
const serverItems = serversList.querySelectorAll('.server-item');
|
|
48
|
+
const newEnvCounters = new Map();
|
|
49
|
+
const newServerRequirementCounters = new Map();
|
|
50
|
+
|
|
51
|
+
serverItems.forEach((serverItem, newServerIndex) => {
|
|
52
|
+
const oldServerIndex = parseInt(serverItem.dataset.index, 10);
|
|
53
|
+
serverItem.dataset.index = newServerIndex;
|
|
54
|
+
|
|
55
|
+
const titleElement = serverItem.querySelector('h3');
|
|
56
|
+
if (titleElement) {
|
|
57
|
+
const baseTitle = `MCP Server #${newServerIndex + 1}`;
|
|
58
|
+
const readOnlySuffix = titleElement.textContent.includes('(Read-only)') ? ' (Read-only)' : '';
|
|
59
|
+
titleElement.textContent = `${baseTitle}${readOnlySuffix}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Update onclick handlers
|
|
63
|
+
serverItem.querySelectorAll('[onclick]').forEach(element => {
|
|
64
|
+
const onclickAttr = element.getAttribute('onclick');
|
|
65
|
+
if (!onclickAttr) return;
|
|
66
|
+
|
|
67
|
+
const updatedOnclick = onclickAttr
|
|
68
|
+
.replace(new RegExp(`\\((\\s*)${oldServerIndex}(\\s*[,\\)])`, 'g'), `($1${newServerIndex}$2`)
|
|
69
|
+
.replace(new RegExp(`(${serversListId}-(?:server|installation|env-vars)-content-${oldServerIndex})`, 'g'), `$1${newServerIndex}`);
|
|
70
|
+
|
|
71
|
+
element.setAttribute('onclick', updatedOnclick);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Update IDs and names
|
|
75
|
+
reindexElements(serverItem, oldServerIndex, newServerIndex, [
|
|
76
|
+
{ selector: `#${serversListId}-server-content-${oldServerIndex}`, idBase: `${serversListId}-server-content-` },
|
|
77
|
+
{ selector: `#${serversListId}-server-deps-content-${oldServerIndex}`, idBase: `${serversListId}-server-deps-content-` },
|
|
78
|
+
{ selector: `#${serversListId}-installation-content-${oldServerIndex}`, idBase: `${serversListId}-installation-content-` },
|
|
79
|
+
{ selector: `#${serversListId}-env-vars-content-${oldServerIndex}`, idBase: `${serversListId}-env-vars-content-` },
|
|
80
|
+
{ selector: `#schema-path-${oldServerIndex}`, idBase: 'schema-path-' },
|
|
81
|
+
{ selector: `#envVarsContainer_${oldServerIndex}`, idBase: 'envVarsContainer_' }
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
// Reindex env variables
|
|
85
|
+
const envVarsContainer = serverItem.querySelector(`#envVarsContainer_${newServerIndex}`);
|
|
86
|
+
let envCounter = 0;
|
|
87
|
+
if (envVarsContainer) {
|
|
88
|
+
envVarsContainer.querySelectorAll('.env-var-item').forEach((envItem, newEnvIndex) => {
|
|
89
|
+
const oldEnvIndex = parseInt(envItem.dataset.envIndex, 10);
|
|
90
|
+
envItem.dataset.envIndex = newEnvIndex;
|
|
91
|
+
reindexElements(envItem, oldEnvIndex, newEnvIndex, [{
|
|
92
|
+
selector: '[name]',
|
|
93
|
+
namePattern: `(servers\\[${newServerIndex}\\]\\.installation\\.env\\[)\\d+(\\]\\..+)`
|
|
94
|
+
}]);
|
|
95
|
+
envCounter++;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
newEnvCounters.set(newServerIndex, envCounter);
|
|
99
|
+
|
|
100
|
+
// Reindex server requirements
|
|
101
|
+
const reqsContainer = serverItem.querySelector(`#server-requirements-list-${newServerIndex}`);
|
|
102
|
+
let reqCounter = 0;
|
|
103
|
+
if (reqsContainer) {
|
|
104
|
+
reqsContainer.querySelectorAll('.server-requirement-item').forEach((reqItem, newReqIndex) => {
|
|
105
|
+
const oldReqIndex = parseInt(reqItem.dataset.reqIndex, 10);
|
|
106
|
+
reqItem.dataset.reqIndex = newReqIndex;
|
|
107
|
+
reindexElements(reqItem, oldReqIndex, newReqIndex, [{
|
|
108
|
+
selector: '[name]',
|
|
109
|
+
namePattern: `(servers\\[${newServerIndex}\\]\\.requirements\\[)\\d+(\\]\\..+)`
|
|
110
|
+
}]);
|
|
111
|
+
reqCounter++;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
newServerRequirementCounters.set(newServerIndex, reqCounter);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Update state for the specific tab
|
|
118
|
+
clearEnvCountersForTab(serversListId);
|
|
119
|
+
newEnvCounters.forEach((count, serverIdx) => setEnvCounter(serversListId, serverIdx, count));
|
|
120
|
+
|
|
121
|
+
clearServerRequirementCountersForTab(serversListId);
|
|
122
|
+
newServerRequirementCounters.forEach((count, serverIdx) => setServerRequirementCounter(serversListId, serverIdx, count));
|
|
123
|
+
|
|
124
|
+
setServerCounter(serversListId, serverItems.length);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function addServer(serversListId = 'serversList', isContextGenerallyReadOnly = false, serverData = null) {
|
|
128
|
+
const container = document.getElementById(serversListId);
|
|
129
|
+
if (!container) return -1;
|
|
130
|
+
|
|
131
|
+
// Initialize counters for this tab if it's the first server being added to this specific list
|
|
132
|
+
if (container.children.length === 0) {
|
|
133
|
+
setServerCounter(serversListId, 0);
|
|
134
|
+
clearEnvCountersForTab(serversListId);
|
|
135
|
+
clearServerRequirementCountersForTab(serversListId);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const newServerIndex = getServerCounter(serversListId);
|
|
139
|
+
let actualReadOnlyForThisServer;
|
|
140
|
+
|
|
141
|
+
if (!isContextGenerallyReadOnly) {
|
|
142
|
+
// Context is not generally read-only (e.g., creating a brand new category,
|
|
143
|
+
// or user clicked "Add Server" button when no specific read-only category context applies).
|
|
144
|
+
actualReadOnlyForThisServer = false;
|
|
145
|
+
} else {
|
|
146
|
+
// Context IS generally read-only (e.g., an existing category was selected, or we are populating from JSON for such a category).
|
|
147
|
+
if (state.originalServerNamesForFormPopulation) {
|
|
148
|
+
// We are in the process of toggling from JSON view back to Form view for an existing category.
|
|
149
|
+
// state.originalServerNamesForFormPopulation contains names of servers that were part of the category *before* any UI/JSON edits.
|
|
150
|
+
if (serverData && serverData.name && state.originalServerNamesForFormPopulation.has(serverData.name)) {
|
|
151
|
+
// This serverData (from JSON) corresponds to one of the original servers. It should be read-only.
|
|
152
|
+
actualReadOnlyForThisServer = true;
|
|
153
|
+
} else {
|
|
154
|
+
// This serverData (from JSON) is new (wasn't in originalServerNamesForFormPopulation). It should be editable.
|
|
155
|
+
// This also covers cases where serverData might not have a name yet if added directly in JSON.
|
|
156
|
+
actualReadOnlyForThisServer = false;
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
// We are NOT toggling from JSON view. This is either:
|
|
160
|
+
// 1. Initial population of an existing category's servers (serverData will be provided).
|
|
161
|
+
// 2. User clicked the "Add Server" button while an existing category context is active (serverData will be null).
|
|
162
|
+
if (serverData) {
|
|
163
|
+
// Case 1: Initial population of an existing server from category data. Should be read-only.
|
|
164
|
+
actualReadOnlyForThisServer = true;
|
|
165
|
+
} else {
|
|
166
|
+
// Case 2: User clicked "Add Server" button. The new server item should be editable.
|
|
167
|
+
actualReadOnlyForThisServer = false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
container.insertAdjacentHTML('beforeend', serverTemplate(newServerIndex, actualReadOnlyForThisServer, serverData, serversListId));
|
|
173
|
+
|
|
174
|
+
setEnvCounter(serversListId, newServerIndex, 0);
|
|
175
|
+
setServerRequirementCounter(serversListId, newServerIndex, 0);
|
|
176
|
+
setServerCounter(serversListId, newServerIndex + 1);
|
|
177
|
+
|
|
178
|
+
const serverItem = container.querySelector(`.server-item[data-index="${newServerIndex}"]`);
|
|
179
|
+
if (serverItem) {
|
|
180
|
+
setupServerMode(serverItem, newServerIndex, serversListId, actualReadOnlyForThisServer, serverData);
|
|
181
|
+
setupReadOnlyState(serverItem, actualReadOnlyForThisServer, serversListId, newServerIndex);
|
|
182
|
+
|
|
183
|
+
// Default expand Package Dependencies, Startup Configuration, and Environment Variables sections
|
|
184
|
+
const sections = [
|
|
185
|
+
{ contentId: `${serversListId}-server-deps-content-${newServerIndex}`, iconSelector: `#${serversListId}-deps-header-${newServerIndex} i` },
|
|
186
|
+
{ contentId: `${serversListId}-installation-content-${newServerIndex}`, iconSelector: `#${serversListId}-startup-header-${newServerIndex} i` },
|
|
187
|
+
{ contentId: `${serversListId}-env-vars-content-${newServerIndex}`, iconSelector: `#${serversListId}-envars-header-${newServerIndex} i` }
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
sections.forEach(({ contentId, iconSelector }) => {
|
|
191
|
+
const contentElement = document.getElementById(contentId);
|
|
192
|
+
const iconElement = serverItem.querySelector(iconSelector);
|
|
193
|
+
if (contentElement && iconElement) {
|
|
194
|
+
contentElement.classList.remove('hidden');
|
|
195
|
+
iconElement.classList.remove('bxs-chevron-down');
|
|
196
|
+
iconElement.classList.add('bxs-chevron-up');
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// New logic: Focus on the newly added server and collapse others,
|
|
201
|
+
// only if it's not an existing read-only server being populated.
|
|
202
|
+
if (!actualReadOnlyForThisServer) {
|
|
203
|
+
const allServerItems = container.querySelectorAll('.server-item');
|
|
204
|
+
allServerItems.forEach(item => {
|
|
205
|
+
const currentIndex = parseInt(item.dataset.index, 10);
|
|
206
|
+
// Ensure item is a direct child of the container to avoid issues if querySelectorAll picks up nested items.
|
|
207
|
+
if (item.parentElement !== container) return;
|
|
208
|
+
|
|
209
|
+
const contentId = `${serversListId}-server-content-${currentIndex}`;
|
|
210
|
+
const iconElement = item.querySelector('.server-header-toggle i.toggle-icon');
|
|
211
|
+
const contentElement = document.getElementById(contentId);
|
|
212
|
+
|
|
213
|
+
if (contentElement && iconElement) {
|
|
214
|
+
if (currentIndex === newServerIndex) {
|
|
215
|
+
// Expand the new server if it's collapsed
|
|
216
|
+
if (contentElement.classList.contains('hidden')) {
|
|
217
|
+
toggleSectionContent(contentId, iconElement);
|
|
218
|
+
}
|
|
219
|
+
// Scroll to the new server
|
|
220
|
+
// Use a slight delay to ensure rendering is complete for smooth scroll
|
|
221
|
+
setTimeout(() => {
|
|
222
|
+
item.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
223
|
+
}, 0);
|
|
224
|
+
} else {
|
|
225
|
+
// Collapse other servers if they are expanded
|
|
226
|
+
if (!contentElement.classList.contains('hidden')) {
|
|
227
|
+
toggleSectionContent(contentId, iconElement);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return newServerIndex;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function setupServerMode(serverItem, serverIndex, serversListId, isReadOnly, serverData) {
|
|
239
|
+
const modeSelect = serverItem.querySelector(`select[name="servers[${serverIndex}].mode"]`);
|
|
240
|
+
if (!modeSelect) return;
|
|
241
|
+
|
|
242
|
+
if (isReadOnly && serverData) {
|
|
243
|
+
modeSelect.value = serverData.mode || 'stdio';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
modeSelect.addEventListener('change', () => renderInstallationConfig(serverIndex, serversListId));
|
|
247
|
+
renderInstallationConfig(serverIndex, serversListId, modeSelect.value, isReadOnly, serverData?.installation);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function setupReadOnlyState(serverItem, isReadOnly, serversListId, serverIndex) {
|
|
251
|
+
if (isReadOnly) {
|
|
252
|
+
serverItem.querySelectorAll('input, select, textarea').forEach(el => {
|
|
253
|
+
el.disabled = true;
|
|
254
|
+
el.classList.add('bg-gray-100', 'cursor-not-allowed', 'opacity-70');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
serverItem.querySelectorAll('.action-button-in-server').forEach(btn => {
|
|
258
|
+
btn.style.display = 'none';
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Expand server content and all key sections
|
|
262
|
+
const sections = [
|
|
263
|
+
{ contentId: `${serversListId}-server-content-${serverIndex}`, iconSelector: '.server-header-toggle i.toggle-icon' },
|
|
264
|
+
{ contentId: `${serversListId}-server-deps-content-${serverIndex}`, iconSelector: `#${serversListId}-deps-header-${serverIndex} i` },
|
|
265
|
+
{ contentId: `${serversListId}-installation-content-${serverIndex}`, iconSelector: `#${serversListId}-startup-header-${serverIndex} i` },
|
|
266
|
+
{ contentId: `${serversListId}-env-vars-content-${serverIndex}`, iconSelector: `#${serversListId}-envars-header-${serverIndex} i` }
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
sections.forEach(({ contentId, iconSelector }) => {
|
|
270
|
+
const contentElement = document.getElementById(contentId);
|
|
271
|
+
const iconElement = serverItem.querySelector(iconSelector);
|
|
272
|
+
if (contentElement && iconElement) {
|
|
273
|
+
contentElement.classList.remove('hidden');
|
|
274
|
+
iconElement.classList.remove('bxs-chevron-down');
|
|
275
|
+
iconElement.classList.add('bxs-chevron-up');
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
} else {
|
|
279
|
+
setTimeout(() => {
|
|
280
|
+
serverItem.querySelectorAll('input, select, textarea').forEach(el => {
|
|
281
|
+
el.disabled = false;
|
|
282
|
+
el.removeAttribute('readonly');
|
|
283
|
+
el.classList.remove('bg-gray-100', 'cursor-not-allowed', 'opacity-70');
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
serverItem.querySelectorAll('.action-button-in-server').forEach(btn => {
|
|
287
|
+
btn.style.display = '';
|
|
288
|
+
btn.disabled = false;
|
|
289
|
+
btn.classList.remove('hidden', 'opacity-50', 'cursor-not-allowed');
|
|
290
|
+
});
|
|
291
|
+
}, 0);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function renderInstallationConfig(serverIndex, serversListId = 'serversList', initialModeValue = null, isReadOnly = false, installationData = null) {
|
|
296
|
+
const serverItem = document.querySelector(`#${serversListId} .server-item[data-index="${serverIndex}"]`);
|
|
297
|
+
if (!serverItem) return;
|
|
298
|
+
|
|
299
|
+
const modeSelect = serverItem.querySelector(`select[name="servers[${serverIndex}].mode"]`);
|
|
300
|
+
const mode = initialModeValue || (modeSelect ? modeSelect.value : 'stdio');
|
|
301
|
+
const container = serverItem.querySelector(`#installation-config-${serverIndex}`);
|
|
302
|
+
const envVarsBlock = serverItem.querySelector(`#env-vars-block-${serverIndex}`);
|
|
303
|
+
|
|
304
|
+
if (!container) return;
|
|
305
|
+
|
|
306
|
+
const disabledAttr = isReadOnly ? 'disabled' : '';
|
|
307
|
+
const readOnlyClasses = isReadOnly ? 'bg-gray-100 cursor-not-allowed opacity-70' : '';
|
|
308
|
+
|
|
309
|
+
container.innerHTML = mode === 'sse' ?
|
|
310
|
+
generateSSETemplate(serverIndex, disabledAttr, readOnlyClasses, installationData) :
|
|
311
|
+
generateStdioTemplate(serverIndex, disabledAttr, readOnlyClasses, installationData);
|
|
312
|
+
|
|
313
|
+
if (envVarsBlock) {
|
|
314
|
+
envVarsBlock.style.display = mode === 'sse' ? 'none' : '';
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function generateSSETemplate(serverIndex, disabledAttr, readOnlyClasses, installationData) {
|
|
319
|
+
return `
|
|
320
|
+
<div>
|
|
321
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">Server URL*</label>
|
|
322
|
+
<input type="text" name="servers[${serverIndex}].installation.url" required ${disabledAttr}
|
|
323
|
+
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}"
|
|
324
|
+
placeholder="e.g., https://your-server.com/api/mcp"
|
|
325
|
+
value="${installationData?.url || ''}">
|
|
326
|
+
</div>
|
|
327
|
+
`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function generateStdioTemplate(serverIndex, disabledAttr, readOnlyClasses, installationData) {
|
|
331
|
+
return `
|
|
332
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
333
|
+
<div>
|
|
334
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">Command*</label>
|
|
335
|
+
<input type="text" name="servers[${serverIndex}].installation.command" required ${disabledAttr}
|
|
336
|
+
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}"
|
|
337
|
+
placeholder="e.g., node, python3"
|
|
338
|
+
value="${installationData?.command || ''}">
|
|
339
|
+
</div>
|
|
340
|
+
<div>
|
|
341
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">Arguments (comma-separated)</label>
|
|
342
|
+
<input type="text" name="servers[${serverIndex}].installation.args" ${disabledAttr}
|
|
343
|
+
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}"
|
|
344
|
+
placeholder="arg1, path/to/script.js, --flag"
|
|
345
|
+
value="${installationData?.args ? installationData.args.join(', ') : ''}">
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
`;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function removeServer(serverIndexToRemove, serversListId = 'serversList') {
|
|
352
|
+
const serverListContainer = document.getElementById(serversListId);
|
|
353
|
+
if (!serverListContainer) return;
|
|
354
|
+
|
|
355
|
+
const item = serverListContainer.querySelector(`.server-item[data-index="${serverIndexToRemove}"]`);
|
|
356
|
+
if (item) {
|
|
357
|
+
item.remove();
|
|
358
|
+
deleteEnvCounter(serversListId, serverIndexToRemove);
|
|
359
|
+
deleteServerRequirementCounter(serversListId, serverIndexToRemove);
|
|
360
|
+
reindexServers(serversListId);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function addEnvVariable(serverIndex, serversListId = 'serversList', isReadOnly = false) {
|
|
365
|
+
const container = document.querySelector(`#${serversListId} .server-item[data-index="${serverIndex}"] #envVarsContainer_${serverIndex}`);
|
|
366
|
+
if (!container) return -1;
|
|
367
|
+
|
|
368
|
+
const envIndex = getEnvCounter(serversListId, serverIndex);
|
|
369
|
+
container.insertAdjacentHTML('beforeend', envVariableTemplate(serverIndex, envIndex, isReadOnly, serversListId));
|
|
370
|
+
setEnvCounter(serversListId, serverIndex, envIndex + 1);
|
|
371
|
+
|
|
372
|
+
if (isReadOnly) {
|
|
373
|
+
const envItem = container.querySelector(`.env-var-item[data-env-index="${envIndex}"]`);
|
|
374
|
+
if (envItem) {
|
|
375
|
+
envItem.querySelectorAll('input, select, textarea').forEach(el => {
|
|
376
|
+
el.disabled = true;
|
|
377
|
+
el.classList.add('bg-gray-100', 'cursor-not-allowed', 'opacity-70');
|
|
378
|
+
});
|
|
379
|
+
envItem.querySelectorAll('.action-button-in-env').forEach(btn => btn.style.display = 'none');
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return envIndex;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function removeEnvVariable(serverIndex, envIndex, serversListId = 'serversList') {
|
|
387
|
+
const item = document.querySelector(`#${serversListId} .server-item[data-index="${serverIndex}"] .env-var-item[data-env-index="${envIndex}"]`);
|
|
388
|
+
if (item) item.remove();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function addServerRequirement(serverIndex, serversListId = 'serversList', isReadOnly = false) {
|
|
392
|
+
const container = document.querySelector(`#${serversListId} .server-item[data-index="${serverIndex}"] #server-requirements-list-${serverIndex}`);
|
|
393
|
+
if (!container) return -1;
|
|
394
|
+
|
|
395
|
+
const reqIndex = getServerRequirementCounter(serversListId, serverIndex);
|
|
396
|
+
container.insertAdjacentHTML('beforeend', serverRequirementTemplate(serverIndex, reqIndex, isReadOnly, serversListId));
|
|
397
|
+
setServerRequirementCounter(serversListId, serverIndex, reqIndex + 1);
|
|
398
|
+
|
|
399
|
+
if (isReadOnly) {
|
|
400
|
+
const reqItem = container.querySelector(`.server-requirement-item[data-req-index="${reqIndex}"]`);
|
|
401
|
+
if (reqItem) {
|
|
402
|
+
reqItem.querySelectorAll('input, select, textarea').forEach(el => {
|
|
403
|
+
el.disabled = true;
|
|
404
|
+
el.classList.add('bg-gray-100', 'cursor-not-allowed', 'opacity-70');
|
|
405
|
+
});
|
|
406
|
+
reqItem.querySelectorAll('.action-button-in-req').forEach(btn => btn.style.display = 'none');
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
toggleServerAliasField(serverIndex, reqIndex, serversListId);
|
|
411
|
+
toggleServerRegistryConfig(serverIndex, reqIndex, serversListId);
|
|
412
|
+
return reqIndex;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function removeServerRequirement(serverIndex, reqIndex, serversListId = 'serversList') {
|
|
416
|
+
const item = document.querySelector(`#${serversListId} .server-item[data-index="${serverIndex}"] .server-requirement-item[data-req-index="${reqIndex}"]`);
|
|
417
|
+
if (item) item.remove();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export function toggleServerAliasField(serverIndex, reqIndex, serversListId = 'serversList') {
|
|
421
|
+
const requirementItem = document.querySelector(`#${serversListId} .server-item[data-index="${serverIndex}"] .server-requirement-item[data-req-index="${reqIndex}"]`);
|
|
422
|
+
if (!requirementItem) return;
|
|
423
|
+
|
|
424
|
+
const typeSelect = requirementItem.querySelector(`select[name="servers[${serverIndex}].requirements[${reqIndex}].type"]`);
|
|
425
|
+
const aliasField = requirementItem.querySelector(`#server-alias-field-${serverIndex}-${reqIndex}`);
|
|
426
|
+
|
|
427
|
+
if (typeSelect && aliasField) {
|
|
428
|
+
aliasField.classList.toggle('hidden', typeSelect.value !== 'command');
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function toggleServerRegistryConfig(serverIndex, reqIndex, serversListId = 'serversList') {
|
|
433
|
+
const requirementItem = document.querySelector(`#${serversListId} .server-item[data-index="${serverIndex}"] .server-requirement-item[data-req-index="${reqIndex}"]`);
|
|
434
|
+
if (!requirementItem) return;
|
|
435
|
+
|
|
436
|
+
const select = requirementItem.querySelector(`select[name="servers[${serverIndex}].requirements[${reqIndex}].registryType"]`);
|
|
437
|
+
if (!select) return;
|
|
438
|
+
|
|
439
|
+
['github', 'artifacts'].forEach(type => {
|
|
440
|
+
const config = requirementItem.querySelector(`#server-${type}-config-${serverIndex}-${reqIndex}`);
|
|
441
|
+
if (config) {
|
|
442
|
+
config.classList.toggle('hidden', select.value !== type);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export async function browseLocalSchema(serverIndex, serversListId = 'serversList') {
|
|
448
|
+
const schemaPath = document.querySelector(`#${serversListId} .server-item[data-index="${serverIndex}"] #schema-path-${serverIndex}`);
|
|
449
|
+
if (!schemaPath) return;
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
if ('showOpenFilePicker' in window) {
|
|
453
|
+
const [fileHandle] = await window.showOpenFilePicker({
|
|
454
|
+
types: [{ description: 'JSON Files', accept: { 'application/json': ['.json'] } }]
|
|
455
|
+
});
|
|
456
|
+
const file = await fileHandle.getFile();
|
|
457
|
+
schemaPath.value = file.name;
|
|
458
|
+
} else {
|
|
459
|
+
const input = document.createElement('input');
|
|
460
|
+
input.type = 'file';
|
|
461
|
+
input.accept = '.json';
|
|
462
|
+
input.onchange = (e) => {
|
|
463
|
+
if (e.target.files.length > 0) {
|
|
464
|
+
schemaPath.value = e.target.files[0].name;
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
input.click();
|
|
468
|
+
}
|
|
469
|
+
} catch (err) {
|
|
470
|
+
console.error('Error browsing for schema file:', err);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export function toggleSectionContent(contentId, iconElement, toggleElement = null) {
|
|
475
|
+
const contentElement = document.getElementById(contentId);
|
|
476
|
+
if (!contentElement) return;
|
|
477
|
+
|
|
478
|
+
toggleElement = toggleElement || (iconElement?.parentElement || null);
|
|
479
|
+
const isHiddenAfterToggle = contentElement.classList.toggle('hidden');
|
|
480
|
+
|
|
481
|
+
if (iconElement) {
|
|
482
|
+
iconElement.classList.toggle('bxs-chevron-up', !isHiddenAfterToggle);
|
|
483
|
+
iconElement.classList.toggle('bxs-chevron-down', isHiddenAfterToggle);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (toggleElement) {
|
|
487
|
+
toggleElement.setAttribute('aria-expanded', (!isHiddenAfterToggle).toString());
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export function toggleViewMode(isJsonView, currentFormId, currentServersListId, baseCategoryData = null, isExistingCategoryContext = false) {
|
|
492
|
+
const elements = {
|
|
493
|
+
createCategory: document.getElementById('panel-create-category'),
|
|
494
|
+
createServer: document.getElementById('panel-create-server'),
|
|
495
|
+
jsonEditor: document.getElementById('panel-json-editor'),
|
|
496
|
+
textarea: document.getElementById('jsonEditorTextarea'),
|
|
497
|
+
activeForm: document.getElementById(currentFormId),
|
|
498
|
+
viewModeToggle: document.getElementById('viewModeToggle'),
|
|
499
|
+
actionButtons: {
|
|
500
|
+
json: document.getElementById('jsonEditorActionsContainer'),
|
|
501
|
+
main: document.querySelector(`#${currentFormId} ~ .flex.justify-end.space-x-4.pt-6.border-t`)
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
if (isViewAlreadyActive(isJsonView, elements, currentFormId)) return;
|
|
506
|
+
|
|
507
|
+
// Clear any previous temporary state
|
|
508
|
+
if (state.originalServerNamesForFormPopulation) {
|
|
509
|
+
state.originalServerNamesForFormPopulation = null;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
try {
|
|
513
|
+
if (isJsonView) {
|
|
514
|
+
handleJsonView(elements, currentFormId, currentServersListId, baseCategoryData, isExistingCategoryContext);
|
|
515
|
+
} else { // Switching to Form View
|
|
516
|
+
// If switching to form view for 'create-server' tab in an existing category context,
|
|
517
|
+
// prepare the set of original server names.
|
|
518
|
+
if (isExistingCategoryContext && baseCategoryData && baseCategoryData.mcpServers && currentFormId === 'onboardServerForm') {
|
|
519
|
+
state.originalServerNamesForFormPopulation = new Set(
|
|
520
|
+
baseCategoryData.mcpServers.map(s => s.name).filter(name => name) // Ensure name exists
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
handleFormView(elements, currentFormId, currentServersListId, baseCategoryData, isExistingCategoryContext);
|
|
524
|
+
// Clean up the temporary state after populateForm has finished using it.
|
|
525
|
+
if (state.originalServerNamesForFormPopulation) {
|
|
526
|
+
state.originalServerNamesForFormPopulation = null;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
} catch (error) {
|
|
530
|
+
console.error('Error in view mode toggle:', error);
|
|
531
|
+
showToast(`Error: ${error.message}`, 'error');
|
|
532
|
+
if (elements.viewModeToggle) elements.viewModeToggle.checked = isJsonView; // Revert toggle on error
|
|
533
|
+
// Ensure cleanup on error too
|
|
534
|
+
if (state.originalServerNamesForFormPopulation) {
|
|
535
|
+
state.originalServerNamesForFormPopulation = null;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function isViewAlreadyActive(isJsonView, elements, currentFormId) {
|
|
541
|
+
if (isJsonView && elements.jsonEditor && !elements.jsonEditor.classList.contains('hidden')) {
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (!isJsonView && elements.jsonEditor?.classList.contains('hidden')) {
|
|
546
|
+
if (currentFormId === 'onboardForm' && elements.createCategory && !elements.createCategory.classList.contains('hidden')) {
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
if (currentFormId === 'onboardServerForm' && elements.createServer && !elements.createServer.classList.contains('hidden')) {
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function handleJsonView(elements, currentFormId, currentServersListId, baseCategoryData, isExistingCategoryContext) {
|
|
558
|
+
const feedConfig = getFeedConfiguration(elements.activeForm, baseCategoryData, isExistingCategoryContext);
|
|
559
|
+
|
|
560
|
+
elements.textarea.value = JSON.stringify(feedConfig, null, 2);
|
|
561
|
+
elements.textarea.readOnly = isExistingCategoryContext && baseCategoryData?.mcpServers?.length > 0;
|
|
562
|
+
|
|
563
|
+
if (elements.textarea.readOnly) {
|
|
564
|
+
elements.textarea.classList.add('bg-gray-100', 'cursor-not-allowed');
|
|
565
|
+
showToast('JSON view is read-only for existing servers in this category.', 'info');
|
|
566
|
+
} else {
|
|
567
|
+
elements.textarea.classList.remove('bg-gray-100', 'cursor-not-allowed');
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
togglePanels(elements, true);
|
|
571
|
+
toggleButtons(elements.actionButtons, true);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function handleFormView(elements, currentFormId, currentServersListId, baseCategoryData, isExistingCategoryContext) {
|
|
575
|
+
const jsonData = JSON.parse(elements.textarea.value);
|
|
576
|
+
// Explicitly clear the server list container before repopulating to prevent UI duplication
|
|
577
|
+
const serverListContainer = document.getElementById(currentServersListId);
|
|
578
|
+
if (serverListContainer) {
|
|
579
|
+
serverListContainer.innerHTML = ''; // Clear existing UI server items
|
|
580
|
+
}
|
|
581
|
+
// Reset counters and other dynamic state
|
|
582
|
+
resetOnboardFormDynamicContent(currentFormId, currentServersListId);
|
|
583
|
+
populateForm(jsonData, currentFormId, isExistingCategoryContext && baseCategoryData?.mcpServers?.length > 0, currentServersListId);
|
|
584
|
+
|
|
585
|
+
togglePanels(elements, false, currentFormId);
|
|
586
|
+
toggleButtons(elements.actionButtons, false);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function getFeedConfiguration(activeForm, baseCategoryData, isExistingCategoryContext) {
|
|
590
|
+
if (!isExistingCategoryContext || !baseCategoryData) {
|
|
591
|
+
return activeForm ? getFormData(activeForm) : {};
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const newData = getFormData(activeForm, true, baseCategoryData);
|
|
595
|
+
const merged = JSON.parse(JSON.stringify(baseCategoryData));
|
|
596
|
+
|
|
597
|
+
merged.mcpServers = (merged.mcpServers || []).concat(newData.mcpServers || []);
|
|
598
|
+
|
|
599
|
+
const existingReqs = new Set(merged.requirements?.map(r => `${r.type}|${r.name}|${r.version}`) || []);
|
|
600
|
+
(newData.requirements || []).forEach(req => {
|
|
601
|
+
const key = `${req.type}|${req.name}|${req.version}`;
|
|
602
|
+
if (!existingReqs.has(key)) {
|
|
603
|
+
merged.requirements = merged.requirements || [];
|
|
604
|
+
merged.requirements.push(req);
|
|
605
|
+
existingReqs.add(key);
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
return merged;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function togglePanels(elements, isJsonView, currentFormId = null) {
|
|
613
|
+
// First ensure all panels are hidden
|
|
614
|
+
elements.createCategory?.classList.add('hidden');
|
|
615
|
+
elements.createServer?.classList.add('hidden');
|
|
616
|
+
elements.jsonEditor?.classList.add('hidden');
|
|
617
|
+
|
|
618
|
+
// Then show only the appropriate panel
|
|
619
|
+
if (isJsonView) {
|
|
620
|
+
elements.jsonEditor?.classList.remove('hidden');
|
|
621
|
+
} else if (currentFormId) {
|
|
622
|
+
// Show only the panel corresponding to the current form
|
|
623
|
+
if (currentFormId === 'onboardForm') {
|
|
624
|
+
elements.createCategory?.classList.remove('hidden');
|
|
625
|
+
} else if (currentFormId === 'onboardServerForm') {
|
|
626
|
+
elements.createServer?.classList.remove('hidden');
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function toggleButtons(buttons, isJsonView) {
|
|
632
|
+
buttons.main?.classList.toggle('hidden', isJsonView);
|
|
633
|
+
buttons.json?.classList.toggle('hidden', isJsonView);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
export async function saveJsonData(activeTab, currentSelectedCategoryData = null) {
|
|
637
|
+
const textarea = document.getElementById('jsonEditorTextarea');
|
|
638
|
+
if (textarea.readOnly) {
|
|
639
|
+
showToast('Cannot save, JSON editor is in read-only mode for existing servers.', 'warning');
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
try {
|
|
644
|
+
const editorData = JSON.parse(textarea.value);
|
|
645
|
+
const config = prepareConfiguration(activeTab, editorData, currentSelectedCategoryData);
|
|
646
|
+
if (!config) return;
|
|
647
|
+
|
|
648
|
+
await saveConfiguration(config);
|
|
649
|
+
} catch (error) {
|
|
650
|
+
console.error('Error saving JSON data:', error);
|
|
651
|
+
showToast(`Error saving JSON data: ${error.message}`, 'error');
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function prepareConfiguration(activeTab, editorData, currentSelectedCategoryData) {
|
|
656
|
+
if (activeTab === 'create-category') {
|
|
657
|
+
if (!editorData.name) {
|
|
658
|
+
showToast('Category Name is required in JSON.', 'error');
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
return {
|
|
662
|
+
data: editorData,
|
|
663
|
+
isUpdate: isUpdateOperation(editorData.name)
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (activeTab === 'create-server') {
|
|
668
|
+
if (!currentSelectedCategoryData?.name) {
|
|
669
|
+
showToast('No existing category context for saving JSON.', 'error');
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
data: mergeConfigurations(currentSelectedCategoryData, editorData),
|
|
674
|
+
isUpdate: true
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
showToast('Invalid tab context for saving JSON.', 'error');
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function isUpdateOperation(categoryName) {
|
|
683
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
684
|
+
return urlParams.get('action') === 'edit' && urlParams.get('category') === categoryName;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function mergeConfigurations(base, editor) {
|
|
688
|
+
const merged = JSON.parse(JSON.stringify(base));
|
|
689
|
+
const existingServerNames = new Set(merged.mcpServers?.map(s => s.name) || []);
|
|
690
|
+
|
|
691
|
+
const newServers = (editor.mcpServers || []).filter(s => !existingServerNames.has(s.name));
|
|
692
|
+
merged.mcpServers = (merged.mcpServers || []).concat(newServers);
|
|
693
|
+
|
|
694
|
+
const existingReqs = new Set(merged.requirements?.map(r => `${r.type}|${r.name}|${r.version}`) || []);
|
|
695
|
+
(editor.requirements || []).forEach(req => {
|
|
696
|
+
const key = `${req.type}|${req.name}|${req.version}`;
|
|
697
|
+
const isNewServerReq = newServers.some(s =>
|
|
698
|
+
s.dependencies?.requirements?.some(r =>
|
|
699
|
+
r.name === req.name && r.version === req.version
|
|
700
|
+
)
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
if (!existingReqs.has(key) && isNewServerReq) {
|
|
704
|
+
merged.requirements = merged.requirements || [];
|
|
705
|
+
merged.requirements.push(req);
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
return merged;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
async function saveConfiguration({ data, isUpdate }) {
|
|
713
|
+
const response = await fetch('/api/categories/onboard', {
|
|
714
|
+
method: 'POST',
|
|
715
|
+
headers: { 'Content-Type': 'application/json' },
|
|
716
|
+
body: JSON.stringify({ categoryData: data, isUpdate })
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
if (!response.ok) {
|
|
720
|
+
const error = await response.json();
|
|
721
|
+
throw new Error(error.error || `HTTP error! status: ${response.status}`);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
showToast('JSON data submitted successfully!', 'success');
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
export async function copyJsonToClipboard() {
|
|
728
|
+
const textarea = document.getElementById('jsonEditorTextarea');
|
|
729
|
+
if (!textarea) {
|
|
730
|
+
showToast('JSON content not found.', 'error');
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
try {
|
|
735
|
+
await navigator.clipboard.writeText(textarea.value);
|
|
736
|
+
showToast('JSON copied to clipboard!', 'success');
|
|
737
|
+
} catch (err) {
|
|
738
|
+
console.error('Failed to copy JSON:', err);
|
|
739
|
+
showToast('Failed to copy JSON. See console for details.', 'error');
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
Object.entries({
|
|
744
|
+
addServer,
|
|
745
|
+
removeServer,
|
|
746
|
+
addEnvVariable,
|
|
747
|
+
removeEnvVariable,
|
|
748
|
+
addServerRequirement,
|
|
749
|
+
removeServerRequirement,
|
|
750
|
+
toggleServerAliasField,
|
|
751
|
+
toggleServerRegistryConfig,
|
|
752
|
+
browseLocalSchema,
|
|
753
|
+
renderInstallationConfig,
|
|
754
|
+
toggleSectionContent,
|
|
755
|
+
copyJsonToClipboard
|
|
756
|
+
}).forEach(([name, fn]) => {
|
|
757
|
+
window[name] = fn;
|
|
758
|
+
});
|