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.
Files changed (157) hide show
  1. package/dist/core/ConfigurationProvider.d.ts +1 -0
  2. package/dist/core/ConfigurationProvider.js +15 -0
  3. package/dist/core/InstallationService.js +2 -7
  4. package/dist/core/MCPManager.d.ts +11 -2
  5. package/dist/core/MCPManager.js +24 -1
  6. package/dist/core/RequirementService.js +2 -8
  7. package/dist/core/installers/clients/BaseClientInstaller.d.ts +51 -0
  8. package/dist/core/installers/clients/BaseClientInstaller.js +160 -0
  9. package/dist/core/installers/clients/ClientInstaller.d.ts +16 -8
  10. package/dist/core/installers/clients/ClientInstaller.js +77 -504
  11. package/dist/core/installers/clients/ClientInstallerFactory.d.ts +19 -0
  12. package/dist/core/installers/clients/ClientInstallerFactory.js +41 -0
  13. package/dist/core/installers/clients/ClineInstaller.d.ts +18 -0
  14. package/dist/core/installers/clients/ClineInstaller.js +124 -0
  15. package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +34 -0
  16. package/dist/core/installers/clients/GithubCopilotInstaller.js +162 -0
  17. package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +15 -0
  18. package/dist/core/installers/clients/MSRooCodeInstaller.js +122 -0
  19. package/dist/core/installers/requirements/BaseInstaller.d.ts +11 -34
  20. package/dist/core/installers/requirements/BaseInstaller.js +5 -116
  21. package/dist/core/installers/requirements/CommandInstaller.d.ts +6 -1
  22. package/dist/core/installers/requirements/CommandInstaller.js +7 -0
  23. package/dist/core/installers/requirements/GeneralInstaller.d.ts +6 -1
  24. package/dist/core/installers/requirements/GeneralInstaller.js +9 -4
  25. package/dist/core/installers/requirements/NpmInstaller.d.ts +46 -7
  26. package/dist/core/installers/requirements/NpmInstaller.js +150 -58
  27. package/dist/core/installers/requirements/PipInstaller.d.ts +9 -0
  28. package/dist/core/installers/requirements/PipInstaller.js +66 -28
  29. package/dist/core/onboard/FeedOnboardService.d.ts +72 -0
  30. package/dist/core/onboard/FeedOnboardService.js +312 -0
  31. package/dist/core/onboard/OnboardProcessor.d.ts +79 -0
  32. package/dist/core/onboard/OnboardProcessor.js +290 -0
  33. package/dist/core/onboard/OnboardStatus.d.ts +49 -0
  34. package/dist/core/onboard/OnboardStatus.js +10 -0
  35. package/dist/core/onboard/OnboardStatusManager.d.ts +57 -0
  36. package/dist/core/onboard/OnboardStatusManager.js +176 -0
  37. package/dist/core/types.d.ts +6 -6
  38. package/dist/core/validators/FeedValidator.d.ts +20 -0
  39. package/dist/core/validators/FeedValidator.js +80 -0
  40. package/dist/core/validators/IServerValidator.d.ts +19 -0
  41. package/dist/core/validators/IServerValidator.js +2 -0
  42. package/dist/core/validators/SSEServerValidator.d.ts +15 -0
  43. package/dist/core/validators/SSEServerValidator.js +39 -0
  44. package/dist/core/validators/ServerValidatorFactory.d.ts +24 -0
  45. package/dist/core/validators/ServerValidatorFactory.js +45 -0
  46. package/dist/core/validators/StdioServerValidator.d.ts +46 -0
  47. package/dist/core/validators/StdioServerValidator.js +229 -0
  48. package/dist/services/InstallRequestValidator.d.ts +1 -1
  49. package/dist/services/ServerService.d.ts +9 -6
  50. package/dist/services/ServerService.js +18 -7
  51. package/dist/utils/adoUtils.d.ts +29 -0
  52. package/dist/utils/adoUtils.js +252 -0
  53. package/dist/utils/clientUtils.d.ts +0 -7
  54. package/dist/utils/clientUtils.js +0 -42
  55. package/dist/utils/githubUtils.d.ts +10 -0
  56. package/dist/utils/githubUtils.js +22 -0
  57. package/dist/utils/macroExpressionUtils.d.ts +38 -0
  58. package/dist/utils/macroExpressionUtils.js +116 -0
  59. package/dist/utils/osUtils.d.ts +4 -20
  60. package/dist/utils/osUtils.js +78 -23
  61. package/dist/web/contract/serverContract.d.ts +66 -0
  62. package/dist/web/contract/serverContract.js +2 -0
  63. package/dist/web/public/css/notifications.css +48 -17
  64. package/dist/web/public/css/onboard.css +107 -0
  65. package/dist/web/public/index.html +90 -18
  66. package/dist/web/public/js/api.js +3 -6
  67. package/dist/web/public/js/flights/flights.js +127 -0
  68. package/dist/web/public/js/modal/index.js +58 -0
  69. package/dist/web/public/js/modal/installHandler.js +227 -0
  70. package/dist/web/public/js/modal/installModal.js +163 -0
  71. package/dist/web/public/js/modal/installation.js +281 -0
  72. package/dist/web/public/js/modal/loadingModal.js +52 -0
  73. package/dist/web/public/js/modal/loadingUI.js +74 -0
  74. package/dist/web/public/js/modal/messageQueue.js +112 -0
  75. package/dist/web/public/js/modal/modalSetup.js +513 -0
  76. package/dist/web/public/js/modal/modalUI.js +214 -0
  77. package/dist/web/public/js/modal/modalUtils.js +49 -0
  78. package/dist/web/public/js/modal/version.js +20 -0
  79. package/dist/web/public/js/modal/versionUtils.js +20 -0
  80. package/dist/web/public/js/modal.js +25 -1041
  81. package/dist/web/public/js/notifications.js +66 -27
  82. package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
  83. package/dist/web/public/js/onboard/formProcessor.js +864 -0
  84. package/dist/web/public/js/onboard/index.js +374 -0
  85. package/dist/web/public/js/onboard/publishHandler.js +132 -0
  86. package/dist/web/public/js/onboard/state.js +76 -0
  87. package/dist/web/public/js/onboard/templates.js +343 -0
  88. package/dist/web/public/js/onboard/uiHandlers.js +758 -0
  89. package/dist/web/public/js/onboard/validationHandlers.js +378 -0
  90. package/dist/web/public/js/serverCategoryDetails.js +43 -17
  91. package/dist/web/public/js/serverCategoryList.js +15 -2
  92. package/dist/web/public/onboard.html +296 -0
  93. package/dist/web/public/styles.css +91 -1
  94. package/dist/web/server.d.ts +0 -10
  95. package/dist/web/server.js +131 -22
  96. package/package.json +2 -2
  97. package/src/core/ConfigurationProvider.ts +15 -0
  98. package/src/core/InstallationService.ts +2 -7
  99. package/src/core/MCPManager.ts +26 -1
  100. package/src/core/RequirementService.ts +2 -9
  101. package/src/core/installers/clients/BaseClientInstaller.ts +196 -0
  102. package/src/core/installers/clients/ClientInstaller.ts +97 -589
  103. package/src/core/installers/clients/ClientInstallerFactory.ts +46 -0
  104. package/src/core/installers/clients/ClineInstaller.ts +135 -0
  105. package/src/core/installers/clients/GithubCopilotInstaller.ts +179 -0
  106. package/src/core/installers/clients/MSRooCodeInstaller.ts +133 -0
  107. package/src/core/installers/requirements/BaseInstaller.ts +13 -136
  108. package/src/core/installers/requirements/CommandInstaller.ts +9 -1
  109. package/src/core/installers/requirements/GeneralInstaller.ts +11 -4
  110. package/src/core/installers/requirements/NpmInstaller.ts +178 -61
  111. package/src/core/installers/requirements/PipInstaller.ts +68 -29
  112. package/src/core/onboard/FeedOnboardService.ts +346 -0
  113. package/src/core/onboard/OnboardProcessor.ts +305 -0
  114. package/src/core/onboard/OnboardStatus.ts +55 -0
  115. package/src/core/onboard/OnboardStatusManager.ts +188 -0
  116. package/src/core/types.ts +6 -6
  117. package/src/core/validators/FeedValidator.ts +79 -0
  118. package/src/core/validators/IServerValidator.ts +21 -0
  119. package/src/core/validators/SSEServerValidator.ts +43 -0
  120. package/src/core/validators/ServerValidatorFactory.ts +51 -0
  121. package/src/core/validators/StdioServerValidator.ts +259 -0
  122. package/src/services/InstallRequestValidator.ts +1 -1
  123. package/src/services/ServerService.ts +22 -7
  124. package/src/utils/adoUtils.ts +291 -0
  125. package/src/utils/clientUtils.ts +0 -44
  126. package/src/utils/githubUtils.ts +24 -0
  127. package/src/utils/macroExpressionUtils.ts +121 -0
  128. package/src/utils/osUtils.ts +89 -24
  129. package/src/web/contract/serverContract.ts +74 -0
  130. package/src/web/public/css/notifications.css +48 -17
  131. package/src/web/public/css/onboard.css +107 -0
  132. package/src/web/public/index.html +90 -18
  133. package/src/web/public/js/api.js +3 -6
  134. package/src/web/public/js/flights/flights.js +127 -0
  135. package/src/web/public/js/modal/index.js +58 -0
  136. package/src/web/public/js/modal/installModal.js +163 -0
  137. package/src/web/public/js/modal/installation.js +281 -0
  138. package/src/web/public/js/modal/loadingModal.js +52 -0
  139. package/src/web/public/js/modal/messageQueue.js +112 -0
  140. package/src/web/public/js/modal/modalSetup.js +513 -0
  141. package/src/web/public/js/modal/modalUtils.js +49 -0
  142. package/src/web/public/js/modal/versionUtils.js +20 -0
  143. package/src/web/public/js/modal.js +25 -1041
  144. package/src/web/public/js/notifications.js +66 -27
  145. package/src/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
  146. package/src/web/public/js/onboard/formProcessor.js +864 -0
  147. package/src/web/public/js/onboard/index.js +374 -0
  148. package/src/web/public/js/onboard/publishHandler.js +132 -0
  149. package/src/web/public/js/onboard/state.js +76 -0
  150. package/src/web/public/js/onboard/templates.js +343 -0
  151. package/src/web/public/js/onboard/uiHandlers.js +758 -0
  152. package/src/web/public/js/onboard/validationHandlers.js +378 -0
  153. package/src/web/public/js/serverCategoryDetails.js +43 -17
  154. package/src/web/public/js/serverCategoryList.js +15 -2
  155. package/src/web/public/onboard.html +296 -0
  156. package/src/web/public/styles.css +91 -1
  157. package/src/web/server.ts +167 -58
@@ -1,1045 +1,29 @@
1
- import { showToast, showConfirm } from './notifications.js';
2
-
3
- // Wait for DOM to be loaded before initializing modal functionality
4
- document.addEventListener('DOMContentLoaded', () => {
5
- setupModalOutsideClick();
6
- });
7
- /**
8
- * Simple version comparison function
9
- * @param {string} v1 First version
10
- * @param {string} v2 Second version
11
- * @returns {number} 1 if v1 > v2, -1 if v1 < v2, 0 if equal
12
- */
13
- function compareVersions(v1, v2) {
14
- const v1Parts = v1.split('.').map(Number);
15
- const v2Parts = v2.split('.').map(Number);
16
-
17
- for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
18
- const v1Part = v1Parts[i] || 0;
19
- const v2Part = v2Parts[i] || 0;
20
-
21
- if (v1Part > v2Part) return 1;
22
- if (v1Part < v2Part) return -1;
23
- }
24
-
25
- return 0;
26
- }
27
-
28
- /**
29
- * Message queue for delayed loading modal updates.
30
- * @type {Array<{msg: string, isCompletion: boolean}>}
31
- */
32
- let messageQueue = [];
33
-
34
- /** @type {boolean} Flag indicating if a message is currently being appended */
35
- let isAppending = false;
36
-
37
- /** @type {boolean} Flag indicating if completion UI update is pending */
38
- let completionPending = false;
39
-
40
- /**
41
- * Process messages in the queue with a delay between each message.
42
- * When the queue is empty and completion is pending, triggers the completion UI.
43
- * @private
44
- */
45
- function processMessageQueue() {
46
- if (isAppending || messageQueue.length === 0) {
47
- // Only update completion UI if all messages have been displayed
48
- if (messageQueue.length === 0 && completionPending) {
49
- // Add small delay before showing completion to ensure last message is visible
50
- setTimeout(() => {
51
- updateCompletionUI();
52
- completionPending = false;
53
- }, 500);
54
- }
55
- return;
56
- }
57
-
58
- isAppending = true;
59
- const { msg, isCompletion } = messageQueue.shift();
60
-
61
- if (isCompletion) {
62
- completionPending = true;
63
- }
64
-
65
- _appendInstallLoadingMessage(msg);
66
-
67
- setTimeout(() => {
68
- isAppending = false;
69
- processMessageQueue();
70
- }, 1000);
71
- }
72
-
73
- /**
74
- * Queue a message to be displayed with a delay.
75
- * Messages are displayed sequentially with a 1-second delay between each.
76
- * @param {string} msg The message to display
77
- * @param {boolean} [isCompletion=false] If true, triggers completion UI after message display
78
- */
79
- function delayedAppendInstallLoadingMessage(msg, isCompletion = false) {
80
- messageQueue.push({ msg, isCompletion });
81
- processMessageQueue();
82
- }
83
-
84
- function updateCompletionUI() {
85
- const loadingTitle = document.querySelector('.loading-title');
86
- const loadingIcon = document.querySelector('.loading-icon');
87
-
88
- if (loadingTitle) {
89
- loadingTitle.textContent = 'Completed';
90
- loadingTitle.classList.add('completed');
91
- }
92
-
93
- if (loadingIcon) {
94
- loadingIcon.innerHTML = `
95
- <svg width="48" height="48" viewBox="0 0 48 48" fill="none">
96
- <circle cx="24" cy="24" r="20" stroke="#059669" stroke-width="4"/>
97
- <path d="M16 24l6 6 12-12" stroke="#059669" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
98
- </svg>
99
- `;
100
- loadingIcon.classList.add('completed');
101
- }
102
- }
103
- /**
104
- * Internal function to append a message to the loading modal with formatting.
105
- * @private
106
- * @param {string} message - The message to append
107
- */
108
- function _appendInstallLoadingMessage(message) {
109
- const loadingMsg = document.getElementById('installLoadingMessage');
110
- if (loadingMsg) {
111
- // Split message on newlines
112
- const lines = message.split('\n');
113
-
114
- lines.forEach(line => {
115
- // Check if line contains an error
116
- const isError = line.toLowerCase().includes('error') ||
117
- line.toLowerCase().includes('failed') ||
118
- line.toLowerCase().includes('timeout');
119
-
120
- const formattedMessage = line;
121
-
122
- // Create message container
123
- const messageDiv = document.createElement('div');
124
- messageDiv.className = 'message-line';
125
-
126
- // Apply yellow color for error messages
127
- if (isError) {
128
- messageDiv.style.color = '#f59e0b';
129
- }
130
- messageDiv.innerHTML = formattedMessage;
131
-
132
-
133
- loadingMsg.appendChild(messageDiv);
134
- loadingMsg.scrollTop = loadingMsg.scrollHeight;
135
- });
136
- } else {
137
- console.error('[LoadingModal] Element not found: installLoadingMessage');
138
- }
139
- }
140
-
141
- /**
142
- * Display the installation loading modal and prepare it for messages.
143
- */
144
- function showInstallLoadingModal(operation = 'Installing') {
145
- const loadingModal = document.getElementById('installLoadingModal');
146
- const loadingMsg = document.getElementById('installLoadingMessage');
147
- const loadingTitle = document.querySelector('.loading-title');
148
- if (loadingModal && loadingMsg && loadingTitle) {
149
- loadingModal.style.display = 'block';
150
- loadingMsg.innerHTML = '';
151
- loadingTitle.textContent = `${operation}...`;
152
- } else {
153
- console.error('[LoadingModal] Required elements not found: installLoadingModal, installLoadingMessage, or loading-title');
154
- }
155
- }
156
-
157
- /**
158
- * Append a message to the loading modal display.
159
- * @param {string} message - The message to display
160
- */
161
- function appendInstallLoadingMessage(message) {
162
- console.log(`[LoadingModal] Message: ${message}`);
163
- _appendInstallLoadingMessage(message);
164
- }
165
-
166
- /**
167
- * Hide the install loading modal.
168
- */
169
- function hideInstallLoadingModal() {
170
- console.log('[LoadingModal] Hiding installation modal');
171
- const loadingModal = document.getElementById('installLoadingModal');
172
- if (loadingModal) {
173
- loadingModal.style.display = 'none';
174
-
175
- // Get the last selected category
176
- const lastSelected = localStorage.getItem('lastSelectedCategory');
177
-
178
- // Refresh page while maintaining category selection
179
- setTimeout(() => {
180
- if (lastSelected) {
181
- window.location.href = window.location.pathname + '?category=' + encodeURIComponent(lastSelected);
182
- } else {
183
- location.reload();
184
- }
185
- }, 100);
186
- } else {
187
- console.error('[LoadingModal] loading modal DOM not found');
188
- }
189
- }
190
-
191
- // Show install modal for MCP tools
192
- async function showInstallModal(categoryName, serverName, callback) {
193
- console.log("Showing install modal for:", serverName);
194
-
195
- // Wait for a short delay to ensure modal is loaded
196
- await new Promise(resolve => setTimeout(resolve, 100));
197
-
198
- const modal = document.getElementById('installModal');
199
- if (!modal) {
200
- console.error('Modal container not found');
201
- return;
202
- }
203
-
204
- const title = modal.querySelector('#modalTitle');
205
- const envInputsDiv = modal.querySelector('#modalEnvInputs');
206
- const targetDiv = modal.querySelector('#modalTargets');
207
- const modalRequirements = modal.querySelector('#modalRequirements');
208
- const modalArguments = modal.querySelector('#modalArguments');
209
-
210
- // Global array to track selected clients
211
- window.selectedClients = [];
212
-
213
- // Verify all required modal elements exist
214
- if (!title || !envInputsDiv || !targetDiv || !modalRequirements || !modalArguments) {
215
- console.error('Required modal elements not found');
216
- return;
217
- }
218
-
219
- title.textContent = `Install ${serverName}`;
220
- envInputsDiv.innerHTML = ''; // Clear previous inputs
221
- targetDiv.innerHTML = ''; // Clear previous targets
222
- modalRequirements.innerHTML = ''; // Clear previous requirements
223
- modalArguments.innerHTML = ''; // Clear previous arguments
224
-
225
- try {
226
- // Fetch both targets and server data simultaneously
227
- const [targetResponse, serverResponse] = await Promise.all([
228
- fetch('/api/targets'),
229
- fetch(`/api/categories/${categoryName}`)
230
- ]);
231
-
232
- if (!targetResponse.ok || !serverResponse.ok) {
233
- throw new Error('Failed to fetch required data');
234
- }
235
-
236
- const [targetData, serverData] = await Promise.all([
237
- targetResponse.json(),
238
- serverResponse.json()
239
- ]);
240
-
241
- if (!targetData.success || !serverData.success) {
242
- throw new Error('Invalid data received');
243
- }
244
-
245
- const mcpServer = serverData.data.feedConfiguration.mcpServers.find(server => server.name === serverName);
246
- if (!mcpServer) {
247
- throw new Error('Server configuration not found');
248
- }
249
-
250
- const installationStatus = serverData.data.installationStatus || {};
251
- const serverStatuses = installationStatus.serversStatus || {};
252
- const serverStatus = serverStatuses[serverName] || { installedStatus: {} };
253
-
254
- // Create client items with switch toggles
255
- targetData.data.forEach(target => {
256
- const operationStatus = serverStatus.installedStatus[target] || { status: 'not-installed', type: 'check', target: 'server' };
257
-
258
- // Determine client status
259
- let statusText = '';
260
- let statusClass = '';
261
-
262
- if (operationStatus.status === 'completed' && operationStatus.type === 'install') {
263
- statusText = 'Installed';
264
- statusClass = 'installed';
265
- } else if (operationStatus.status === 'pending') {
266
- statusText = 'Pending Requirements';
267
- statusClass = 'pending';
268
- } else if (operationStatus.status === 'in-progress') {
269
- statusText = 'In Progress';
270
- statusClass = 'pending';
271
- } else if (operationStatus.status === 'failed') {
272
- statusText = 'Failed';
273
- statusClass = 'not-installed';
274
- }
275
- // Do not show not-installed status for targets that aren't installed
276
-
277
- const isConfigured = operationStatus.status === 'completed' && operationStatus.type === 'install';
278
-
279
- // Create client item
280
- const clientItem = document.createElement('div');
281
- clientItem.className = 'client-item';
282
- clientItem.dataset.target = target;
283
- clientItem.dataset.selected = 'false';
284
-
285
- // Determine if item should be selectable
286
- const isInProgress = operationStatus.status === 'in-progress';
287
- const isSelectable = !isConfigured && !isInProgress;
288
-
289
- // Add appropriate non-selectable classes
290
- if (!isSelectable) {
291
- clientItem.classList.add('non-selectable');
292
-
293
- if (isConfigured) {
294
- clientItem.classList.add('installed-item');
295
- clientItem.title = 'Already installed';
296
- } else if (isInProgress) {
297
- clientItem.classList.add('in-progress-item');
298
- clientItem.title = 'Installation in progress';
299
- }
300
- }
301
-
302
- // Make selectable clients clickable
303
- if (isSelectable) {
304
- clientItem.addEventListener('click', (e) => {
305
- const isSelected = clientItem.dataset.selected === 'true';
306
- clientItem.dataset.selected = isSelected ? 'false' : 'true';
307
-
308
- // Update class
309
- if (isSelected) {
310
- clientItem.classList.remove('selected');
311
- window.selectedClients = window.selectedClients.filter(c => c !== target);
312
- } else {
313
- clientItem.classList.add('selected');
314
- if (!window.selectedClients.includes(target)) {
315
- window.selectedClients.push(target);
316
- }
317
- }
318
-
319
- console.log('Selected clients:', window.selectedClients);
320
- });
321
- }
322
-
323
- // Create client info (name)
324
- const clientInfo = document.createElement('div');
325
- clientInfo.className = 'client-info';
326
-
327
- // Client name label
328
- const clientName = document.createElement('span');
329
- clientName.className = 'text-sm font-medium text-gray-900';
330
- clientName.textContent = target;
331
-
332
- // Add elements to client info
333
- clientInfo.appendChild(clientName);
334
-
335
- // Add elements to client item
336
- clientItem.appendChild(clientInfo);
337
-
338
- // Status container for badge and uninstall button
339
- const statusContainer = document.createElement('div');
340
- statusContainer.className = 'status-container';
341
-
342
- // Status badge - only show if we have status text
343
- if (statusText) {
344
- const statusBadge = document.createElement('span');
345
- statusBadge.className = `status-badge ${statusClass}`;
346
- statusBadge.textContent = statusText;
347
- statusContainer.appendChild(statusBadge);
348
-
349
- // Add uninstall button right after status badge if installed
350
- if (operationStatus.status === 'completed' && operationStatus.type === 'install') {
351
- const uninstallBtn = document.createElement('button');
352
- uninstallBtn.className = 'uninstall-btn text-red-600 hover:text-red-800 ml-2';
353
- uninstallBtn.innerHTML = '<i class="bx bx-trash"></i>';
354
- uninstallBtn.title = 'Uninstall from this client';
355
- uninstallBtn.onclick = async (e) => {
356
- e.stopPropagation(); // Prevent item selection
357
- e.preventDefault(); // Prevent form submission
358
- const confirmed = await showConfirm('Uninstall Confirmation', `Are you sure you want to uninstall ${serverName} from ${target}?`);
359
- if (confirmed) {
360
- // Add target to selectedClients for uninstallation
361
- window.selectedClients = [target];
362
- showInstallLoadingModal('Uninstalling');
363
- const serverList = {
364
- [serverName]: {
365
- removeData: true // Include removal of associated data
366
- }
367
- };
368
- try {
369
- delayedAppendInstallLoadingMessage(`Uninstalling ${serverName} from ${target}...`);
370
- await uninstallTools(categoryName, serverList, [target]);
371
- } catch (error) {
372
- delayedAppendInstallLoadingMessage(`Error: ${error.message}`);
373
- throw error; // Re-throw to trigger error handling in uninstallTools
374
- }
375
- }
376
- return false; // Prevent form submission
377
- };
378
- statusContainer.appendChild(uninstallBtn);
379
- }
380
- }
381
-
382
- clientItem.appendChild(statusContainer);
383
-
384
- // Add client item to target div
385
- targetDiv.appendChild(clientItem);
386
- });
387
-
388
- // Add section title at the top of the modal target div
389
- if (!targetDiv.querySelector('.section-title')) {
390
- const titleElement = document.createElement('h3');
391
- titleElement.className = 'section-title text-lg font-semibold text-gray-700 mb-3';
392
- titleElement.textContent = 'Client Status';
393
- targetDiv.insertBefore(titleElement, targetDiv.firstChild);
394
- }
395
-
396
- // Handle environment variables section
397
- const envRequirements = mcpServer.installation?.['env'] || mcpServer.installation?.env || {};
398
-
399
- // Make sure environment variables section has a title
400
- if (!envInputsDiv.querySelector('.section-title')) {
401
- const envTitle = document.createElement('h3');
402
- envTitle.className = 'section-title text-lg font-semibold text-gray-700 mb-3';
403
- envTitle.textContent = 'Environment Variables';
404
- envInputsDiv.insertBefore(envTitle, envInputsDiv.firstChild);
405
- }
406
-
407
- // Handle installation arguments section
408
- const installation = mcpServer.installation;
409
- if (installation) {
410
- // Add section title
411
- const argsTitle = document.createElement('h3');
412
- argsTitle.className = 'section-title text-lg font-semibold text-gray-700 mb-3';
413
- argsTitle.textContent = 'Arguments';
414
- modalArguments.appendChild(argsTitle);
415
-
416
- const argsContainer = document.createElement('div');
417
- argsContainer.className = 'args-container mb-3';
418
-
419
- // Add button to add new argument
420
- const addButton = document.createElement('button');
421
- addButton.type = 'button';
422
- addButton.className = 'add-arg-button px-3 py-1 text-sm text-blue-600 hover:text-blue-800 border border-blue-600 hover:border-blue-800 rounded-md mb-2';
423
- addButton.innerHTML = '<i class="bx bx-plus"></i> Add Argument';
424
- argsContainer.appendChild(addButton);
425
-
426
- // Function to create new argument input
427
- const createArgInput = (value = '') => {
428
- const argWrapper = document.createElement('div');
429
- argWrapper.className = 'arg-wrapper flex items-center gap-2 mb-2';
430
-
431
- const input = document.createElement('input');
432
- input.type = 'text';
433
- input.className = 'arg-input flex-grow';
434
- input.value = value;
435
- input.placeholder = 'Enter argument value';
436
-
437
- const removeButton = document.createElement('button');
438
- removeButton.type = 'button';
439
- removeButton.className = 'remove-arg-button text-red-600 hover:text-red-800';
440
- removeButton.innerHTML = '<i class="bx bx-trash"></i>';
441
- removeButton.onclick = () => argWrapper.remove();
442
-
443
- argWrapper.appendChild(input);
444
- argWrapper.appendChild(removeButton);
445
- return argWrapper;
446
- };
447
-
448
- // Add click handler for add button
449
- addButton.onclick = () => {
450
- argsContainer.appendChild(createArgInput());
451
- };
452
-
453
- // Create wrapper for args section
454
- const argsWrapper = document.createElement('div');
455
- argsWrapper.className = 'mb-3';
456
-
457
- // Set default values from installation args if available
458
- if (installation.args && Array.isArray(installation.args)) {
459
- installation.args.forEach(arg => {
460
- argsContainer.appendChild(createArgInput(arg));
461
- });
462
- }
463
-
464
- // Add initial empty input if no args
465
- if (!installation.args || installation.args.length === 0) {
466
- argsContainer.appendChild(createArgInput());
467
- }
468
-
469
- argsWrapper.appendChild(argsContainer);
470
-
471
- // Add description
472
- const description = document.createElement('p');
473
- description.className = 'text-xs text-gray-500 mt-1';
474
- description.textContent = 'Specify installation arguments, one per line. These will be used when installing the server.';
475
- argsWrapper.appendChild(description);
476
-
477
- modalArguments.appendChild(argsWrapper);
478
-
479
- // If command type is python, add python environment input
480
- if (installation.command === 'python' || installation.command.includes('python')) {
481
- const pythonEnvWrapper = document.createElement('div');
482
- pythonEnvWrapper.className = 'mt-4';
483
-
484
- const pythonEnvLabel = document.createElement('label');
485
- pythonEnvLabel.htmlFor = 'python_env';
486
- pythonEnvLabel.className = 'block text-sm font-medium text-gray-700 mb-1';
487
- pythonEnvLabel.textContent = 'Python Environment';
488
-
489
- const pythonEnvInput = document.createElement('input');
490
- pythonEnvInput.type = 'text';
491
- pythonEnvInput.id = 'python_env';
492
- pythonEnvInput.className = 'input-field';
493
- pythonEnvInput.placeholder = 'Enter Python environment path (optional)';
494
-
495
- pythonEnvWrapper.appendChild(pythonEnvLabel);
496
- pythonEnvWrapper.appendChild(pythonEnvInput);
497
-
498
- const envDescription = document.createElement('p');
499
- envDescription.className = 'text-xs text-gray-500 mt-1';
500
- envDescription.textContent = 'Specify the Python executable file(e.g. C:/python312/python) to use for installation. Leave empty to use system default.';
501
- pythonEnvWrapper.appendChild(envDescription);
502
-
503
- modalArguments.appendChild(pythonEnvWrapper);
504
- }
505
- }
506
-
507
- if (Object.keys(envRequirements).length === 0) {
508
- const noEnvMessage = document.createElement('p');
509
- noEnvMessage.className = 'text-gray-600';
510
- noEnvMessage.textContent = 'No environment variables required for this MCP server.';
511
- envInputsDiv.appendChild(noEnvMessage);
512
- } else {
513
- // Get clientMcpSettings from the targets endpoint
514
- let clientSettings = targetData.clientMcpSettings;
515
-
516
- // Create inputs for each environment variable
517
- Object.keys(envRequirements).forEach(key => {
518
- const req = envRequirements[key];
519
- const inputId = `env_${key}`;
520
- const inputWrapper = document.createElement('div');
521
- inputWrapper.className = 'mb-3';
522
-
523
- const label = document.createElement('label');
524
- label.htmlFor = inputId;
525
- label.className = 'block text-sm font-medium text-gray-700 mb-1';
526
- label.innerHTML = `${key} ${req.Required ? '<span class="text-red-500">*</span>' : ''}`;
527
-
528
- const input = document.createElement('input');
529
- input.type = req.isSecret ? 'password' : 'text';
530
- input.id = inputId;
531
- input.name = key;
532
- input.placeholder = req.Description || key;
533
-
534
- // For default value, first check MSRooCode for the target server, then use the provided default
535
- let defaultValue = req.Default || '';
536
-
537
- // Check if we have settings from MSRooCode
538
- if (clientSettings && clientSettings.MSRooCode &&
539
- clientSettings.MSRooCode.mcpServers &&
540
- clientSettings.MSRooCode.mcpServers[serverName] &&
541
- clientSettings.MSRooCode.mcpServers[serverName].env &&
542
- clientSettings.MSRooCode.mcpServers[serverName].env[key]) {
543
- defaultValue = clientSettings.MSRooCode.mcpServers[serverName].env[key];
544
- console.log(`Using MSRooCode value for ${key}: ${defaultValue}`);
545
- }
546
-
547
- input.value = defaultValue;
548
- input.required = req.Required;
549
- input.className = 'input-field';
550
-
551
- inputWrapper.appendChild(label);
552
- inputWrapper.appendChild(input);
553
-
554
- if (req.Description) {
555
- const description = document.createElement('p');
556
- description.className = 'text-xs text-gray-500 mt-1';
557
- description.textContent = req.Description;
558
- inputWrapper.appendChild(description);
559
- }
560
-
561
- envInputsDiv.appendChild(inputWrapper);
562
- });
563
- }
564
-
565
- // Handle server requirements section last
566
- const serverRequirements = mcpServer.dependencies?.requirements || [];
567
- const requirements = serverData.data.installationStatus?.requirementsStatus || {};
568
-
569
- if (serverRequirements.length > 0) {
570
- const reqHtml = serverRequirements.map(req => {
571
- const status = requirements[req.name] || {};
572
- let statusClass = status.installed
573
- ? 'text-green-600 bg-green-50'
574
- : 'text-yellow-600 bg-yellow-50';
575
- let statusText = status.installed ? 'Installed' : 'Required';
576
- let versionDisplay = status.version ? ` • <span class="font-medium">${status.version}</span>` : '';
577
- let updateToggle = '';
578
-
579
- // Check if there's an available update
580
- if (status.installed && status.availableUpdate && status.availableUpdate.version) {
581
- if (status.version && compareVersions(status.availableUpdate.version, status.version) > 0) {
582
- // Show version update information with yellow color and icon
583
- statusClass = 'text-yellow-600 bg-yellow-50';
584
- statusText = `<span style="color: #f59e0b; font-weight: bold; margin-right: 5px;">↑</span>${status.availableUpdate.version}`;
585
-
586
- // Create a toggle switch for update
587
- updateToggle = `
588
- <label class="inline-flex items-center cursor-pointer ml-2">
589
- <input type="checkbox" class="toggle-update sr-only"
590
- data-name="${req.name}"
591
- data-version="${status.availableUpdate.version}"
592
- data-category="${categoryName}"
593
- data-server="${serverName}">
594
- <div class="relative w-10 h-5 bg-gray-200 rounded-full toggle-bg">
595
- <div class="absolute inset-y-0 left-0 w-5 h-5 bg-white rounded-full transition-transform duration-300 transform"></div>
596
- </div>
597
- <span class="ml-2 text-sm text-gray-700">Update</span>
598
- </label>
599
- `;
600
- }
601
- }
602
-
603
- return `
604
- <div class="border border-gray-200 p-3 rounded-lg mb-2 hover:bg-gray-50">
605
- <div class="flex justify-between items-center">
606
- <div>
607
- <div class="font-semibold text-gray-800">${req.name}</div>
608
- <div class="text-sm text-gray-600 shadow-sm p-1 rounded bg-gray-50">
609
- <span class="font-medium">${status.type || 'package'}</span>${versionDisplay}
610
- </div>
611
- </div>
612
- <div class="flex items-center">
613
- <span class="${statusClass} inline-flex items-center px-3 py-1 rounded-full text-sm">
614
- ${statusText}
615
- </span>
616
- ${updateToggle}
617
- </div>
618
- </div>
619
- </div>
620
- `;
621
- }).join('');
622
-
623
- modalRequirements.innerHTML = `
624
- <h3 class="text-lg font-semibold text-gray-700 mb-3">Dependencies</h3>
625
- <p class="text-sm text-gray-600 mb-4">These dependencies will be automatically installed when installing the server</p>
626
- ${reqHtml}
627
- `;
628
- } else {
629
- modalRequirements.innerHTML = '<p class="text-gray-600">No additional dependencies required.</p>';
630
- }
631
-
632
- // Add event listeners for update toggles
633
- setTimeout(() => {
634
- const updateToggles = modalRequirements.querySelectorAll('.toggle-update');
635
- updateToggles.forEach(toggle => {
636
- toggle.addEventListener('change', function () {
637
- // When toggled, update the visual appearance
638
- const toggleBg = this.parentElement.querySelector('.toggle-bg');
639
- if (this.checked) {
640
- toggleBg.classList.add('bg-blue-500');
641
- toggleBg.querySelector('div').classList.add('translate-x-5');
642
- } else {
643
- toggleBg.classList.remove('bg-blue-500');
644
- toggleBg.querySelector('div').classList.remove('translate-x-5');
645
- }
646
- });
647
- });
648
- }, 100);
649
-
650
- // Set up the install form submit handler
651
- const installForm = document.getElementById('installForm');
652
- installForm.onsubmit = (e) => {
653
- e.preventDefault();
654
-
655
- // Get all environment variables and arguments
656
- const envVars = {};
657
- const inputs = envInputsDiv.querySelectorAll('input');
658
- inputs.forEach(input => {
659
- if (input.name && input.value) {
660
- envVars[input.name] = input.value;
661
- }
662
- });
663
-
664
- // Get installation arguments from individual input fields
665
- const argInputs = modalArguments.querySelectorAll('.arg-input');
666
- const args = Array.from(argInputs)
667
- .map(input => input.value.trim())
668
- .filter(val => val !== '');
669
-
670
- // Get Python environment if available
671
- const pythonEnvInput = document.getElementById('python_env');
672
- const pythonEnv = pythonEnvInput?.value?.trim();
673
-
674
- // Check for enabled update toggles and collect requirements to update
675
- const requirementsToUpdate = [];
676
- const updateToggles = modalRequirements.querySelectorAll('.toggle-update:checked');
677
- updateToggles.forEach(toggle => {
678
- requirementsToUpdate.push({
679
- name: toggle.dataset.name,
680
- version: toggle.dataset.version
681
- });
682
- });
683
-
684
- // Check if we have any requirements selected for update
685
- const hasRequirementsToUpdate = requirementsToUpdate.length > 0;
686
-
687
- // Only proceed if this isn't an uninstall operation
688
- const uninstallBtn = document.querySelector('.uninstall-btn');
689
- if (!uninstallBtn || !uninstallBtn.matches(':active')) {
690
- // Get selected clients
691
- const selectedTargets = window.selectedClients.length > 0 ?
692
- window.selectedClients :
693
- Array.from(document.querySelectorAll('.client-item.selected'))
694
- .map(item => item.dataset.target);
695
-
696
- console.log('Selected targets:', selectedTargets);
697
- console.log('Requirements to update:', requirementsToUpdate);
698
- if (selectedTargets.length === 0 && !hasRequirementsToUpdate) {
699
- showToast('Please select at least one client to configure.', 'error');
700
- return;
701
- }
702
- window.selectedClients = selectedTargets;
703
- }
704
-
705
- // Call install function with selected targets
706
- // Find installing message for the first selected target
707
- let installingMessage = "Starting installation...";
708
- const serverStatus = serverStatuses[serverName] || { installedStatus: {} };
709
- if (window.selectedClients.length > 0) {
710
- const target = window.selectedClients[0];
711
- const msg = serverStatus.installedStatus?.[target]?.message;
712
- if (msg) installingMessage = msg;
713
- }
714
-
715
- // Add requirements to update to serverInstallOptions if any
716
- const serverInstallOptions = {
717
- targetClients: window.selectedClients,
718
- env: envVars,
719
- args: args,
720
- settings: pythonEnv ? { pythonEnv } : undefined
721
- };
722
-
723
- // Only add requirements if we have any to update
724
- if (requirementsToUpdate.length > 0) {
725
- serverInstallOptions.requirements = requirementsToUpdate;
726
- }
727
-
728
- // For installation, use the selectedTargets from the validation above
729
- const targetsToUse = document.querySelector('.uninstall-btn:hover') ? [] : window.selectedClients;
730
- handleBulkClientInstall(categoryName, serverName, targetsToUse, envVars, installingMessage, serverData, serverInstallOptions);
731
- };
732
-
733
- } catch (error) {
734
- console.error("Error loading data:", error);
735
- targetDiv.innerHTML = `<p class="text-red-500">Error: ${error.message}</p>`;
736
- }
737
-
738
- modal.style.display = "block";
739
- }
740
-
741
- // Function to handle bulk client installations
742
- async function handleBulkClientInstall(categoryName, serverName, targets, envVars = {}, installingMessage = "Starting installation...", serverData = null, serverInstallOptions = null) {
743
- console.log('[LoadingModal] handleBulkClientInstall called', { categoryName, serverName, targets, envVars, serverInstallOptions });
744
- // Hide install modal, show loading modal
745
- const installModal = document.getElementById('installModal');
746
- console.log('[LoadingModal] installModal:', installModal);
747
- if (installModal) installModal.style.display = "none";
748
-
749
- // If serverData is provided, extract the installing message from it (latest status)
750
- if (serverData && serverData.data && serverData.data.installationStatus && serverData.data.installationStatus.serversStatus) {
751
- const serverStatuses = serverData.data.installationStatus.serversStatus;
752
- const serverStatus = serverStatuses[serverName] || { installedStatus: {} };
753
- if (targets && targets.length > 0) {
754
- const target = targets[0];
755
- const msg = serverStatus.installedStatus?.[target]?.message;
756
- if (msg) installingMessage = msg;
757
- }
758
- // Append the installing message for user visibility
759
- if (installingMessage && installingMessage !== "Starting installation...") {
760
- delayedAppendInstallLoadingMessage(installingMessage);
761
- }
762
- }
763
-
764
- showInstallLoadingModal();
765
- delayedAppendInstallLoadingMessage(installingMessage);
766
-
767
- try {
768
- delayedAppendInstallLoadingMessage("Installing, please wait...");
769
-
770
- // Use serverInstallOptions if provided, otherwise build the traditional options
771
- const requestBody = {
772
- serverList: {
773
- [serverName]: serverInstallOptions || {
774
- targetClients: targets,
775
- env: envVars
776
- }
777
- }
778
- };
779
-
780
- const response = await fetch(`/api/categories/${categoryName}/install`, {
781
- method: 'POST',
782
- headers: {
783
- 'Content-Type': 'application/json',
784
- 'Accept': 'application/json'
785
- },
786
- body: JSON.stringify(requestBody)
787
- });
788
-
789
- console.log('[LoadingModal] fetch install response:', response);
790
-
791
- if (!response.ok) {
792
- const errorData = await response.text();
793
- delayedAppendInstallLoadingMessage(`<span style="color:#f59e0b;">Failed: ${errorData || response.statusText}</span>`);
794
- console.error('[LoadingModal] Failed:', errorData || response.statusText);
795
- throw new Error(`Installation failed: ${errorData || response.statusText}`);
796
- }
797
-
798
- const result = await response.json();
799
- console.log('[LoadingModal] install result:', result);
800
- if (!result.success) {
801
- delayedAppendInstallLoadingMessage(`<span style="color:#f59e0b;">${result.error || 'Operation failed'}</span>`);
802
- console.error('[LoadingModal] Error:', result.error || 'Operation failed');
803
- throw new Error(result.error || 'Installation failed');
804
- }
805
-
806
- // Optionally, you can refresh modal data here or trigger a callback
807
- // Start polling for install status, pass requirements if available
808
- const requirements = serverInstallOptions?.requirements || [];
809
- pollInstallStatus(categoryName, serverName, targets, 2000, requirements);
810
- } catch (error) {
811
- console.error('[LoadingModal] Error applying configuration:', error);
812
- delayedAppendInstallLoadingMessage(`<span style="color:red;">Error: ${error.message}</span>`);
813
- }
814
- }
815
-
816
- // Poll install status for the given server/targets and optional requirements
817
- async function pollInstallStatus(categoryName, serverName, targets, interval = 2000, requirements = []) {
818
- let lastMessages = {};
819
- let lastRequirementMessages = {};
820
- let lastReqirementErrorMessages = {};
821
- let completionMessageSent = false;
822
-
823
- const startTime = Date.now();
824
- const maxTimeout = 10 * 60 * 1000; // 10 minutes in milliseconds
825
-
826
- while (Date.now() - startTime < maxTimeout) {
827
- try {
828
- const resp = await fetch(`/api/categories/${categoryName}`);
829
- if (resp.ok) {
830
- const data = await resp.json();
831
- // If requirements is empty and we have targets, get requirements from server's dependencies
832
- if (requirements.length === 0 && targets?.length > 0) {
833
- const feedConfig = data?.data?.feedConfiguration;
834
- if (feedConfig?.mcpServers) {
835
- const server = feedConfig.mcpServers.find(s => s.name === serverName);
836
- if (server?.dependencies?.requirements) {
837
- requirements = server.dependencies.requirements;
838
- }
839
- }
840
- }
841
- const installationStatus = data?.data?.installationStatus || {};
842
- const serverStatuses = installationStatus.serversStatus || {};
843
- const requirementsStatus = installationStatus.requirementsStatus || {};
844
- const serverStatus = serverStatuses[serverName] || { installedStatus: {} };
845
-
846
- // First check requirements status if we have any
847
- let allRequirementsCompleted = true;
848
- let hasRequirements = requirements.length > 0;
849
-
850
- for (const req of requirements) {
851
- const reqStatus = requirementsStatus[req.name] || {};
852
- if (reqStatus.installed === true && !reqStatus.operationStatus) {
853
- const msg = `Requirement [${req.name}] already installed.`;
854
- if (msg && lastRequirementMessages[req.name] !== msg) {
855
- delayedAppendInstallLoadingMessage(msg);
856
- lastRequirementMessages[req.name] = msg;
857
- }
858
- continue;
859
- }
860
- const opStatus = reqStatus.operationStatus || {};
861
- const msg = opStatus.message;
862
- const status = opStatus.status;
863
-
864
- // Only append new messages for requirements
865
- if (msg && lastRequirementMessages[req.name] !== msg) {
866
- delayedAppendInstallLoadingMessage(`${req.name}: ${msg}`);
867
- lastRequirementMessages[req.name] = msg;
868
- }
869
-
870
- if (status !== "completed" && status !== "failed") {
871
- allRequirementsCompleted = false;
872
- }
873
-
874
- // If a requirement failed, show an error
875
- if (status === "failed") {
876
- if (lastReqirementErrorMessages[req.name] !== reqStatus.error) {
877
- delayedAppendInstallLoadingMessage(`${req.name} failed: ${reqStatus.error}`);
878
- lastReqirementErrorMessages[req.name] = reqStatus.error;
879
- }
880
- if (lastRequirementMessages[req.name] !== msg) {
881
- delayedAppendInstallLoadingMessage(`<span style="color:#f59e0b;">${req.name}: Update failed - ${msg || 'Unknown error'}</span>`);
882
- }
883
- }
884
- }
885
-
886
- // Now check target statuses
887
- let allTargetsCompleted = true;
888
- let hasTargets = targets && targets.length > 0;
889
-
890
- if (hasTargets) {
891
- for (const target of targets) {
892
- const status = serverStatus.installedStatus?.[target]?.status;
893
- const msg = serverStatus.installedStatus?.[target]?.message;
894
- // Only append new messages for targets, making output more compact
895
- if (msg && lastMessages[target] !== msg) {
896
- delayedAppendInstallLoadingMessage(`${target}: ${msg}`);
897
- lastMessages[target] = msg;
898
- }
899
- if (status !== "completed") {
900
- allTargetsCompleted = false;
901
- }
902
- }
903
- }
904
-
905
- // Complete if all operations are done
906
- const allCompleted = (!hasRequirements || allRequirementsCompleted) &&
907
- (!hasTargets || allTargetsCompleted);
908
-
909
- if (allCompleted && !completionMessageSent) {
910
- completionMessageSent = true;
911
-
912
- // Compose completion message
913
- const completionMessage = hasRequirements && hasTargets ?
914
- `Updated requirements and configured ${targets.length} client(s)` :
915
- hasRequirements ? 'Requirements updated' :
916
- hasTargets ? `Configured ${targets.length} client(s)` :
917
- 'Operation completed';
918
-
919
- console.log('[LoadingModal] Completed:', completionMessage);
920
- delayedAppendInstallLoadingMessage(`<span style="color:green;">${completionMessage}</span>`, true);
921
-
922
- // Give time for message queue to process before returning
923
- await new Promise(resolve => setTimeout(resolve, 1500));
924
- return;
925
- }
926
- }
927
- } catch (error) {
928
- console.error('[LoadingModal] Error polling status:', error);
929
- }
930
- await new Promise(resolve => setTimeout(resolve, interval)); // 2 second interval
931
- }
932
-
933
- // If we reach here, we've timed out
934
- console.error('[LoadingModal] Operation timed out after 10 minutes');
935
-
936
- // On timeout, show timeout message but don't auto-close
937
- if (!completionMessageSent) {
938
- delayedAppendInstallLoadingMessage(`<span style="color:#f59e0b;">Operation timed out - Please refresh the page</span>`, true);
939
- }
940
- }
941
-
942
- // Function to handle client uninstallation for multiple targets
943
- async function uninstallTools(categoryName, serverList, targets) {
944
- // Get selected targets from window.selectedClients or the provided targets
945
- const selectedTargets = window.selectedClients || (Array.isArray(targets) ? targets : [targets]);
946
-
947
- // Validate selected targets
948
- if (!selectedTargets || selectedTargets.length === 0) {
949
- showToast('Please select at least one client to uninstall.', 'error');
950
- return;
951
- }
952
-
953
- try {
954
- delayedAppendInstallLoadingMessage('Starting uninstallation...');
955
-
956
- // Ensure serverList is an object where each key is a server name
957
- if (Array.isArray(serverList)) {
958
- const formattedServerList = {};
959
- serverList.forEach(server => {
960
- formattedServerList[server] = { removeData: true };
961
- });
962
- serverList = formattedServerList;
963
- }
964
-
965
- const response = await fetch(`/api/categories/${categoryName}/uninstall`, {
966
- method: 'POST',
967
- headers: { 'Content-Type': 'application/json' },
968
- body: JSON.stringify({
969
- serverList: serverList,
970
- options: {
971
- targets: selectedTargets,
972
- removeData: true
973
- }
974
- })
975
- });
976
-
977
- if (!response.ok) {
978
- const errorData = await response.text();
979
- throw new Error(`Uninstallation failed: ${errorData || response.statusText}`);
980
- }
981
-
982
- const result = await response.json();
983
- if (!result.success) {
984
- throw new Error(result.error || 'Uninstallation failed');
985
- }
986
-
987
- // Add completion message and trigger completion UI
988
- delayedAppendInstallLoadingMessage(`Successfully uninstalled from ${selectedTargets.join(', ')}`, true);
989
-
990
- // Clear selected clients after successful uninstall
991
- window.selectedClients = [];
992
- } catch (error) {
993
- console.error('Error uninstalling tools:', error);
994
- delayedAppendInstallLoadingMessage(`Error: ${error.message}`, true);
995
- showToast(`Error uninstalling tools: ${error.message}`, 'error');
996
- }
997
- }
998
-
999
- // Close modal and ensure selected category persists
1000
- function closeModal() {
1001
- document.getElementById('installModal').style.display = "none";
1002
-
1003
- // Get the last selected category from localStorage
1004
- const lastSelected = localStorage.getItem('lastSelectedCategory');
1005
-
1006
- // Refresh page and restore selection
1007
- if (lastSelected) {
1008
- setTimeout(() => {
1009
- window.location.href = window.location.pathname + '?category=' + encodeURIComponent(lastSelected);
1010
- }, 100);
1011
- } else {
1012
- location.reload();
1013
- }
1014
- }
1015
-
1016
- // Close modal if clicked outside content
1017
- function setupModalOutsideClick() {
1018
- window.onclick = function (event) {
1019
- const installModal = document.getElementById('installModal');
1020
- if (event.target == installModal) {
1021
- closeModal();
1022
- }
1023
- };
1024
- }
1025
-
1
+ // Import all functionality from the modal folder
2
+ import {
3
+ showInstallModal,
4
+ closeModal,
5
+ setupModalOutsideClick,
6
+ uninstallTools,
7
+ showInstallLoadingModal,
8
+ appendInstallLoadingMessage,
9
+ hideInstallLoadingModal
10
+ } from './modal/index.js';
11
+
12
+ // Re-export all modal functionality
13
+ export {
14
+ showInstallModal,
15
+ closeModal,
16
+ setupModalOutsideClick,
17
+ uninstallTools
18
+ };
19
+
20
+ // Make certain functions available globally
21
+ window.showInstallModal = showInstallModal;
1026
22
  window.showInstallLoadingModal = showInstallLoadingModal;
1027
23
  window.appendInstallLoadingMessage = appendInstallLoadingMessage;
1028
24
  window.hideInstallLoadingModal = hideInstallLoadingModal;
1029
25
 
1030
- // CSS styles for the toggle switch
1031
- const styleElement = document.createElement('style');
1032
- styleElement.textContent = `
1033
- .toggle-bg.bg-blue-500 {
1034
- background-color: #3b82f6;
1035
- }
1036
- .toggle-bg {
1037
- transition: background-color 0.3s;
1038
- }
1039
- .toggle-bg div {
1040
- transition: transform 0.3s;
1041
- }
1042
- `;
1043
- document.head.appendChild(styleElement);
1044
-
1045
- export { showInstallModal, closeModal, setupModalOutsideClick, uninstallTools };
26
+ // Initialize modal functionality when DOM is loaded
27
+ document.addEventListener('DOMContentLoaded', () => {
28
+ setupModalOutsideClick();
29
+ });