imcp 0.0.1
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/.github/ISSUE_TEMPLATE/JitAccess.yml +28 -0
- package/.github/acl/access.yml +20 -0
- package/.github/compliance/inventory.yml +5 -0
- package/.github/policies/jit.yml +19 -0
- package/README.md +137 -0
- package/dist/cli/commands/install.d.ts +2 -0
- package/dist/cli/commands/install.js +105 -0
- package/dist/cli/commands/list.d.ts +2 -0
- package/dist/cli/commands/list.js +90 -0
- package/dist/cli/commands/pull.d.ts +2 -0
- package/dist/cli/commands/pull.js +17 -0
- package/dist/cli/commands/serve.d.ts +2 -0
- package/dist/cli/commands/serve.js +32 -0
- package/dist/cli/commands/start.d.ts +2 -0
- package/dist/cli/commands/start.js +32 -0
- package/dist/cli/commands/sync.d.ts +2 -0
- package/dist/cli/commands/sync.js +17 -0
- package/dist/cli/commands/uninstall.d.ts +2 -0
- package/dist/cli/commands/uninstall.js +39 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +114 -0
- package/dist/core/ConfigurationProvider.d.ts +31 -0
- package/dist/core/ConfigurationProvider.js +416 -0
- package/dist/core/InstallationService.d.ts +17 -0
- package/dist/core/InstallationService.js +144 -0
- package/dist/core/MCPManager.d.ts +17 -0
- package/dist/core/MCPManager.js +98 -0
- package/dist/core/RequirementService.d.ts +45 -0
- package/dist/core/RequirementService.js +123 -0
- package/dist/core/constants.d.ts +29 -0
- package/dist/core/constants.js +55 -0
- package/dist/core/installers/BaseInstaller.d.ts +73 -0
- package/dist/core/installers/BaseInstaller.js +247 -0
- package/dist/core/installers/ClientInstaller.d.ts +17 -0
- package/dist/core/installers/ClientInstaller.js +307 -0
- package/dist/core/installers/CommandInstaller.d.ts +36 -0
- package/dist/core/installers/CommandInstaller.js +170 -0
- package/dist/core/installers/GeneralInstaller.d.ts +32 -0
- package/dist/core/installers/GeneralInstaller.js +87 -0
- package/dist/core/installers/InstallerFactory.d.ts +52 -0
- package/dist/core/installers/InstallerFactory.js +95 -0
- package/dist/core/installers/NpmInstaller.d.ts +25 -0
- package/dist/core/installers/NpmInstaller.js +123 -0
- package/dist/core/installers/PipInstaller.d.ts +25 -0
- package/dist/core/installers/PipInstaller.js +114 -0
- package/dist/core/installers/RequirementInstaller.d.ts +32 -0
- package/dist/core/installers/RequirementInstaller.js +3 -0
- package/dist/core/installers/index.d.ts +6 -0
- package/dist/core/installers/index.js +7 -0
- package/dist/core/types.d.ts +152 -0
- package/dist/core/types.js +16 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +19 -0
- package/dist/services/InstallRequestValidator.d.ts +21 -0
- package/dist/services/InstallRequestValidator.js +99 -0
- package/dist/services/ServerService.d.ts +47 -0
- package/dist/services/ServerService.js +145 -0
- package/dist/utils/UpdateCheckTracker.d.ts +39 -0
- package/dist/utils/UpdateCheckTracker.js +80 -0
- package/dist/utils/clientUtils.d.ts +29 -0
- package/dist/utils/clientUtils.js +105 -0
- package/dist/utils/feedUtils.d.ts +5 -0
- package/dist/utils/feedUtils.js +29 -0
- package/dist/utils/githubAuth.d.ts +1 -0
- package/dist/utils/githubAuth.js +123 -0
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.js +90 -0
- package/dist/utils/osUtils.d.ts +16 -0
- package/dist/utils/osUtils.js +235 -0
- package/dist/web/public/css/modal.css +250 -0
- package/dist/web/public/css/notifications.css +70 -0
- package/dist/web/public/index.html +157 -0
- package/dist/web/public/js/api.js +213 -0
- package/dist/web/public/js/modal.js +572 -0
- package/dist/web/public/js/notifications.js +99 -0
- package/dist/web/public/js/serverCategoryDetails.js +210 -0
- package/dist/web/public/js/serverCategoryList.js +82 -0
- package/dist/web/public/modal.html +61 -0
- package/dist/web/public/styles.css +155 -0
- package/dist/web/server.d.ts +5 -0
- package/dist/web/server.js +150 -0
- package/package.json +53 -0
- package/src/cli/commands/install.ts +140 -0
- package/src/cli/commands/list.ts +112 -0
- package/src/cli/commands/pull.ts +16 -0
- package/src/cli/commands/serve.ts +37 -0
- package/src/cli/commands/uninstall.ts +54 -0
- package/src/cli/index.ts +127 -0
- package/src/core/ConfigurationProvider.ts +489 -0
- package/src/core/InstallationService.ts +173 -0
- package/src/core/MCPManager.ts +134 -0
- package/src/core/RequirementService.ts +147 -0
- package/src/core/constants.ts +61 -0
- package/src/core/installers/BaseInstaller.ts +292 -0
- package/src/core/installers/ClientInstaller.ts +423 -0
- package/src/core/installers/CommandInstaller.ts +185 -0
- package/src/core/installers/GeneralInstaller.ts +89 -0
- package/src/core/installers/InstallerFactory.ts +109 -0
- package/src/core/installers/NpmInstaller.ts +128 -0
- package/src/core/installers/PipInstaller.ts +121 -0
- package/src/core/installers/RequirementInstaller.ts +38 -0
- package/src/core/installers/index.ts +9 -0
- package/src/core/types.ts +163 -0
- package/src/index.ts +44 -0
- package/src/services/InstallRequestValidator.ts +112 -0
- package/src/services/ServerService.ts +181 -0
- package/src/utils/UpdateCheckTracker.ts +86 -0
- package/src/utils/clientUtils.ts +112 -0
- package/src/utils/feedUtils.ts +31 -0
- package/src/utils/githubAuth.ts +142 -0
- package/src/utils/logger.ts +101 -0
- package/src/utils/osUtils.ts +250 -0
- package/src/web/public/css/modal.css +250 -0
- package/src/web/public/css/notifications.css +70 -0
- package/src/web/public/index.html +157 -0
- package/src/web/public/js/api.js +213 -0
- package/src/web/public/js/modal.js +572 -0
- package/src/web/public/js/notifications.js +99 -0
- package/src/web/public/js/serverCategoryDetails.js +210 -0
- package/src/web/public/js/serverCategoryList.js +82 -0
- package/src/web/public/modal.html +61 -0
- package/src/web/public/styles.css +155 -0
- package/src/web/server.ts +195 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,572 @@
|
|
|
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
|
+
/** Delayed message queue for loading modal */
|
|
8
|
+
let messageQueue = [];
|
|
9
|
+
let isAppending = false;
|
|
10
|
+
function processMessageQueue() {
|
|
11
|
+
if (isAppending || messageQueue.length === 0) return;
|
|
12
|
+
isAppending = true;
|
|
13
|
+
const { msg } = messageQueue.shift();
|
|
14
|
+
_appendInstallLoadingMessage(msg);
|
|
15
|
+
setTimeout(() => {
|
|
16
|
+
isAppending = false;
|
|
17
|
+
processMessageQueue();
|
|
18
|
+
}, 1000);
|
|
19
|
+
}
|
|
20
|
+
// Always use this to append messages with delay
|
|
21
|
+
function delayedAppendInstallLoadingMessage(msg) {
|
|
22
|
+
messageQueue.push({ msg });
|
|
23
|
+
processMessageQueue();
|
|
24
|
+
}
|
|
25
|
+
// Internal: the original append function
|
|
26
|
+
function _appendInstallLoadingMessage(message) {
|
|
27
|
+
const loadingMsg = document.getElementById('installLoadingMessage');
|
|
28
|
+
if (loadingMsg) {
|
|
29
|
+
loadingMsg.innerHTML += `<div>${message}</div>`;
|
|
30
|
+
loadingMsg.scrollTop = loadingMsg.scrollHeight;
|
|
31
|
+
} else {
|
|
32
|
+
console.error('[LoadingModal] loading message DOM not found');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Show the install loading modal and optionally append a message.
|
|
38
|
+
* @param {string} message
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Show the install loading modal and optionally append a message.
|
|
43
|
+
* @param {string} message
|
|
44
|
+
*/
|
|
45
|
+
function showInstallLoadingModal() {
|
|
46
|
+
const loadingModal = document.getElementById('installLoadingModal');
|
|
47
|
+
const loadingMsg = document.getElementById('installLoadingMessage');
|
|
48
|
+
if (loadingModal && loadingMsg) {
|
|
49
|
+
loadingModal.style.display = 'block';
|
|
50
|
+
loadingMsg.innerHTML = '';
|
|
51
|
+
} else {
|
|
52
|
+
console.error('[LoadingModal] loading modal DOM not found');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Append a message to the loading modal.
|
|
58
|
+
* @param {string} message
|
|
59
|
+
*/
|
|
60
|
+
function appendInstallLoadingMessage(message) {
|
|
61
|
+
console.log('[LoadingModal] appendInstallLoadingMessage:', message);
|
|
62
|
+
const loadingMsg = document.getElementById('installLoadingMessage');
|
|
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
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Hide the install loading modal.
|
|
73
|
+
*/
|
|
74
|
+
function hideInstallLoadingModal() {
|
|
75
|
+
console.log('[LoadingModal] hideInstallLoadingModal called');
|
|
76
|
+
const loadingModal = document.getElementById('installLoadingModal');
|
|
77
|
+
if (loadingModal) {
|
|
78
|
+
loadingModal.style.display = 'none';
|
|
79
|
+
} else {
|
|
80
|
+
console.error('[LoadingModal] loading modal DOM not found');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Show install modal for MCP tools
|
|
85
|
+
async function showInstallModal(categoryName, serverName, callback) {
|
|
86
|
+
console.log("Showing install modal for:", serverName);
|
|
87
|
+
|
|
88
|
+
// Wait for a short delay to ensure modal is loaded
|
|
89
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
90
|
+
|
|
91
|
+
const modal = document.getElementById('installModal');
|
|
92
|
+
if (!modal) {
|
|
93
|
+
console.error('Modal container not found');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const title = modal.querySelector('#modalTitle');
|
|
98
|
+
const envInputsDiv = modal.querySelector('#modalEnvInputs');
|
|
99
|
+
const targetDiv = modal.querySelector('#modalTargets');
|
|
100
|
+
const modalRequirements = modal.querySelector('#modalRequirements');
|
|
101
|
+
|
|
102
|
+
// Global array to track selected clients
|
|
103
|
+
window.selectedClients = [];
|
|
104
|
+
|
|
105
|
+
// Verify all required modal elements exist
|
|
106
|
+
if (!title || !envInputsDiv || !targetDiv || !modalRequirements) {
|
|
107
|
+
console.error('Required modal elements not found');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
title.textContent = `Install ${serverName}`;
|
|
112
|
+
envInputsDiv.innerHTML = ''; // Clear previous inputs
|
|
113
|
+
targetDiv.innerHTML = ''; // Clear previous targets
|
|
114
|
+
modalRequirements.innerHTML = ''; // Clear previous requirements
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
// Fetch both targets and server data simultaneously
|
|
118
|
+
const [targetResponse, serverResponse] = await Promise.all([
|
|
119
|
+
fetch('/api/targets'),
|
|
120
|
+
fetch(`/api/categories/${categoryName}`)
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
if (!targetResponse.ok || !serverResponse.ok) {
|
|
124
|
+
throw new Error('Failed to fetch required data');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const [targetData, serverData] = await Promise.all([
|
|
128
|
+
targetResponse.json(),
|
|
129
|
+
serverResponse.json()
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
if (!targetData.success || !serverData.success) {
|
|
133
|
+
throw new Error('Invalid data received');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const mcpServer = serverData.data.feedConfiguration.mcpServers.find(server => server.name === serverName);
|
|
137
|
+
if (!mcpServer) {
|
|
138
|
+
throw new Error('Server configuration not found');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const installationStatus = serverData.data.installationStatus || {};
|
|
142
|
+
const serverStatuses = installationStatus.serversStatus || {};
|
|
143
|
+
const serverStatus = serverStatuses[serverName] || { installedStatus: {} };
|
|
144
|
+
|
|
145
|
+
// Create client items with switch toggles
|
|
146
|
+
targetData.data.forEach(target => {
|
|
147
|
+
const operationStatus = serverStatus.installedStatus[target] || { status: 'not-installed', type: 'check', target: 'server' };
|
|
148
|
+
|
|
149
|
+
// Determine client status
|
|
150
|
+
let statusText = 'Not Installed';
|
|
151
|
+
let statusClass = 'not-installed';
|
|
152
|
+
|
|
153
|
+
if (operationStatus.status === 'completed' && operationStatus.type === 'install') {
|
|
154
|
+
statusText = 'Installed';
|
|
155
|
+
statusClass = 'installed';
|
|
156
|
+
} else if (operationStatus.status === 'pending') {
|
|
157
|
+
statusText = 'Pending Requirements';
|
|
158
|
+
statusClass = 'pending';
|
|
159
|
+
} else if (operationStatus.status === 'in-progress') {
|
|
160
|
+
statusText = 'In Progress';
|
|
161
|
+
statusClass = 'pending';
|
|
162
|
+
} else if (operationStatus.status === 'failed') {
|
|
163
|
+
statusText = 'Failed';
|
|
164
|
+
statusClass = 'not-installed';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const isConfigured = operationStatus.status === 'completed' && operationStatus.type === 'install';
|
|
168
|
+
|
|
169
|
+
// Create client item
|
|
170
|
+
const clientItem = document.createElement('div');
|
|
171
|
+
clientItem.className = 'client-item';
|
|
172
|
+
clientItem.dataset.target = target;
|
|
173
|
+
clientItem.dataset.selected = 'false';
|
|
174
|
+
|
|
175
|
+
// Determine if item should be selectable
|
|
176
|
+
const isInProgress = operationStatus.status === 'in-progress';
|
|
177
|
+
const isSelectable = !isConfigured && !isInProgress;
|
|
178
|
+
|
|
179
|
+
// Add appropriate non-selectable classes
|
|
180
|
+
if (!isSelectable) {
|
|
181
|
+
clientItem.classList.add('non-selectable');
|
|
182
|
+
|
|
183
|
+
if (isConfigured) {
|
|
184
|
+
clientItem.classList.add('installed-item');
|
|
185
|
+
clientItem.title = 'Already installed';
|
|
186
|
+
} else if (isInProgress) {
|
|
187
|
+
clientItem.classList.add('in-progress-item');
|
|
188
|
+
clientItem.title = 'Installation in progress';
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Make selectable clients clickable
|
|
193
|
+
if (isSelectable) {
|
|
194
|
+
clientItem.addEventListener('click', (e) => {
|
|
195
|
+
const isSelected = clientItem.dataset.selected === 'true';
|
|
196
|
+
clientItem.dataset.selected = isSelected ? 'false' : 'true';
|
|
197
|
+
|
|
198
|
+
// Update class
|
|
199
|
+
if (isSelected) {
|
|
200
|
+
clientItem.classList.remove('selected');
|
|
201
|
+
window.selectedClients = window.selectedClients.filter(c => c !== target);
|
|
202
|
+
} else {
|
|
203
|
+
clientItem.classList.add('selected');
|
|
204
|
+
if (!window.selectedClients.includes(target)) {
|
|
205
|
+
window.selectedClients.push(target);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log('Selected clients:', window.selectedClients);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Create client info (name)
|
|
214
|
+
const clientInfo = document.createElement('div');
|
|
215
|
+
clientInfo.className = 'client-info';
|
|
216
|
+
|
|
217
|
+
// Client name label
|
|
218
|
+
const clientName = document.createElement('span');
|
|
219
|
+
clientName.className = 'text-sm font-medium text-gray-900';
|
|
220
|
+
clientName.textContent = target;
|
|
221
|
+
|
|
222
|
+
// Add elements to client info
|
|
223
|
+
clientInfo.appendChild(clientName);
|
|
224
|
+
|
|
225
|
+
// Status badge
|
|
226
|
+
const statusBadge = document.createElement('span');
|
|
227
|
+
statusBadge.className = `status-badge ${statusClass}`;
|
|
228
|
+
statusBadge.textContent = statusText;
|
|
229
|
+
|
|
230
|
+
// Add components to client item
|
|
231
|
+
clientItem.appendChild(clientInfo);
|
|
232
|
+
clientItem.appendChild(statusBadge);
|
|
233
|
+
|
|
234
|
+
// Add client item to target div
|
|
235
|
+
targetDiv.appendChild(clientItem);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Add section title at the top of the modal target div
|
|
239
|
+
if (!targetDiv.querySelector('.section-title')) {
|
|
240
|
+
const titleElement = document.createElement('h3');
|
|
241
|
+
titleElement.className = 'section-title text-lg font-semibold text-gray-700 mb-3';
|
|
242
|
+
titleElement.textContent = 'Client Status';
|
|
243
|
+
targetDiv.insertBefore(titleElement, targetDiv.firstChild);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Handle environment variables section
|
|
247
|
+
const envRequirements = mcpServer.installation?.['env'] || mcpServer.installation?.env || {};
|
|
248
|
+
|
|
249
|
+
// Make sure environment variables section has a title
|
|
250
|
+
if (!envInputsDiv.querySelector('.section-title')) {
|
|
251
|
+
const envTitle = document.createElement('h3');
|
|
252
|
+
envTitle.className = 'section-title text-lg font-semibold text-gray-700 mb-3';
|
|
253
|
+
envTitle.textContent = 'Environment Variables';
|
|
254
|
+
envInputsDiv.insertBefore(envTitle, envInputsDiv.firstChild);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (Object.keys(envRequirements).length === 0) {
|
|
258
|
+
const noEnvMessage = document.createElement('p');
|
|
259
|
+
noEnvMessage.className = 'text-gray-600';
|
|
260
|
+
noEnvMessage.textContent = 'No environment variables required for this MCP server.';
|
|
261
|
+
envInputsDiv.appendChild(noEnvMessage);
|
|
262
|
+
} else {
|
|
263
|
+
// Create inputs for each environment variable
|
|
264
|
+
Object.keys(envRequirements).forEach(key => {
|
|
265
|
+
const req = envRequirements[key];
|
|
266
|
+
const inputId = `env_${key}`;
|
|
267
|
+
const inputWrapper = document.createElement('div');
|
|
268
|
+
inputWrapper.className = 'mb-3';
|
|
269
|
+
|
|
270
|
+
const label = document.createElement('label');
|
|
271
|
+
label.htmlFor = inputId;
|
|
272
|
+
label.className = 'block text-sm font-medium text-gray-700 mb-1';
|
|
273
|
+
label.innerHTML = `${key} ${req.Required ? '<span class="text-red-500">*</span>' : ''}`;
|
|
274
|
+
|
|
275
|
+
const input = document.createElement('input');
|
|
276
|
+
input.type = 'text';
|
|
277
|
+
input.id = inputId;
|
|
278
|
+
input.name = key;
|
|
279
|
+
input.placeholder = req.Description || key;
|
|
280
|
+
input.value = req.Default || '';
|
|
281
|
+
input.required = req.Required;
|
|
282
|
+
input.className = 'input-field';
|
|
283
|
+
|
|
284
|
+
inputWrapper.appendChild(label);
|
|
285
|
+
inputWrapper.appendChild(input);
|
|
286
|
+
|
|
287
|
+
if (req.Description) {
|
|
288
|
+
const description = document.createElement('p');
|
|
289
|
+
description.className = 'text-xs text-gray-500 mt-1';
|
|
290
|
+
description.textContent = req.Description;
|
|
291
|
+
inputWrapper.appendChild(description);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
envInputsDiv.appendChild(inputWrapper);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Handle server requirements section last
|
|
299
|
+
const serverRequirements = mcpServer.dependencies?.requirements || [];
|
|
300
|
+
const requirements = serverData.data.installationStatus?.requirementsStatus || {};
|
|
301
|
+
|
|
302
|
+
if (serverRequirements.length > 0) {
|
|
303
|
+
const reqHtml = serverRequirements.map(req => {
|
|
304
|
+
const status = requirements[req.name] || {};
|
|
305
|
+
const statusClass = status.installed
|
|
306
|
+
? 'text-green-600 bg-green-50'
|
|
307
|
+
: 'text-yellow-600 bg-yellow-50';
|
|
308
|
+
const statusText = status.installed ? 'Installed' : 'Required';
|
|
309
|
+
|
|
310
|
+
return `
|
|
311
|
+
<div class="border border-gray-200 p-3 rounded-lg mb-2 hover:bg-gray-50">
|
|
312
|
+
<div class="flex justify-between items-center">
|
|
313
|
+
<div>
|
|
314
|
+
<div class="font-semibold text-gray-800">${req.name}</div>
|
|
315
|
+
<div class="text-sm text-gray-600 shadow-sm p-1 rounded bg-gray-50">
|
|
316
|
+
<span class="font-medium">${status.type || 'package'}</span>${status.version ? ` • <span class="font-medium">${status.version}</span>` : ''}
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
<span class="${statusClass} inline-flex items-center px-3 py-1 rounded-full text-sm">
|
|
320
|
+
${statusText}
|
|
321
|
+
</span>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
`;
|
|
325
|
+
}).join('');
|
|
326
|
+
|
|
327
|
+
modalRequirements.innerHTML = `
|
|
328
|
+
<h3 class="text-lg font-semibold text-gray-700 mb-3">Dependencies</h3>
|
|
329
|
+
<p class="text-sm text-gray-600 mb-4">These dependencies will be automatically installed when installing the server</p>
|
|
330
|
+
${reqHtml}
|
|
331
|
+
`;
|
|
332
|
+
} else {
|
|
333
|
+
modalRequirements.innerHTML = '<p class="text-gray-600">No additional dependencies required.</p>';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Set up the install form submit handler
|
|
337
|
+
const installForm = document.getElementById('installForm');
|
|
338
|
+
installForm.onsubmit = (e) => {
|
|
339
|
+
e.preventDefault();
|
|
340
|
+
|
|
341
|
+
// Get all environment variables
|
|
342
|
+
const envVars = {};
|
|
343
|
+
const inputs = envInputsDiv.querySelectorAll('input');
|
|
344
|
+
inputs.forEach(input => {
|
|
345
|
+
if (input.name && input.value) {
|
|
346
|
+
envVars[input.name] = input.value;
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Get selected clients
|
|
351
|
+
const selectedTargets = window.selectedClients.length > 0 ?
|
|
352
|
+
window.selectedClients :
|
|
353
|
+
Array.from(document.querySelectorAll('.client-item.selected'))
|
|
354
|
+
.map(item => item.dataset.target);
|
|
355
|
+
|
|
356
|
+
if (selectedTargets.length === 0) {
|
|
357
|
+
showToast('Please select at least one client to configure.', 'error');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Call install function with selected targets
|
|
362
|
+
// Find installing message for the first selected target
|
|
363
|
+
let installingMessage = "Starting installation...";
|
|
364
|
+
const serverStatus = serverStatuses[serverName] || { installedStatus: {} };
|
|
365
|
+
if (selectedTargets.length > 0) {
|
|
366
|
+
const target = selectedTargets[0];
|
|
367
|
+
const msg = serverStatus.installedStatus?.[target]?.message;
|
|
368
|
+
if (msg) installingMessage = msg;
|
|
369
|
+
}
|
|
370
|
+
handleBulkClientInstall(categoryName, serverName, selectedTargets, envVars, installingMessage, serverData);
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
} catch (error) {
|
|
374
|
+
console.error("Error loading data:", error);
|
|
375
|
+
targetDiv.innerHTML = `<p class="text-red-500">Error: ${error.message}</p>`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
modal.style.display = "block";
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// 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 });
|
|
384
|
+
// Hide install modal, show loading modal
|
|
385
|
+
const installModal = document.getElementById('installModal');
|
|
386
|
+
console.log('[LoadingModal] installModal:', installModal);
|
|
387
|
+
if (installModal) installModal.style.display = "none";
|
|
388
|
+
|
|
389
|
+
// If serverData is provided, extract the installing message from it (latest status)
|
|
390
|
+
if (serverData && serverData.data && serverData.data.installationStatus && serverData.data.installationStatus.serversStatus) {
|
|
391
|
+
const serverStatuses = serverData.data.installationStatus.serversStatus;
|
|
392
|
+
const serverStatus = serverStatuses[serverName] || { installedStatus: {} };
|
|
393
|
+
if (targets && targets.length > 0) {
|
|
394
|
+
const target = targets[0];
|
|
395
|
+
const msg = serverStatus.installedStatus?.[target]?.message;
|
|
396
|
+
if (msg) installingMessage = msg;
|
|
397
|
+
}
|
|
398
|
+
// Append the installing message for user visibility
|
|
399
|
+
if (installingMessage && installingMessage !== "Starting installation...") {
|
|
400
|
+
delayedAppendInstallLoadingMessage(installingMessage);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
showInstallLoadingModal(installingMessage);
|
|
405
|
+
|
|
406
|
+
delayedAppendInstallLoadingMessage(installingMessage);
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
delayedAppendInstallLoadingMessage("Installing, please wait...");
|
|
410
|
+
const response = await fetch(`/api/categories/${categoryName}/install`, {
|
|
411
|
+
method: 'POST',
|
|
412
|
+
headers: {
|
|
413
|
+
'Content-Type': 'application/json',
|
|
414
|
+
'Accept': 'application/json'
|
|
415
|
+
},
|
|
416
|
+
body: JSON.stringify({
|
|
417
|
+
serverList: {
|
|
418
|
+
[serverName]: {
|
|
419
|
+
targetClients: targets,
|
|
420
|
+
env: envVars
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
})
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
console.log('[LoadingModal] fetch install response:', response);
|
|
427
|
+
|
|
428
|
+
if (!response.ok) {
|
|
429
|
+
const errorData = await response.text();
|
|
430
|
+
delayedAppendInstallLoadingMessage(`<span style="color:red;">Installation failed: ${errorData || response.statusText}</span>`);
|
|
431
|
+
console.error('[LoadingModal] Installation failed:', errorData || response.statusText);
|
|
432
|
+
throw new Error(`Installation failed: ${errorData || response.statusText}`);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const result = await response.json();
|
|
436
|
+
console.log('[LoadingModal] install result:', result);
|
|
437
|
+
if (!result.success) {
|
|
438
|
+
delayedAppendInstallLoadingMessage(`<span style="color:red;">${result.error || 'Installation failed'}</span>`);
|
|
439
|
+
console.error('[LoadingModal] install result error:', result.error || 'Installation failed');
|
|
440
|
+
throw new Error(result.error || 'Installation failed');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Optionally, you can refresh modal data here or trigger a callback
|
|
444
|
+
// Start polling for install status
|
|
445
|
+
pollInstallStatus(categoryName, serverName, targets);
|
|
446
|
+
} catch (error) {
|
|
447
|
+
console.error('[LoadingModal] Error applying configuration:', error);
|
|
448
|
+
delayedAppendInstallLoadingMessage(`<span style="color:red;">Error: ${error.message}</span>`);
|
|
449
|
+
setTimeout(() => {
|
|
450
|
+
hideInstallLoadingModal();
|
|
451
|
+
setTimeout(() => {
|
|
452
|
+
location.reload();
|
|
453
|
+
}, 200);
|
|
454
|
+
}, 3200);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Poll install status for the given server/targets
|
|
459
|
+
async function pollInstallStatus(categoryName, serverName, targets, interval = 2000, maxTries = 60) {
|
|
460
|
+
let tries = 0;
|
|
461
|
+
let lastMessages = {};
|
|
462
|
+
// Use global delayedAppendInstallLoadingMessage and queue logic
|
|
463
|
+
while (tries < maxTries) {
|
|
464
|
+
try {
|
|
465
|
+
const resp = await fetch(`/api/categories/${categoryName}`);
|
|
466
|
+
if (resp.ok) {
|
|
467
|
+
const data = await resp.json();
|
|
468
|
+
const serverStatuses = data?.data?.installationStatus?.serversStatus || {};
|
|
469
|
+
const serverStatus = serverStatuses[serverName] || { installedStatus: {} };
|
|
470
|
+
let allCompleted = true;
|
|
471
|
+
for (const target of targets) {
|
|
472
|
+
const status = serverStatus.installedStatus?.[target]?.status;
|
|
473
|
+
const msg = serverStatus.installedStatus?.[target]?.message;
|
|
474
|
+
// Only append new messages
|
|
475
|
+
if (msg && lastMessages[target] !== msg) {
|
|
476
|
+
delayedAppendInstallLoadingMessage(`[${target}] ${msg}`);
|
|
477
|
+
lastMessages[target] = msg;
|
|
478
|
+
}
|
|
479
|
+
if (status !== "completed") {
|
|
480
|
+
allCompleted = false;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (allCompleted) {
|
|
484
|
+
delayedAppendInstallLoadingMessage(`<span style="color:green;">Configuration applied successfully for ${targets.length} client(s).</span>`);
|
|
485
|
+
setTimeout(() => {
|
|
486
|
+
hideInstallLoadingModal();
|
|
487
|
+
setTimeout(() => {
|
|
488
|
+
location.reload();
|
|
489
|
+
}, 200);
|
|
490
|
+
}, 5000);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
} catch (e) {
|
|
495
|
+
// Ignore errors and continue polling
|
|
496
|
+
}
|
|
497
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
498
|
+
tries++;
|
|
499
|
+
}
|
|
500
|
+
// Timeout: close modal and reload anyway
|
|
501
|
+
hideInstallLoadingModal();
|
|
502
|
+
setTimeout(() => {
|
|
503
|
+
location.reload();
|
|
504
|
+
}, 200);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Function to handle client uninstallation for multiple targets
|
|
508
|
+
async function uninstallTools(categoryName, serverList, targets) {
|
|
509
|
+
if (!Array.isArray(targets)) {
|
|
510
|
+
targets = [targets]; // Convert single target to array for backward compatibility
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const confirmed = await showConfirm(`Are you sure you want to uninstall this server for ${targets.length} client(s)?`);
|
|
514
|
+
if (!confirmed) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
const response = await fetch(`/api/categories/${categoryName}/uninstall`, {
|
|
520
|
+
method: 'POST',
|
|
521
|
+
headers: { 'Content-Type': 'application/json' },
|
|
522
|
+
body: JSON.stringify({
|
|
523
|
+
toolList: serverList,
|
|
524
|
+
options: {
|
|
525
|
+
targets: targets
|
|
526
|
+
}
|
|
527
|
+
})
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
if (!response.ok) {
|
|
531
|
+
const errorData = await response.text();
|
|
532
|
+
throw new Error(`Uninstallation failed: ${errorData || response.statusText}`);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const result = await response.json();
|
|
536
|
+
if (!result.success) {
|
|
537
|
+
throw new Error(result.error || 'Uninstallation failed');
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
showToast(`Successfully uninstalled for ${targets.length} client(s).`, 'success');
|
|
541
|
+
location.reload(); // Refresh the page to update the UI
|
|
542
|
+
} catch (error) {
|
|
543
|
+
console.error('Error uninstalling tools:', error);
|
|
544
|
+
showToast(`Error uninstalling tools: ${error.message}`, 'error');
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Close modal
|
|
549
|
+
function closeModal() {
|
|
550
|
+
document.getElementById('installModal').style.display = "none";
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Close modal if clicked outside content
|
|
554
|
+
function setupModalOutsideClick() {
|
|
555
|
+
window.onclick = function (event) {
|
|
556
|
+
const modal = document.getElementById('installModal');
|
|
557
|
+
if (event.target == modal) {
|
|
558
|
+
closeModal();
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Compatibility function for individual client installation
|
|
564
|
+
async function handleClientInstall(categoryName, serverName, target, envVars = {}) {
|
|
565
|
+
return handleBulkClientInstall(categoryName, serverName, [target], envVars);
|
|
566
|
+
}
|
|
567
|
+
window.showInstallLoadingModal = showInstallLoadingModal;
|
|
568
|
+
window.appendInstallLoadingMessage = appendInstallLoadingMessage;
|
|
569
|
+
window.hideInstallLoadingModal = hideInstallLoadingModal;
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
export { showInstallModal, closeModal, setupModalOutsideClick, uninstallTools };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Function to show a Bootstrap alert notification
|
|
2
|
+
function showToast(message, type = 'success') {
|
|
3
|
+
const alertContainer = document.querySelector('.alert-container');
|
|
4
|
+
const alertId = `alert-${Date.now()}`;
|
|
5
|
+
|
|
6
|
+
const alertHtml = `
|
|
7
|
+
<div id="${alertId}" class="alert alert-${type} alert-dismissible fade show" role="alert">
|
|
8
|
+
${type === 'success' ?
|
|
9
|
+
'<i class="bi bi-check-circle-fill me-2"></i>' :
|
|
10
|
+
'<i class="bi bi-exclamation-triangle-fill me-2"></i>'
|
|
11
|
+
}
|
|
12
|
+
${message}
|
|
13
|
+
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
14
|
+
</div>
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
alertContainer.insertAdjacentHTML('beforeend', alertHtml);
|
|
18
|
+
|
|
19
|
+
const alertElement = document.getElementById(alertId);
|
|
20
|
+
|
|
21
|
+
// Remove the alert after 5 seconds
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
const bsAlert = new bootstrap.Alert(alertElement);
|
|
24
|
+
bsAlert.close();
|
|
25
|
+
}, 3000); // Reduced to 3 seconds
|
|
26
|
+
|
|
27
|
+
// Remove the element after animation
|
|
28
|
+
alertElement.addEventListener('closed.bs.alert', () => {
|
|
29
|
+
alertElement.remove();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Function to show a Bootstrap confirmation dialog
|
|
34
|
+
function showConfirm(message) {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
const modalId = `confirm-modal-${Date.now()}`;
|
|
37
|
+
const modalHtml = `
|
|
38
|
+
<div class="modal fade" id="${modalId}" tabindex="-1" aria-labelledby="${modalId}-label" aria-hidden="true">
|
|
39
|
+
<div class="modal-dialog">
|
|
40
|
+
<div class="modal-content">
|
|
41
|
+
<div class="modal-header">
|
|
42
|
+
<h5 class="modal-title" id="${modalId}-label">Confirm Action</h5>
|
|
43
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="modal-body">
|
|
46
|
+
${message}
|
|
47
|
+
</div>
|
|
48
|
+
<div class="modal-footer">
|
|
49
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
50
|
+
<button type="button" class="btn btn-primary confirm-action">
|
|
51
|
+
<span class="spinner-border spinner-border-sm d-none me-2" role="status" aria-hidden="true"></span>
|
|
52
|
+
Confirm
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
61
|
+
|
|
62
|
+
const modalElement = document.getElementById(modalId);
|
|
63
|
+
const modal = new bootstrap.Modal(modalElement);
|
|
64
|
+
|
|
65
|
+
// Handle confirm button click
|
|
66
|
+
modalElement.querySelector('.confirm-action').addEventListener('click', (event) => {
|
|
67
|
+
const button = event.currentTarget;
|
|
68
|
+
const spinner = button.querySelector('.spinner-border');
|
|
69
|
+
button.disabled = true;
|
|
70
|
+
spinner.classList.remove('d-none');
|
|
71
|
+
|
|
72
|
+
// Add small delay to show the loading state
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
modal.hide();
|
|
75
|
+
resolve(true);
|
|
76
|
+
}, 300);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Handle modal hidden event
|
|
80
|
+
modalElement.addEventListener('hidden.bs.modal', () => {
|
|
81
|
+
modalElement.remove();
|
|
82
|
+
resolve(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
modal.show();
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { showToast, showConfirm };
|
|
90
|
+
|
|
91
|
+
// Display install notification after page reload
|
|
92
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
93
|
+
const data = sessionStorage.getItem('installNotification');
|
|
94
|
+
if (data) {
|
|
95
|
+
const { message, type } = JSON.parse(data);
|
|
96
|
+
showToast(message, type);
|
|
97
|
+
sessionStorage.removeItem('installNotification');
|
|
98
|
+
}
|
|
99
|
+
});
|