imcp 0.0.14 → 0.0.16
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/core/ConfigurationProvider.d.ts +1 -0
- package/dist/core/ConfigurationProvider.js +15 -0
- package/dist/core/InstallationService.js +2 -7
- package/dist/core/MCPManager.d.ts +11 -2
- package/dist/core/MCPManager.js +24 -1
- package/dist/core/RequirementService.js +2 -8
- package/dist/core/installers/clients/BaseClientInstaller.d.ts +51 -0
- package/dist/core/installers/clients/BaseClientInstaller.js +160 -0
- package/dist/core/installers/clients/ClientInstaller.d.ts +16 -9
- package/dist/core/installers/clients/ClientInstaller.js +80 -527
- package/dist/core/installers/clients/ClientInstallerFactory.d.ts +20 -0
- package/dist/core/installers/clients/ClientInstallerFactory.js +37 -0
- package/dist/core/installers/clients/ClineInstaller.d.ts +18 -0
- package/dist/core/installers/clients/ClineInstaller.js +124 -0
- package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +34 -0
- package/dist/core/installers/clients/GithubCopilotInstaller.js +162 -0
- package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +15 -0
- package/dist/core/installers/clients/MSRooCodeInstaller.js +122 -0
- package/dist/core/installers/requirements/BaseInstaller.d.ts +11 -34
- package/dist/core/installers/requirements/BaseInstaller.js +5 -116
- package/dist/core/installers/requirements/CommandInstaller.d.ts +6 -1
- package/dist/core/installers/requirements/CommandInstaller.js +7 -0
- package/dist/core/installers/requirements/GeneralInstaller.d.ts +6 -1
- package/dist/core/installers/requirements/GeneralInstaller.js +9 -4
- package/dist/core/installers/requirements/NpmInstaller.d.ts +46 -7
- package/dist/core/installers/requirements/NpmInstaller.js +150 -58
- package/dist/core/installers/requirements/PipInstaller.d.ts +9 -0
- package/dist/core/installers/requirements/PipInstaller.js +66 -28
- package/dist/core/onboard/FeedOnboardService.d.ts +50 -13
- package/dist/core/onboard/FeedOnboardService.js +263 -88
- package/dist/core/onboard/OnboardProcessor.d.ts +79 -0
- package/dist/core/onboard/OnboardProcessor.js +290 -0
- package/dist/core/onboard/OnboardStatus.d.ts +49 -0
- package/dist/core/onboard/OnboardStatus.js +10 -0
- package/dist/core/onboard/OnboardStatusManager.d.ts +57 -0
- package/dist/core/onboard/OnboardStatusManager.js +176 -0
- package/dist/core/types.d.ts +4 -5
- package/dist/core/validators/FeedValidator.d.ts +8 -1
- package/dist/core/validators/FeedValidator.js +60 -7
- package/dist/core/validators/IServerValidator.d.ts +19 -0
- package/dist/core/validators/IServerValidator.js +2 -0
- package/dist/core/validators/SSEServerValidator.d.ts +15 -0
- package/dist/core/validators/SSEServerValidator.js +39 -0
- package/dist/core/validators/ServerValidatorFactory.d.ts +24 -0
- package/dist/core/validators/ServerValidatorFactory.js +45 -0
- package/dist/core/validators/StdioServerValidator.d.ts +46 -0
- package/dist/core/validators/StdioServerValidator.js +229 -0
- package/dist/services/InstallRequestValidator.d.ts +1 -1
- package/dist/services/ServerService.d.ts +9 -6
- package/dist/services/ServerService.js +18 -7
- package/dist/utils/adoUtils.d.ts +29 -0
- package/dist/utils/adoUtils.js +252 -0
- package/dist/utils/clientUtils.d.ts +0 -7
- package/dist/utils/clientUtils.js +0 -42
- package/dist/utils/githubUtils.d.ts +10 -0
- package/dist/utils/githubUtils.js +22 -0
- package/dist/utils/macroExpressionUtils.d.ts +38 -0
- package/dist/utils/macroExpressionUtils.js +116 -0
- package/dist/utils/osUtils.d.ts +4 -20
- package/dist/utils/osUtils.js +78 -23
- package/dist/web/contract/serverContract.d.ts +3 -1
- package/dist/web/public/css/notifications.css +48 -17
- package/dist/web/public/css/onboard.css +66 -3
- package/dist/web/public/index.html +84 -16
- package/dist/web/public/js/api.js +3 -6
- package/dist/web/public/js/flights/flights.js +127 -0
- package/dist/web/public/js/modal/installation.js +5 -5
- package/dist/web/public/js/modal/modalSetup.js +3 -2
- package/dist/web/public/js/notifications.js +66 -27
- package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
- package/dist/web/public/js/onboard/formProcessor.js +810 -255
- package/dist/web/public/js/onboard/index.js +328 -85
- package/dist/web/public/js/onboard/publishHandler.js +132 -0
- package/dist/web/public/js/onboard/state.js +61 -17
- package/dist/web/public/js/onboard/templates.js +217 -249
- package/dist/web/public/js/onboard/uiHandlers.js +679 -117
- package/dist/web/public/js/onboard/validationHandlers.js +378 -0
- package/dist/web/public/js/serverCategoryList.js +15 -2
- package/dist/web/public/onboard.html +191 -45
- package/dist/web/public/styles.css +91 -1
- package/dist/web/server.d.ts +0 -10
- package/dist/web/server.js +131 -22
- package/package.json +2 -2
- package/src/core/ConfigurationProvider.ts +15 -0
- package/src/core/InstallationService.ts +2 -7
- package/src/core/MCPManager.ts +26 -1
- package/src/core/RequirementService.ts +2 -9
- package/src/core/installers/clients/BaseClientInstaller.ts +196 -0
- package/src/core/installers/clients/ClientInstaller.ts +97 -608
- package/src/core/installers/clients/ClientInstallerFactory.ts +43 -0
- package/src/core/installers/clients/ClineInstaller.ts +135 -0
- package/src/core/installers/clients/GithubCopilotInstaller.ts +179 -0
- package/src/core/installers/clients/MSRooCodeInstaller.ts +133 -0
- package/src/core/installers/requirements/BaseInstaller.ts +13 -136
- package/src/core/installers/requirements/CommandInstaller.ts +9 -1
- package/src/core/installers/requirements/GeneralInstaller.ts +11 -4
- package/src/core/installers/requirements/NpmInstaller.ts +178 -61
- package/src/core/installers/requirements/PipInstaller.ts +68 -29
- package/src/core/onboard/FeedOnboardService.ts +346 -0
- package/src/core/onboard/OnboardProcessor.ts +305 -0
- package/src/core/onboard/OnboardStatus.ts +55 -0
- package/src/core/onboard/OnboardStatusManager.ts +188 -0
- package/src/core/types.ts +4 -5
- package/src/core/validators/FeedValidator.ts +79 -0
- package/src/core/validators/IServerValidator.ts +21 -0
- package/src/core/validators/SSEServerValidator.ts +43 -0
- package/src/core/validators/ServerValidatorFactory.ts +51 -0
- package/src/core/validators/StdioServerValidator.ts +259 -0
- package/src/services/InstallRequestValidator.ts +1 -1
- package/src/services/ServerService.ts +22 -7
- package/src/utils/adoUtils.ts +291 -0
- package/src/utils/clientUtils.ts +0 -44
- package/src/utils/githubUtils.ts +24 -0
- package/src/utils/macroExpressionUtils.ts +121 -0
- package/src/utils/osUtils.ts +89 -24
- package/src/web/contract/serverContract.ts +74 -0
- package/src/web/public/css/notifications.css +48 -17
- package/src/web/public/css/onboard.css +107 -0
- package/src/web/public/index.html +84 -16
- package/src/web/public/js/api.js +3 -6
- package/src/web/public/js/flights/flights.js +127 -0
- package/src/web/public/js/modal/installation.js +5 -5
- package/src/web/public/js/modal/modalSetup.js +3 -2
- package/src/web/public/js/notifications.js +66 -27
- package/src/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
- package/src/web/public/js/onboard/formProcessor.js +864 -0
- package/src/web/public/js/onboard/index.js +374 -0
- package/src/web/public/js/onboard/publishHandler.js +132 -0
- package/src/web/public/js/onboard/state.js +76 -0
- package/src/web/public/js/onboard/templates.js +343 -0
- package/src/web/public/js/onboard/uiHandlers.js +758 -0
- package/src/web/public/js/onboard/validationHandlers.js +378 -0
- package/src/web/public/js/serverCategoryList.js +15 -2
- package/src/web/public/onboard.html +296 -0
- package/src/web/public/styles.css +91 -1
- package/src/web/server.ts +167 -58
package/dist/utils/osUtils.js
CHANGED
|
@@ -88,8 +88,6 @@ export async function installCLI(tool) {
|
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
90
90
|
* Refreshes the PATH environment variable for the current Node.js process
|
|
91
|
-
* This is necessary because when tools are installed using package managers,
|
|
92
|
-
* they update the system PATH but the current Node process doesn't see those changes.
|
|
93
91
|
*/
|
|
94
92
|
export async function refreshPathEnv() {
|
|
95
93
|
const osType = getOSType();
|
|
@@ -139,6 +137,61 @@ export async function refreshPathEnv() {
|
|
|
139
137
|
// Continue execution even if PATH refresh fails
|
|
140
138
|
}
|
|
141
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Check if a command is available on the system
|
|
142
|
+
* Handles special cases for VS Code on different platforms
|
|
143
|
+
*/
|
|
144
|
+
export async function isCommandAvailable(command) {
|
|
145
|
+
try {
|
|
146
|
+
// For VS Code on macOS, check both command-line tool and app bundle
|
|
147
|
+
if (process.platform === 'darwin' && (command === 'code' || command === 'code-insiders')) {
|
|
148
|
+
try {
|
|
149
|
+
// Try which command first
|
|
150
|
+
await execAsync(`which ${command}`);
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
// If which fails, check application bundles
|
|
155
|
+
const systemVSCodePath = command === 'code' ?
|
|
156
|
+
'/Applications/Visual Studio Code.app' :
|
|
157
|
+
'/Applications/Visual Studio Code - Insiders.app';
|
|
158
|
+
try {
|
|
159
|
+
// Check system Applications first
|
|
160
|
+
await execAsync(`test -d "${systemVSCodePath}"`);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
// If system Applications check fails, try user Applications
|
|
165
|
+
const homedir = process.env.HOME;
|
|
166
|
+
if (homedir) {
|
|
167
|
+
const userVSCodePath = command === 'code' ?
|
|
168
|
+
`${homedir}/Applications/Visual Studio Code.app` :
|
|
169
|
+
`${homedir}/Applications/Visual Studio Code - Insiders.app`;
|
|
170
|
+
try {
|
|
171
|
+
await execAsync(`test -d "${userVSCodePath}"`);
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// For Windows, use where command
|
|
183
|
+
if (process.platform === 'win32') {
|
|
184
|
+
await execAsync(`where ${command}`);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
// For all other cases (Unix-like systems), use which command
|
|
188
|
+
await execAsync(`which ${command}`);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
142
195
|
export async function isToolInstalled(tool, retries = 3) {
|
|
143
196
|
try {
|
|
144
197
|
Logger.debug({
|
|
@@ -181,11 +234,6 @@ export async function isToolInstalled(tool, retries = 3) {
|
|
|
181
234
|
return false;
|
|
182
235
|
}
|
|
183
236
|
}
|
|
184
|
-
/**
|
|
185
|
-
* Opens a URL in the default browser
|
|
186
|
-
* @param url The URL to open
|
|
187
|
-
* @returns A promise that resolves when the browser is launched
|
|
188
|
-
*/
|
|
189
237
|
export async function openBrowser(url) {
|
|
190
238
|
const osType = getOSType();
|
|
191
239
|
Logger.debug({
|
|
@@ -234,12 +282,6 @@ export async function openBrowser(url) {
|
|
|
234
282
|
// Don't throw the error - just log it and continue
|
|
235
283
|
}
|
|
236
284
|
}
|
|
237
|
-
/**
|
|
238
|
-
* Gets the directory containing Python packages (site-packages) based on Python executable path.
|
|
239
|
-
* Handles different Python installations (system, venv, conda) across different OS platforms.
|
|
240
|
-
* @param pythonExecutablePath The full path to the Python executable
|
|
241
|
-
* @returns The site-packages directory path
|
|
242
|
-
*/
|
|
243
285
|
export function getPythonPackagePath(pythonExecutablePath) {
|
|
244
286
|
Logger.debug({
|
|
245
287
|
action: 'get_python_package_path',
|
|
@@ -250,8 +292,29 @@ export function getPythonPackagePath(pythonExecutablePath) {
|
|
|
250
292
|
const isWindows = process.platform === 'win32';
|
|
251
293
|
// Handle common Python installation patterns
|
|
252
294
|
if (isWindows) {
|
|
253
|
-
// Windows:
|
|
254
|
-
if (dir.endsWith('
|
|
295
|
+
// Windows: Handle different Python installations
|
|
296
|
+
if (dir.endsWith('WindowsApps')) {
|
|
297
|
+
// For Windows Store Python, find the actual Python installation directory
|
|
298
|
+
const entries = fs.readdirSync(dir);
|
|
299
|
+
const pythonDir = entries.find(e => e.startsWith('PythonSoftwareFoundation.Python'));
|
|
300
|
+
if (pythonDir && pythonDir.includes('.')) {
|
|
301
|
+
// Extract version from format like "PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0"
|
|
302
|
+
const parts = pythonDir.split('.');
|
|
303
|
+
const majorVer = parts[2];
|
|
304
|
+
const minorVer = parts[3].split('_')[0];
|
|
305
|
+
const version = majorVer + minorVer; // Combines "3" and "13" to "313"
|
|
306
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
307
|
+
if (localAppData) {
|
|
308
|
+
const sitePkgsPath = path.join(localAppData, 'Packages', pythonDir, 'LocalCache', 'local-packages', 'Python' + version, 'site-packages');
|
|
309
|
+
Logger.debug(`Resolved Windows Store Python site-packages path: ${sitePkgsPath}`);
|
|
310
|
+
return sitePkgsPath;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
Logger.debug('Could not resolve Windows Store Python site-packages path');
|
|
314
|
+
// Fallback to user's site-packages
|
|
315
|
+
return path.join(process.env.APPDATA || '', 'Python', 'Python3', 'site-packages');
|
|
316
|
+
}
|
|
317
|
+
else if (dir.endsWith('Scripts')) {
|
|
255
318
|
// Virtual environment structure on Windows: <venv>/Scripts/python.exe
|
|
256
319
|
const venvRoot = path.dirname(dir);
|
|
257
320
|
return path.join(venvRoot, 'Lib', 'site-packages');
|
|
@@ -304,10 +367,6 @@ export function getPythonPackagePath(pythonExecutablePath) {
|
|
|
304
367
|
return path.dirname(pythonExecutablePath);
|
|
305
368
|
}
|
|
306
369
|
}
|
|
307
|
-
/**
|
|
308
|
-
* Finds the directory of the system's default Python installation.
|
|
309
|
-
* @returns The directory path or null if not found.
|
|
310
|
-
*/
|
|
311
370
|
export async function getSystemPythonPackageDirectory() {
|
|
312
371
|
const command = process.platform === 'win32' ? 'where python' : 'which python';
|
|
313
372
|
Logger.debug({
|
|
@@ -335,10 +394,6 @@ export async function getSystemPythonPackageDirectory() {
|
|
|
335
394
|
return null;
|
|
336
395
|
}
|
|
337
396
|
}
|
|
338
|
-
/**
|
|
339
|
-
* Gets the path of the system's default browser.
|
|
340
|
-
* @returns The path to the default browser executable.
|
|
341
|
-
*/
|
|
342
397
|
export async function GetBrowserPath() {
|
|
343
398
|
const osType = getOSType();
|
|
344
399
|
Logger.debug({
|
|
@@ -2,7 +2,7 @@ import { DependencyConfig, RegistryConfig, ServerInstallOptions } from '../../co
|
|
|
2
2
|
export interface OnboardServerConfig {
|
|
3
3
|
name: string;
|
|
4
4
|
description: string;
|
|
5
|
-
mode: 'stdio' | '
|
|
5
|
+
mode: 'stdio' | 'sse';
|
|
6
6
|
schemas?: string;
|
|
7
7
|
dependencies?: DependencyConfig;
|
|
8
8
|
repository?: string;
|
|
@@ -35,6 +35,8 @@ export interface OnboardRequestBody {
|
|
|
35
35
|
mcpServers?: OnboardServerConfig[];
|
|
36
36
|
};
|
|
37
37
|
isUpdate: boolean;
|
|
38
|
+
forExistingCategory?: boolean;
|
|
39
|
+
serverName?: string;
|
|
38
40
|
}
|
|
39
41
|
export interface ListQueryParams {
|
|
40
42
|
local?: string;
|
|
@@ -2,53 +2,84 @@
|
|
|
2
2
|
.alert-container {
|
|
3
3
|
z-index: 999999; /* Extremely high to appear above modals */
|
|
4
4
|
position: fixed;
|
|
5
|
-
top:
|
|
6
|
-
|
|
7
|
-
right: 0 !important;
|
|
5
|
+
top: 20px; /* Adjusted for top-right positioning */
|
|
6
|
+
right: 20px; /* Adjusted for top-right positioning */
|
|
8
7
|
display: flex;
|
|
9
8
|
flex-direction: column;
|
|
10
|
-
align-items:
|
|
9
|
+
align-items: flex-end; /* Align items to the right */
|
|
11
10
|
gap: 10px;
|
|
12
11
|
pointer-events: none; /* Allow clicking through the container */
|
|
12
|
+
max-width: 350px; /* Max width for the container */
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
.alert {
|
|
16
|
-
min-width:
|
|
17
|
-
max-width:
|
|
18
|
-
box-shadow: 0
|
|
16
|
+
min-width: 300px; /* Adjusted min-width */
|
|
17
|
+
max-width: 100%; /* Alert can take full width of container */
|
|
18
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); /* Softer shadow */
|
|
19
19
|
backdrop-filter: blur(8px);
|
|
20
|
-
border-width:
|
|
20
|
+
border-width: 1px; /* Thinner border */
|
|
21
|
+
border-radius: 8px; /* Rounded corners */
|
|
21
22
|
pointer-events: auto; /* Re-enable pointer events for alerts */
|
|
22
|
-
padding:
|
|
23
|
-
font-size:
|
|
23
|
+
padding: 12px 20px; /* Adjusted padding */
|
|
24
|
+
font-size: 0.95rem; /* Slightly smaller font size */
|
|
24
25
|
font-weight: 500;
|
|
25
|
-
|
|
26
|
+
display: flex; /* For icon and text alignment */
|
|
27
|
+
align-items: center; /* Vertically center icon and text */
|
|
28
|
+
opacity: 0; /* Start hidden for animation */
|
|
29
|
+
transform: translateX(100%); /* Start off-screen for slide-in */
|
|
30
|
+
transition: opacity 0.3s ease-out, transform 0.3s ease-out; /* Smooth animation */
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.alert.show {
|
|
34
|
+
opacity: 1;
|
|
35
|
+
transform: translateX(0);
|
|
26
36
|
}
|
|
27
37
|
|
|
28
38
|
.alert:first-child {
|
|
29
39
|
margin-top: 0;
|
|
30
40
|
}
|
|
31
41
|
|
|
42
|
+
.alert-icon {
|
|
43
|
+
margin-right: 10px; /* Space between icon and text */
|
|
44
|
+
font-size: 1.2rem; /* Icon size */
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
.alert.alert-success {
|
|
33
|
-
background-color: rgba(
|
|
34
|
-
border-color: #
|
|
48
|
+
background-color: rgba(30, 136, 229, 0.9); /* Blue for success */
|
|
49
|
+
border-color: #1e88e5;
|
|
35
50
|
color: white;
|
|
36
|
-
font-weight: 500;
|
|
37
51
|
}
|
|
38
52
|
|
|
39
53
|
.alert.alert-error {
|
|
40
|
-
background-color: rgba(
|
|
41
|
-
border-color: #
|
|
54
|
+
background-color: rgba(229, 57, 53, 0.9); /* Red for error */
|
|
55
|
+
border-color: #e53935;
|
|
42
56
|
color: white;
|
|
43
|
-
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.alert.alert-info {
|
|
60
|
+
background-color: rgba(2, 136, 209, 0.9); /* Light blue for info */
|
|
61
|
+
border-color: #0288d1;
|
|
62
|
+
color: white;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.alert.alert-warning {
|
|
66
|
+
background-color: rgba(251, 192, 45, 0.9); /* Yellow for warning */
|
|
67
|
+
border-color: #fbc02d;
|
|
68
|
+
color: #333; /* Darker text for better contrast on yellow */
|
|
44
69
|
}
|
|
45
70
|
|
|
46
71
|
.alert .btn-close {
|
|
47
72
|
filter: brightness(0) invert(1);
|
|
48
73
|
opacity: 0.8;
|
|
49
74
|
padding: 0.75rem;
|
|
75
|
+
margin-left: auto; /* Push close button to the right */
|
|
50
76
|
}
|
|
51
77
|
|
|
78
|
+
.alert.alert-warning .btn-close {
|
|
79
|
+
filter: none; /* No filter for warning close button for better visibility */
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
52
83
|
/* When modal is open, add backdrop to alerts */
|
|
53
84
|
.modal.show ~ .alert-container .alert {
|
|
54
85
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
|
|
@@ -15,15 +15,15 @@
|
|
|
15
15
|
.env-variables {
|
|
16
16
|
background-color: #f9fafb;
|
|
17
17
|
border-radius: 0.5rem;
|
|
18
|
-
padding:
|
|
19
|
-
margin-top:
|
|
18
|
+
padding: 0.75rem; /* Reduced padding */
|
|
19
|
+
margin-top: 0.75rem; /* Reduced margin-top */
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
.registry-github,
|
|
23
23
|
.registry-artifacts {
|
|
24
24
|
background-color: #ffffff;
|
|
25
25
|
border-radius: 0.5rem;
|
|
26
|
-
padding:
|
|
26
|
+
padding: 0.75rem; /* Reduced padding */
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/* Order field styling */
|
|
@@ -41,4 +41,67 @@ input[type="number"][name$=".order"]:focus {
|
|
|
41
41
|
border-color: #3b82f6;
|
|
42
42
|
outline: none;
|
|
43
43
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Collapsible sections */
|
|
47
|
+
.collapsible-content {
|
|
48
|
+
overflow: hidden;
|
|
49
|
+
transition: max-height 0.3s ease-out, opacity 0.3s ease-out, padding 0.3s ease-out, margin 0.3s ease-out;
|
|
50
|
+
opacity: 1;
|
|
51
|
+
max-height: 1000px; /* Adjust as needed, should be larger than the content */
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.collapsible-content.hidden {
|
|
55
|
+
max-height: 0;
|
|
56
|
+
opacity: 0;
|
|
57
|
+
padding-top: 0 !important; /* Use important to override potential inline styles or more specific selectors */
|
|
58
|
+
padding-bottom: 0 !important;
|
|
59
|
+
margin-top: 0 !important;
|
|
60
|
+
margin-bottom: 0 !important; /* Ensure bottom margin also collapses */
|
|
61
|
+
border-top-width: 0;
|
|
62
|
+
border-bottom-width: 0;
|
|
63
|
+
overflow: hidden; /* Ensure content is clipped during transition */
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Styling for the clickable header of collapsible sections */
|
|
67
|
+
.collapsible-header {
|
|
68
|
+
cursor: pointer;
|
|
69
|
+
display: flex;
|
|
70
|
+
justify-content: space-between;
|
|
71
|
+
align-items: center;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.collapsible-header i {
|
|
75
|
+
transition: transform 0.3s ease;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.collapsible-content.hidden + .collapsible-header i {
|
|
79
|
+
transform: rotate(-90deg);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Specific styling for scrollable server content */
|
|
83
|
+
.server-content-scrollable {
|
|
84
|
+
/* This max-height is for when the section is *expanded*.
|
|
85
|
+
The .collapsible-content.hidden will still set max-height to 0 for collapse.
|
|
86
|
+
The general .collapsible-content max-height is a fallback for other sections.
|
|
87
|
+
*/
|
|
88
|
+
max-height: 60vh; /* Reduced max-height for compactness */
|
|
89
|
+
overflow-y: auto; /* Add vertical scrollbar when content exceeds max-height */
|
|
90
|
+
padding-right: 0.5rem; /* Add a little padding so scrollbar doesn't overlap content too much */
|
|
91
|
+
/* Ensure it inherits or has its own transitions for smooth animation */
|
|
92
|
+
transition: max-height 0.3s ease-out, opacity 0.3s ease-out, padding 0.3s ease-out, margin 0.3s ease-out;
|
|
93
|
+
opacity: 1; /* Explicitly set opacity for visible state */
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Ensure that when server-content-scrollable is hidden, its specific max-height is overridden */
|
|
97
|
+
.server-content-scrollable.hidden {
|
|
98
|
+
max-height: 0 !important; /* Override the 60vh for hidden state */
|
|
99
|
+
opacity: 0 !important; /* Ensure opacity transition for hidden state */
|
|
100
|
+
overflow-y: hidden; /* Hide scrollbar when collapsed */
|
|
101
|
+
padding-top: 0 !important;
|
|
102
|
+
padding-bottom: 0 !important;
|
|
103
|
+
padding-left: 0 !important; /* Ensure all paddings are zeroed */
|
|
104
|
+
padding-right: 0 !important;
|
|
105
|
+
margin-top: 0 !important;
|
|
106
|
+
margin-bottom: 0 !important;
|
|
44
107
|
}
|
|
@@ -23,18 +23,42 @@
|
|
|
23
23
|
|
|
24
24
|
<body class="bg-gray-50 min-h-screen">
|
|
25
25
|
<div class="container mx-auto px-4 py-6">
|
|
26
|
-
<
|
|
27
|
-
<h1 class="text-3xl font-bold text-gray-900 flex items-center
|
|
26
|
+
<div class="flex items-center justify-between mb-8">
|
|
27
|
+
<h1 class="text-3xl font-bold text-gray-900 flex items-center">
|
|
28
28
|
<i class='bx bx-server mr-3 text-blue-600'></i>
|
|
29
29
|
IMCP Server Manager
|
|
30
30
|
</h1>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
<div class="lg:w-2/3 flex justify-end items-center gap-4">
|
|
32
|
+
<div class="relative w-48 focus-within:w-96 transition-all duration-300 ease-in-out">
|
|
33
|
+
<input type="text" id="searchBox"
|
|
34
|
+
placeholder="Search..."
|
|
35
|
+
data-expanded-placeholder="Search servers or tools..."
|
|
36
|
+
class="w-full px-10 py-2.5 bg-gray-50 border border-gray-200 rounded-lg
|
|
37
|
+
text-gray-700 placeholder-gray-400
|
|
38
|
+
focus:border-blue-400 focus:ring-2 focus:ring-blue-100
|
|
39
|
+
transition-all duration-300 ease-in-out">
|
|
40
|
+
<i class='bx bx-search absolute left-3.5 top-1/2 transform -translate-y-1/2 text-gray-400'></i>
|
|
41
|
+
</div>
|
|
42
|
+
<button id="onboardButton" style="display: none;"
|
|
43
|
+
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center">
|
|
44
|
+
<i class='bx bx-plus-circle mr-2'></i>
|
|
45
|
+
Onboard
|
|
46
|
+
</button>
|
|
37
47
|
</div>
|
|
48
|
+
<script>
|
|
49
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
50
|
+
const searchBox = document.getElementById('searchBox');
|
|
51
|
+
const expandedPlaceholder = searchBox.dataset.expandedPlaceholder;
|
|
52
|
+
searchBox.addEventListener('focus', () => {
|
|
53
|
+
setTimeout(() => searchBox.placeholder = expandedPlaceholder, 150);
|
|
54
|
+
});
|
|
55
|
+
searchBox.addEventListener('blur', () => {
|
|
56
|
+
if (!searchBox.value) {
|
|
57
|
+
searchBox.placeholder = 'Search...';
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
</script>
|
|
38
62
|
</div>
|
|
39
63
|
|
|
40
64
|
<div class="flex flex-col lg:flex-row gap-6">
|
|
@@ -147,10 +171,15 @@
|
|
|
147
171
|
import { setupSearch } from './js/serverCategoryList.js';
|
|
148
172
|
import { showServerDetails } from './js/serverCategoryDetails.js';
|
|
149
173
|
import { showInstallModal, closeModal, setupModalOutsideClick } from './js/modal.js';
|
|
174
|
+
// import { showOnboardModal as navigateToOnboard } from './js/onboard/index.js'; // We are directly navigating
|
|
175
|
+
import flights, { getFlightQueryParameters, buildUrlWithFlights } from './js/flights/flights.js';
|
|
150
176
|
|
|
151
177
|
// Make necessary functions available globally
|
|
152
178
|
window.showServerDetails = showServerDetails;
|
|
153
179
|
window.showInstallModal = showInstallModal;
|
|
180
|
+
// window.showOnboardModal = showOnboardModal; // This might conflict if showOnboardModal is also in onboard/index.js
|
|
181
|
+
// We'll rely on the event listener above for the main page's onboard button.
|
|
182
|
+
// If other parts of the app call window.showOnboardModal, they'll need updating too.
|
|
154
183
|
window.closeModal = closeModal;
|
|
155
184
|
window.handleInstallServer = handleInstallServer;
|
|
156
185
|
window.installRequirement = installRequirement;
|
|
@@ -162,23 +191,62 @@
|
|
|
162
191
|
setupSearch();
|
|
163
192
|
setupModalOutsideClick();
|
|
164
193
|
|
|
194
|
+
const onboardButton = document.getElementById('onboardButton');
|
|
195
|
+
if (flights.enableOnboard) {
|
|
196
|
+
onboardButton.style.display = 'flex'; // Make it visible
|
|
197
|
+
onboardButton.addEventListener('click', () => {
|
|
198
|
+
// The original showOnboardModal in onboard/index.js likely does window.location.href = 'onboard.html';
|
|
199
|
+
// We need to preserve existing flight parameters.
|
|
200
|
+
const flightQueryString = getFlightQueryParameters();
|
|
201
|
+
// Assuming navigateToOnboard from onboard/index.js handles the actual navigation
|
|
202
|
+
// If it directly sets window.location.href, we might need to adjust how it's called or modify it.
|
|
203
|
+
// For now, let's assume we can adapt its usage or it's a simple navigation.
|
|
204
|
+
// The simplest way if showOnboardModal in onboard/index.js is just `window.location.href = 'onboard.html'`
|
|
205
|
+
// would be to directly navigate here.
|
|
206
|
+
window.location.href = buildUrlWithFlights('onboard.html');
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
// No 'else' block needed as it's hidden by default via inline style
|
|
210
|
+
|
|
165
211
|
try {
|
|
166
212
|
// First fetch categories and wait for completion
|
|
167
|
-
|
|
213
|
+
// fetchServerCategories will call renderServerCategoryList, which might call loadLastSelectedCategory.
|
|
214
|
+
// loadLastSelectedCategory calls showServerDetails.
|
|
215
|
+
// The logic for setting initial category URL is now more centralized.
|
|
216
|
+
|
|
217
|
+
await fetchServerCategories(); // This populates allServerCategoriesData
|
|
168
218
|
|
|
169
|
-
// Check URL parameters for category
|
|
170
219
|
const urlParams = new URLSearchParams(window.location.search);
|
|
171
220
|
const categoryParam = urlParams.get('category');
|
|
221
|
+
const lastSelectedCategoryFromStorage = localStorage.getItem('lastSelectedCategory');
|
|
172
222
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
223
|
+
if (categoryParam) {
|
|
224
|
+
// Category is in URL, load it
|
|
225
|
+
await window.showServerDetails(categoryParam);
|
|
226
|
+
} else if (lastSelectedCategoryFromStorage) {
|
|
227
|
+
// Category not in URL, but in localStorage. Redirect to URL with this category and flights.
|
|
228
|
+
window.location.href = buildUrlWithFlights('index.html', { category: lastSelectedCategoryFromStorage });
|
|
229
|
+
return; // Stop further execution, page will reload
|
|
230
|
+
} else if (allServerCategoriesData && allServerCategoriesData.length > 0) {
|
|
231
|
+
// No category in URL or localStorage, pick first from list and redirect.
|
|
232
|
+
window.location.href = buildUrlWithFlights('index.html', { category: allServerCategoriesData[0].name });
|
|
233
|
+
return; // Stop further execution, page will reload
|
|
234
|
+
} else {
|
|
235
|
+
// No categories at all, or data still loading. Default placeholder will be shown.
|
|
236
|
+
// Or, if allServerCategoriesData is empty after fetch, the placeholder from HTML remains.
|
|
179
237
|
}
|
|
238
|
+
|
|
180
239
|
} catch (error) {
|
|
181
240
|
console.error('Error during initialization:', error);
|
|
241
|
+
// Potentially show a user-friendly error message in the UI
|
|
242
|
+
const serverCategoryDetailsDiv = document.getElementById('serverCategoryDetails');
|
|
243
|
+
if (serverCategoryDetailsDiv) {
|
|
244
|
+
serverCategoryDetailsDiv.innerHTML = `<p class="text-red-500">Error initializing page: ${error.message}</p>`;
|
|
245
|
+
}
|
|
246
|
+
const serverCategoryListDiv = document.getElementById('serverCategoryList');
|
|
247
|
+
if (serverCategoryListDiv) {
|
|
248
|
+
serverCategoryListDiv.innerHTML = `<p class="text-red-500">Could not load categories.</p>`;
|
|
249
|
+
}
|
|
182
250
|
}
|
|
183
251
|
});
|
|
184
252
|
</script>
|
|
@@ -3,6 +3,8 @@ import { showToast } from './notifications.js';
|
|
|
3
3
|
|
|
4
4
|
// Global store for server data
|
|
5
5
|
let allServerCategoriesData = [];
|
|
6
|
+
// Make it available globally for legacy code
|
|
7
|
+
window.allServerCategoriesData = allServerCategoriesData;
|
|
6
8
|
|
|
7
9
|
// Fetch and display servers
|
|
8
10
|
async function fetchServerCategories() {
|
|
@@ -14,16 +16,11 @@ async function fetchServerCategories() {
|
|
|
14
16
|
if (!responseData.success || !responseData.data) {
|
|
15
17
|
throw new Error(`API Error fetching servers: ${responseData.error || 'Invalid data format'}`);
|
|
16
18
|
}
|
|
17
|
-
allServerCategoriesData = responseData.data; //
|
|
19
|
+
allServerCategoriesData = window.allServerCategoriesData = responseData.data; // Update both local and global references
|
|
18
20
|
|
|
19
21
|
// Use the renderServerCategoryList function to display servers
|
|
20
22
|
renderServerCategoryList(allServerCategoriesData);
|
|
21
23
|
|
|
22
|
-
// Show the first category by default if there are categories
|
|
23
|
-
if (allServerCategoriesData.length > 0) {
|
|
24
|
-
window.showServerDetails(allServerCategoriesData[0].name);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
24
|
// Check if search is active
|
|
28
25
|
const searchTerm = document.getElementById('searchBox').value.toLowerCase();
|
|
29
26
|
if (searchTerm) {
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Centralized flight control configuration
|
|
2
|
+
|
|
3
|
+
const FLIGHT_STORAGE_KEY = 'activeFlightSettings';
|
|
4
|
+
|
|
5
|
+
// Default flight settings
|
|
6
|
+
const defaultFlights = {
|
|
7
|
+
enableOnboard: false, // Default: Onboarding is enabled. Set to false to disable.
|
|
8
|
+
// Add other default flights here, e.g., newFeatureX: false,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let effectiveFlightSettings = {};
|
|
12
|
+
|
|
13
|
+
// Initialize flight settings
|
|
14
|
+
function initializeFlightSettings() {
|
|
15
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
16
|
+
let settingsToStore = {};
|
|
17
|
+
let sourcedFromUrl = false;
|
|
18
|
+
|
|
19
|
+
// Check URL parameters first
|
|
20
|
+
for (const key in defaultFlights) {
|
|
21
|
+
if (urlParams.has(key)) {
|
|
22
|
+
const paramValue = urlParams.get(key);
|
|
23
|
+
settingsToStore[key] = (paramValue === 'true') ? true : (paramValue === 'false' ? false : paramValue);
|
|
24
|
+
sourcedFromUrl = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (sourcedFromUrl) {
|
|
29
|
+
// If any flight was set via URL, these override sessionStorage for this load
|
|
30
|
+
// and then update sessionStorage.
|
|
31
|
+
// Fill any missing flights with defaults before storing.
|
|
32
|
+
for (const key in defaultFlights) {
|
|
33
|
+
if (!Object.hasOwnProperty.call(settingsToStore, key)) {
|
|
34
|
+
settingsToStore[key] = defaultFlights[key];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
sessionStorage.setItem(FLIGHT_STORAGE_KEY, JSON.stringify(settingsToStore));
|
|
39
|
+
effectiveFlightSettings = settingsToStore;
|
|
40
|
+
} catch (e) {
|
|
41
|
+
console.error("Failed to write to sessionStorage:", e);
|
|
42
|
+
effectiveFlightSettings = settingsToStore; // Use them anyway for this page load
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
// No flight params in URL, try sessionStorage
|
|
46
|
+
try {
|
|
47
|
+
const storedSettings = sessionStorage.getItem(FLIGHT_STORAGE_KEY);
|
|
48
|
+
if (storedSettings) {
|
|
49
|
+
effectiveFlightSettings = JSON.parse(storedSettings);
|
|
50
|
+
// Ensure all default keys are present, add if missing (e.g., new flight added)
|
|
51
|
+
for (const key in defaultFlights) {
|
|
52
|
+
if (!Object.hasOwnProperty.call(effectiveFlightSettings, key)) {
|
|
53
|
+
effectiveFlightSettings[key] = defaultFlights[key];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
// Not in sessionStorage, use defaults and store them
|
|
58
|
+
effectiveFlightSettings = { ...defaultFlights };
|
|
59
|
+
sessionStorage.setItem(FLIGHT_STORAGE_KEY, JSON.stringify(effectiveFlightSettings));
|
|
60
|
+
}
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.error("Failed to read/write sessionStorage:", e);
|
|
63
|
+
effectiveFlightSettings = { ...defaultFlights }; // Fallback to defaults
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
initializeFlightSettings();
|
|
69
|
+
|
|
70
|
+
// The `flights` object provides these effective values.
|
|
71
|
+
const flights = {};
|
|
72
|
+
for (const key in defaultFlights) { // Iterate defaultFlights to define getters for all known flights
|
|
73
|
+
if (Object.hasOwnProperty.call(defaultFlights, key)) {
|
|
74
|
+
Object.defineProperty(flights, key, {
|
|
75
|
+
get: () => effectiveFlightSettings[key] !== undefined ? effectiveFlightSettings[key] : defaultFlights[key],
|
|
76
|
+
enumerable: true,
|
|
77
|
+
configurable: false
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Helper to build a URLSearchParams object with the effective flight values.
|
|
84
|
+
* @returns {URLSearchParams}
|
|
85
|
+
*/
|
|
86
|
+
function _buildFlightParams() {
|
|
87
|
+
const flightParams = new URLSearchParams();
|
|
88
|
+
for (const flightName in effectiveFlightSettings) {
|
|
89
|
+
if (Object.hasOwnProperty.call(effectiveFlightSettings, flightName)) {
|
|
90
|
+
// Only add if it's a recognized flight from defaultFlights to avoid polluting URL
|
|
91
|
+
// with unrelated sessionStorage items if FLIGHT_STORAGE_KEY was somehow corrupted.
|
|
92
|
+
if (Object.hasOwnProperty.call(defaultFlights, flightName)) {
|
|
93
|
+
flightParams.set(flightName, String(effectiveFlightSettings[flightName]));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return flightParams;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Constructs a URL with the effective flight parameters and any additional parameters.
|
|
102
|
+
* @param {string} baseUrl The base URL (e.g., "index.html").
|
|
103
|
+
* @param {object} [additionalParams={}] - An object of additional key-value pairs to include.
|
|
104
|
+
* @returns {string} A full URL with query string.
|
|
105
|
+
*/
|
|
106
|
+
export function buildUrlWithFlights(baseUrl, additionalParams = {}) {
|
|
107
|
+
const params = _buildFlightParams(); // Start with all effective flight values
|
|
108
|
+
|
|
109
|
+
for (const key in additionalParams) {
|
|
110
|
+
if (Object.hasOwnProperty.call(additionalParams, key) && additionalParams[key] !== undefined) {
|
|
111
|
+
params.set(key, additionalParams[key]);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const queryString = params.toString();
|
|
115
|
+
return `${baseUrl}${queryString ? `?${queryString}` : ''}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Gets a query string containing only the effective flight parameters.
|
|
120
|
+
* @returns {string} A query string like "?enableOnboard=true" or empty.
|
|
121
|
+
*/
|
|
122
|
+
export function getFlightQueryParameters() {
|
|
123
|
+
const queryString = _buildFlightParams().toString();
|
|
124
|
+
return queryString ? `?${queryString}` : '';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export default flights;
|
|
@@ -129,11 +129,11 @@ async function pollInstallStatus(categoryName, serverName, targets, interval = 2
|
|
|
129
129
|
for (const req of requirements) {
|
|
130
130
|
const reqStatus = requirementsStatus[req.name] || {};
|
|
131
131
|
if (reqStatus.installed === true && !reqStatus.operationStatus) {
|
|
132
|
-
const msg = `Requirement [${req.name}] already installed.`;
|
|
133
|
-
if (msg && lastRequirementMessages[req.name] !== msg) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
132
|
+
// const msg = `Requirement [${req.name}] already installed.`;
|
|
133
|
+
// if (msg && lastRequirementMessages[req.name] !== msg) {
|
|
134
|
+
// delayedAppendInstallLoadingMessage(msg);
|
|
135
|
+
// lastRequirementMessages[req.name] = msg;
|
|
136
|
+
// }
|
|
137
137
|
continue;
|
|
138
138
|
}
|
|
139
139
|
const opStatus = reqStatus.operationStatus || {};
|