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
|
@@ -6,19 +6,27 @@ export function createUninstallCommand(): Command {
|
|
|
6
6
|
.description('Uninstall specific MCP servers')
|
|
7
7
|
.requiredOption(
|
|
8
8
|
'--category <category>',
|
|
9
|
+
'Server category'
|
|
10
|
+
)
|
|
11
|
+
.requiredOption(
|
|
9
12
|
'--names <names>',
|
|
10
13
|
'Server names (semicolon separated)'
|
|
11
14
|
)
|
|
15
|
+
.requiredOption(
|
|
16
|
+
'--targets <targets>',
|
|
17
|
+
'Target clients to uninstall from (semicolon separated)'
|
|
18
|
+
)
|
|
12
19
|
.option(
|
|
13
20
|
'--remove-data',
|
|
14
21
|
'Remove all associated data',
|
|
15
|
-
|
|
22
|
+
true // Change default to true to ensure cleanup happens
|
|
16
23
|
)
|
|
17
24
|
.action(async (options) => {
|
|
18
25
|
try {
|
|
19
26
|
const serverNames = options.names.split(';').map((name: string) => name.trim());
|
|
20
27
|
|
|
21
|
-
|
|
28
|
+
const validNames = await serverService.validateServerName(options.category, serverNames);
|
|
29
|
+
if (!validNames) {
|
|
22
30
|
console.error('Invalid server names provided');
|
|
23
31
|
process.exit(1);
|
|
24
32
|
}
|
|
@@ -26,11 +34,13 @@ export function createUninstallCommand(): Command {
|
|
|
26
34
|
console.log(`Uninstalling servers: ${serverNames.join(', ')}`);
|
|
27
35
|
|
|
28
36
|
const results = await Promise.all(
|
|
29
|
-
serverNames.map((serverName: string) =>
|
|
30
|
-
|
|
37
|
+
serverNames.map((serverName: string) => {
|
|
38
|
+
const targets = options.targets ? options.targets.split(';').map((t: string) => t.trim()) : [];
|
|
39
|
+
return serverService.uninstallMcpServer(options.category, serverName, {
|
|
31
40
|
removeData: options.removeData,
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
targets: targets
|
|
42
|
+
});
|
|
43
|
+
})
|
|
34
44
|
);
|
|
35
45
|
|
|
36
46
|
const { success, messages } = serverService.formatOperationResults(results);
|
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { exec } from 'child_process';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import { GITHUB_REPO, LOCAL_FEEDS_DIR, SETTINGS_DIR } from './constants.js';
|
|
6
|
+
import { GITHUB_REPO, LOCAL_FEEDS_DIR, SETTINGS_DIR, SUPPORTED_CLIENTS } from './constants.js';
|
|
7
7
|
import { Logger } from '../utils/logger.js';
|
|
8
8
|
import { checkGithubAuth } from '../utils/githubAuth.js';
|
|
9
9
|
import {
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
InstallationStatus,
|
|
14
14
|
RequirementStatus,
|
|
15
15
|
MCPServerStatus,
|
|
16
|
-
OperationStatus
|
|
16
|
+
OperationStatus,
|
|
17
|
+
ClientSettings
|
|
17
18
|
} from './types.js';
|
|
18
19
|
import { ConfigurationLoader } from './ConfigurationLoader.js';
|
|
19
20
|
|
|
@@ -356,6 +357,74 @@ export class ConfigurationProvider {
|
|
|
356
357
|
await this.loadClientMCPSettings();
|
|
357
358
|
});
|
|
358
359
|
}
|
|
360
|
+
|
|
361
|
+
async removeServerFromClientMCPSettings(serverName: string, target?: string): Promise<void> {
|
|
362
|
+
return await this.withLock(async () => {
|
|
363
|
+
// Load utils in async context to avoid circular dependencies
|
|
364
|
+
const { readJsonFile, writeJsonFile } = await import('../utils/clientUtils.js');
|
|
365
|
+
|
|
366
|
+
// Filter clients if target is specified
|
|
367
|
+
const clientEntries = Object.entries(SUPPORTED_CLIENTS as Record<string, ClientSettings>);
|
|
368
|
+
const targetClients = target
|
|
369
|
+
? clientEntries.filter(([clientName]) => clientName === target)
|
|
370
|
+
: clientEntries;
|
|
371
|
+
|
|
372
|
+
for (const [clientName, clientSettings] of targetClients) {
|
|
373
|
+
const settingPath = process.env.CODE_INSIDERS
|
|
374
|
+
? clientSettings.codeInsiderSettingPath
|
|
375
|
+
: clientSettings.codeSettingPath;
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
const content = await readJsonFile(settingPath, true);
|
|
379
|
+
let modified = false;
|
|
380
|
+
|
|
381
|
+
// Handle GitHub Copilot's different structure
|
|
382
|
+
if (clientName === 'GithubCopilot' && content.mcp?.servers?.[serverName]) {
|
|
383
|
+
delete content.mcp.servers[serverName];
|
|
384
|
+
modified = true;
|
|
385
|
+
} else if (content.mcpServers?.[serverName]) {
|
|
386
|
+
delete content.mcpServers[serverName];
|
|
387
|
+
modified = true;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Only write if we actually modified the content
|
|
391
|
+
if (modified) {
|
|
392
|
+
await writeJsonFile(settingPath, content);
|
|
393
|
+
Logger.debug(`Removed server ${serverName} from client ${clientName} settings`);
|
|
394
|
+
}
|
|
395
|
+
} catch (error) {
|
|
396
|
+
Logger.error(`Failed to remove server ${serverName} from client ${clientName} settings:`, error);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Also update our in-memory configuration
|
|
401
|
+
if (this.configuration.clientMCPSettings) {
|
|
402
|
+
if (target) {
|
|
403
|
+
// Only update settings for the target client
|
|
404
|
+
const clientSettings = this.configuration.clientMCPSettings[target];
|
|
405
|
+
if (clientSettings) {
|
|
406
|
+
if (clientSettings.mcpServers?.[serverName]) {
|
|
407
|
+
delete clientSettings.mcpServers[serverName];
|
|
408
|
+
}
|
|
409
|
+
if (clientSettings.servers?.[serverName]) { // For GitHub Copilot
|
|
410
|
+
delete clientSettings.servers[serverName];
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
// Update all clients if no target specified
|
|
415
|
+
for (const clientSettings of Object.values(this.configuration.clientMCPSettings)) {
|
|
416
|
+
if (clientSettings.mcpServers?.[serverName]) {
|
|
417
|
+
delete clientSettings.mcpServers[serverName];
|
|
418
|
+
}
|
|
419
|
+
if (clientSettings.servers?.[serverName]) { // For GitHub Copilot
|
|
420
|
+
delete clientSettings.servers[serverName];
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
await this.saveConfiguration();
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
}
|
|
359
428
|
}
|
|
360
429
|
|
|
361
430
|
// Export a singleton instance
|
package/src/core/MCPManager.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
ServerCategoryListOptions,
|
|
10
10
|
ServerOperationResult,
|
|
11
11
|
ServerUninstallOptions,
|
|
12
|
+
InstallationStatus
|
|
12
13
|
} from './types.js';
|
|
13
14
|
import path from 'path';
|
|
14
15
|
|
|
@@ -93,18 +94,44 @@ export class MCPManager extends EventEmitter {
|
|
|
93
94
|
};
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
const { targets = [], removeData = false } = options;
|
|
98
|
+
|
|
99
|
+
// Clear installation status for specified targets
|
|
100
|
+
const currentStatus: InstallationStatus = serverCategory.installationStatus || {
|
|
101
|
+
requirementsStatus: {},
|
|
102
|
+
serversStatus: {},
|
|
103
|
+
lastUpdated: new Date().toISOString()
|
|
104
|
+
};
|
|
105
|
+
const serversStatus = currentStatus.serversStatus || {};
|
|
106
|
+
const serverStatus = serversStatus[serverName] || { installedStatus: {}, name: serverName };
|
|
107
|
+
|
|
108
|
+
// Only reset installedStatus for specified targets
|
|
109
|
+
for (const target of targets) {
|
|
110
|
+
if (serverStatus.installedStatus) {
|
|
111
|
+
delete serverStatus.installedStatus[target];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (removeData) {
|
|
115
|
+
for (const target of targets) {
|
|
116
|
+
await this.configProvider.removeServerFromClientMCPSettings(serverName, target);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Update server status
|
|
121
|
+
serversStatus[serverName] = serverStatus;
|
|
122
|
+
|
|
123
|
+
// Update status keeping requirements
|
|
97
124
|
await this.configProvider.updateInstallationStatus(
|
|
98
125
|
categoryName,
|
|
99
|
-
{},
|
|
100
|
-
|
|
126
|
+
currentStatus.requirementsStatus || {},
|
|
127
|
+
serversStatus
|
|
101
128
|
);
|
|
102
129
|
|
|
103
|
-
this.emit(MCPEvent.SERVER_UNINSTALLED, { serverName });
|
|
130
|
+
this.emit(MCPEvent.SERVER_UNINSTALLED, { serverName, targets });
|
|
104
131
|
|
|
105
132
|
return {
|
|
106
133
|
success: true,
|
|
107
|
-
message: `Successfully uninstalled ${serverName}`,
|
|
134
|
+
message: `Successfully uninstalled ${serverName} from ${targets.join(', ')}`,
|
|
108
135
|
};
|
|
109
136
|
} catch (error) {
|
|
110
137
|
return {
|
package/src/core/constants.ts
CHANGED
|
@@ -58,8 +58,18 @@ const CODE_INSIDER_STRORAGE_DIR = (() => {
|
|
|
58
58
|
* Value: Client-specific settings or configuration details.
|
|
59
59
|
* TODO: Define actual client settings structure.
|
|
60
60
|
*/
|
|
61
|
-
export const SUPPORTED_CLIENTS: Record<string,
|
|
62
|
-
|
|
61
|
+
export const SUPPORTED_CLIENTS: Record<string, {
|
|
62
|
+
extension: {
|
|
63
|
+
extensionId: string;
|
|
64
|
+
leastVersion?: string;
|
|
65
|
+
repository?: string;
|
|
66
|
+
assetName?: string;
|
|
67
|
+
private?: boolean;
|
|
68
|
+
};
|
|
69
|
+
codeSettingPath: string;
|
|
70
|
+
codeInsiderSettingPath: string;
|
|
71
|
+
}> = {
|
|
72
|
+
'MSRooCode': {
|
|
63
73
|
|
|
64
74
|
extension: {
|
|
65
75
|
extensionId: 'microsoftai.ms-roo-cline',
|
|
@@ -140,6 +140,9 @@ export class ExtensionInstaller {
|
|
|
140
140
|
} else {
|
|
141
141
|
// Install private extension from GitHub release using latest version
|
|
142
142
|
try {
|
|
143
|
+
if (!repository || !assetName) {
|
|
144
|
+
throw new Error(`Missing repository or assetName for private extension ${extensionId}`);
|
|
145
|
+
}
|
|
143
146
|
const { resolvedPath } = await handleGitHubRelease(
|
|
144
147
|
{ name: extensionId, version: 'latest', type: 'extension' },
|
|
145
148
|
{ repository, assetName }
|
package/src/core/types.ts
CHANGED
|
@@ -84,6 +84,7 @@ export interface UpdateRequirementOptions {
|
|
|
84
84
|
|
|
85
85
|
export interface ServerUninstallOptions {
|
|
86
86
|
removeData?: boolean;
|
|
87
|
+
targets?: string[]; // List of client targets to uninstall from
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
// Types related to server feed configuration (e.g., ai-coder-tools.json)
|
|
@@ -150,6 +151,11 @@ export interface FeedConfiguration {
|
|
|
150
151
|
mcpServers: McpConfig[];
|
|
151
152
|
}
|
|
152
153
|
|
|
154
|
+
export interface ClientSettings {
|
|
155
|
+
codeSettingPath: string;
|
|
156
|
+
codeInsiderSettingPath: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
153
159
|
// Events that can be emitted by the SDK
|
|
154
160
|
export enum MCPEvent {
|
|
155
161
|
SERVER_INSTALLED = 'server:installed',
|
|
@@ -161,7 +167,7 @@ export enum MCPEvent {
|
|
|
161
167
|
|
|
162
168
|
export interface MCPEventData {
|
|
163
169
|
[MCPEvent.SERVER_INSTALLED]: { server: MCPServerCategory };
|
|
164
|
-
[MCPEvent.SERVER_UNINSTALLED]: { serverName: string };
|
|
170
|
+
[MCPEvent.SERVER_UNINSTALLED]: { serverName: string; targets?: string[] };
|
|
165
171
|
[MCPEvent.SERVER_STARTED]: { server: MCPServerCategory };
|
|
166
172
|
[MCPEvent.SERVER_STOPPED]: { serverName: string };
|
|
167
173
|
[MCPEvent.CONFIG_CHANGED]: { configuration: MCPConfiguration };
|
|
@@ -147,19 +147,34 @@ export class ServerService {
|
|
|
147
147
|
* Installs a specific mcp tool for a server.
|
|
148
148
|
* TODO: This might require enhancing MCPManager to handle category-specific installs.
|
|
149
149
|
*/
|
|
150
|
+
/**
|
|
151
|
+
* Uninstall MCP server from specified client targets
|
|
152
|
+
* @param category The server category
|
|
153
|
+
* @param serverName The server name to uninstall
|
|
154
|
+
* @param options Uninstall options including target clients and data removal flags
|
|
155
|
+
*/
|
|
150
156
|
async uninstallMcpServer(
|
|
151
157
|
category: string,
|
|
152
158
|
serverName: string,
|
|
153
|
-
options: ServerUninstallOptions = {}
|
|
159
|
+
options: ServerUninstallOptions = {}
|
|
154
160
|
): Promise<ServerOperationResult> {
|
|
155
|
-
|
|
161
|
+
Logger.debug(`Uninstalling MCP server: ${JSON.stringify({ category, serverName, options })}`);
|
|
162
|
+
try {
|
|
163
|
+
const result = await mcpManager.uninstallServer(category, serverName, options);
|
|
164
|
+
Logger.debug(`Uninstallation result: ${JSON.stringify(result)}`);
|
|
165
|
+
return result;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
Logger.error(`Failed to uninstall MCP server: ${serverName}`, error);
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
156
170
|
}
|
|
157
171
|
|
|
158
172
|
/**
|
|
159
173
|
* Validates server names
|
|
160
174
|
*/
|
|
161
|
-
async validateServerName(category: string,
|
|
162
|
-
|
|
175
|
+
async validateServerName(category: string, names: string | string[]): Promise<boolean> {
|
|
176
|
+
const serverNames = Array.isArray(names) ? names : [names];
|
|
177
|
+
Logger.debug(`Validating server names: ${JSON.stringify({ category, names: serverNames })}`);
|
|
163
178
|
|
|
164
179
|
// Check if category exists in feeds
|
|
165
180
|
const feedConfig = await mcpManager.getFeedConfiguration(category);
|
|
@@ -168,10 +183,13 @@ export class ServerService {
|
|
|
168
183
|
return false;
|
|
169
184
|
}
|
|
170
185
|
|
|
171
|
-
// Check if
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
186
|
+
// Check if all servers exist in the category's mcpServers
|
|
187
|
+
const invalidServers = serverNames.filter(name =>
|
|
188
|
+
!feedConfig.mcpServers.some(server => server.name === name)
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
if (invalidServers.length > 0) {
|
|
192
|
+
Logger.debug(`Validation failed: Servers "${invalidServers.join(', ')}" not found in category "${category}"`);
|
|
175
193
|
return false;
|
|
176
194
|
}
|
|
177
195
|
|
package/src/utils/osUtils.ts
CHANGED
|
@@ -273,7 +273,7 @@ export function getPythonPackagePath(pythonExecutablePath: string): string {
|
|
|
273
273
|
// Virtual environment structure on Windows: <venv>/Scripts/python.exe
|
|
274
274
|
const venvRoot = path.dirname(dir);
|
|
275
275
|
return path.join(venvRoot, 'Lib', 'site-packages');
|
|
276
|
-
} else
|
|
276
|
+
} else {
|
|
277
277
|
// System Python or Conda on Windows
|
|
278
278
|
return path.join(dir, 'Lib', 'site-packages');
|
|
279
279
|
}
|
|
@@ -160,6 +160,15 @@ body {
|
|
|
160
160
|
transition: all 0.2s ease;
|
|
161
161
|
cursor: pointer;
|
|
162
162
|
user-select: none;
|
|
163
|
+
gap: 0.5rem;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* Client actions container */
|
|
167
|
+
.client-actions {
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: center;
|
|
170
|
+
gap: 0.5rem;
|
|
171
|
+
margin-left: auto;
|
|
163
172
|
}
|
|
164
173
|
|
|
165
174
|
.client-item:hover {
|
|
@@ -180,6 +189,14 @@ body {
|
|
|
180
189
|
gap: 0.75rem;
|
|
181
190
|
}
|
|
182
191
|
|
|
192
|
+
/* Status container */
|
|
193
|
+
.status-container {
|
|
194
|
+
display: flex;
|
|
195
|
+
align-items: center;
|
|
196
|
+
gap: 0.5rem;
|
|
197
|
+
margin-left: auto;
|
|
198
|
+
}
|
|
199
|
+
|
|
183
200
|
/* Status badges */
|
|
184
201
|
.status-badge {
|
|
185
202
|
display: inline-flex;
|
|
@@ -227,6 +244,31 @@ body {
|
|
|
227
244
|
background-color: #fef3c7;
|
|
228
245
|
}
|
|
229
246
|
|
|
247
|
+
/* Uninstall button styling */
|
|
248
|
+
.uninstall-btn {
|
|
249
|
+
display: inline-flex;
|
|
250
|
+
align-items: center;
|
|
251
|
+
justify-content: center;
|
|
252
|
+
width: 28px;
|
|
253
|
+
height: 28px;
|
|
254
|
+
border-radius: 6px;
|
|
255
|
+
border: 1px solid transparent;
|
|
256
|
+
background: transparent;
|
|
257
|
+
cursor: pointer;
|
|
258
|
+
transition: all 0.2s ease;
|
|
259
|
+
padding: 0;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.uninstall-btn:hover {
|
|
263
|
+
background-color: #fee2e2;
|
|
264
|
+
border-color: #ef4444;
|
|
265
|
+
transform: scale(1.05);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.uninstall-btn i {
|
|
269
|
+
font-size: 1.25rem;
|
|
270
|
+
}
|
|
271
|
+
|
|
230
272
|
/* Environment variables section */
|
|
231
273
|
#modalEnvInputs input {
|
|
232
274
|
width: 100%;
|
|
@@ -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
|
}
|