imcp 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/cli/commands/install.js +8 -8
- package/dist/cli/index.js +3 -2
- package/dist/core/ConfigurationProvider.d.ts +2 -0
- package/dist/core/ConfigurationProvider.js +49 -3
- package/dist/core/InstallationService.d.ts +8 -0
- package/dist/core/InstallationService.js +117 -0
- package/dist/core/MCPManager.d.ts +1 -0
- package/dist/core/MCPManager.js +42 -0
- package/dist/core/RequirementService.d.ts +7 -0
- package/dist/core/RequirementService.js +17 -0
- package/dist/core/constants.d.ts +5 -0
- package/dist/core/constants.js +29 -6
- package/dist/core/installers/BaseInstaller.js +26 -9
- package/dist/core/installers/ClientInstaller.d.ts +6 -0
- package/dist/core/installers/ClientInstaller.js +149 -12
- package/dist/core/installers/GeneralInstaller.js +0 -5
- package/dist/core/installers/NpmInstaller.js +2 -1
- package/dist/core/types.d.ts +7 -6
- package/dist/services/ServerService.js +16 -0
- package/dist/utils/clientUtils.js +3 -1
- package/dist/utils/versionUtils.d.ts +12 -0
- package/dist/utils/versionUtils.js +26 -0
- package/dist/web/public/css/modal.css +89 -9
- package/dist/web/public/index.html +12 -6
- package/dist/web/public/js/modal.js +357 -97
- package/dist/web/public/modal.html +20 -11
- package/dist/web/server.d.ts +6 -0
- package/dist/web/server.js +6 -1
- package/package.json +1 -1
- package/src/cli/commands/install.ts +11 -14
- package/src/cli/index.ts +4 -2
- package/src/core/ConfigurationProvider.ts +51 -3
- package/src/core/InstallationService.ts +131 -0
- package/src/core/MCPManager.ts +60 -1
- package/src/core/RequirementService.ts +21 -1
- package/src/core/constants.ts +32 -7
- package/src/core/installers/BaseInstaller.ts +33 -17
- package/src/core/installers/ClientInstaller.ts +148 -12
- package/src/core/installers/GeneralInstaller.ts +0 -5
- package/src/core/installers/NpmInstaller.ts +2 -1
- package/src/core/types.ts +8 -6
- package/src/services/ServerService.ts +22 -0
- package/src/utils/clientUtils.ts +3 -1
- package/src/utils/versionUtils.ts +29 -0
- package/src/web/public/css/modal.css +89 -9
- package/src/web/public/index.html +12 -6
- package/src/web/public/js/modal.js +357 -97
- package/src/web/public/modal.html +20 -11
- package/src/web/server.ts +16 -2
|
@@ -4,43 +4,142 @@ import { showToast, showConfirm } from './notifications.js';
|
|
|
4
4
|
document.addEventListener('DOMContentLoaded', () => {
|
|
5
5
|
setupModalOutsideClick();
|
|
6
6
|
});
|
|
7
|
-
/**
|
|
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
|
+
*/
|
|
8
32
|
let messageQueue = [];
|
|
33
|
+
|
|
34
|
+
/** @type {boolean} Flag indicating if a message is currently being appended */
|
|
9
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
|
+
*/
|
|
10
45
|
function processMessageQueue() {
|
|
11
|
-
if (isAppending || messageQueue.length === 0)
|
|
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
|
+
|
|
12
58
|
isAppending = true;
|
|
13
|
-
const { msg } = messageQueue.shift();
|
|
59
|
+
const { msg, isCompletion } = messageQueue.shift();
|
|
60
|
+
|
|
61
|
+
if (isCompletion) {
|
|
62
|
+
completionPending = true;
|
|
63
|
+
}
|
|
64
|
+
|
|
14
65
|
_appendInstallLoadingMessage(msg);
|
|
66
|
+
|
|
15
67
|
setTimeout(() => {
|
|
16
68
|
isAppending = false;
|
|
17
69
|
processMessageQueue();
|
|
18
70
|
}, 1000);
|
|
19
71
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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 });
|
|
23
81
|
processMessageQueue();
|
|
24
82
|
}
|
|
25
|
-
|
|
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
|
+
*/
|
|
26
108
|
function _appendInstallLoadingMessage(message) {
|
|
27
109
|
const loadingMsg = document.getElementById('installLoadingMessage');
|
|
28
110
|
if (loadingMsg) {
|
|
29
|
-
|
|
30
|
-
|
|
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
|
+
});
|
|
31
136
|
} else {
|
|
32
|
-
console.error('[LoadingModal]
|
|
137
|
+
console.error('[LoadingModal] Element not found: installLoadingMessage');
|
|
33
138
|
}
|
|
34
139
|
}
|
|
35
140
|
|
|
36
141
|
/**
|
|
37
|
-
*
|
|
38
|
-
* @param {string} message
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Show the install loading modal and optionally append a message.
|
|
43
|
-
* @param {string} message
|
|
142
|
+
* Display the installation loading modal and prepare it for messages.
|
|
44
143
|
*/
|
|
45
144
|
function showInstallLoadingModal() {
|
|
46
145
|
const loadingModal = document.getElementById('installLoadingModal');
|
|
@@ -49,33 +148,30 @@ function showInstallLoadingModal() {
|
|
|
49
148
|
loadingModal.style.display = 'block';
|
|
50
149
|
loadingMsg.innerHTML = '';
|
|
51
150
|
} else {
|
|
52
|
-
console.error('[LoadingModal]
|
|
151
|
+
console.error('[LoadingModal] Required elements not found: installLoadingModal or installLoadingMessage');
|
|
53
152
|
}
|
|
54
153
|
}
|
|
55
154
|
|
|
56
155
|
/**
|
|
57
|
-
* Append a message to the loading modal.
|
|
58
|
-
* @param {string} message
|
|
156
|
+
* Append a message to the loading modal display.
|
|
157
|
+
* @param {string} message - The message to display
|
|
59
158
|
*/
|
|
60
159
|
function appendInstallLoadingMessage(message) {
|
|
61
|
-
console.log(
|
|
62
|
-
|
|
63
|
-
if (loadingMsg) {
|
|
64
|
-
loadingMsg.innerHTML += `<div>${message}</div>`;
|
|
65
|
-
loadingMsg.scrollTop = loadingMsg.scrollHeight;
|
|
66
|
-
} else {
|
|
67
|
-
console.error('[LoadingModal] loading message DOM not found');
|
|
68
|
-
}
|
|
160
|
+
console.log(`[LoadingModal] Message: ${message}`);
|
|
161
|
+
_appendInstallLoadingMessage(message);
|
|
69
162
|
}
|
|
70
163
|
|
|
71
164
|
/**
|
|
72
165
|
* Hide the install loading modal.
|
|
73
166
|
*/
|
|
74
167
|
function hideInstallLoadingModal() {
|
|
75
|
-
console.log('[LoadingModal]
|
|
168
|
+
console.log('[LoadingModal] Hiding installation modal');
|
|
76
169
|
const loadingModal = document.getElementById('installLoadingModal');
|
|
77
170
|
if (loadingModal) {
|
|
78
171
|
loadingModal.style.display = 'none';
|
|
172
|
+
setTimeout(() => {
|
|
173
|
+
location.reload();
|
|
174
|
+
}, 100);
|
|
79
175
|
} else {
|
|
80
176
|
console.error('[LoadingModal] loading modal DOM not found');
|
|
81
177
|
}
|
|
@@ -147,8 +243,8 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
147
243
|
const operationStatus = serverStatus.installedStatus[target] || { status: 'not-installed', type: 'check', target: 'server' };
|
|
148
244
|
|
|
149
245
|
// Determine client status
|
|
150
|
-
let statusText = '
|
|
151
|
-
let statusClass = '
|
|
246
|
+
let statusText = '';
|
|
247
|
+
let statusClass = '';
|
|
152
248
|
|
|
153
249
|
if (operationStatus.status === 'completed' && operationStatus.type === 'install') {
|
|
154
250
|
statusText = 'Installed';
|
|
@@ -163,6 +259,7 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
163
259
|
statusText = 'Failed';
|
|
164
260
|
statusClass = 'not-installed';
|
|
165
261
|
}
|
|
262
|
+
// Do not show not-installed status for targets that aren't installed
|
|
166
263
|
|
|
167
264
|
const isConfigured = operationStatus.status === 'completed' && operationStatus.type === 'install';
|
|
168
265
|
|
|
@@ -222,14 +319,16 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
222
319
|
// Add elements to client info
|
|
223
320
|
clientInfo.appendChild(clientName);
|
|
224
321
|
|
|
225
|
-
//
|
|
226
|
-
const statusBadge = document.createElement('span');
|
|
227
|
-
statusBadge.className = `status-badge ${statusClass}`;
|
|
228
|
-
statusBadge.textContent = statusText;
|
|
229
|
-
|
|
230
|
-
// Add components to client item
|
|
322
|
+
// Add client info (name) to the item first
|
|
231
323
|
clientItem.appendChild(clientInfo);
|
|
232
|
-
|
|
324
|
+
|
|
325
|
+
// Status badge - only show if we have status text
|
|
326
|
+
if (statusText) {
|
|
327
|
+
const statusBadge = document.createElement('span');
|
|
328
|
+
statusBadge.className = `status-badge ${statusClass}`;
|
|
329
|
+
statusBadge.textContent = statusText;
|
|
330
|
+
clientItem.appendChild(statusBadge);
|
|
331
|
+
}
|
|
233
332
|
|
|
234
333
|
// Add client item to target div
|
|
235
334
|
targetDiv.appendChild(clientItem);
|
|
@@ -260,6 +359,9 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
260
359
|
noEnvMessage.textContent = 'No environment variables required for this MCP server.';
|
|
261
360
|
envInputsDiv.appendChild(noEnvMessage);
|
|
262
361
|
} else {
|
|
362
|
+
// Get clientMcpSettings from the targets endpoint
|
|
363
|
+
let clientSettings = targetData.clientMcpSettings;
|
|
364
|
+
|
|
263
365
|
// Create inputs for each environment variable
|
|
264
366
|
Object.keys(envRequirements).forEach(key => {
|
|
265
367
|
const req = envRequirements[key];
|
|
@@ -277,7 +379,21 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
277
379
|
input.id = inputId;
|
|
278
380
|
input.name = key;
|
|
279
381
|
input.placeholder = req.Description || key;
|
|
280
|
-
|
|
382
|
+
|
|
383
|
+
// For default value, first check MSRooCode for the target server, then use the provided default
|
|
384
|
+
let defaultValue = req.Default || '';
|
|
385
|
+
|
|
386
|
+
// Check if we have settings from MSRooCode
|
|
387
|
+
if (clientSettings && clientSettings.MSRooCode &&
|
|
388
|
+
clientSettings.MSRooCode.mcpServers &&
|
|
389
|
+
clientSettings.MSRooCode.mcpServers[serverName] &&
|
|
390
|
+
clientSettings.MSRooCode.mcpServers[serverName].env &&
|
|
391
|
+
clientSettings.MSRooCode.mcpServers[serverName].env[key]) {
|
|
392
|
+
defaultValue = clientSettings.MSRooCode.mcpServers[serverName].env[key];
|
|
393
|
+
console.log(`Using MSRooCode value for ${key}: ${defaultValue}`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
input.value = defaultValue;
|
|
281
397
|
input.required = req.Required;
|
|
282
398
|
input.className = 'input-field';
|
|
283
399
|
|
|
@@ -302,10 +418,36 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
302
418
|
if (serverRequirements.length > 0) {
|
|
303
419
|
const reqHtml = serverRequirements.map(req => {
|
|
304
420
|
const status = requirements[req.name] || {};
|
|
305
|
-
|
|
421
|
+
let statusClass = status.installed
|
|
306
422
|
? 'text-green-600 bg-green-50'
|
|
307
423
|
: 'text-yellow-600 bg-yellow-50';
|
|
308
|
-
|
|
424
|
+
let statusText = status.installed ? 'Installed' : 'Required';
|
|
425
|
+
let versionDisplay = status.version ? ` • <span class="font-medium">${status.version}</span>` : '';
|
|
426
|
+
let updateToggle = '';
|
|
427
|
+
|
|
428
|
+
// Check if there's an available update
|
|
429
|
+
if (status.installed && status.availableUpdate && status.availableUpdate.version) {
|
|
430
|
+
if (status.version && compareVersions(status.availableUpdate.version, status.version) > 0) {
|
|
431
|
+
// Show version update information with yellow color and icon
|
|
432
|
+
statusClass = 'text-yellow-600 bg-yellow-50';
|
|
433
|
+
statusText = `<span style="color: #f59e0b; font-weight: bold; margin-right: 5px;">↑</span>${status.availableUpdate.version}`;
|
|
434
|
+
|
|
435
|
+
// Create a toggle switch for update
|
|
436
|
+
updateToggle = `
|
|
437
|
+
<label class="inline-flex items-center cursor-pointer ml-2">
|
|
438
|
+
<input type="checkbox" class="toggle-update sr-only"
|
|
439
|
+
data-name="${req.name}"
|
|
440
|
+
data-version="${status.availableUpdate.version}"
|
|
441
|
+
data-category="${categoryName}"
|
|
442
|
+
data-server="${serverName}">
|
|
443
|
+
<div class="relative w-10 h-5 bg-gray-200 rounded-full toggle-bg">
|
|
444
|
+
<div class="absolute inset-y-0 left-0 w-5 h-5 bg-white rounded-full transition-transform duration-300 transform"></div>
|
|
445
|
+
</div>
|
|
446
|
+
<span class="ml-2 text-sm text-gray-700">Update</span>
|
|
447
|
+
</label>
|
|
448
|
+
`;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
309
451
|
|
|
310
452
|
return `
|
|
311
453
|
<div class="border border-gray-200 p-3 rounded-lg mb-2 hover:bg-gray-50">
|
|
@@ -313,12 +455,15 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
313
455
|
<div>
|
|
314
456
|
<div class="font-semibold text-gray-800">${req.name}</div>
|
|
315
457
|
<div class="text-sm text-gray-600 shadow-sm p-1 rounded bg-gray-50">
|
|
316
|
-
<span class="font-medium">${status.type || 'package'}</span>${
|
|
458
|
+
<span class="font-medium">${status.type || 'package'}</span>${versionDisplay}
|
|
317
459
|
</div>
|
|
318
460
|
</div>
|
|
319
|
-
<
|
|
320
|
-
${
|
|
321
|
-
|
|
461
|
+
<div class="flex items-center">
|
|
462
|
+
<span class="${statusClass} inline-flex items-center px-3 py-1 rounded-full text-sm">
|
|
463
|
+
${statusText}
|
|
464
|
+
</span>
|
|
465
|
+
${updateToggle}
|
|
466
|
+
</div>
|
|
322
467
|
</div>
|
|
323
468
|
</div>
|
|
324
469
|
`;
|
|
@@ -333,6 +478,24 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
333
478
|
modalRequirements.innerHTML = '<p class="text-gray-600">No additional dependencies required.</p>';
|
|
334
479
|
}
|
|
335
480
|
|
|
481
|
+
// Add event listeners for update toggles
|
|
482
|
+
setTimeout(() => {
|
|
483
|
+
const updateToggles = modalRequirements.querySelectorAll('.toggle-update');
|
|
484
|
+
updateToggles.forEach(toggle => {
|
|
485
|
+
toggle.addEventListener('change', function () {
|
|
486
|
+
// When toggled, update the visual appearance
|
|
487
|
+
const toggleBg = this.parentElement.querySelector('.toggle-bg');
|
|
488
|
+
if (this.checked) {
|
|
489
|
+
toggleBg.classList.add('bg-blue-500');
|
|
490
|
+
toggleBg.querySelector('div').classList.add('translate-x-5');
|
|
491
|
+
} else {
|
|
492
|
+
toggleBg.classList.remove('bg-blue-500');
|
|
493
|
+
toggleBg.querySelector('div').classList.remove('translate-x-5');
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
}, 100);
|
|
498
|
+
|
|
336
499
|
// Set up the install form submit handler
|
|
337
500
|
const installForm = document.getElementById('installForm');
|
|
338
501
|
installForm.onsubmit = (e) => {
|
|
@@ -347,13 +510,27 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
347
510
|
}
|
|
348
511
|
});
|
|
349
512
|
|
|
513
|
+
// Check for enabled update toggles and collect requirements to update
|
|
514
|
+
const requirementsToUpdate = [];
|
|
515
|
+
const updateToggles = modalRequirements.querySelectorAll('.toggle-update:checked');
|
|
516
|
+
updateToggles.forEach(toggle => {
|
|
517
|
+
requirementsToUpdate.push({
|
|
518
|
+
name: toggle.dataset.name,
|
|
519
|
+
version: toggle.dataset.version
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
|
|
350
523
|
// Get selected clients
|
|
351
524
|
const selectedTargets = window.selectedClients.length > 0 ?
|
|
352
525
|
window.selectedClients :
|
|
353
526
|
Array.from(document.querySelectorAll('.client-item.selected'))
|
|
354
527
|
.map(item => item.dataset.target);
|
|
355
528
|
|
|
356
|
-
if
|
|
529
|
+
// Check if we have any requirements selected for update
|
|
530
|
+
const hasRequirementsToUpdate = requirementsToUpdate.length > 0;
|
|
531
|
+
|
|
532
|
+
// Only require client selection if we don't have any requirements to update
|
|
533
|
+
if (selectedTargets.length === 0 && !hasRequirementsToUpdate) {
|
|
357
534
|
showToast('Please select at least one client to configure.', 'error');
|
|
358
535
|
return;
|
|
359
536
|
}
|
|
@@ -367,7 +544,19 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
367
544
|
const msg = serverStatus.installedStatus?.[target]?.message;
|
|
368
545
|
if (msg) installingMessage = msg;
|
|
369
546
|
}
|
|
370
|
-
|
|
547
|
+
|
|
548
|
+
// Add requirements to update to serverInstallOptions if any
|
|
549
|
+
const serverInstallOptions = {
|
|
550
|
+
targetClients: selectedTargets,
|
|
551
|
+
env: envVars
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
// Only add requirements if we have any to update
|
|
555
|
+
if (requirementsToUpdate.length > 0) {
|
|
556
|
+
serverInstallOptions.requirements = requirementsToUpdate;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
handleBulkClientInstall(categoryName, serverName, selectedTargets, envVars, installingMessage, serverData, serverInstallOptions);
|
|
371
560
|
};
|
|
372
561
|
|
|
373
562
|
} catch (error) {
|
|
@@ -379,8 +568,8 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
379
568
|
}
|
|
380
569
|
|
|
381
570
|
// Function to handle bulk client installations
|
|
382
|
-
async function handleBulkClientInstall(categoryName, serverName, targets, envVars = {}, installingMessage = "Starting installation...", serverData = null) {
|
|
383
|
-
console.log('[LoadingModal] handleBulkClientInstall called', { categoryName, serverName, targets, envVars });
|
|
571
|
+
async function handleBulkClientInstall(categoryName, serverName, targets, envVars = {}, installingMessage = "Starting installation...", serverData = null, serverInstallOptions = null) {
|
|
572
|
+
console.log('[LoadingModal] handleBulkClientInstall called', { categoryName, serverName, targets, envVars, serverInstallOptions });
|
|
384
573
|
// Hide install modal, show loading modal
|
|
385
574
|
const installModal = document.getElementById('installModal');
|
|
386
575
|
console.log('[LoadingModal] installModal:', installModal);
|
|
@@ -401,48 +590,52 @@ async function handleBulkClientInstall(categoryName, serverName, targets, envVar
|
|
|
401
590
|
}
|
|
402
591
|
}
|
|
403
592
|
|
|
404
|
-
showInstallLoadingModal(
|
|
405
|
-
|
|
593
|
+
showInstallLoadingModal();
|
|
406
594
|
delayedAppendInstallLoadingMessage(installingMessage);
|
|
407
595
|
|
|
408
596
|
try {
|
|
409
597
|
delayedAppendInstallLoadingMessage("Installing, please wait...");
|
|
598
|
+
|
|
599
|
+
// Use serverInstallOptions if provided, otherwise build the traditional options
|
|
600
|
+
const requestBody = {
|
|
601
|
+
serverList: {
|
|
602
|
+
[serverName]: serverInstallOptions || {
|
|
603
|
+
targetClients: targets,
|
|
604
|
+
env: envVars
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
410
609
|
const response = await fetch(`/api/categories/${categoryName}/install`, {
|
|
411
610
|
method: 'POST',
|
|
412
611
|
headers: {
|
|
413
612
|
'Content-Type': 'application/json',
|
|
414
613
|
'Accept': 'application/json'
|
|
415
614
|
},
|
|
416
|
-
body: JSON.stringify(
|
|
417
|
-
serverList: {
|
|
418
|
-
[serverName]: {
|
|
419
|
-
targetClients: targets,
|
|
420
|
-
env: envVars
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
})
|
|
615
|
+
body: JSON.stringify(requestBody)
|
|
424
616
|
});
|
|
425
617
|
|
|
426
618
|
console.log('[LoadingModal] fetch install response:', response);
|
|
427
619
|
|
|
428
620
|
if (!response.ok) {
|
|
429
621
|
const errorData = await response.text();
|
|
430
|
-
delayedAppendInstallLoadingMessage(`<span style="color
|
|
431
|
-
console.error('[LoadingModal]
|
|
622
|
+
delayedAppendInstallLoadingMessage(`<span style="color:#f59e0b;">Failed: ${errorData || response.statusText}</span>`);
|
|
623
|
+
console.error('[LoadingModal] Failed:', errorData || response.statusText);
|
|
432
624
|
throw new Error(`Installation failed: ${errorData || response.statusText}`);
|
|
433
625
|
}
|
|
434
626
|
|
|
435
627
|
const result = await response.json();
|
|
436
628
|
console.log('[LoadingModal] install result:', result);
|
|
437
629
|
if (!result.success) {
|
|
438
|
-
delayedAppendInstallLoadingMessage(`<span style="color
|
|
439
|
-
console.error('[LoadingModal]
|
|
630
|
+
delayedAppendInstallLoadingMessage(`<span style="color:#f59e0b;">${result.error || 'Operation failed'}</span>`);
|
|
631
|
+
console.error('[LoadingModal] Error:', result.error || 'Operation failed');
|
|
440
632
|
throw new Error(result.error || 'Installation failed');
|
|
441
633
|
}
|
|
442
634
|
|
|
443
635
|
// Optionally, you can refresh modal data here or trigger a callback
|
|
444
|
-
// Start polling for install status
|
|
445
|
-
|
|
636
|
+
// Start polling for install status, pass requirements if available
|
|
637
|
+
const requirements = serverInstallOptions?.requirements || [];
|
|
638
|
+
pollInstallStatus(categoryName, serverName, targets, 2000, 60, requirements);
|
|
446
639
|
} catch (error) {
|
|
447
640
|
console.error('[LoadingModal] Error applying configuration:', error);
|
|
448
641
|
delayedAppendInstallLoadingMessage(`<span style="color:red;">Error: ${error.message}</span>`);
|
|
@@ -455,53 +648,101 @@ async function handleBulkClientInstall(categoryName, serverName, targets, envVar
|
|
|
455
648
|
}
|
|
456
649
|
}
|
|
457
650
|
|
|
458
|
-
// Poll install status for the given server/targets
|
|
459
|
-
async function pollInstallStatus(categoryName, serverName, targets, interval = 2000, maxTries = 60) {
|
|
651
|
+
// Poll install status for the given server/targets and optional requirements
|
|
652
|
+
async function pollInstallStatus(categoryName, serverName, targets, interval = 2000, maxTries = 60, requirements = []) {
|
|
460
653
|
let tries = 0;
|
|
461
654
|
let lastMessages = {};
|
|
462
|
-
|
|
655
|
+
let lastRequirementMessages = {};
|
|
656
|
+
let completionMessageSent = false;
|
|
657
|
+
|
|
463
658
|
while (tries < maxTries) {
|
|
464
659
|
try {
|
|
465
660
|
const resp = await fetch(`/api/categories/${categoryName}`);
|
|
466
661
|
if (resp.ok) {
|
|
467
662
|
const data = await resp.json();
|
|
468
|
-
const
|
|
663
|
+
const installationStatus = data?.data?.installationStatus || {};
|
|
664
|
+
const serverStatuses = installationStatus.serversStatus || {};
|
|
665
|
+
const requirementsStatus = installationStatus.requirementsStatus || {};
|
|
469
666
|
const serverStatus = serverStatuses[serverName] || { installedStatus: {} };
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
667
|
+
|
|
668
|
+
// First check requirements status if we have any
|
|
669
|
+
let allRequirementsCompleted = true;
|
|
670
|
+
let hasRequirements = requirements.length > 0;
|
|
671
|
+
|
|
672
|
+
for (const req of requirements) {
|
|
673
|
+
const reqStatus = requirementsStatus[req.name] || {};
|
|
674
|
+
const opStatus = reqStatus.operationStatus || {};
|
|
675
|
+
const msg = opStatus.message;
|
|
676
|
+
const status = opStatus.status;
|
|
677
|
+
|
|
678
|
+
// Only append new messages for requirements
|
|
679
|
+
if (msg && lastRequirementMessages[req.name] !== msg) {
|
|
680
|
+
delayedAppendInstallLoadingMessage(`${req.name}: ${msg}`);
|
|
681
|
+
lastRequirementMessages[req.name] = msg;
|
|
478
682
|
}
|
|
479
|
-
|
|
480
|
-
|
|
683
|
+
|
|
684
|
+
if (status !== "completed" && status !== "failed") {
|
|
685
|
+
allRequirementsCompleted = false;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// If a requirement failed, show an error
|
|
689
|
+
if (status === "failed") {
|
|
690
|
+
delayedAppendInstallLoadingMessage(`<span style="color:#f59e0b;">${req.name}: Update failed - ${msg || 'Unknown error'}</span>`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Now check target statuses
|
|
695
|
+
let allTargetsCompleted = true;
|
|
696
|
+
let hasTargets = targets && targets.length > 0;
|
|
697
|
+
|
|
698
|
+
if (hasTargets) {
|
|
699
|
+
for (const target of targets) {
|
|
700
|
+
const status = serverStatus.installedStatus?.[target]?.status;
|
|
701
|
+
const msg = serverStatus.installedStatus?.[target]?.message;
|
|
702
|
+
// Only append new messages for targets, making output more compact
|
|
703
|
+
if (msg && lastMessages[target] !== msg) {
|
|
704
|
+
delayedAppendInstallLoadingMessage(`${target}: ${msg}`);
|
|
705
|
+
lastMessages[target] = msg;
|
|
706
|
+
}
|
|
707
|
+
if (status !== "completed") {
|
|
708
|
+
allTargetsCompleted = false;
|
|
709
|
+
}
|
|
481
710
|
}
|
|
482
711
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
712
|
+
|
|
713
|
+
// Complete if all operations are done
|
|
714
|
+
const allCompleted = (!hasRequirements || allRequirementsCompleted) &&
|
|
715
|
+
(!hasTargets || allTargetsCompleted);
|
|
716
|
+
|
|
717
|
+
if (allCompleted && !completionMessageSent) {
|
|
718
|
+
completionMessageSent = true;
|
|
719
|
+
|
|
720
|
+
// Compose completion message
|
|
721
|
+
const completionMessage = hasRequirements && hasTargets ?
|
|
722
|
+
`Updated requirements and configured ${targets.length} client(s)` :
|
|
723
|
+
hasRequirements ? 'Requirements updated' :
|
|
724
|
+
hasTargets ? `Configured ${targets.length} client(s)` :
|
|
725
|
+
'Operation completed';
|
|
726
|
+
|
|
727
|
+
console.log('[LoadingModal] Completed:', completionMessage);
|
|
728
|
+
delayedAppendInstallLoadingMessage(`<span style="color:green;">${completionMessage}</span>`, true);
|
|
729
|
+
|
|
730
|
+
// Give time for message queue to process before returning
|
|
731
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
491
732
|
return;
|
|
492
733
|
}
|
|
493
734
|
}
|
|
494
|
-
} catch (
|
|
495
|
-
|
|
735
|
+
} catch (error) {
|
|
736
|
+
console.error('[LoadingModal] Error polling status:', error);
|
|
496
737
|
}
|
|
497
738
|
await new Promise(resolve => setTimeout(resolve, interval));
|
|
498
739
|
tries++;
|
|
499
740
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
741
|
+
|
|
742
|
+
// On timeout, show timeout message but don't auto-close
|
|
743
|
+
if (!completionMessageSent) {
|
|
744
|
+
delayedAppendInstallLoadingMessage(`<span style="color:#f59e0b;">Operation timed out - Please refresh the page</span>`, true);
|
|
745
|
+
}
|
|
505
746
|
}
|
|
506
747
|
|
|
507
748
|
// Function to handle client uninstallation for multiple targets
|
|
@@ -545,17 +786,22 @@ async function uninstallTools(categoryName, serverList, targets) {
|
|
|
545
786
|
}
|
|
546
787
|
}
|
|
547
788
|
|
|
548
|
-
// Close modal
|
|
789
|
+
// Close modal and reload
|
|
549
790
|
function closeModal() {
|
|
550
791
|
document.getElementById('installModal').style.display = "none";
|
|
792
|
+
location.reload();
|
|
551
793
|
}
|
|
552
794
|
|
|
553
795
|
// Close modal if clicked outside content
|
|
554
796
|
function setupModalOutsideClick() {
|
|
555
797
|
window.onclick = function (event) {
|
|
556
|
-
const
|
|
557
|
-
|
|
798
|
+
const installModal = document.getElementById('installModal');
|
|
799
|
+
const loadingModal = document.getElementById('installLoadingModal');
|
|
800
|
+
|
|
801
|
+
if (event.target == installModal) {
|
|
558
802
|
closeModal();
|
|
803
|
+
} else if (event.target == loadingModal) {
|
|
804
|
+
hideInstallLoadingModal();
|
|
559
805
|
}
|
|
560
806
|
};
|
|
561
807
|
}
|
|
@@ -568,5 +814,19 @@ window.showInstallLoadingModal = showInstallLoadingModal;
|
|
|
568
814
|
window.appendInstallLoadingMessage = appendInstallLoadingMessage;
|
|
569
815
|
window.hideInstallLoadingModal = hideInstallLoadingModal;
|
|
570
816
|
|
|
817
|
+
// CSS styles for the toggle switch
|
|
818
|
+
const styleElement = document.createElement('style');
|
|
819
|
+
styleElement.textContent = `
|
|
820
|
+
.toggle-bg.bg-blue-500 {
|
|
821
|
+
background-color: #3b82f6;
|
|
822
|
+
}
|
|
823
|
+
.toggle-bg {
|
|
824
|
+
transition: background-color 0.3s;
|
|
825
|
+
}
|
|
826
|
+
.toggle-bg div {
|
|
827
|
+
transition: transform 0.3s;
|
|
828
|
+
}
|
|
829
|
+
`;
|
|
830
|
+
document.head.appendChild(styleElement);
|
|
571
831
|
|
|
572
832
|
export { showInstallModal, closeModal, setupModalOutsideClick, uninstallTools };
|