imcp 0.0.19 → 0.1.2

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.
Files changed (183) hide show
  1. package/.roo/rules-code/rules.md +88 -0
  2. package/dist/cli/index.js +1 -45
  3. package/dist/core/installers/clients/BaseClientInstaller.d.ts +1 -5
  4. package/dist/core/installers/clients/BaseClientInstaller.js +40 -38
  5. package/dist/core/installers/clients/ClientInstaller.d.ts +9 -9
  6. package/dist/core/installers/clients/ClientInstaller.js +105 -99
  7. package/dist/core/installers/requirements/BaseInstaller.d.ts +9 -1
  8. package/dist/core/installers/requirements/CommandInstaller.d.ts +9 -1
  9. package/dist/core/installers/requirements/CommandInstaller.js +46 -12
  10. package/dist/core/installers/requirements/GeneralInstaller.d.ts +11 -1
  11. package/dist/core/installers/requirements/GeneralInstaller.js +46 -10
  12. package/dist/core/installers/requirements/InstallerFactory.d.ts +3 -1
  13. package/dist/core/installers/requirements/InstallerFactory.js +3 -2
  14. package/dist/core/installers/requirements/NpmInstaller.d.ts +4 -2
  15. package/dist/core/installers/requirements/NpmInstaller.js +38 -22
  16. package/dist/core/installers/requirements/PipInstaller.d.ts +3 -1
  17. package/dist/core/installers/requirements/PipInstaller.js +58 -36
  18. package/dist/core/installers/requirements/RequirementInstaller.d.ts +4 -1
  19. package/dist/core/loaders/InstallOperationManager.d.ts +115 -0
  20. package/dist/core/loaders/InstallOperationManager.js +311 -0
  21. package/dist/core/loaders/SystemSettingsManager.d.ts +54 -0
  22. package/dist/core/loaders/SystemSettingsManager.js +257 -0
  23. package/dist/core/metadatas/constants.d.ts +7 -0
  24. package/dist/core/metadatas/constants.js +7 -0
  25. package/dist/core/metadatas/recordingConstants.d.ts +44 -0
  26. package/dist/core/metadatas/recordingConstants.js +45 -0
  27. package/dist/core/metadatas/types.d.ts +21 -0
  28. package/dist/core/onboard/FeedOnboardService.d.ts +7 -3
  29. package/dist/core/onboard/FeedOnboardService.js +52 -5
  30. package/dist/core/onboard/InstallOperationManager.d.ts +23 -0
  31. package/dist/core/onboard/InstallOperationManager.js +144 -0
  32. package/dist/core/onboard/OnboardStatusManager.js +2 -1
  33. package/dist/core/validators/StdioServerValidator.js +4 -3
  34. package/dist/services/InstallationService.d.ts +2 -37
  35. package/dist/services/InstallationService.js +45 -313
  36. package/dist/services/MCPManager.d.ts +1 -1
  37. package/dist/services/MCPManager.js +53 -47
  38. package/dist/services/RequirementService.d.ts +85 -12
  39. package/dist/services/RequirementService.js +488 -49
  40. package/dist/services/ServerService.d.ts +0 -6
  41. package/dist/services/ServerService.js +0 -74
  42. package/dist/services/TelemetryService.d.ts +15 -0
  43. package/dist/services/TelemetryService.js +54 -0
  44. package/dist/utils/adoUtils.js +6 -3
  45. package/dist/utils/githubAuth.js +65 -0
  46. package/dist/utils/logger.d.ts +16 -0
  47. package/dist/utils/logger.js +78 -1
  48. package/dist/utils/macroExpressionUtils.js +3 -25
  49. package/dist/utils/osUtils.d.ts +22 -1
  50. package/dist/utils/osUtils.js +92 -1
  51. package/dist/utils/versionUtils.d.ts +20 -0
  52. package/dist/utils/versionUtils.js +76 -0
  53. package/dist/web/public/css/modal.css +292 -1
  54. package/dist/web/public/css/serverCategoryList.css +120 -0
  55. package/dist/web/public/css/serverDetails.css +14 -1
  56. package/dist/web/public/index.html +126 -21
  57. package/dist/web/public/js/flights/flights.js +1 -1
  58. package/dist/web/public/js/modal/index.js +8 -14
  59. package/dist/web/public/js/modal/installModal.js +3 -4
  60. package/dist/web/public/js/modal/installation.js +122 -137
  61. package/dist/web/public/js/modal/loadingModal.js +155 -25
  62. package/dist/web/public/js/modal/messageQueue.js +45 -101
  63. package/dist/web/public/js/modal/modalSetup.js +125 -43
  64. package/dist/web/public/js/modal/modalUtils.js +0 -12
  65. package/dist/web/public/js/modal.js +23 -10
  66. package/dist/web/public/js/onboard/formProcessor.js +18 -11
  67. package/dist/web/public/js/onboard/publishHandler.js +35 -3
  68. package/dist/web/public/js/onboard/templates.js +5 -1
  69. package/dist/web/public/js/onboard/uiHandlers.js +266 -39
  70. package/dist/web/public/js/onboard/validationHandlers.js +71 -39
  71. package/dist/web/public/js/serverCategoryDetails.js +60 -11
  72. package/dist/web/public/js/serverCategoryList.js +93 -9
  73. package/dist/web/public/js/settings.js +314 -0
  74. package/dist/web/public/onboard.html +2 -2
  75. package/dist/web/public/settings.html +135 -0
  76. package/dist/web/public/styles.css +32 -0
  77. package/dist/web/server.js +93 -1
  78. package/{src/web/public/js/onboard → docs}/ONBOARDING_PAGE_DESIGN.md +15 -125
  79. package/docs/Telemetry.md +136 -0
  80. package/memory-bank/activeContext.md +26 -0
  81. package/memory-bank/decisionLog.md +91 -0
  82. package/memory-bank/productContext.md +41 -0
  83. package/memory-bank/progress.md +35 -0
  84. package/memory-bank/systemPatterns.md +10 -0
  85. package/package.json +2 -1
  86. package/src/cli/index.ts +1 -48
  87. package/src/core/installers/clients/BaseClientInstaller.ts +64 -50
  88. package/src/core/installers/clients/ClientInstaller.ts +130 -130
  89. package/src/core/installers/requirements/BaseInstaller.ts +9 -1
  90. package/src/core/installers/requirements/CommandInstaller.ts +47 -13
  91. package/src/core/installers/requirements/GeneralInstaller.ts +48 -10
  92. package/src/core/installers/requirements/InstallerFactory.ts +4 -3
  93. package/src/core/installers/requirements/NpmInstaller.ts +90 -68
  94. package/src/core/installers/requirements/PipInstaller.ts +81 -55
  95. package/src/core/installers/requirements/RequirementInstaller.ts +4 -3
  96. package/src/core/loaders/InstallOperationManager.ts +367 -0
  97. package/src/core/loaders/SystemSettingsManager.ts +278 -0
  98. package/src/core/metadatas/constants.ts +9 -0
  99. package/src/core/metadatas/recordingConstants.ts +62 -0
  100. package/src/core/metadatas/types.ts +23 -0
  101. package/src/core/onboard/FeedOnboardService.ts +59 -5
  102. package/src/core/onboard/OnboardStatusManager.ts +2 -1
  103. package/src/core/validators/StdioServerValidator.ts +4 -3
  104. package/src/services/InstallationService.ts +54 -399
  105. package/src/services/MCPManager.ts +61 -64
  106. package/src/services/RequirementService.ts +564 -67
  107. package/src/services/ServerService.ts +0 -90
  108. package/src/services/TelemetryService.ts +59 -0
  109. package/src/utils/adoUtils.ts +6 -4
  110. package/src/utils/githubAuth.ts +84 -1
  111. package/src/utils/logger.ts +83 -1
  112. package/src/utils/macroExpressionUtils.ts +4 -21
  113. package/src/utils/osUtils.ts +92 -1
  114. package/src/utils/versionUtils.ts +98 -13
  115. package/src/web/public/css/modal.css +292 -1
  116. package/src/web/public/css/serverCategoryList.css +120 -0
  117. package/src/web/public/css/serverDetails.css +14 -1
  118. package/src/web/public/index.html +126 -21
  119. package/src/web/public/js/flights/flights.js +1 -1
  120. package/src/web/public/js/modal/index.js +8 -14
  121. package/src/web/public/js/modal/installModal.js +3 -4
  122. package/src/web/public/js/modal/installation.js +122 -137
  123. package/src/web/public/js/modal/loadingModal.js +155 -25
  124. package/src/web/public/js/modal/modalSetup.js +125 -43
  125. package/src/web/public/js/modal/modalUtils.js +0 -12
  126. package/src/web/public/js/modal.js +23 -10
  127. package/src/web/public/js/onboard/formProcessor.js +18 -11
  128. package/src/web/public/js/onboard/publishHandler.js +35 -3
  129. package/src/web/public/js/onboard/templates.js +5 -1
  130. package/src/web/public/js/onboard/uiHandlers.js +266 -39
  131. package/src/web/public/js/onboard/validationHandlers.js +71 -39
  132. package/src/web/public/js/serverCategoryDetails.js +60 -11
  133. package/src/web/public/js/serverCategoryList.js +93 -9
  134. package/src/web/public/js/settings.js +314 -0
  135. package/src/web/public/onboard.html +2 -2
  136. package/src/web/public/settings.html +135 -0
  137. package/src/web/public/styles.css +32 -0
  138. package/src/web/server.ts +96 -1
  139. package/dist/cli/commands/start.d.ts +0 -2
  140. package/dist/cli/commands/start.js +0 -32
  141. package/dist/cli/commands/sync.d.ts +0 -2
  142. package/dist/cli/commands/sync.js +0 -17
  143. package/dist/core/ConfigurationLoader.d.ts +0 -32
  144. package/dist/core/ConfigurationLoader.js +0 -236
  145. package/dist/core/ConfigurationProvider.d.ts +0 -35
  146. package/dist/core/ConfigurationProvider.js +0 -375
  147. package/dist/core/InstallationService.d.ts +0 -50
  148. package/dist/core/InstallationService.js +0 -350
  149. package/dist/core/MCPManager.d.ts +0 -28
  150. package/dist/core/MCPManager.js +0 -188
  151. package/dist/core/RequirementService.d.ts +0 -40
  152. package/dist/core/RequirementService.js +0 -110
  153. package/dist/core/ServerSchemaLoader.d.ts +0 -11
  154. package/dist/core/ServerSchemaLoader.js +0 -43
  155. package/dist/core/ServerSchemaProvider.d.ts +0 -17
  156. package/dist/core/ServerSchemaProvider.js +0 -120
  157. package/dist/core/constants.d.ts +0 -47
  158. package/dist/core/constants.js +0 -94
  159. package/dist/core/installers/BaseInstaller.d.ts +0 -74
  160. package/dist/core/installers/BaseInstaller.js +0 -253
  161. package/dist/core/installers/ClientInstaller.d.ts +0 -23
  162. package/dist/core/installers/ClientInstaller.js +0 -564
  163. package/dist/core/installers/CommandInstaller.d.ts +0 -37
  164. package/dist/core/installers/CommandInstaller.js +0 -173
  165. package/dist/core/installers/GeneralInstaller.d.ts +0 -33
  166. package/dist/core/installers/GeneralInstaller.js +0 -85
  167. package/dist/core/installers/InstallerFactory.d.ts +0 -54
  168. package/dist/core/installers/InstallerFactory.js +0 -97
  169. package/dist/core/installers/NpmInstaller.d.ts +0 -26
  170. package/dist/core/installers/NpmInstaller.js +0 -127
  171. package/dist/core/installers/PipInstaller.d.ts +0 -28
  172. package/dist/core/installers/PipInstaller.js +0 -127
  173. package/dist/core/installers/RequirementInstaller.d.ts +0 -33
  174. package/dist/core/installers/RequirementInstaller.js +0 -3
  175. package/dist/core/types.d.ts +0 -166
  176. package/dist/core/types.js +0 -16
  177. package/dist/services/InstallRequestValidator.d.ts +0 -21
  178. package/dist/services/InstallRequestValidator.js +0 -99
  179. package/dist/web/public/js/modal/installHandler.js +0 -227
  180. package/dist/web/public/js/modal/loadingUI.js +0 -74
  181. package/dist/web/public/js/modal/modalUI.js +0 -214
  182. package/dist/web/public/js/modal/version.js +0 -20
  183. package/src/web/public/js/modal/messageQueue.js +0 -112
@@ -3,6 +3,9 @@ import { showServerDetails } from './serverCategoryDetails.js'; // Still needed
3
3
  import { showToast } from './notifications.js';
4
4
  import { buildUrlWithFlights } from './flights/flights.js';
5
5
 
6
+ // Store state for categories
7
+ let pinnedCategories = {};
8
+
6
9
  // Wait for data to be loaded
7
10
  async function waitForData() {
8
11
  if (allServerCategoriesData && allServerCategoriesData.length > 0) {
@@ -12,6 +15,54 @@ async function waitForData() {
12
15
  return allServerCategoriesData && allServerCategoriesData.length > 0;
13
16
  }
14
17
 
18
+ // Load pinned state from localStorage
19
+ function loadPinnedState() {
20
+ const saved = localStorage.getItem('pinnedCategories');
21
+ if (saved) {
22
+ try {
23
+ pinnedCategories = JSON.parse(saved);
24
+ } catch (e) {
25
+ console.error('Error parsing pinned categories from localStorage:', e);
26
+ pinnedCategories = {};
27
+ }
28
+ }
29
+ }
30
+
31
+ // Save pinned state to localStorage
32
+ function savePinnedState() {
33
+ localStorage.setItem('pinnedCategories', JSON.stringify(pinnedCategories));
34
+ }
35
+
36
+ // Toggle pin status for a category
37
+ function togglePinCategory(categoryName, event) {
38
+ // Stop propagation to prevent navigation
39
+ event.stopPropagation();
40
+
41
+ // Toggle pin status
42
+ if (pinnedCategories[categoryName]) {
43
+ delete pinnedCategories[categoryName];
44
+ } else {
45
+ pinnedCategories[categoryName] = true;
46
+ }
47
+
48
+ // Save to localStorage
49
+ savePinnedState();
50
+
51
+ // Find the server item element and add a temporary animation class
52
+ const serverItem = event.target.closest('.server-item');
53
+ if (serverItem) {
54
+ serverItem.classList.add('pin-animation');
55
+ setTimeout(() => {
56
+ serverItem.classList.remove('pin-animation');
57
+ }, 300); // Animation duration
58
+ }
59
+
60
+ // Re-render list with updated pin status
61
+ if (allServerCategoriesData && allServerCategoriesData.length > 0) {
62
+ renderServerCategoryList(allServerCategoriesData);
63
+ }
64
+ }
65
+
15
66
  // Function to show the last selected category on page load
16
67
  async function loadLastSelectedCategory() {
17
68
  const lastSelected = localStorage.getItem('lastSelectedCategory');
@@ -33,8 +84,24 @@ function renderServerCategoryList(servers) {
33
84
  document.getElementById('serverCategoryDetails').innerHTML = '<p>Select a server from the list to see details.</p>'; // Clear details
34
85
  return;
35
86
  }
87
+
88
+ // Load pinned state
89
+ loadPinnedState();
90
+
91
+ // Create a copy of the servers array to avoid modifying the original
92
+ const sortedServers = [...servers];
93
+
94
+ // Sort servers with pinned ones at the top
95
+ sortedServers.sort((a, b) => {
96
+ const isPinnedA = pinnedCategories[a.name] === true;
97
+ const isPinnedB = pinnedCategories[b.name] === true;
98
+
99
+ if (isPinnedA && !isPinnedB) return -1;
100
+ if (!isPinnedA && isPinnedB) return 1;
101
+ return 0; // Keep original order for items with same pin status
102
+ });
36
103
 
37
- serverCategoryList.innerHTML = servers.map(server => {
104
+ serverCategoryList.innerHTML = sortedServers.map(server => {
38
105
  let statusHtml = '';
39
106
 
40
107
  // Add tool status summary if available
@@ -61,8 +128,8 @@ function renderServerCategoryList(servers) {
61
128
  statusText = "Partial Configured";
62
129
  } else {
63
130
  // No tools installed
64
- colorClass = "text-red-600 bg-red-50";
65
- icon = '<svg class="w-5 h-5 mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path></svg>';
131
+ colorClass = "text-yellow-600 bg-orange-50";
132
+ icon = '<svg class="w-5 h-5 mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM10 13a1 1 0 110-2 1 1 0 010 2zm-1.75-5.75a.75.75 0 00-1.5 0v3a.75.75 0 001.5 0v-3z" clip-rule="evenodd" /></svg>';
66
133
  statusText = "Not Configured";
67
134
  }
68
135
 
@@ -80,13 +147,27 @@ function renderServerCategoryList(servers) {
80
147
  }
81
148
  systemTagsHtml += '</div>';
82
149
  }
150
+ // Check if this server is pinned
151
+ const isPinned = pinnedCategories[server.name] === true;
152
+ const pinnedClass = isPinned ? 'pinned' : '';
153
+
154
+ // Pin/unpin button with appropriate icon
155
+ const pinIcon = isPinned
156
+ ? '<i class="bx bxs-pin"></i>' // Solid pin icon for pinned items
157
+ : '<i class="bx bx-pin"></i>'; // Outline pin icon for unpinned items
83
158
 
84
159
  return `
85
- <div class="server-item border border-gray-200 p-3 rounded hover:bg-gray-50 cursor-pointer transition duration-150 ease-in-out"
86
- onclick="navigateToCategory('${server.name}')"
160
+ <div class="server-item border border-gray-200 p-3 rounded hover:bg-gray-50 cursor-pointer transition duration-150 ease-in-out ${pinnedClass}"
87
161
  data-server-name="${server.name}">
88
- <h3 class="font-semibold text-gray-800">${server.displayName || server.name}</h3>
89
- <div class="text-sm text-gray-500 flex items-center mt-1">
162
+ <div class="flex justify-between items-center">
163
+ <h3 class="font-semibold text-gray-800" onclick="navigateToCategory('${server.name}')">${server.displayName || server.name}</h3>
164
+ <div class="flex items-center">
165
+ <div class="pin-button ${pinnedClass}" onclick="togglePinCategoryItem('${server.name}', event)" title="${isPinned ? 'Unpin' : 'Pin'} this category">
166
+ ${pinIcon}
167
+ </div>
168
+ </div>
169
+ </div>
170
+ <div class="text-sm text-gray-500 flex items-center mt-1" onclick="navigateToCategory('${server.name}')">
90
171
  ${statusHtml}
91
172
  ${systemTagsHtml}
92
173
  </div>
@@ -101,6 +182,9 @@ function renderServerCategoryList(servers) {
101
182
  // Setup search functionality
102
183
  function setupSearch() {
103
184
  const searchBox = document.getElementById('searchBox');
185
+
186
+ // Load pinned state on page load
187
+ loadPinnedState();
104
188
 
105
189
  searchBox.addEventListener('input', async function () {
106
190
  const searchTerm = this.value.toLowerCase();
@@ -151,7 +235,7 @@ function navigateToCategory(categoryName) {
151
235
  // Alternatively, attach event listeners dynamically after rendering the list.
152
236
  // For simplicity with current structure, we'll make it global.
153
237
  window.navigateToCategory = navigateToCategory;
154
-
238
+ window.togglePinCategoryItem = togglePinCategory;
155
239
 
156
240
  // Export functions
157
- export { renderServerCategoryList, setupSearch, loadLastSelectedCategory };
241
+ export { renderServerCategoryList, setupSearch, loadLastSelectedCategory, togglePinCategory };
@@ -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
+ });
@@ -106,8 +106,8 @@
106
106
  placeholder="e.g., Coder Tools">
107
107
  </div>
108
108
  <div class="md:col-span-2">
109
- <label class="block text-sm font-medium text-gray-700 mb-1">Description</label>
110
- <textarea name="description" rows="3"
109
+ <label class="block text-sm font-medium text-gray-700 mb-1">Description*</label>
110
+ <textarea name="description" rows="3" required
111
111
  class="w-full px-3 py-1.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
112
112
  placeholder="Describe the purpose and capabilities of this server category"></textarea>
113
113
  </div>
@@ -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
+ }