imcp 0.1.1 → 0.1.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/dist/cli/index.js +1 -45
- package/dist/core/installers/clients/BaseClientInstaller.d.ts +1 -5
- package/dist/core/installers/clients/BaseClientInstaller.js +40 -38
- package/dist/core/installers/clients/ClientInstaller.d.ts +9 -9
- package/dist/core/installers/clients/ClientInstaller.js +105 -99
- package/dist/core/installers/requirements/BaseInstaller.d.ts +9 -1
- package/dist/core/installers/requirements/CommandInstaller.d.ts +9 -1
- package/dist/core/installers/requirements/CommandInstaller.js +46 -12
- package/dist/core/installers/requirements/GeneralInstaller.d.ts +11 -1
- package/dist/core/installers/requirements/GeneralInstaller.js +46 -10
- package/dist/core/installers/requirements/InstallerFactory.d.ts +3 -1
- package/dist/core/installers/requirements/InstallerFactory.js +3 -2
- package/dist/core/installers/requirements/NpmInstaller.d.ts +4 -2
- package/dist/core/installers/requirements/NpmInstaller.js +38 -22
- package/dist/core/installers/requirements/PipInstaller.d.ts +3 -1
- package/dist/core/installers/requirements/PipInstaller.js +58 -36
- package/dist/core/installers/requirements/RequirementInstaller.d.ts +4 -1
- package/dist/core/loaders/InstallOperationManager.d.ts +115 -0
- package/dist/core/loaders/InstallOperationManager.js +311 -0
- package/dist/core/loaders/SystemSettingsManager.d.ts +54 -0
- package/dist/core/loaders/SystemSettingsManager.js +257 -0
- package/dist/core/metadatas/recordingConstants.d.ts +44 -0
- package/dist/core/metadatas/recordingConstants.js +45 -0
- package/dist/core/metadatas/types.d.ts +21 -0
- package/dist/core/onboard/InstallOperationManager.d.ts +23 -0
- package/dist/core/onboard/InstallOperationManager.js +144 -0
- package/dist/core/onboard/OnboardStatusManager.js +2 -1
- package/dist/core/validators/StdioServerValidator.js +4 -3
- package/dist/services/InstallationService.d.ts +2 -37
- package/dist/services/InstallationService.js +45 -313
- package/dist/services/MCPManager.d.ts +1 -1
- package/dist/services/MCPManager.js +4 -58
- package/dist/services/RequirementService.d.ts +85 -12
- package/dist/services/RequirementService.js +488 -49
- package/dist/services/ServerService.d.ts +0 -6
- package/dist/services/ServerService.js +0 -74
- package/dist/utils/adoUtils.js +6 -3
- package/dist/utils/logger.js +1 -1
- package/dist/utils/macroExpressionUtils.js +3 -25
- package/dist/utils/osUtils.d.ts +22 -1
- package/dist/utils/osUtils.js +92 -1
- package/dist/utils/versionUtils.d.ts +20 -1
- package/dist/utils/versionUtils.js +51 -4
- package/dist/web/public/css/modal.css +292 -1
- package/dist/web/public/css/serverDetails.css +14 -1
- package/dist/web/public/index.html +122 -20
- package/dist/web/public/js/flights/flights.js +1 -0
- package/dist/web/public/js/modal/index.js +8 -14
- package/dist/web/public/js/modal/installModal.js +3 -4
- package/dist/web/public/js/modal/installation.js +122 -137
- package/dist/web/public/js/modal/loadingModal.js +155 -25
- package/dist/web/public/js/modal/messageQueue.js +45 -101
- package/dist/web/public/js/modal/modalSetup.js +125 -43
- package/dist/web/public/js/modal/modalUtils.js +0 -12
- package/dist/web/public/js/modal.js +23 -10
- package/dist/web/public/js/onboard/publishHandler.js +22 -20
- package/dist/web/public/js/serverCategoryDetails.js +60 -11
- package/dist/web/public/js/serverCategoryList.js +2 -2
- package/dist/web/public/js/settings.js +314 -0
- package/dist/web/public/settings.html +135 -0
- package/dist/web/public/styles.css +32 -0
- package/dist/web/server.js +82 -0
- package/memory-bank/activeContext.md +13 -1
- package/memory-bank/decisionLog.md +63 -0
- package/memory-bank/progress.md +30 -0
- package/memory-bank/systemPatterns.md +7 -0
- package/package.json +1 -1
- package/src/cli/index.ts +1 -48
- package/src/core/installers/clients/BaseClientInstaller.ts +64 -50
- package/src/core/installers/clients/ClientInstaller.ts +130 -130
- package/src/core/installers/requirements/BaseInstaller.ts +9 -1
- package/src/core/installers/requirements/CommandInstaller.ts +47 -13
- package/src/core/installers/requirements/GeneralInstaller.ts +48 -10
- package/src/core/installers/requirements/InstallerFactory.ts +4 -3
- package/src/core/installers/requirements/NpmInstaller.ts +90 -68
- package/src/core/installers/requirements/PipInstaller.ts +81 -55
- package/src/core/installers/requirements/RequirementInstaller.ts +4 -3
- package/src/core/loaders/InstallOperationManager.ts +367 -0
- package/src/core/loaders/SystemSettingsManager.ts +278 -0
- package/src/core/metadatas/recordingConstants.ts +62 -0
- package/src/core/metadatas/types.ts +23 -0
- package/src/core/onboard/OnboardStatusManager.ts +2 -1
- package/src/core/validators/StdioServerValidator.ts +4 -3
- package/src/services/InstallationService.ts +54 -399
- package/src/services/MCPManager.ts +4 -77
- package/src/services/RequirementService.ts +564 -67
- package/src/services/ServerService.ts +0 -90
- package/src/utils/adoUtils.ts +6 -4
- package/src/utils/logger.ts +1 -1
- package/src/utils/macroExpressionUtils.ts +4 -21
- package/src/utils/osUtils.ts +92 -1
- package/src/utils/versionUtils.ts +71 -19
- package/src/web/public/css/modal.css +292 -1
- package/src/web/public/css/serverDetails.css +14 -1
- package/src/web/public/index.html +122 -20
- package/src/web/public/js/flights/flights.js +1 -1
- package/src/web/public/js/modal/index.js +8 -14
- package/src/web/public/js/modal/installModal.js +3 -4
- package/src/web/public/js/modal/installation.js +122 -137
- package/src/web/public/js/modal/loadingModal.js +155 -25
- package/src/web/public/js/modal/modalSetup.js +125 -43
- package/src/web/public/js/modal/modalUtils.js +0 -12
- package/src/web/public/js/modal.js +23 -10
- package/src/web/public/js/onboard/publishHandler.js +22 -20
- package/src/web/public/js/serverCategoryDetails.js +60 -11
- package/src/web/public/js/serverCategoryList.js +5 -5
- package/src/web/public/js/settings.js +314 -0
- package/src/web/public/settings.html +135 -0
- package/src/web/public/styles.css +32 -0
- package/src/web/server.ts +85 -0
- package/src/web/public/js/modal/messageQueue.js +0 -112
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { buildUrlWithFlights } from './flights/flights.js';
|
|
2
|
+
|
|
3
|
+
// Toast notification function
|
|
4
|
+
function showToast(message, type = 'success') {
|
|
5
|
+
const toastContainer = document.querySelector('.toast-container');
|
|
6
|
+
if (!toastContainer) return;
|
|
7
|
+
|
|
8
|
+
const toastId = `toast-${Date.now()}`;
|
|
9
|
+
const toastHTML = `
|
|
10
|
+
<div id="${toastId}" class="toast align-items-center text-white bg-${type === 'success' ? 'success' : 'danger'} border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
|
11
|
+
<div class="d-flex">
|
|
12
|
+
<div class="toast-body">
|
|
13
|
+
${message}
|
|
14
|
+
</div>
|
|
15
|
+
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
`;
|
|
19
|
+
toastContainer.insertAdjacentHTML('beforeend', toastHTML);
|
|
20
|
+
const toastElement = document.getElementById(toastId);
|
|
21
|
+
const toast = new bootstrap.Toast(toastElement, { delay: 3000 });
|
|
22
|
+
toast.show();
|
|
23
|
+
toastElement.addEventListener('hidden.bs.toast', () => {
|
|
24
|
+
toastElement.remove();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
29
|
+
// All DOM element queries and event listeners must be inside this block!
|
|
30
|
+
const settingsForm = document.getElementById('settingsForm');
|
|
31
|
+
const nodePathInput = document.getElementById('nodePath');
|
|
32
|
+
const browserPathInput = document.getElementById('browserPath');
|
|
33
|
+
const systemEnvironmentsDiv = document.getElementById('systemEnvironments');
|
|
34
|
+
|
|
35
|
+
// Python Environments elements
|
|
36
|
+
const pythonEnvsContainer = document.getElementById('pythonEnvsContainer');
|
|
37
|
+
const addPythonEnvButton = document.getElementById('addPythonEnvButton');
|
|
38
|
+
const pythonEnvsLoadingMsg = document.getElementById('pythonEnvsLoadingMsg');
|
|
39
|
+
|
|
40
|
+
// User Configurations elements
|
|
41
|
+
const userConfigurationsContainer = document.getElementById('userConfigurationsContainer');
|
|
42
|
+
const addUserConfigButton = document.getElementById('addUserConfigButton');
|
|
43
|
+
const userConfigLoadingMsg = document.getElementById('userConfigLoadingMsg');
|
|
44
|
+
|
|
45
|
+
const cancelButton = document.getElementById('cancelButton');
|
|
46
|
+
const setupButton = document.getElementById('setupButton');
|
|
47
|
+
|
|
48
|
+
// (Removed duplicate showToast function here)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async function loadSettings() {
|
|
52
|
+
try {
|
|
53
|
+
const response = await fetch('/api/settings');
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
const errorData = await response.json();
|
|
56
|
+
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
57
|
+
}
|
|
58
|
+
const apiResponse = await response.json();
|
|
59
|
+
if (apiResponse.success && apiResponse.data) {
|
|
60
|
+
const settings = apiResponse.data;
|
|
61
|
+
|
|
62
|
+
// Populate Python Environments
|
|
63
|
+
if(pythonEnvsLoadingMsg) pythonEnvsLoadingMsg.remove(); // Remove loading message
|
|
64
|
+
pythonEnvsContainer.innerHTML = ''; // Clear previous entries
|
|
65
|
+
|
|
66
|
+
if (settings.pythonEnvs && Object.keys(settings.pythonEnvs).length > 0) {
|
|
67
|
+
Object.entries(settings.pythonEnvs).forEach(([server, path]) => {
|
|
68
|
+
renderPythonEnvInput(server, path);
|
|
69
|
+
});
|
|
70
|
+
} else if (settings.pythonEnv) {
|
|
71
|
+
// Handle legacy pythonEnv
|
|
72
|
+
renderPythonEnvInput('system', settings.pythonEnv);
|
|
73
|
+
} else {
|
|
74
|
+
if (!pythonEnvsContainer.querySelector('.python-env-row')) { // Add placeholder if no rows exist
|
|
75
|
+
const p = document.createElement('p');
|
|
76
|
+
p.className = 'text-gray-500 python-env-placeholder';
|
|
77
|
+
p.textContent = 'No Python environments defined. Click "Add Python Environment" to add one.';
|
|
78
|
+
pythonEnvsContainer.appendChild(p);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
nodePathInput.value = settings.nodePath || '';
|
|
83
|
+
browserPathInput.value = settings.browserPath || '';
|
|
84
|
+
|
|
85
|
+
// Populate System Environments
|
|
86
|
+
systemEnvironmentsDiv.innerHTML = ''; // Clear loading message
|
|
87
|
+
if (settings.systemEnvironments && Object.keys(settings.systemEnvironments).length > 0) {
|
|
88
|
+
// Create a modern, scrollable list (not a table)
|
|
89
|
+
const list = document.createElement('div');
|
|
90
|
+
list.className = 'flex flex-col';
|
|
91
|
+
|
|
92
|
+
Object.entries(settings.systemEnvironments).forEach(([key, value], idx) => {
|
|
93
|
+
const item = document.createElement('div');
|
|
94
|
+
item.className =
|
|
95
|
+
'flex flex-col sm:flex-row sm:items-center bg-white rounded-md border border-gray-200 px-4 py-2 shadow-sm hover:shadow transition group';
|
|
96
|
+
item.innerHTML = `
|
|
97
|
+
<div class="flex items-center min-w-0 w-full sm:w-1/4 sm:mb-0">
|
|
98
|
+
<i class='bx bx-cog text-blue-400 mr-2'></i>
|
|
99
|
+
<span class="font-mono font-semibold text-gray-800 text-base truncate" title="${key}">${key}</span>
|
|
100
|
+
</div>
|
|
101
|
+
<div class="flex-1 min-w-0 break-all text-gray-700 text-sm pl-7 sm:pl-4">
|
|
102
|
+
${value}
|
|
103
|
+
</div>
|
|
104
|
+
`;
|
|
105
|
+
list.appendChild(item);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Restore frame styling for the new layout
|
|
109
|
+
systemEnvironmentsDiv.classList.remove('shadow');
|
|
110
|
+
systemEnvironmentsDiv.classList.add('rounded-lg', 'bg-white', 'border', 'border-gray-200', 'border-1');
|
|
111
|
+
systemEnvironmentsDiv.classList.remove('border-2', 'border-4', 'border-8');
|
|
112
|
+
systemEnvironmentsDiv.classList.add('p-0');
|
|
113
|
+
systemEnvironmentsDiv.style.padding = '0.5rem 0.5rem 0.5rem 0.5rem';
|
|
114
|
+
systemEnvironmentsDiv.appendChild(list);
|
|
115
|
+
} else {
|
|
116
|
+
systemEnvironmentsDiv.innerHTML = '<p class="text-gray-500">No system environment variables loaded or available.</p>';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Populate User Configurations
|
|
120
|
+
if(userConfigLoadingMsg) userConfigLoadingMsg.remove(); // Remove loading message
|
|
121
|
+
userConfigurationsContainer.innerHTML = ''; // Clear previous entries
|
|
122
|
+
if (settings.userConfigurations && Object.keys(settings.userConfigurations).length > 0) {
|
|
123
|
+
Object.entries(settings.userConfigurations).forEach(([key, value]) => {
|
|
124
|
+
renderUserConfigInput(key, value);
|
|
125
|
+
});
|
|
126
|
+
} else {
|
|
127
|
+
if (!userConfigurationsContainer.querySelector('.user-config-row')) { // Add placeholder if no rows exist
|
|
128
|
+
const p = document.createElement('p');
|
|
129
|
+
p.className = 'text-gray-500 user-config-placeholder';
|
|
130
|
+
p.textContent = 'No user configurations defined. Click "Add User Configuration" to add one.';
|
|
131
|
+
userConfigurationsContainer.appendChild(p);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
} else {
|
|
136
|
+
throw new Error(apiResponse.error || 'Failed to load settings.');
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error('Error loading settings:', error);
|
|
140
|
+
showToast(`Error loading settings: ${error.message}`, 'danger');
|
|
141
|
+
systemEnvironmentsDiv.innerHTML = `<p class="text-red-500">Error loading environment variables: ${error.message}</p>`;
|
|
142
|
+
if(userConfigLoadingMsg) userConfigLoadingMsg.textContent = `Error loading user configurations: ${error.message}`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function renderPythonEnvInput(server = 'system', path = '') {
|
|
147
|
+
const placeholder = pythonEnvsContainer.querySelector('.python-env-placeholder');
|
|
148
|
+
if (placeholder) placeholder.remove();
|
|
149
|
+
|
|
150
|
+
const uniqueId = `python-env-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
|
|
151
|
+
const div = document.createElement('div');
|
|
152
|
+
div.className = 'python-env-row flex items-center gap-3 mb-2';
|
|
153
|
+
div.innerHTML = `
|
|
154
|
+
<input type="text" value="${server}" class="form-input form-control w-1/3 px-3 py-2 rounded-md python-env-server" placeholder="Server Name (e.g., system)">
|
|
155
|
+
<input type="text" value="${path}" class="form-input form-control w-2/3 px-3 py-2 rounded-md python-env-path" placeholder="e.g., /usr/bin/python3 or C:/Python39/python.exe">
|
|
156
|
+
<button type="button" class="btn btn-danger btn-sm remove-python-env-button flex items-center">
|
|
157
|
+
<i class='bx bx-trash'></i>
|
|
158
|
+
</button>
|
|
159
|
+
`;
|
|
160
|
+
pythonEnvsContainer.appendChild(div);
|
|
161
|
+
div.querySelector('.remove-python-env-button').addEventListener('click', function() {
|
|
162
|
+
this.closest('.python-env-row').remove();
|
|
163
|
+
if (!pythonEnvsContainer.querySelector('.python-env-row')) { // Add placeholder if no rows exist after removal
|
|
164
|
+
const p = document.createElement('p');
|
|
165
|
+
p.className = 'text-gray-500 python-env-placeholder';
|
|
166
|
+
p.textContent = 'No Python environments defined. Click "Add Python Environment" to add one.';
|
|
167
|
+
pythonEnvsContainer.appendChild(p);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function renderUserConfigInput(key = '', value = '') {
|
|
173
|
+
const placeholder = userConfigurationsContainer.querySelector('.user-config-placeholder');
|
|
174
|
+
if (placeholder) placeholder.remove();
|
|
175
|
+
|
|
176
|
+
const uniqueId = `user-config-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
|
|
177
|
+
const div = document.createElement('div');
|
|
178
|
+
div.className = 'user-config-row flex items-center gap-3 mb-2';
|
|
179
|
+
const isSecret = key.toLowerCase().includes('key');
|
|
180
|
+
const valueInputType = isSecret ? 'password' : 'text';
|
|
181
|
+
|
|
182
|
+
// Key Input
|
|
183
|
+
const keyInputHTML = `<input type="text" value="${key}" class="form-input form-control w-1/3 px-3 py-2 rounded-md user-config-key" placeholder="Key">`;
|
|
184
|
+
|
|
185
|
+
// Value Input & Eye Icon (if secret)
|
|
186
|
+
let valueSectionHTML;
|
|
187
|
+
if (isSecret) {
|
|
188
|
+
valueSectionHTML = `
|
|
189
|
+
<div class="relative w-2/3">
|
|
190
|
+
<input type="${valueInputType}" value="${value}" class="form-input form-control w-full px-3 py-2 rounded-md user-config-value pr-10" placeholder="Value">
|
|
191
|
+
<button type="button" class="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500 hover:text-gray-700 toggle-visibility-button" style="background: transparent; border: none;">
|
|
192
|
+
<i class='bx bx-show text-lg'></i>
|
|
193
|
+
</button>
|
|
194
|
+
</div>
|
|
195
|
+
`;
|
|
196
|
+
} else {
|
|
197
|
+
valueSectionHTML = `<input type="${valueInputType}" value="${value}" class="form-input form-control w-2/3 px-3 py-2 rounded-md user-config-value" placeholder="Value">`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Remove Button
|
|
201
|
+
const removeButtonHTML = `
|
|
202
|
+
<button type="button" class="btn btn-danger btn-sm remove-user-config-button flex items-center">
|
|
203
|
+
<i class='bx bx-trash'></i>
|
|
204
|
+
</button>
|
|
205
|
+
`;
|
|
206
|
+
|
|
207
|
+
div.innerHTML = keyInputHTML + valueSectionHTML + removeButtonHTML;
|
|
208
|
+
userConfigurationsContainer.appendChild(div);
|
|
209
|
+
|
|
210
|
+
if (isSecret) {
|
|
211
|
+
const toggleButton = div.querySelector('.toggle-visibility-button');
|
|
212
|
+
const valueInput = div.querySelector('.user-config-value');
|
|
213
|
+
const eyeIcon = toggleButton.querySelector('i');
|
|
214
|
+
|
|
215
|
+
toggleButton.addEventListener('click', (e) => {
|
|
216
|
+
e.preventDefault(); // Prevent form submission if inside a form
|
|
217
|
+
if (valueInput.type === 'password') {
|
|
218
|
+
valueInput.type = 'text';
|
|
219
|
+
eyeIcon.classList.remove('bx-show');
|
|
220
|
+
eyeIcon.classList.add('bx-hide');
|
|
221
|
+
} else {
|
|
222
|
+
valueInput.type = 'password';
|
|
223
|
+
eyeIcon.classList.remove('bx-hide');
|
|
224
|
+
eyeIcon.classList.add('bx-show');
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
div.querySelector('.remove-user-config-button').addEventListener('click', function() {
|
|
230
|
+
this.closest('.user-config-row').remove();
|
|
231
|
+
if (!userConfigurationsContainer.querySelector('.user-config-row')) { // Add placeholder if no rows exist after removal
|
|
232
|
+
const p = document.createElement('p');
|
|
233
|
+
p.className = 'text-gray-500 user-config-placeholder';
|
|
234
|
+
p.textContent = 'No user configurations defined. Click "Add User Configuration" to add one.';
|
|
235
|
+
userConfigurationsContainer.appendChild(p);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
async function saveSettings(event) {
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
setupButton.disabled = true;
|
|
244
|
+
setupButton.innerHTML = `<i class='bx bx-loader-alt bx-spin mr-2'></i>Saving...`;
|
|
245
|
+
|
|
246
|
+
const pythonEnvs = {};
|
|
247
|
+
document.querySelectorAll('.python-env-row').forEach(row => {
|
|
248
|
+
const serverInput = row.querySelector('.python-env-server');
|
|
249
|
+
const pathInput = row.querySelector('.python-env-path');
|
|
250
|
+
if (serverInput && pathInput && serverInput.value.trim()) {
|
|
251
|
+
pythonEnvs[serverInput.value.trim()] = pathInput.value.trim();
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const userConfigs = {};
|
|
256
|
+
document.querySelectorAll('.user-config-row').forEach(row => {
|
|
257
|
+
const keyInput = row.querySelector('.user-config-key');
|
|
258
|
+
const valueInput = row.querySelector('.user-config-value');
|
|
259
|
+
if (keyInput && valueInput && keyInput.value.trim()) {
|
|
260
|
+
userConfigs[keyInput.value.trim()] = valueInput.value.trim();
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const settingsData = {
|
|
265
|
+
pythonEnvs: pythonEnvs,
|
|
266
|
+
// Include pythonEnv for backward compatibility (use system value if available)
|
|
267
|
+
pythonEnv: pythonEnvs['system'] || null,
|
|
268
|
+
nodePath: nodePathInput.value.trim() || null,
|
|
269
|
+
browserPath: browserPathInput.value.trim() || null,
|
|
270
|
+
userConfigurations: userConfigs,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
const response = await fetch('/api/settings', {
|
|
275
|
+
method: 'POST',
|
|
276
|
+
headers: {
|
|
277
|
+
'Content-Type': 'application/json',
|
|
278
|
+
},
|
|
279
|
+
body: JSON.stringify(settingsData),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const apiResponse = await response.json();
|
|
283
|
+
|
|
284
|
+
if (response.ok && apiResponse.success) {
|
|
285
|
+
showToast('Settings saved successfully!', 'success');
|
|
286
|
+
// Optionally, reload settings to reflect any backend-applied defaults
|
|
287
|
+
await loadSettings();
|
|
288
|
+
} else {
|
|
289
|
+
throw new Error(apiResponse.error || `HTTP error! status: ${response.status}`);
|
|
290
|
+
}
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.error('Error saving settings:', error);
|
|
293
|
+
showToast(`Error saving settings: ${error.message}`, 'danger');
|
|
294
|
+
} finally {
|
|
295
|
+
setupButton.disabled = false;
|
|
296
|
+
setupButton.innerHTML = `<i class='bx bx-save mr-2'></i>Save Settings`;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
cancelButton.addEventListener('click', () => {
|
|
301
|
+
// Navigate back to index.html, preserving flight parameters
|
|
302
|
+
window.location.href = buildUrlWithFlights('index.html');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
addPythonEnvButton.addEventListener('click', () => renderPythonEnvInput());
|
|
306
|
+
addUserConfigButton.addEventListener('click', () => renderUserConfigInput());
|
|
307
|
+
|
|
308
|
+
settingsForm.addEventListener('submit', saveSettings);
|
|
309
|
+
|
|
310
|
+
// Initial load
|
|
311
|
+
loadSettings();
|
|
312
|
+
|
|
313
|
+
// (Collapse/expand functionality removed as requested)
|
|
314
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>IMCP System Settings</title>
|
|
8
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
9
|
+
<link href="https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css" rel="stylesheet">
|
|
10
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
11
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css" rel="stylesheet">
|
|
12
|
+
<link rel="stylesheet" href="styles.css">
|
|
13
|
+
<link rel="stylesheet" href="css/notifications.css">
|
|
14
|
+
<style>
|
|
15
|
+
.form-label {
|
|
16
|
+
font-weight: 600;
|
|
17
|
+
color: #374151; /* text-gray-700 */
|
|
18
|
+
}
|
|
19
|
+
.form-input {
|
|
20
|
+
border-color: #D1D5DB; /* border-gray-300 */
|
|
21
|
+
transition: all 0.15s ease-in-out;
|
|
22
|
+
}
|
|
23
|
+
.form-input:focus {
|
|
24
|
+
border-color: #3B82F6; /* focus:border-blue-500 */
|
|
25
|
+
box-shadow: 0 0 0 0.2rem rgba(59, 130, 246, 0.25); /* focus:ring-blue-200 focus:ring-opacity-50 */
|
|
26
|
+
}
|
|
27
|
+
.btn-primary {
|
|
28
|
+
background-color: #2563EB; /* bg-blue-600 */
|
|
29
|
+
border-color: #2563EB;
|
|
30
|
+
}
|
|
31
|
+
.btn-primary:hover {
|
|
32
|
+
background-color: #1D4ED8; /* hover:bg-blue-700 */
|
|
33
|
+
border-color: #1D4ED8;
|
|
34
|
+
}
|
|
35
|
+
.btn-secondary {
|
|
36
|
+
background-color: #6B7280; /* bg-gray-500 */
|
|
37
|
+
border-color: #6B7280;
|
|
38
|
+
color: white;
|
|
39
|
+
}
|
|
40
|
+
.btn-secondary:hover {
|
|
41
|
+
background-color: #4B5563; /* hover:bg-gray-600 */
|
|
42
|
+
border-color: #4B5563;
|
|
43
|
+
}
|
|
44
|
+
.toast-container {
|
|
45
|
+
z-index: 1090; /* Ensure toast is above other elements */
|
|
46
|
+
}
|
|
47
|
+
</style>
|
|
48
|
+
</head>
|
|
49
|
+
|
|
50
|
+
<body class="bg-gray-50 min-h-screen">
|
|
51
|
+
<div class="container mx-auto px-4 py-6">
|
|
52
|
+
<div class="flex items-center justify-between mb-8">
|
|
53
|
+
<h1 class="text-3xl font-bold text-gray-900 flex items-center">
|
|
54
|
+
<a href="index.html" class="flex items-center text-gray-900 hover:text-blue-600 transition-colors">
|
|
55
|
+
<i class='bx bx-left-arrow-alt mr-3 text-blue-600'></i>
|
|
56
|
+
System Settings
|
|
57
|
+
</a>
|
|
58
|
+
</h1>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 lg:p-8">
|
|
62
|
+
<form id="settingsForm">
|
|
63
|
+
<!-- Paths Section -->
|
|
64
|
+
<h2 class="text-xl font-semibold text-gray-800 mb-3">Path Configurations</h2>
|
|
65
|
+
<fieldset class="border border-gray-300 p-4 rounded-md mb-6">
|
|
66
|
+
<!-- Python Environments Section -->
|
|
67
|
+
<div class="mb-4">
|
|
68
|
+
<h3 class="text-lg font-semibold text-gray-800 mb-2">Python Environments</h3>
|
|
69
|
+
<div id="pythonEnvsContainer" class="space-y-3 mb-3">
|
|
70
|
+
<!-- Python environments will be dynamically added here -->
|
|
71
|
+
<p class="text-gray-500" id="pythonEnvsLoadingMsg">Loading Python environments...</p>
|
|
72
|
+
</div>
|
|
73
|
+
<button type="button" id="addPythonEnvButton" class="btn btn-outline-primary btn-sm mb-4 flex items-center">
|
|
74
|
+
<i class='bx bx-plus mr-1'></i>Add Python Environment
|
|
75
|
+
</button>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<!-- Other path configurations -->
|
|
79
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
80
|
+
<div>
|
|
81
|
+
<label for="nodePath" class="form-label block mb-2">Node.js Path</label>
|
|
82
|
+
<input type="text" id="nodePath" name="nodePath" class="form-input form-control w-full px-3 py-2 rounded-md" placeholder="e.g., /usr/local/bin/node or C:/Program Files/nodejs/node.exe">
|
|
83
|
+
</div>
|
|
84
|
+
<div>
|
|
85
|
+
<label for="browserPath" class="form-label block mb-2">Browser Executable Path</label>
|
|
86
|
+
<input type="text" id="browserPath" name="browserPath" class="form-input form-control w-full px-3 py-2 rounded-md" placeholder="e.g., /usr/bin/google-chrome or C:/Program Files/Google/Chrome/Application/chrome.exe">
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</fieldset>
|
|
90
|
+
|
|
91
|
+
<!-- User Configurations Section -->
|
|
92
|
+
<div class="flex items-center mb-3">
|
|
93
|
+
<h2 class="text-xl font-semibold text-gray-800 mr-2">User Configurations</h2>
|
|
94
|
+
<p class="text-sm text-gray-500 flex items-center">
|
|
95
|
+
<i class='bx bx-help-circle mr-1 text-gray-400'></i>
|
|
96
|
+
Stored and used for mcp server installation environments.
|
|
97
|
+
</p>
|
|
98
|
+
</div>
|
|
99
|
+
<fieldset class="border border-gray-300 p-4 rounded-md mb-6">
|
|
100
|
+
<div id="userConfigurationsContainer" class="space-y-3">
|
|
101
|
+
<!-- User configs will be dynamically added here -->
|
|
102
|
+
<p class="text-gray-500" id="userConfigLoadingMsg">Loading user configurations...</p>
|
|
103
|
+
</div>
|
|
104
|
+
<button type="button" id="addUserConfigButton" class="btn btn-outline-primary btn-sm mt-3 flex items-center">
|
|
105
|
+
<i class='bx bx-plus mr-1'></i>Add User Configuration
|
|
106
|
+
</button>
|
|
107
|
+
</fieldset>
|
|
108
|
+
|
|
109
|
+
<!-- System Environment Variables Section -->
|
|
110
|
+
<div class="mb-6">
|
|
111
|
+
<h2 class="text-xl font-semibold text-gray-800 mb-2 py-2">System Environment Variables</h2>
|
|
112
|
+
<div id="systemEnvironments" class="max-h-96 overflow-y-auto bg-white p-2 rounded-lg border border-gray-200">
|
|
113
|
+
<p class="text-gray-500">Loading environment variables...</p>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div class="flex justify-end gap-4 mt-8">
|
|
118
|
+
<button type="button" id="cancelButton" class="btn btn-secondary px-6 py-2.5 rounded-md font-medium">Cancel</button>
|
|
119
|
+
<button type="submit" id="setupButton" class="btn btn-primary px-6 py-2.5 rounded-md font-medium flex items-center">
|
|
120
|
+
<i class='bx bx-save mr-2'></i>Save Settings
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
</form>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<!-- Toast Notification Container -->
|
|
128
|
+
<div class="toast-container position-fixed top-0 end-0 p-3">
|
|
129
|
+
<!-- Toasts will be appended here -->
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
133
|
+
<script src="js/settings.js" type="module"></script>
|
|
134
|
+
</body>
|
|
135
|
+
</html>
|
|
@@ -243,3 +243,35 @@ input[type="radio"]:checked {
|
|
|
243
243
|
grid-template-columns: repeat(2, 1fr);
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
|
+
|
|
247
|
+
.version-tooltip {
|
|
248
|
+
position: absolute;
|
|
249
|
+
background-color: white; /* Changed background to white */
|
|
250
|
+
color: #333; /* Changed text color to dark gray */
|
|
251
|
+
padding: 10px 15px; /* Increased padding */
|
|
252
|
+
border-radius: 6px; /* Slightly larger border radius */
|
|
253
|
+
font-size: 0.875rem;
|
|
254
|
+
z-index: 1000;
|
|
255
|
+
bottom: 100%;
|
|
256
|
+
left: 50%;
|
|
257
|
+
transform: translateX(-50%) translateY(-8px); /* Adjusted gap */
|
|
258
|
+
white-space: pre-wrap;
|
|
259
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15); /* Softer, more modern shadow */
|
|
260
|
+
opacity: 0;
|
|
261
|
+
visibility: hidden;
|
|
262
|
+
transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out;
|
|
263
|
+
min-width: 280px; /* Increased min-width for a wider tooltip */
|
|
264
|
+
border: 1px solid #e0e0e0; /* Added a light border */
|
|
265
|
+
cursor: text;
|
|
266
|
+
user-select: text;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.version-tooltip.visible {
|
|
270
|
+
opacity: 1;
|
|
271
|
+
visibility: visible;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.version-info-container {
|
|
275
|
+
position: relative; /* For tooltip positioning */
|
|
276
|
+
cursor: pointer;
|
|
277
|
+
}
|
package/src/web/server.ts
CHANGED
|
@@ -28,6 +28,10 @@ import { openBrowser } from '../utils/osUtils.js';
|
|
|
28
28
|
import { Logger, EventType, EventStatus } from '../utils/logger.js';
|
|
29
29
|
import { configProvider } from '../core/loaders/ConfigurationProvider.js';
|
|
30
30
|
import { onboardStatusManager } from '../core/onboard/OnboardStatusManager.js';
|
|
31
|
+
import { InstallOperationManager } from '../core/loaders/InstallOperationManager.js';
|
|
32
|
+
import { systemSettingsManager } from '../core/loaders/SystemSettingsManager.js';
|
|
33
|
+
import { SystemSettings } from '../core/metadatas/types.js';
|
|
34
|
+
import { getAppVersion } from '../utils/versionUtils.js';
|
|
31
35
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
32
36
|
const app = express();
|
|
33
37
|
|
|
@@ -308,6 +312,34 @@ app.get('/api/categories/:categoryName/onboard/status', async (req: Request<{ ca
|
|
|
308
312
|
});
|
|
309
313
|
}
|
|
310
314
|
});
|
|
315
|
+
|
|
316
|
+
// Get installation operation status
|
|
317
|
+
app.get('/api/categories/:categoryName/servers/:serverName/installation/status', async (req: Request<{ categoryName: string; serverName: string }>, res: Response) => {
|
|
318
|
+
try {
|
|
319
|
+
const { categoryName, serverName } = req.params;
|
|
320
|
+
const details = await InstallOperationManager.getInstance(categoryName, serverName).getDetails();
|
|
321
|
+
|
|
322
|
+
if (!details) {
|
|
323
|
+
return res.status(404).json({
|
|
324
|
+
success: false,
|
|
325
|
+
error: `No installation operation found for server ${serverName} in category ${categoryName}`
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const response: ApiResponse<typeof details> = {
|
|
330
|
+
success: true,
|
|
331
|
+
data: details
|
|
332
|
+
};
|
|
333
|
+
res.json(response);
|
|
334
|
+
} catch (error) {
|
|
335
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
336
|
+
res.status(500).json({
|
|
337
|
+
success: false,
|
|
338
|
+
error: `Failed to get installation operation status for server ${req.params.serverName} in category ${req.params.categoryName}: ${message}`
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
311
343
|
// Uninstall tools from a server
|
|
312
344
|
app.post('/api/categories/:categoryName/uninstall', async (req: Request<{ categoryName: string }, {}, UninstallServersRequestBody>, res: Response) => {
|
|
313
345
|
try {
|
|
@@ -356,6 +388,59 @@ app.post('/api/categories/:categoryName/uninstall', async (req: Request<{ catego
|
|
|
356
388
|
}
|
|
357
389
|
});
|
|
358
390
|
|
|
391
|
+
// System Settings API
|
|
392
|
+
app.get('/api/settings', async (req: Request, res: Response) => {
|
|
393
|
+
try {
|
|
394
|
+
const settings = await systemSettingsManager.getSystemSettings();
|
|
395
|
+
const response: ApiResponse<SystemSettings> = {
|
|
396
|
+
success: true,
|
|
397
|
+
data: settings
|
|
398
|
+
};
|
|
399
|
+
res.json(response);
|
|
400
|
+
} catch (error) {
|
|
401
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
402
|
+
res.status(500).json({
|
|
403
|
+
success: false,
|
|
404
|
+
error: `Failed to get system settings: ${message}`
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
app.post('/api/settings', async (req: Request<{}, {}, Partial<SystemSettings>>, res: Response) => {
|
|
410
|
+
try {
|
|
411
|
+
const newSettings = req.body;
|
|
412
|
+
const updatedSettings = await systemSettingsManager.createOrUpdateSystemSettings(newSettings);
|
|
413
|
+
const response: ApiResponse<SystemSettings> = {
|
|
414
|
+
success: true,
|
|
415
|
+
data: updatedSettings
|
|
416
|
+
};
|
|
417
|
+
res.json(response);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
420
|
+
res.status(500).json({
|
|
421
|
+
success: false,
|
|
422
|
+
error: `Failed to update system settings: ${message}`
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// API to get package version
|
|
428
|
+
app.get('/api/version', async (req: Request, res: Response) => {
|
|
429
|
+
try {
|
|
430
|
+
const appVersionData = await getAppVersion();
|
|
431
|
+
const response: ApiResponse<typeof appVersionData> = {
|
|
432
|
+
success: true,
|
|
433
|
+
data: appVersionData
|
|
434
|
+
};
|
|
435
|
+
res.json(response);
|
|
436
|
+
} catch (error) {
|
|
437
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
438
|
+
res.status(500).json({
|
|
439
|
+
success: false,
|
|
440
|
+
error: `Failed to get package version: ${message}`
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
});
|
|
359
444
|
|
|
360
445
|
export async function startWebServer(port = 3000): Promise<void> {
|
|
361
446
|
return new Promise((resolve, reject) => {
|