imcp 0.0.11 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/uninstall.js +14 -6
- package/dist/core/ConfigurationProvider.d.ts +1 -0
- package/dist/core/ConfigurationProvider.js +65 -1
- package/dist/core/MCPManager.js +27 -5
- package/dist/core/ServerSchemaLoader.js +2 -2
- package/dist/core/constants.d.ts +11 -1
- package/dist/core/installers/clients/ExtensionInstaller.js +3 -0
- package/dist/core/types.d.ts +6 -0
- package/dist/services/ServerService.d.ts +7 -1
- package/dist/services/ServerService.js +24 -9
- package/dist/utils/osUtils.js +1 -1
- package/dist/web/public/css/detailsWidget.css +67 -60
- package/dist/web/public/css/modal.css +42 -0
- package/dist/web/public/css/serverDetails.css +2 -1
- package/dist/web/public/js/detailsWidget.js +162 -50
- package/dist/web/public/js/modal.js +93 -29
- package/dist/web/public/js/notifications.js +34 -35
- package/dist/web/server.js +13 -2
- package/package.json +1 -1
- package/src/cli/commands/uninstall.ts +16 -6
- package/src/core/ConfigurationProvider.ts +71 -2
- package/src/core/MCPManager.ts +32 -5
- package/src/core/constants.ts +12 -2
- package/src/core/installers/clients/ExtensionInstaller.ts +3 -0
- package/src/core/types.ts +7 -1
- package/src/services/ServerService.ts +26 -8
- package/src/utils/osUtils.ts +1 -1
- package/src/web/public/css/modal.css +42 -0
- package/src/web/public/js/modal.js +93 -29
- package/src/web/public/js/notifications.js +34 -35
- package/src/web/server.ts +21 -3
|
@@ -2,13 +2,20 @@ export class DetailsWidget {
|
|
|
2
2
|
constructor(container) {
|
|
3
3
|
this.container = container;
|
|
4
4
|
this.isExpanded = false;
|
|
5
|
+
this.expandedTool = null;
|
|
5
6
|
this.init();
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
init() {
|
|
9
|
-
// Create
|
|
10
|
+
// Create and append stylesheet
|
|
11
|
+
const link = document.createElement('link');
|
|
12
|
+
link.rel = 'stylesheet';
|
|
13
|
+
link.href = '/css/detailsWidget.css';
|
|
14
|
+
document.head.appendChild(link);
|
|
15
|
+
|
|
10
16
|
this.widgetElement = document.createElement('div');
|
|
11
17
|
this.widgetElement.className = 'details-widget';
|
|
18
|
+
this.widgetElement.style.overflow = 'auto'; // Enable scrolling
|
|
12
19
|
this.contentElement = document.createElement('div');
|
|
13
20
|
this.contentElement.className = 'details-widget-content';
|
|
14
21
|
this.widgetElement.appendChild(this.contentElement);
|
|
@@ -25,41 +32,130 @@ export class DetailsWidget {
|
|
|
25
32
|
return;
|
|
26
33
|
}
|
|
27
34
|
|
|
28
|
-
//
|
|
29
|
-
if (content
|
|
30
|
-
|
|
35
|
+
// Handle direct schema object or wrapped schema object
|
|
36
|
+
if (typeof content === 'object') {
|
|
37
|
+
const schemaData = content.schema || content; // Handle both {schema: data} and direct data
|
|
38
|
+
this.renderToolsList(schemaData);
|
|
39
|
+
return;
|
|
31
40
|
}
|
|
41
|
+
|
|
42
|
+
this.contentElement.innerHTML = '<p>Invalid content format</p>';
|
|
32
43
|
}
|
|
33
44
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
// Maintain compatibility with old toggle behavior
|
|
46
|
+
toggle() {
|
|
47
|
+
if (this.isExpanded) {
|
|
48
|
+
this.collapse();
|
|
49
|
+
} else {
|
|
50
|
+
this.expand();
|
|
37
51
|
}
|
|
52
|
+
}
|
|
38
53
|
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
expand() {
|
|
55
|
+
this.isExpanded = true;
|
|
56
|
+
this.widgetElement.classList.add('expanded');
|
|
57
|
+
if (this.container.querySelector('.server-item-content')) {
|
|
58
|
+
this.container.querySelector('.server-item-content').classList.add('expanded');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
41
61
|
|
|
42
|
-
|
|
62
|
+
collapse() {
|
|
63
|
+
this.isExpanded = false;
|
|
64
|
+
this.widgetElement.classList.remove('expanded');
|
|
65
|
+
if (this.container.querySelector('.server-item-content')) {
|
|
66
|
+
this.container.querySelector('.server-item-content').classList.remove('expanded');
|
|
67
|
+
}
|
|
68
|
+
// Reset expanded tool state
|
|
69
|
+
this.expandedTool = null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
renderToolsList(schemaContent) {
|
|
73
|
+
if (!schemaContent || typeof schemaContent !== 'object') {
|
|
74
|
+
this.contentElement.innerHTML = '<p>Invalid schema format</p>';
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let html = `
|
|
79
|
+
<div class="tools-list p-2">
|
|
80
|
+
<div class="tools-grid grid gap-2">
|
|
81
|
+
`;
|
|
43
82
|
|
|
44
|
-
// Iterate through each tool in schema
|
|
45
83
|
Object.entries(schemaContent).forEach(([toolName, toolInfo]) => {
|
|
46
|
-
if (!toolInfo) return;
|
|
84
|
+
if (!toolInfo) return;
|
|
47
85
|
|
|
48
86
|
html += `
|
|
49
|
-
<div class="tool-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<
|
|
87
|
+
<div class="tool-card" data-tool="${this.escapeHtml(toolName)}">
|
|
88
|
+
<div class="tool-card-header">
|
|
89
|
+
<div class="tool-header mb-2">
|
|
90
|
+
<h3 class="text-md font-semibold text-blue-600">${toolInfo.name || toolName}</h3>
|
|
91
|
+
</div>
|
|
92
|
+
<p class="text-gray-700 text-sm">${toolInfo.description || 'No description available'}</p>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="tool-details hidden mt-3" data-tool-details="${this.escapeHtml(toolName)}">
|
|
55
95
|
${this.renderInputSchema(toolInfo.inputSchema)}
|
|
56
96
|
</div>
|
|
57
97
|
</div>
|
|
58
98
|
`;
|
|
59
99
|
});
|
|
60
100
|
|
|
61
|
-
html +=
|
|
62
|
-
|
|
101
|
+
html += `
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
this.contentElement.innerHTML = html;
|
|
107
|
+
|
|
108
|
+
// Add event listeners to tool cards
|
|
109
|
+
this.contentElement.querySelectorAll('.tool-card').forEach(card => {
|
|
110
|
+
const header = card.querySelector('.tool-card-header');
|
|
111
|
+
header.addEventListener('click', (e) => {
|
|
112
|
+
e.stopPropagation(); // Prevent click from bubbling up to server-item
|
|
113
|
+
const toolName = card.dataset.tool;
|
|
114
|
+
this.toggleToolDetails(toolName, card);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Add click handler for the details area to prevent bubbling
|
|
118
|
+
const details = card.querySelector('.tool-details');
|
|
119
|
+
details.addEventListener('click', (e) => {
|
|
120
|
+
e.stopPropagation(); // Prevent click from bubbling up
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
toggleToolDetails(toolName, card) {
|
|
126
|
+
const detailsElement = card.querySelector(`[data-tool-details="${toolName}"]`);
|
|
127
|
+
const isExpanded = !detailsElement.classList.contains('hidden');
|
|
128
|
+
|
|
129
|
+
// Collapse all other expanded tools
|
|
130
|
+
if (this.expandedTool && this.expandedTool !== toolName) {
|
|
131
|
+
const prevCard = this.contentElement.querySelector(`[data-tool="${this.expandedTool}"]`);
|
|
132
|
+
if (prevCard) {
|
|
133
|
+
const prevDetails = prevCard.querySelector(`[data-tool-details="${this.expandedTool}"]`);
|
|
134
|
+
prevDetails?.classList.add('hidden');
|
|
135
|
+
prevCard.classList.remove('active');
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
prevDetails.classList.remove('visible');
|
|
138
|
+
}, 0);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Toggle current tool with animation
|
|
143
|
+
if (isExpanded) {
|
|
144
|
+
detailsElement.classList.remove('visible');
|
|
145
|
+
card.classList.remove('active');
|
|
146
|
+
setTimeout(() => {
|
|
147
|
+
detailsElement.classList.add('hidden');
|
|
148
|
+
}, 300); // Match transition duration from CSS
|
|
149
|
+
this.expandedTool = null;
|
|
150
|
+
} else {
|
|
151
|
+
detailsElement.classList.remove('hidden');
|
|
152
|
+
card.classList.add('active');
|
|
153
|
+
setTimeout(() => {
|
|
154
|
+
detailsElement.classList.add('visible');
|
|
155
|
+
}, 0);
|
|
156
|
+
this.expandedTool = toolName;
|
|
157
|
+
detailsElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
158
|
+
}
|
|
63
159
|
}
|
|
64
160
|
|
|
65
161
|
renderInputSchema(schema) {
|
|
@@ -67,7 +163,6 @@ export class DetailsWidget {
|
|
|
67
163
|
return '<p>No input schema available</p>';
|
|
68
164
|
}
|
|
69
165
|
|
|
70
|
-
// Handle case where schema might be empty or have no properties
|
|
71
166
|
if (!schema.properties || Object.keys(schema.properties).length === 0) {
|
|
72
167
|
return '<p>No input properties defined</p>';
|
|
73
168
|
}
|
|
@@ -75,55 +170,77 @@ export class DetailsWidget {
|
|
|
75
170
|
let html = '<div class="properties-list">';
|
|
76
171
|
|
|
77
172
|
try {
|
|
78
|
-
// Required fields banner
|
|
79
|
-
if (schema.required
|
|
173
|
+
// Required fields banner
|
|
174
|
+
if (schema.required?.length > 0) {
|
|
80
175
|
html += `
|
|
81
176
|
<div class="required-fields mb-3 bg-yellow-50 p-2 rounded">
|
|
82
|
-
<span class="text-sm font-medium text-yellow-800">
|
|
177
|
+
<span class="text-sm font-medium text-yellow-800">
|
|
178
|
+
Required: ${schema.required.join(', ')}
|
|
179
|
+
</span>
|
|
83
180
|
</div>
|
|
84
181
|
`;
|
|
85
182
|
}
|
|
86
183
|
|
|
87
184
|
// Render each property
|
|
88
185
|
Object.entries(schema.properties).forEach(([propName, propDetails]) => {
|
|
89
|
-
if (!propDetails) return;
|
|
186
|
+
if (!propDetails) return;
|
|
90
187
|
|
|
91
|
-
const isRequired = schema.required
|
|
92
|
-
const type = propDetails
|
|
188
|
+
const isRequired = schema.required?.includes(propName);
|
|
189
|
+
const type = this.getPropertyType(propDetails);
|
|
93
190
|
|
|
94
191
|
html += `
|
|
95
|
-
<div class="property-item
|
|
96
|
-
<div class="property-header
|
|
97
|
-
<span class="property-name
|
|
98
|
-
<span class="property-type
|
|
99
|
-
${isRequired ? '<span class="required-badge
|
|
192
|
+
<div class="property-item ${isRequired ? 'required' : ''}">
|
|
193
|
+
<div class="property-header">
|
|
194
|
+
<span class="property-name">${this.escapeHtml(propName)}</span>
|
|
195
|
+
<span class="property-type">${this.escapeHtml(type)}</span>
|
|
196
|
+
${isRequired ? '<span class="required-badge">Required</span>' : ''}
|
|
100
197
|
</div>
|
|
101
|
-
${propDetails.description ?
|
|
102
|
-
|
|
198
|
+
${propDetails.description ?
|
|
199
|
+
`<p class="property-desc">${this.escapeHtml(propDetails.description)}</p>` : ''}
|
|
200
|
+
${propDetails.default !== undefined ?
|
|
201
|
+
`<p class="property-default">Default: ${this.escapeHtml(JSON.stringify(propDetails.default))}</p>` : ''}
|
|
202
|
+
${this.renderNestedProperties(propDetails)}
|
|
103
203
|
</div>
|
|
104
204
|
`;
|
|
105
205
|
});
|
|
106
206
|
|
|
107
|
-
html
|
|
108
|
-
return html;
|
|
207
|
+
return html + '</div>';
|
|
109
208
|
} catch (error) {
|
|
110
209
|
console.error('Error rendering input schema:', error);
|
|
111
210
|
return '<p>Error rendering input schema</p>';
|
|
112
211
|
}
|
|
113
212
|
}
|
|
114
213
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
214
|
+
renderNestedProperties(propDetails) {
|
|
215
|
+
if (propDetails.properties) {
|
|
216
|
+
let html = '<div class="nested-properties">';
|
|
217
|
+
Object.entries(propDetails.properties).forEach(([name, details]) => {
|
|
218
|
+
const type = this.getPropertyType(details);
|
|
219
|
+
html += `
|
|
220
|
+
<div class="nested-property-item">
|
|
221
|
+
<div class="property-header">
|
|
222
|
+
<span class="property-name">${this.escapeHtml(name)}</span>
|
|
223
|
+
<span class="property-type">${this.escapeHtml(type)}</span>
|
|
224
|
+
</div>
|
|
225
|
+
${details.description ?
|
|
226
|
+
`<p class="property-desc">${this.escapeHtml(details.description)}</p>` : ''}
|
|
227
|
+
</div>
|
|
228
|
+
`;
|
|
229
|
+
});
|
|
230
|
+
return html + '</div>';
|
|
121
231
|
}
|
|
232
|
+
return '';
|
|
122
233
|
}
|
|
123
234
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
235
|
+
getPropertyType(propDetails) {
|
|
236
|
+
if (propDetails.anyOf) {
|
|
237
|
+
return `oneOf: [${propDetails.anyOf.map(type => type.type || 'any').join(', ')}]`;
|
|
238
|
+
}
|
|
239
|
+
if (propDetails.type === 'array') {
|
|
240
|
+
const itemType = propDetails.items?.type || 'any';
|
|
241
|
+
return `array<${itemType}>`;
|
|
242
|
+
}
|
|
243
|
+
return propDetails.type || 'any';
|
|
127
244
|
}
|
|
128
245
|
|
|
129
246
|
escapeHtml(unsafe) {
|
|
@@ -138,11 +255,6 @@ export class DetailsWidget {
|
|
|
138
255
|
.replace(/'/g, "'");
|
|
139
256
|
}
|
|
140
257
|
|
|
141
|
-
collapse() {
|
|
142
|
-
this.widgetElement.classList.remove('expanded');
|
|
143
|
-
this.container.querySelector('.server-item-content').classList.remove('expanded');
|
|
144
|
-
}
|
|
145
|
-
|
|
146
258
|
isVisible() {
|
|
147
259
|
return this.isExpanded;
|
|
148
260
|
}
|
|
@@ -141,14 +141,16 @@ function _appendInstallLoadingMessage(message) {
|
|
|
141
141
|
/**
|
|
142
142
|
* Display the installation loading modal and prepare it for messages.
|
|
143
143
|
*/
|
|
144
|
-
function showInstallLoadingModal() {
|
|
144
|
+
function showInstallLoadingModal(operation = 'Installing') {
|
|
145
145
|
const loadingModal = document.getElementById('installLoadingModal');
|
|
146
146
|
const loadingMsg = document.getElementById('installLoadingMessage');
|
|
147
|
-
|
|
147
|
+
const loadingTitle = document.querySelector('.loading-title');
|
|
148
|
+
if (loadingModal && loadingMsg && loadingTitle) {
|
|
148
149
|
loadingModal.style.display = 'block';
|
|
149
150
|
loadingMsg.innerHTML = '';
|
|
151
|
+
loadingTitle.textContent = `${operation}...`;
|
|
150
152
|
} else {
|
|
151
|
-
console.error('[LoadingModal] Required elements not found: installLoadingModal or
|
|
153
|
+
console.error('[LoadingModal] Required elements not found: installLoadingModal, installLoadingMessage, or loading-title');
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
|
|
@@ -330,17 +332,55 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
330
332
|
// Add elements to client info
|
|
331
333
|
clientInfo.appendChild(clientName);
|
|
332
334
|
|
|
333
|
-
// Add
|
|
335
|
+
// Add elements to client item
|
|
334
336
|
clientItem.appendChild(clientInfo);
|
|
335
337
|
|
|
338
|
+
// Status container for badge and uninstall button
|
|
339
|
+
const statusContainer = document.createElement('div');
|
|
340
|
+
statusContainer.className = 'status-container';
|
|
341
|
+
|
|
336
342
|
// Status badge - only show if we have status text
|
|
337
343
|
if (statusText) {
|
|
338
344
|
const statusBadge = document.createElement('span');
|
|
339
345
|
statusBadge.className = `status-badge ${statusClass}`;
|
|
340
346
|
statusBadge.textContent = statusText;
|
|
341
|
-
|
|
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
|
+
}
|
|
342
380
|
}
|
|
343
381
|
|
|
382
|
+
clientItem.appendChild(statusContainer);
|
|
383
|
+
|
|
344
384
|
// Add client item to target div
|
|
345
385
|
targetDiv.appendChild(clientItem);
|
|
346
386
|
});
|
|
@@ -641,34 +681,40 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
641
681
|
});
|
|
642
682
|
});
|
|
643
683
|
|
|
644
|
-
// Get selected clients
|
|
645
|
-
const selectedTargets = window.selectedClients.length > 0 ?
|
|
646
|
-
window.selectedClients :
|
|
647
|
-
Array.from(document.querySelectorAll('.client-item.selected'))
|
|
648
|
-
.map(item => item.dataset.target);
|
|
649
|
-
|
|
650
684
|
// Check if we have any requirements selected for update
|
|
651
685
|
const hasRequirementsToUpdate = requirementsToUpdate.length > 0;
|
|
652
686
|
|
|
653
|
-
// Only
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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;
|
|
657
703
|
}
|
|
658
704
|
|
|
659
705
|
// Call install function with selected targets
|
|
660
706
|
// Find installing message for the first selected target
|
|
661
707
|
let installingMessage = "Starting installation...";
|
|
662
708
|
const serverStatus = serverStatuses[serverName] || { installedStatus: {} };
|
|
663
|
-
if (
|
|
664
|
-
const target =
|
|
709
|
+
if (window.selectedClients.length > 0) {
|
|
710
|
+
const target = window.selectedClients[0];
|
|
665
711
|
const msg = serverStatus.installedStatus?.[target]?.message;
|
|
666
712
|
if (msg) installingMessage = msg;
|
|
667
713
|
}
|
|
668
714
|
|
|
669
715
|
// Add requirements to update to serverInstallOptions if any
|
|
670
716
|
const serverInstallOptions = {
|
|
671
|
-
targetClients:
|
|
717
|
+
targetClients: window.selectedClients,
|
|
672
718
|
env: envVars,
|
|
673
719
|
args: args,
|
|
674
720
|
settings: pythonEnv ? { pythonEnv } : undefined
|
|
@@ -679,7 +725,9 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
679
725
|
serverInstallOptions.requirements = requirementsToUpdate;
|
|
680
726
|
}
|
|
681
727
|
|
|
682
|
-
|
|
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);
|
|
683
731
|
};
|
|
684
732
|
|
|
685
733
|
} catch (error) {
|
|
@@ -893,23 +941,35 @@ async function pollInstallStatus(categoryName, serverName, targets, interval = 2
|
|
|
893
941
|
|
|
894
942
|
// Function to handle client uninstallation for multiple targets
|
|
895
943
|
async function uninstallTools(categoryName, serverList, targets) {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
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');
|
|
902
950
|
return;
|
|
903
951
|
}
|
|
904
952
|
|
|
905
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
|
+
|
|
906
965
|
const response = await fetch(`/api/categories/${categoryName}/uninstall`, {
|
|
907
966
|
method: 'POST',
|
|
908
967
|
headers: { 'Content-Type': 'application/json' },
|
|
909
968
|
body: JSON.stringify({
|
|
910
|
-
|
|
969
|
+
serverList: serverList,
|
|
911
970
|
options: {
|
|
912
|
-
targets:
|
|
971
|
+
targets: selectedTargets,
|
|
972
|
+
removeData: true
|
|
913
973
|
}
|
|
914
974
|
})
|
|
915
975
|
});
|
|
@@ -924,10 +984,14 @@ async function uninstallTools(categoryName, serverList, targets) {
|
|
|
924
984
|
throw new Error(result.error || 'Uninstallation failed');
|
|
925
985
|
}
|
|
926
986
|
|
|
927
|
-
|
|
928
|
-
|
|
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 = [];
|
|
929
992
|
} catch (error) {
|
|
930
993
|
console.error('Error uninstalling tools:', error);
|
|
994
|
+
delayedAppendInstallLoadingMessage(`Error: ${error.message}`, true);
|
|
931
995
|
showToast(`Error uninstalling tools: ${error.message}`, 'error');
|
|
932
996
|
}
|
|
933
997
|
}
|
|
@@ -30,28 +30,26 @@ function showToast(message, type = 'success') {
|
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// Function to show a
|
|
34
|
-
function showConfirm(message) {
|
|
33
|
+
// Function to show a confirmation dialog using custom modal system
|
|
34
|
+
function showConfirm(title, message) {
|
|
35
35
|
return new Promise((resolve) => {
|
|
36
36
|
const modalId = `confirm-modal-${Date.now()}`;
|
|
37
37
|
const modalHtml = `
|
|
38
|
-
<div
|
|
39
|
-
<div class="modal-
|
|
40
|
-
<div class="modal-
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
</button>
|
|
54
|
-
</div>
|
|
38
|
+
<div id="${modalId}" class="modal">
|
|
39
|
+
<div class="modal-content" style="max-width: 400px; margin: 15% auto;">
|
|
40
|
+
<div class="modal-header">
|
|
41
|
+
<h3 class="text-lg font-semibold text-gray-700">${title}</h3>
|
|
42
|
+
</div>
|
|
43
|
+
<div style="margin: 1rem 0;">
|
|
44
|
+
${message}
|
|
45
|
+
</div>
|
|
46
|
+
<div style="display: flex; justify-content: flex-end; gap: 0.5rem; margin-top: 1rem;">
|
|
47
|
+
<button type="button" class="cancel-button px-4 py-2 text-gray-600 hover:text-gray-800 font-medium rounded-lg hover:bg-gray-100 transition-colors">
|
|
48
|
+
Cancel
|
|
49
|
+
</button>
|
|
50
|
+
<button type="button" class="confirm-button submit-button">
|
|
51
|
+
Confirm
|
|
52
|
+
</button>
|
|
55
53
|
</div>
|
|
56
54
|
</div>
|
|
57
55
|
</div>
|
|
@@ -60,29 +58,30 @@ function showConfirm(message) {
|
|
|
60
58
|
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
61
59
|
|
|
62
60
|
const modalElement = document.getElementById(modalId);
|
|
63
|
-
|
|
61
|
+
modalElement.style.display = 'block';
|
|
64
62
|
|
|
65
63
|
// Handle confirm button click
|
|
66
|
-
modalElement.querySelector('.confirm-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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);
|
|
64
|
+
modalElement.querySelector('.confirm-button').addEventListener('click', () => {
|
|
65
|
+
modalElement.style.display = 'none';
|
|
66
|
+
modalElement.remove();
|
|
67
|
+
resolve(true);
|
|
77
68
|
});
|
|
78
69
|
|
|
79
|
-
// Handle
|
|
80
|
-
modalElement.
|
|
70
|
+
// Handle cancel button click
|
|
71
|
+
modalElement.querySelector('.cancel-button').addEventListener('click', () => {
|
|
72
|
+
modalElement.style.display = 'none';
|
|
81
73
|
modalElement.remove();
|
|
82
74
|
resolve(false);
|
|
83
75
|
});
|
|
84
|
-
|
|
85
|
-
modal
|
|
76
|
+
|
|
77
|
+
// Handle click outside modal
|
|
78
|
+
modalElement.addEventListener('click', (event) => {
|
|
79
|
+
if (event.target === modalElement) {
|
|
80
|
+
modalElement.style.display = 'none';
|
|
81
|
+
modalElement.remove();
|
|
82
|
+
resolve(false);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
86
85
|
});
|
|
87
86
|
}
|
|
88
87
|
|
package/dist/web/server.js
CHANGED
|
@@ -110,13 +110,24 @@ app.post('/api/categories/:categoryName/uninstall', async (req, res) => {
|
|
|
110
110
|
try {
|
|
111
111
|
const { categoryName } = req.params;
|
|
112
112
|
const { serverList } = req.body;
|
|
113
|
-
if (!
|
|
113
|
+
if (!serverList || Object.keys(serverList).length === 0) {
|
|
114
114
|
return res.status(400).json({
|
|
115
115
|
success: false,
|
|
116
116
|
error: 'Invalid tool list provided'
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
|
-
const
|
|
119
|
+
const { options } = req.body;
|
|
120
|
+
if (!options?.targets || options.targets.length === 0) {
|
|
121
|
+
return res.status(400).json({
|
|
122
|
+
success: false,
|
|
123
|
+
error: 'No target clients specified'
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const results = await Promise.all(Object.entries(serverList).map(([serverName, serverOptions]) => serverService.uninstallMcpServer(categoryName, serverName, {
|
|
127
|
+
...serverOptions,
|
|
128
|
+
targets: options.targets,
|
|
129
|
+
removeData: options.removeData ?? serverOptions.removeData
|
|
130
|
+
})));
|
|
120
131
|
const { success, messages } = serverService.formatOperationResults(results);
|
|
121
132
|
const response = {
|
|
122
133
|
success,
|