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
|
@@ -3,6 +3,7 @@ import { SUPPORTED_CLIENTS } from '../constants.js';
|
|
|
3
3
|
import { resolveNpmModulePath, readJsonFile, writeJsonFile } from '../../utils/clientUtils.js';
|
|
4
4
|
import { exec } from 'child_process';
|
|
5
5
|
import { promisify } from 'util';
|
|
6
|
+
import { Logger } from '../../utils/logger.js';
|
|
6
7
|
export class ClientInstaller {
|
|
7
8
|
categoryName;
|
|
8
9
|
serverName;
|
|
@@ -34,6 +35,28 @@ export class ClientInstaller {
|
|
|
34
35
|
return 'C:\\Program Files\\nodejs';
|
|
35
36
|
}
|
|
36
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Check if a command is available on the system
|
|
40
|
+
* @param command The command to check
|
|
41
|
+
* @returns True if the command is available, false otherwise
|
|
42
|
+
*/
|
|
43
|
+
async isCommandAvailable(command) {
|
|
44
|
+
const execAsync = promisify(exec);
|
|
45
|
+
try {
|
|
46
|
+
if (process.platform === 'win32') {
|
|
47
|
+
// Windows-specific command check
|
|
48
|
+
await execAsync(`where ${command}`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// Unix-like systems
|
|
52
|
+
await execAsync(`which ${command}`);
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
37
60
|
async installClient(clientName, env) {
|
|
38
61
|
// Check if client is supported
|
|
39
62
|
if (!SUPPORTED_CLIENTS[clientName]) {
|
|
@@ -174,15 +197,29 @@ export class ClientInstaller {
|
|
|
174
197
|
async installClientConfig(clientName, env, serverConfig, feedConfig) {
|
|
175
198
|
try {
|
|
176
199
|
if (!SUPPORTED_CLIENTS[clientName]) {
|
|
200
|
+
Logger.debug(`Client ${clientName} is not supported.`);
|
|
177
201
|
return { success: false, message: `Unsupported client: ${clientName}` };
|
|
178
202
|
}
|
|
179
203
|
const clientSettings = SUPPORTED_CLIENTS[clientName];
|
|
180
|
-
//
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
204
|
+
// Get both setting paths for VS Code and VS Code Insiders
|
|
205
|
+
const regularSettingPath = clientSettings.codeSettingPath;
|
|
206
|
+
const insidersSettingPath = clientSettings.codeInsiderSettingPath;
|
|
207
|
+
Logger.debug(`Starting installation of ${this.serverName} for client ${clientName}`);
|
|
208
|
+
Logger.debug(`VS Code settings path configured as: ${regularSettingPath}`);
|
|
209
|
+
Logger.debug(`VS Code Insiders settings path configured as: ${insidersSettingPath}`);
|
|
210
|
+
// Check if VS Code and VS Code Insiders are installed
|
|
211
|
+
const isVSCodeInstalled = await this.isCommandAvailable('code');
|
|
212
|
+
const isVSCodeInsidersInstalled = await this.isCommandAvailable('code-insiders');
|
|
213
|
+
Logger.debug(isVSCodeInstalled ? 'VS Code detected on system' : 'VS Code not detected on system');
|
|
214
|
+
Logger.debug(isVSCodeInsidersInstalled ? 'VS Code Insiders detected on system' : 'VS Code Insiders not detected on system');
|
|
215
|
+
Logger.debug(`VS Code Insiders installed: ${isVSCodeInsidersInstalled}`);
|
|
216
|
+
// If neither is installed, we can't proceed
|
|
217
|
+
if (!isVSCodeInstalled && !isVSCodeInsidersInstalled) {
|
|
218
|
+
Logger.debug('No VS Code installations detected on system. Cannot update settings.');
|
|
219
|
+
return {
|
|
220
|
+
success: false,
|
|
221
|
+
message: `Neither VS Code nor VS Code Insiders are installed on this system. Cannot update settings for client: ${clientName}. Please install VS Code or VS Code Insiders and try again.`
|
|
222
|
+
};
|
|
186
223
|
}
|
|
187
224
|
// Clone the installation configuration to make modifications
|
|
188
225
|
const installConfig = JSON.parse(JSON.stringify(serverConfig.installation));
|
|
@@ -203,28 +240,128 @@ export class ClientInstaller {
|
|
|
203
240
|
if (env) {
|
|
204
241
|
Object.assign(installConfig.env, env);
|
|
205
242
|
}
|
|
206
|
-
//
|
|
243
|
+
// Keep track of success for both installations
|
|
244
|
+
let regularSuccess = false;
|
|
245
|
+
let insidersSuccess = false;
|
|
246
|
+
let errorMessages = [];
|
|
247
|
+
// Update client-specific settings for both VS Code and VS Code Insiders
|
|
207
248
|
if (clientName === 'MSRooCode' || clientName === 'Cline') {
|
|
208
|
-
|
|
249
|
+
// Update VS Code settings if VS Code is installed
|
|
250
|
+
if (isVSCodeInstalled) {
|
|
251
|
+
try {
|
|
252
|
+
Logger.debug(`Updating VS Code settings for ${clientName} at path: ${regularSettingPath}`);
|
|
253
|
+
await this.updateClineOrMSRooSettings(regularSettingPath, this.serverName, installConfig, clientName);
|
|
254
|
+
regularSuccess = true;
|
|
255
|
+
Logger.debug(`Settings successfully updated to ${regularSettingPath} for ${clientName} (VS Code)`);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
const errorMsg = `Error updating VS Code settings: ${error instanceof Error ? error.message : String(error)}`;
|
|
259
|
+
errorMessages.push(errorMsg);
|
|
260
|
+
console.error(errorMsg);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Update VS Code Insiders settings if VS Code Insiders is installed
|
|
264
|
+
if (isVSCodeInsidersInstalled) {
|
|
265
|
+
try {
|
|
266
|
+
Logger.debug(`Updating VS Code Insiders settings for ${clientName} at path: ${insidersSettingPath}`);
|
|
267
|
+
await this.updateClineOrMSRooSettings(insidersSettingPath, this.serverName, installConfig, clientName);
|
|
268
|
+
insidersSuccess = true;
|
|
269
|
+
Logger.debug(`Settings successfully updated to ${insidersSettingPath} for ${clientName} (VS Code Insiders)`);
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
const errorMsg = `Error updating VS Code Insiders settings: ${error instanceof Error ? error.message : String(error)}`;
|
|
273
|
+
errorMessages.push(errorMsg);
|
|
274
|
+
console.error(errorMsg);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
209
277
|
}
|
|
210
278
|
else if (clientName === 'GithubCopilot') {
|
|
211
|
-
|
|
279
|
+
// Update VS Code settings if VS Code is installed
|
|
280
|
+
if (isVSCodeInstalled) {
|
|
281
|
+
try {
|
|
282
|
+
Logger.debug(`Updating VS Code settings for ${clientName} at path: ${regularSettingPath}`);
|
|
283
|
+
await this.updateGithubCopilotSettings(regularSettingPath, this.serverName, installConfig);
|
|
284
|
+
regularSuccess = true;
|
|
285
|
+
Logger.debug(`Settings successfully updated to ${regularSettingPath} for ${clientName} (VS Code)`);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
const errorMsg = `Error updating VS Code settings: ${error instanceof Error ? error.message : String(error)}`;
|
|
289
|
+
errorMessages.push(errorMsg);
|
|
290
|
+
console.error(errorMsg);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Update VS Code Insiders settings if VS Code Insiders is installed
|
|
294
|
+
if (isVSCodeInsidersInstalled) {
|
|
295
|
+
try {
|
|
296
|
+
Logger.debug(`Updating VS Code Insiders settings for ${clientName} at path: ${insidersSettingPath}`);
|
|
297
|
+
await this.updateGithubCopilotSettings(insidersSettingPath, this.serverName, installConfig);
|
|
298
|
+
insidersSuccess = true;
|
|
299
|
+
Logger.debug(`Settings successfully updated to ${insidersSettingPath} for ${clientName} (VS Code Insiders)`);
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
const errorMsg = `Error updating VS Code Insiders settings: ${error instanceof Error ? error.message : String(error)}`;
|
|
303
|
+
errorMessages.push(errorMsg);
|
|
304
|
+
console.error(errorMsg);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
212
307
|
}
|
|
213
308
|
else {
|
|
309
|
+
Logger.debug(`No implementation exists for updating settings for client: ${clientName}`);
|
|
214
310
|
return {
|
|
215
311
|
success: false,
|
|
216
312
|
message: `Client ${clientName} is supported but no implementation exists for updating its settings`
|
|
217
313
|
};
|
|
218
314
|
}
|
|
315
|
+
// Determine overall success status and message
|
|
316
|
+
const overallSuccess = regularSuccess || insidersSuccess;
|
|
317
|
+
let message = '';
|
|
318
|
+
if (overallSuccess) {
|
|
319
|
+
const successDetails = [];
|
|
320
|
+
if (regularSuccess)
|
|
321
|
+
successDetails.push('VS Code');
|
|
322
|
+
if (insidersSuccess)
|
|
323
|
+
successDetails.push('VS Code Insiders');
|
|
324
|
+
// Create a more compact success message with specific paths
|
|
325
|
+
const pathDetails = [];
|
|
326
|
+
if (regularSuccess) {
|
|
327
|
+
pathDetails.push(`\n[VS Code]: ${regularSettingPath}`);
|
|
328
|
+
}
|
|
329
|
+
if (insidersSuccess) {
|
|
330
|
+
pathDetails.push(`\n[VS Code Insiders]: ${insidersSettingPath}`);
|
|
331
|
+
}
|
|
332
|
+
message = `Successfully installed ${this.serverName} for client: ${clientName}. ${pathDetails.join(' ')}`;
|
|
333
|
+
// Add partial failure information if applicable
|
|
334
|
+
if (errorMessages.length > 0) {
|
|
335
|
+
message += `\nHowever, some errors occurred:\n${errorMessages.join('\n- ')}`;
|
|
336
|
+
}
|
|
337
|
+
Logger.debug(`Installation complete: ${message}`);
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
// Create a more detailed failure message that includes the detection results
|
|
341
|
+
const detectionInfo = [];
|
|
342
|
+
if (!isVSCodeInstalled)
|
|
343
|
+
detectionInfo.push('VS Code not detected');
|
|
344
|
+
if (!isVSCodeInsidersInstalled)
|
|
345
|
+
detectionInfo.push('VS Code Insiders not detected');
|
|
346
|
+
if (errorMessages.length > 0) {
|
|
347
|
+
message = `Failed to install ${this.serverName} for client: ${clientName}.\nDetection status: [${detectionInfo.join(', ')}].\nErrors:\n- ${errorMessages.join('\n- ')}`;
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
message = `Failed to install ${this.serverName} for client: ${clientName}.\nDetection status: [${detectionInfo.join(', ')}]`;
|
|
351
|
+
}
|
|
352
|
+
console.error(`Installation failed: ${message}`);
|
|
353
|
+
}
|
|
219
354
|
return {
|
|
220
|
-
success:
|
|
221
|
-
message:
|
|
355
|
+
success: overallSuccess,
|
|
356
|
+
message: message
|
|
222
357
|
};
|
|
223
358
|
}
|
|
224
359
|
catch (error) {
|
|
360
|
+
const errorMsg = `Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`;
|
|
361
|
+
console.error(errorMsg);
|
|
225
362
|
return {
|
|
226
363
|
success: false,
|
|
227
|
-
message:
|
|
364
|
+
message: errorMsg
|
|
228
365
|
};
|
|
229
366
|
}
|
|
230
367
|
}
|
|
@@ -66,11 +66,6 @@ export class GeneralInstaller extends BaseInstaller {
|
|
|
66
66
|
installed: true,
|
|
67
67
|
version: requirement.version,
|
|
68
68
|
inProgress: false,
|
|
69
|
-
// Store installation path in a way that it can be retrieved later if needed
|
|
70
|
-
updateInfo: {
|
|
71
|
-
available: false,
|
|
72
|
-
installPath
|
|
73
|
-
}
|
|
74
69
|
};
|
|
75
70
|
}
|
|
76
71
|
catch (error) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseInstaller } from './BaseInstaller.js';
|
|
2
|
+
import { compareVersions } from '../../utils/versionUtils.js';
|
|
2
3
|
/**
|
|
3
4
|
* Installer implementation for NPM packages
|
|
4
5
|
*/
|
|
@@ -47,7 +48,7 @@ export class NpmInstaller extends BaseInstaller {
|
|
|
47
48
|
async install(requirement) {
|
|
48
49
|
try {
|
|
49
50
|
const status = await this.checkInstallation(requirement);
|
|
50
|
-
if (status.installed) {
|
|
51
|
+
if (status.installed && status.version && compareVersions(status.version, requirement.version) === 0) {
|
|
51
52
|
return status;
|
|
52
53
|
}
|
|
53
54
|
let resolvedVersion = requirement.version;
|
package/dist/core/types.d.ts
CHANGED
|
@@ -13,14 +13,9 @@ export interface RequirementStatus {
|
|
|
13
13
|
availableUpdate?: {
|
|
14
14
|
version: string;
|
|
15
15
|
message: string;
|
|
16
|
-
checkTime: string;
|
|
17
16
|
};
|
|
17
|
+
lastCheckTime?: string;
|
|
18
18
|
operationStatus?: OperationStatus;
|
|
19
|
-
updateInfo?: {
|
|
20
|
-
available: boolean;
|
|
21
|
-
latestVersion?: string;
|
|
22
|
-
[key: string]: any;
|
|
23
|
-
} | null;
|
|
24
19
|
}
|
|
25
20
|
export interface MCPServerStatus {
|
|
26
21
|
installedStatus: Record<string, OperationStatus>;
|
|
@@ -62,11 +57,17 @@ export interface ServerOperationResult {
|
|
|
62
57
|
export interface MCPConfiguration {
|
|
63
58
|
localServerCategories: MCPServerCategory[];
|
|
64
59
|
feeds: Record<string, FeedConfiguration>;
|
|
60
|
+
clientMCPSettings?: Record<string, Record<string, any>>;
|
|
65
61
|
}
|
|
66
62
|
export interface ServerInstallOptions {
|
|
67
63
|
force?: boolean;
|
|
68
64
|
env?: Record<string, string>;
|
|
69
65
|
targetClients?: string[];
|
|
66
|
+
requirements?: RequirementConfig[];
|
|
67
|
+
}
|
|
68
|
+
export interface UpdateRequirementOptions {
|
|
69
|
+
requirementName: string;
|
|
70
|
+
updateVersion: string;
|
|
70
71
|
}
|
|
71
72
|
export interface ServerUninstallOptions {
|
|
72
73
|
removeData?: boolean;
|
|
@@ -2,6 +2,7 @@ import path from 'path';
|
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import { Logger } from '../utils/logger.js';
|
|
4
4
|
import { mcpManager } from '../core/MCPManager.js';
|
|
5
|
+
import { UPDATE_CHECK_INTERVAL_MS } from '../core/constants.js';
|
|
5
6
|
import { updateCheckTracker } from '../utils/UpdateCheckTracker.js';
|
|
6
7
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
8
|
/**
|
|
@@ -59,6 +60,16 @@ export class ServerService {
|
|
|
59
60
|
const currentStatus = serverCategory.installationStatus?.requirementsStatus[requirement.name];
|
|
60
61
|
if (!currentStatus)
|
|
61
62
|
continue;
|
|
63
|
+
// Skip update check if last check was less than UPDATE_CHECK_INTERVAL_MS ago
|
|
64
|
+
if (currentStatus.lastCheckTime) {
|
|
65
|
+
const lastCheckTime = new Date(currentStatus.lastCheckTime);
|
|
66
|
+
const currentTime = new Date();
|
|
67
|
+
const timeSinceLastCheck = currentTime.getTime() - lastCheckTime.getTime();
|
|
68
|
+
if (timeSinceLastCheck < UPDATE_CHECK_INTERVAL_MS) {
|
|
69
|
+
Logger.debug(`Skipping update check for ${requirement.name}, last check was ${Math.round(timeSinceLastCheck / 1000)} seconds ago`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
62
73
|
// Check for updates
|
|
63
74
|
const updatedStatus = await requirementService.checkRequirementForUpdates(requirement);
|
|
64
75
|
// If update information is found, update the configuration
|
|
@@ -69,6 +80,11 @@ export class ServerService {
|
|
|
69
80
|
serverCategory.installationStatus.requirementsStatus[requirement.name] = updatedStatus;
|
|
70
81
|
}
|
|
71
82
|
}
|
|
83
|
+
currentStatus.lastCheckTime = new Date().toISOString();
|
|
84
|
+
await configProvider.updateRequirementStatus(serverCategory.name, requirement.name, currentStatus);
|
|
85
|
+
if (serverCategory.installationStatus?.requirementsStatus) {
|
|
86
|
+
serverCategory.installationStatus.requirementsStatus[requirement.name] = updatedStatus;
|
|
87
|
+
}
|
|
72
88
|
}
|
|
73
89
|
}
|
|
74
90
|
}
|
|
@@ -84,7 +84,9 @@ export async function readJsonFile(filePath, createIfNotExist = false) {
|
|
|
84
84
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
85
85
|
}
|
|
86
86
|
const content = await fs.readFile(filePath, 'utf8');
|
|
87
|
-
|
|
87
|
+
// Remove trailing commas from JSON content
|
|
88
|
+
const sanitizedContent = content.replace(/,(\s*[}\]])/g, '$1');
|
|
89
|
+
return JSON.parse(sanitizedContent);
|
|
88
90
|
}
|
|
89
91
|
catch (error) {
|
|
90
92
|
if (error.code === 'ENOENT' && createIfNotExist) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for version comparison and management
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Compare two semantic version strings
|
|
6
|
+
* @param v1 First version
|
|
7
|
+
* @param v2 Second version
|
|
8
|
+
* @returns -1 if v1 < v2, 0 if v1 = v2, 1 if v1 > v2
|
|
9
|
+
* (or more specifically, a negative number if v1 < v2,
|
|
10
|
+
* a positive number if v1 > v2, 0 if equal)
|
|
11
|
+
*/
|
|
12
|
+
export declare function compareVersions(v1: string, v2: string): number;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for version comparison and management
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Compare two semantic version strings
|
|
6
|
+
* @param v1 First version
|
|
7
|
+
* @param v2 Second version
|
|
8
|
+
* @returns -1 if v1 < v2, 0 if v1 = v2, 1 if v1 > v2
|
|
9
|
+
* (or more specifically, a negative number if v1 < v2,
|
|
10
|
+
* a positive number if v1 > v2, 0 if equal)
|
|
11
|
+
*/
|
|
12
|
+
export function compareVersions(v1, v2) {
|
|
13
|
+
const v1Parts = v1.split('.').map(Number);
|
|
14
|
+
const v2Parts = v2.split('.').map(Number);
|
|
15
|
+
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
|
|
16
|
+
const v1Part = i < v1Parts.length ? v1Parts[i] : 0;
|
|
17
|
+
const v2Part = i < v2Parts.length ? v2Parts[i] : 0;
|
|
18
|
+
if (v1Part !== v2Part) {
|
|
19
|
+
// This returns the actual difference, which is:
|
|
20
|
+
// negative if v1Part < v2Part, positive if v1Part > v2Part
|
|
21
|
+
return v1Part - v2Part;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=versionUtils.js.map
|
|
@@ -34,6 +34,12 @@ body {
|
|
|
34
34
|
min-height: 120px;
|
|
35
35
|
opacity: 1 !important;
|
|
36
36
|
box-shadow: 0 0 16px #3498db;
|
|
37
|
+
position: relative;
|
|
38
|
+
padding-top: 24px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#installLoadingModal .modal-close-button {
|
|
42
|
+
z-index: 3200 !important;
|
|
37
43
|
}
|
|
38
44
|
/* Loading modal always on top */
|
|
39
45
|
#installLoadingModal {
|
|
@@ -61,8 +67,37 @@ body {
|
|
|
61
67
|
transition: all 0.3s ease-out;
|
|
62
68
|
animation: slideIn 0.3s ease-out;
|
|
63
69
|
}
|
|
64
|
-
|
|
65
70
|
/* Close button */
|
|
71
|
+
.modal-close-button {
|
|
72
|
+
position: absolute;
|
|
73
|
+
right: 12px;
|
|
74
|
+
top: 12px;
|
|
75
|
+
width: 32px;
|
|
76
|
+
height: 32px;
|
|
77
|
+
border-radius: 50%;
|
|
78
|
+
background: #ffffff;
|
|
79
|
+
border: 2px solid #3498db;
|
|
80
|
+
color: #3498db;
|
|
81
|
+
font-size: 22px;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
transition: all 0.2s ease;
|
|
87
|
+
z-index: 10;
|
|
88
|
+
padding: 0;
|
|
89
|
+
line-height: 1;
|
|
90
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.modal-close-button:hover {
|
|
94
|
+
background-color: #3498db;
|
|
95
|
+
color: #ffffff;
|
|
96
|
+
border-color: #3498db;
|
|
97
|
+
transform: scale(1.05);
|
|
98
|
+
box-shadow: 0 0 8px rgba(52, 152, 219, 0.4);
|
|
99
|
+
}
|
|
100
|
+
|
|
66
101
|
.close {
|
|
67
102
|
position: absolute;
|
|
68
103
|
right: 1.5rem;
|
|
@@ -71,20 +106,14 @@ body {
|
|
|
71
106
|
font-weight: 600;
|
|
72
107
|
color: #6b7280;
|
|
73
108
|
cursor: pointer;
|
|
74
|
-
width: 32px;
|
|
75
|
-
height: 32px;
|
|
76
|
-
display: flex;
|
|
77
|
-
align-items: center;
|
|
78
|
-
justify-content: center;
|
|
79
|
-
border-radius: 50%;
|
|
80
109
|
transition: color 0.2s ease;
|
|
81
110
|
}
|
|
82
111
|
|
|
83
112
|
.close:hover {
|
|
84
|
-
background-color: #f3f4f6;
|
|
85
113
|
color: #111827;
|
|
86
114
|
}
|
|
87
115
|
|
|
116
|
+
|
|
88
117
|
/* Sections layout */
|
|
89
118
|
.modal-sections {
|
|
90
119
|
margin-top: 1.5rem;
|
|
@@ -240,11 +269,62 @@ body {
|
|
|
240
269
|
}
|
|
241
270
|
}
|
|
242
271
|
|
|
243
|
-
|
|
272
|
+
/* Center loading icon in loading modal */
|
|
244
273
|
#installLoadingModal .loading-icon {
|
|
245
274
|
display: flex;
|
|
246
275
|
justify-content: center;
|
|
247
276
|
align-items: center;
|
|
248
277
|
width: 100%;
|
|
249
278
|
margin-bottom: 8px;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* Loading message styles */
|
|
282
|
+
#installLoadingMessage {
|
|
283
|
+
font-size: 0.85rem !important;
|
|
284
|
+
line-height: 1.6;
|
|
285
|
+
word-break: break-word;
|
|
286
|
+
max-height: 200px;
|
|
287
|
+
overflow-y: auto;
|
|
288
|
+
scrollbar-width: thin;
|
|
289
|
+
scrollbar-color: #3498db #f0f0f0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
#installLoadingMessage::-webkit-scrollbar {
|
|
293
|
+
width: 6px;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
#installLoadingMessage::-webkit-scrollbar-track {
|
|
297
|
+
background: #f0f0f0;
|
|
298
|
+
border-radius: 3px;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
#installLoadingMessage::-webkit-scrollbar-thumb {
|
|
302
|
+
background-color: #3498db;
|
|
303
|
+
border-radius: 3px;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
#installLoadingMessage div {
|
|
307
|
+
margin-bottom: 8px;
|
|
308
|
+
padding: 4px 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* Error message styling */
|
|
312
|
+
#installLoadingMessage span[style*="color:red"] {
|
|
313
|
+
color: #f59e0b !important;
|
|
314
|
+
font-weight: 500;
|
|
315
|
+
display: block;
|
|
316
|
+
padding: 4px 8px;
|
|
317
|
+
background: rgba(245, 158, 11, 0.1);
|
|
318
|
+
border-radius: 4px;
|
|
319
|
+
margin: 4px 0;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* File path styling */
|
|
323
|
+
#installLoadingMessage .file-path {
|
|
324
|
+
font-family: 'Consolas', monospace;
|
|
325
|
+
background: #f8fafc;
|
|
326
|
+
padding: 2px 4px;
|
|
327
|
+
border-radius: 3px;
|
|
328
|
+
border: 1px solid #e2e8f0;
|
|
329
|
+
color: #2563eb;
|
|
250
330
|
}
|
|
@@ -112,18 +112,24 @@
|
|
|
112
112
|
</div>
|
|
113
113
|
<!-- Loading Modal -->
|
|
114
114
|
<div id="installLoadingModal" class="modal" style="display:none; z-index:2000;">
|
|
115
|
-
<div class="modal-content" style="text-align:center; pointer-events:auto;">
|
|
115
|
+
<div class="modal-content" style="text-align:center; pointer-events:auto; position:relative;">
|
|
116
|
+
<button class="modal-close-button" onclick="hideInstallLoadingModal()" aria-label="Close">×</button>
|
|
116
117
|
<div style="margin-top:40px;">
|
|
117
118
|
<div class="loading-icon" style="margin-bottom:16px;">
|
|
118
119
|
<svg width="48" height="48" viewBox="0 0 48 48" fill="none">
|
|
119
|
-
<circle cx="24" cy="24" r="20" stroke="#888" stroke-width="4" opacity="0.2"/>
|
|
120
|
-
<circle cx="24" cy="24" r="20" stroke="#3498db" stroke-width="4" stroke-linecap="round"
|
|
121
|
-
|
|
120
|
+
<circle cx="24" cy="24" r="20" stroke="#888" stroke-width="4" opacity="0.2" />
|
|
121
|
+
<circle cx="24" cy="24" r="20" stroke="#3498db" stroke-width="4" stroke-linecap="round"
|
|
122
|
+
stroke-dasharray="100" stroke-dashoffset="60">
|
|
123
|
+
<animateTransform attributeName="transform" type="rotate" from="0 24 24" to="360 24 24"
|
|
124
|
+
dur="1s" repeatCount="indefinite" />
|
|
122
125
|
</circle>
|
|
123
126
|
</svg>
|
|
124
127
|
</div>
|
|
125
|
-
<div class="loading-title" style="font-size:1.5rem; font-weight:bold; margin-bottom:8px;">Installing
|
|
126
|
-
|
|
128
|
+
<div class="loading-title" style="font-size:1.5rem; font-weight:bold; margin-bottom:8px;">Installing...
|
|
129
|
+
</div>
|
|
130
|
+
<div id="installLoadingMessage"
|
|
131
|
+
style="min-height:48px; max-height:160px; overflow:auto; background:#f8f8f8; border-radius:6px; padding:12px; font-size:1rem; color:#444; text-align:left;">
|
|
132
|
+
</div>
|
|
127
133
|
</div>
|
|
128
134
|
</div>
|
|
129
135
|
</div>
|