imcp 0.1.1 → 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 (111) hide show
  1. package/dist/cli/index.js +1 -45
  2. package/dist/core/installers/clients/BaseClientInstaller.d.ts +1 -5
  3. package/dist/core/installers/clients/BaseClientInstaller.js +40 -38
  4. package/dist/core/installers/clients/ClientInstaller.d.ts +9 -9
  5. package/dist/core/installers/clients/ClientInstaller.js +105 -99
  6. package/dist/core/installers/requirements/BaseInstaller.d.ts +9 -1
  7. package/dist/core/installers/requirements/CommandInstaller.d.ts +9 -1
  8. package/dist/core/installers/requirements/CommandInstaller.js +46 -12
  9. package/dist/core/installers/requirements/GeneralInstaller.d.ts +11 -1
  10. package/dist/core/installers/requirements/GeneralInstaller.js +46 -10
  11. package/dist/core/installers/requirements/InstallerFactory.d.ts +3 -1
  12. package/dist/core/installers/requirements/InstallerFactory.js +3 -2
  13. package/dist/core/installers/requirements/NpmInstaller.d.ts +4 -2
  14. package/dist/core/installers/requirements/NpmInstaller.js +38 -22
  15. package/dist/core/installers/requirements/PipInstaller.d.ts +3 -1
  16. package/dist/core/installers/requirements/PipInstaller.js +58 -36
  17. package/dist/core/installers/requirements/RequirementInstaller.d.ts +4 -1
  18. package/dist/core/loaders/InstallOperationManager.d.ts +115 -0
  19. package/dist/core/loaders/InstallOperationManager.js +311 -0
  20. package/dist/core/loaders/SystemSettingsManager.d.ts +54 -0
  21. package/dist/core/loaders/SystemSettingsManager.js +257 -0
  22. package/dist/core/metadatas/recordingConstants.d.ts +44 -0
  23. package/dist/core/metadatas/recordingConstants.js +45 -0
  24. package/dist/core/metadatas/types.d.ts +21 -0
  25. package/dist/core/onboard/InstallOperationManager.d.ts +23 -0
  26. package/dist/core/onboard/InstallOperationManager.js +144 -0
  27. package/dist/core/onboard/OnboardStatusManager.js +2 -1
  28. package/dist/core/validators/StdioServerValidator.js +4 -3
  29. package/dist/services/InstallationService.d.ts +2 -37
  30. package/dist/services/InstallationService.js +45 -313
  31. package/dist/services/MCPManager.d.ts +1 -1
  32. package/dist/services/MCPManager.js +4 -58
  33. package/dist/services/RequirementService.d.ts +85 -12
  34. package/dist/services/RequirementService.js +488 -49
  35. package/dist/services/ServerService.d.ts +0 -6
  36. package/dist/services/ServerService.js +0 -74
  37. package/dist/utils/adoUtils.js +6 -3
  38. package/dist/utils/logger.js +1 -1
  39. package/dist/utils/macroExpressionUtils.js +3 -25
  40. package/dist/utils/osUtils.d.ts +22 -1
  41. package/dist/utils/osUtils.js +92 -1
  42. package/dist/utils/versionUtils.d.ts +20 -1
  43. package/dist/utils/versionUtils.js +51 -4
  44. package/dist/web/public/css/modal.css +292 -1
  45. package/dist/web/public/css/serverDetails.css +14 -1
  46. package/dist/web/public/index.html +122 -20
  47. package/dist/web/public/js/flights/flights.js +1 -0
  48. package/dist/web/public/js/modal/index.js +8 -14
  49. package/dist/web/public/js/modal/installModal.js +3 -4
  50. package/dist/web/public/js/modal/installation.js +122 -137
  51. package/dist/web/public/js/modal/loadingModal.js +155 -25
  52. package/dist/web/public/js/modal/messageQueue.js +45 -101
  53. package/dist/web/public/js/modal/modalSetup.js +125 -43
  54. package/dist/web/public/js/modal/modalUtils.js +0 -12
  55. package/dist/web/public/js/modal.js +23 -10
  56. package/dist/web/public/js/onboard/publishHandler.js +22 -20
  57. package/dist/web/public/js/serverCategoryDetails.js +60 -11
  58. package/dist/web/public/js/serverCategoryList.js +2 -2
  59. package/dist/web/public/js/settings.js +314 -0
  60. package/dist/web/public/settings.html +135 -0
  61. package/dist/web/public/styles.css +32 -0
  62. package/dist/web/server.js +82 -0
  63. package/memory-bank/activeContext.md +13 -1
  64. package/memory-bank/decisionLog.md +63 -0
  65. package/memory-bank/progress.md +30 -0
  66. package/memory-bank/systemPatterns.md +7 -0
  67. package/package.json +1 -1
  68. package/src/cli/index.ts +1 -48
  69. package/src/core/installers/clients/BaseClientInstaller.ts +64 -50
  70. package/src/core/installers/clients/ClientInstaller.ts +130 -130
  71. package/src/core/installers/requirements/BaseInstaller.ts +9 -1
  72. package/src/core/installers/requirements/CommandInstaller.ts +47 -13
  73. package/src/core/installers/requirements/GeneralInstaller.ts +48 -10
  74. package/src/core/installers/requirements/InstallerFactory.ts +4 -3
  75. package/src/core/installers/requirements/NpmInstaller.ts +90 -68
  76. package/src/core/installers/requirements/PipInstaller.ts +81 -55
  77. package/src/core/installers/requirements/RequirementInstaller.ts +4 -3
  78. package/src/core/loaders/InstallOperationManager.ts +367 -0
  79. package/src/core/loaders/SystemSettingsManager.ts +278 -0
  80. package/src/core/metadatas/recordingConstants.ts +62 -0
  81. package/src/core/metadatas/types.ts +23 -0
  82. package/src/core/onboard/OnboardStatusManager.ts +2 -1
  83. package/src/core/validators/StdioServerValidator.ts +4 -3
  84. package/src/services/InstallationService.ts +54 -399
  85. package/src/services/MCPManager.ts +4 -77
  86. package/src/services/RequirementService.ts +564 -67
  87. package/src/services/ServerService.ts +0 -90
  88. package/src/utils/adoUtils.ts +6 -4
  89. package/src/utils/logger.ts +1 -1
  90. package/src/utils/macroExpressionUtils.ts +4 -21
  91. package/src/utils/osUtils.ts +92 -1
  92. package/src/utils/versionUtils.ts +71 -19
  93. package/src/web/public/css/modal.css +292 -1
  94. package/src/web/public/css/serverDetails.css +14 -1
  95. package/src/web/public/index.html +122 -20
  96. package/src/web/public/js/flights/flights.js +1 -1
  97. package/src/web/public/js/modal/index.js +8 -14
  98. package/src/web/public/js/modal/installModal.js +3 -4
  99. package/src/web/public/js/modal/installation.js +122 -137
  100. package/src/web/public/js/modal/loadingModal.js +155 -25
  101. package/src/web/public/js/modal/modalSetup.js +125 -43
  102. package/src/web/public/js/modal/modalUtils.js +0 -12
  103. package/src/web/public/js/modal.js +23 -10
  104. package/src/web/public/js/onboard/publishHandler.js +22 -20
  105. package/src/web/public/js/serverCategoryDetails.js +60 -11
  106. package/src/web/public/js/serverCategoryList.js +2 -2
  107. package/src/web/public/js/settings.js +314 -0
  108. package/src/web/public/settings.html +135 -0
  109. package/src/web/public/styles.css +32 -0
  110. package/src/web/server.ts +85 -0
  111. package/src/web/public/js/modal/messageQueue.js +0 -112
@@ -1,5 +1,5 @@
1
- import { delayedAppendInstallLoadingMessage } from './messageQueue.js';
2
- import { showInstallLoadingModal } from './loadingModal.js';
1
+ // import { delayedAppendInstallLoadingMessage } from './messageQueue.js'; // No longer used directly for steps
2
+ import { showInstallLoadingModal, updateOverallInstallStatus, addInstallationStep } from './loadingModal.js';
3
3
  import { showToast } from '../notifications.js';
4
4
 
5
5
  /**
@@ -19,6 +19,14 @@ export async function handleBulkClientInstall(categoryName, serverName, targets,
19
19
  console.log('[LoadingModal] installModal:', installModal);
20
20
  if (installModal) installModal.style.display = "none";
21
21
 
22
+ // Store category and server name for potential refresh
23
+ if (categoryName) {
24
+ localStorage.setItem('lastSelectedCategory', categoryName);
25
+ }
26
+ if (serverName) {
27
+ localStorage.setItem('lastSelectedServerName', serverName);
28
+ }
29
+
22
30
  // If serverData is provided, extract the installing message from it (latest status)
23
31
  if (serverData?.data?.installationStatus?.serversStatus) {
24
32
  const serverStatuses = serverData.data.installationStatus.serversStatus;
@@ -34,11 +42,11 @@ export async function handleBulkClientInstall(categoryName, serverName, targets,
34
42
  }
35
43
  }
36
44
 
37
- showInstallLoadingModal();
38
- delayedAppendInstallLoadingMessage(installingMessage);
45
+ showInstallLoadingModal(`Installing ${serverName}`); // Pass initial server name
46
+ // delayedAppendInstallLoadingMessage(installingMessage); // Old way
39
47
 
40
48
  try {
41
- delayedAppendInstallLoadingMessage("Installing, please wait...");
49
+ // delayedAppendInstallLoadingMessage("Installing, please wait..."); // Old way
42
50
 
43
51
  // Use serverInstallOptions if provided, otherwise build the traditional options
44
52
  const requestBody = {
@@ -63,7 +71,8 @@ export async function handleBulkClientInstall(categoryName, serverName, targets,
63
71
 
64
72
  if (!response.ok) {
65
73
  const errorData = await response.text();
66
- delayedAppendInstallLoadingMessage(`<span style="color:#f59e0b;">Failed: ${errorData || response.statusText}</span>`);
74
+ // delayedAppendInstallLoadingMessage(`<span style="color:#f59e0b;">Failed: ${errorData || response.statusText}</span>`); // Old way
75
+ updateOverallInstallStatus('failed', `Installation request failed: ${errorData || response.statusText}`);
67
76
  console.error('[LoadingModal] Failed:', errorData || response.statusText);
68
77
  throw new Error(`Installation failed: ${errorData || response.statusText}`);
69
78
  }
@@ -71,150 +80,86 @@ export async function handleBulkClientInstall(categoryName, serverName, targets,
71
80
  const result = await response.json();
72
81
  console.log('[LoadingModal] install result:', result);
73
82
  if (!result.success) {
74
- delayedAppendInstallLoadingMessage(`<span style="color:#f59e0b;">${result.error || 'Operation failed'}</span>`);
83
+ // delayedAppendInstallLoadingMessage(`<span style="color:#f59e0b;">${result.error || 'Operation failed'}</span>`); // Old way
84
+ updateOverallInstallStatus('failed', result.error || 'Installation operation failed to start.');
75
85
  console.error('[LoadingModal] Error:', result.error || 'Operation failed');
76
86
  throw new Error(result.error || 'Installation failed');
77
87
  }
78
88
 
79
- // Start polling for install status, pass requirements if available
80
- const requirements = serverInstallOptions?.requirements || [];
81
- pollInstallStatus(categoryName, serverName, targets, 2000, requirements);
89
+ // Start polling for install status
90
+ pollNewInstallStatus(categoryName, serverName, 2000); // New polling
82
91
  } catch (error) {
83
92
  console.error('[LoadingModal] Error applying configuration:', error);
84
- delayedAppendInstallLoadingMessage(`<span style="color:red;">Error: ${error.message}</span>`);
93
+ // delayedAppendInstallLoadingMessage(`<span style="color:red;">Error: ${error.message}</span>`); // Old way
94
+ updateOverallInstallStatus('failed', `Error: ${error.message}`);
85
95
  }
86
96
  }
87
97
 
88
98
  /**
89
- * Poll install status for the given server/targets and optional requirements
99
+ * Poll install status using the new API endpoint for detailed steps.
90
100
  * @param {string} categoryName - The category name
91
101
  * @param {string} serverName - The server name
92
- * @param {string[]} targets - Target clients
93
102
  * @param {number} interval - Polling interval in milliseconds
94
- * @param {Array} requirements - Requirements to check
95
103
  */
96
- async function pollInstallStatus(categoryName, serverName, targets, interval = 2000, requirements = []) {
97
- let lastMessages = {};
98
- let lastRequirementMessages = {};
99
- let lastReqirementErrorMessages = {};
104
+ async function pollNewInstallStatus(categoryName, serverName, interval = 2000) {
100
105
  let completionMessageSent = false;
101
-
102
106
  const startTime = Date.now();
103
- const maxTimeout = 10 * 60 * 1000; // 10 minutes in milliseconds
107
+ const maxTimeout = 10 * 60 * 1000; // 10 minutes
104
108
 
105
- while (Date.now() - startTime < maxTimeout) {
109
+ while (Date.now() - startTime < maxTimeout && !completionMessageSent) {
106
110
  try {
107
- const resp = await fetch(`/api/categories/${categoryName}`);
111
+ const resp = await fetch(`/api/categories/${categoryName}/servers/${serverName}/installation/status`);
108
112
  if (resp.ok) {
109
- const data = await resp.json();
110
- // If requirements is empty and we have targets, get requirements from server's dependencies
111
- if (requirements.length === 0 && targets?.length > 0) {
112
- const feedConfig = data?.data?.feedConfiguration;
113
- if (feedConfig?.mcpServers) {
114
- const server = feedConfig.mcpServers.find(s => s.name === serverName);
115
- if (server?.dependencies?.requirements) {
116
- requirements = server.dependencies.requirements;
117
- }
118
- }
119
- }
120
- const installationStatus = data?.data?.installationStatus || {};
121
- const serverStatuses = installationStatus.serversStatus || {};
122
- const requirementsStatus = installationStatus.requirementsStatus || {};
123
- const serverStatus = serverStatuses[serverName] || { installedStatus: {} };
124
-
125
- // First check requirements status if we have any
126
- let allRequirementsCompleted = true;
127
- let hasRequirements = requirements.length > 0;
128
-
129
- for (const req of requirements) {
130
- const reqStatus = requirementsStatus[req.name] || {};
131
- if (reqStatus.installed === true && !reqStatus.operationStatus) {
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
- continue;
138
- }
139
- const opStatus = reqStatus.operationStatus || {};
140
- const msg = opStatus.message;
141
- const status = opStatus.status;
142
-
143
- // Only append new messages for requirements
144
- if (msg && lastRequirementMessages[req.name] !== msg) {
145
- delayedAppendInstallLoadingMessage(`${req.name}: ${msg}`);
146
- lastRequirementMessages[req.name] = msg;
147
- }
148
-
149
- if (status !== "completed" && status !== "failed") {
150
- allRequirementsCompleted = false;
151
- }
152
-
153
- // If a requirement failed, show an error
154
- if (status === "failed") {
155
- if (lastReqirementErrorMessages[req.name] !== reqStatus.error) {
156
- delayedAppendInstallLoadingMessage(`${req.name} failed: ${reqStatus.error}`);
157
- lastReqirementErrorMessages[req.name] = reqStatus.error;
158
- }
159
- if (lastRequirementMessages[req.name] !== msg) {
160
- delayedAppendInstallLoadingMessage(`<span style="color:#f59e0b;">${req.name}: Update failed - ${msg || 'Unknown error'}</span>`);
161
- }
162
- }
163
- }
164
-
165
- // Now check target statuses
166
- let allTargetsCompleted = true;
167
- let hasTargets = targets && targets.length > 0;
168
-
169
- if (hasTargets) {
170
- for (const target of targets) {
171
- const status = serverStatus.installedStatus?.[target]?.status;
172
- const msg = serverStatus.installedStatus?.[target]?.message;
173
- // Only append new messages for targets, making output more compact
174
- if (msg && lastMessages[target] !== msg) {
175
- delayedAppendInstallLoadingMessage(`${target}: ${msg}`);
176
- lastMessages[target] = msg;
177
- }
178
- if (status !== "completed" && status !== "failed") {
179
- allTargetsCompleted = false;
180
- }
113
+ const result = await resp.json();
114
+ if (result.success && result.data) {
115
+ const { steps, overallStatus, error } = result.data;
116
+
117
+ updateOverallInstallStatus(overallStatus, error || `Installation ${overallStatus}`);
118
+
119
+ // Always update or insert each step, so UI stays in sync with backend
120
+ steps.forEach(step => {
121
+ addInstallationStep(step.name, step.message, step.status, step.timestamp);
122
+ });
123
+
124
+ if (overallStatus === 'completed' || overallStatus === 'failed') {
125
+ completionMessageSent = true;
126
+ // The hideInstallLoadingModal will be called by the user or after a timeout.
127
+ // We can add a "Close" button or auto-close logic here if needed.
128
+ // For now, just stop polling.
129
+ // Ensure final status is displayed
130
+ const finalMessage = overallStatus === 'completed' ?
131
+ `Installation completed successfully for ${serverName}.` :
132
+ `Installation failed for ${serverName}`;
133
+ updateOverallInstallStatus(overallStatus, finalMessage);
134
+ return;
181
135
  }
182
- }
183
-
184
- // Complete if all operations are done
185
- const allCompleted = (!hasRequirements || allRequirementsCompleted) &&
186
- (!hasTargets || allTargetsCompleted);
187
-
188
- if (allCompleted && !completionMessageSent) {
189
- completionMessageSent = true;
190
-
191
- // Compose completion message
192
- const completionMessage = hasRequirements && hasTargets ?
193
- `Updated requirements and configured ${targets.length} client(s)` :
194
- hasRequirements ? 'Requirements updated' :
195
- hasTargets ? `Configured ${targets.length} client(s)` :
196
- 'Operation completed';
197
-
198
- console.log('[LoadingModal] Completed:', completionMessage);
199
- delayedAppendInstallLoadingMessage(`<span style="color:green;">${completionMessage}</span>`, true);
200
-
201
- // Give time for message queue to process before returning
202
- await new Promise(resolve => setTimeout(resolve, 1500));
136
+ } else if (!result.success && resp.status === 404) {
137
+ // Still waiting for the status file to be created.
138
+ updateOverallInstallStatus('in-progress', `Waiting for installation to start for ${serverName}...`);
139
+ } else if (!result.success) {
140
+ updateOverallInstallStatus('failed', `Error fetching status: ${result.error || 'Unknown error'}`);
141
+ completionMessageSent = true; // Stop polling on error
203
142
  return;
204
143
  }
144
+ } else if (resp.status === 404) {
145
+ updateOverallInstallStatus('in-progress', `Installation status not yet available for ${serverName}. Retrying...`);
146
+ } else {
147
+ const errorText = await resp.text();
148
+ updateOverallInstallStatus('failed', `Error fetching status: ${errorText || resp.statusText}`);
149
+ completionMessageSent = true; // Stop polling on error
150
+ return;
205
151
  }
206
152
  } catch (error) {
207
- console.error('[LoadingModal] Error polling status:', error);
153
+ console.error('[LoadingModal] Error polling new install status:', error);
154
+ updateOverallInstallStatus('failed', `Error polling status: ${error.message}`);
155
+ completionMessageSent = true; // Stop polling on error
156
+ return;
208
157
  }
209
- await new Promise(resolve => setTimeout(resolve, interval)); // 2 second interval
158
+ await new Promise(resolve => setTimeout(resolve, interval));
210
159
  }
211
160
 
212
- // If we reach here, we've timed out
213
- console.error('[LoadingModal] Operation timed out after 10 minutes');
214
-
215
- // On timeout, show timeout message but don't auto-close
216
161
  if (!completionMessageSent) {
217
- delayedAppendInstallLoadingMessage(`<span style="color:#f59e0b;">Operation timed out - Please refresh the page</span>`, true);
162
+ updateOverallInstallStatus('failed', `Operation timed out for ${serverName} - Please check logs or refresh.`);
218
163
  }
219
164
  }
220
165
 
@@ -225,6 +170,14 @@ async function pollInstallStatus(categoryName, serverName, targets, interval = 2
225
170
  * @param {string[]} targets - Target clients
226
171
  */
227
172
  export async function uninstallTools(categoryName, serverList, targets) {
173
+
174
+ console.log('[LoadingModal] uninstallTools called', { categoryName, serverList, targets });
175
+ // Store category and server name for potential refresh
176
+ if (categoryName) {
177
+ console.log('[LoadingModal] Setting lastSelectedCategory:', categoryName);
178
+ localStorage.setItem('lastSelectedCategory', categoryName);
179
+ }
180
+
228
181
  // Get selected targets from window.selectedClients or the provided targets
229
182
  const selectedTargets = window.selectedClients || (Array.isArray(targets) ? targets : [targets]);
230
183
 
@@ -234,17 +187,26 @@ export async function uninstallTools(categoryName, serverList, targets) {
234
187
  return;
235
188
  }
236
189
 
237
- try {
238
- delayedAppendInstallLoadingMessage('Starting uninstallation...');
190
+ // Ensure showInstallLoadingModal is called if not already visible,
191
+ // or ensure overall status is updated if it is.
192
+ // For simplicity, we assume the modal is shown by the caller (e.g., modalSetup.js)
193
+ // Add uninstall started step
194
+ if (typeof addInstallationStep === 'function') {
195
+ addInstallationStep(
196
+ 'Uninstalling',
197
+ `Uninstallation process started for ${Object.keys(serverList).join(', ')}`,
198
+ 'in-progress',
199
+ new Date().toISOString()
200
+ );
201
+ }
202
+ updateOverallInstallStatus('in-progress', `Starting uninstallation for ${Object.keys(serverList).join(', ')}...`);
239
203
 
240
- // Ensure serverList is an object where each key is a server name
241
- if (Array.isArray(serverList)) {
242
- const formattedServerList = {};
243
- serverList.forEach(server => {
244
- formattedServerList[server] = { removeData: true };
245
- });
246
- serverList = formattedServerList;
247
- }
204
+ try {
205
+ const serverListKeys = Object.keys(serverList);
206
+ serverListKeys.forEach(server => {
207
+ console.log('[LoadingModal] Setting lastSelectedServerName:', server);
208
+ localStorage.setItem('lastSelectedServerName', server);
209
+ });
248
210
 
249
211
  const response = await fetch(`/api/categories/${categoryName}/uninstall`, {
250
212
  method: 'POST',
@@ -260,22 +222,45 @@ export async function uninstallTools(categoryName, serverList, targets) {
260
222
 
261
223
  if (!response.ok) {
262
224
  const errorData = await response.text();
225
+ updateOverallInstallStatus('failed', `Uninstallation request failed: ${errorData || response.statusText}`);
263
226
  throw new Error(`Uninstallation failed: ${errorData || response.statusText}`);
264
227
  }
265
228
 
266
229
  const result = await response.json();
267
230
  if (!result.success) {
231
+ updateOverallInstallStatus('failed', `Uninstallation failed: ${result.error || 'Operation failed'}`);
268
232
  throw new Error(result.error || 'Uninstallation failed');
269
233
  }
270
234
 
271
- // Add completion message and trigger completion UI
272
- delayedAppendInstallLoadingMessage(`Successfully uninstalled from ${selectedTargets.join(', ')}`, true);
235
+ // Add uninstall finished step with 1s delay before updating overall status
236
+ if (typeof addInstallationStep === 'function') {
237
+ await new Promise(resolve => setTimeout(resolve, 1000));
238
+ addInstallationStep(
239
+ 'Uninstalling',
240
+ `Uninstallation process finished for ${Object.keys(serverList).join(', ')}`,
241
+ 'completed',
242
+ new Date().toISOString()
243
+ );
244
+ await new Promise(resolve => setTimeout(resolve, 1000));
245
+ addInstallationStep(
246
+ 'Uninstall Finished',
247
+ `Uninstallation finished for ${Object.keys(serverList).join(', ')}`,
248
+ 'completed',
249
+ new Date().toISOString()
250
+ );
251
+ }
252
+ await new Promise(resolve => setTimeout(resolve, 1000));
253
+ updateOverallInstallStatus('completed', `Successfully uninstalled from ${selectedTargets.join(', ')}.`);
254
+ window.selectedClients = []; // Clear selected clients
273
255
 
274
- // Clear selected clients after successful uninstall
275
- window.selectedClients = [];
276
256
  } catch (error) {
277
257
  console.error('Error uninstalling tools:', error);
278
- delayedAppendInstallLoadingMessage(`Error: ${error.message}`, true);
258
+ // The status should have been updated by the specific error handlers above.
259
+ // If an error occurs before those, update status here.
260
+ const overallStatusTextEl = document.getElementById('overallStatusText'); // Check if element exists
261
+ if (overallStatusTextEl && overallStatusTextEl.textContent.startsWith('Starting uninstallation')) {
262
+ updateOverallInstallStatus('failed', `Error uninstalling tools: ${error.message}`);
263
+ }
279
264
  showToast(`Error uninstalling tools: ${error.message}`, 'error');
280
265
  }
281
266
  }
@@ -1,31 +1,164 @@
1
- import { delayedAppendInstallLoadingMessage } from './messageQueue.js';
1
+ // import { delayedAppendInstallLoadingMessage } from './messageQueue.js'; // Not directly used here anymore for appending
2
+
3
+ const ICONS = {
4
+ IN_PROGRESS: '<div class="status-spinner"></div>',
5
+ COMPLETED: '<div class="status-icon-circled icon-check-container"><span class="status-icon icon-check">✓</span></div>',
6
+ FAILED: '<span class="status-icon icon-cross">✗</span>', // Assuming cross doesn't need a circle for now
7
+ CANCELED: '<span class="status-icon icon-minus">–</span>', // Simple minus for canceled
8
+ };
9
+
10
+ let overallStatusIconEl, overallStatusTextEl, installStepDetailsListEl;
2
11
 
3
12
  /**
4
13
  * Display the installation loading modal and prepare it for messages.
5
- * @param {string} [operation='Installing'] - The operation being performed
14
+ * @param {string} [initialOperationText='Installing'] - The initial overall operation text.
6
15
  */
7
- export function showInstallLoadingModal(operation = 'Installing') {
16
+ export function showInstallLoadingModal(initialOperationText = 'Installing') {
8
17
  const loadingModal = document.getElementById('installLoadingModal');
9
- const loadingMsg = document.getElementById('installLoadingMessage');
10
- const loadingTitle = document.querySelector('.loading-title');
11
- if (loadingModal && loadingMsg && loadingTitle) {
18
+ // Assuming modal-content is the direct child where content should be placed.
19
+ const modalContent = loadingModal ? loadingModal.querySelector('.modal-content') : null;
20
+
21
+ if (loadingModal && modalContent) {
22
+ modalContent.innerHTML = `
23
+ <button id="installLoadingModalCloseBtn" class="modal-close-btn">&times;</button>
24
+ <div class="installation-status-header">
25
+ <div id="overallStatusIconContainer" class="overall-status-icon">
26
+ ${ICONS.IN_PROGRESS}
27
+ </div>
28
+ <h3 id="overallStatusText" class="overall-status-text">${initialOperationText}...</h3>
29
+ </div>
30
+ <div class="installation-steps-container">
31
+ <ul id="installStepDetailsList" class="install-step-details-list"></ul>
32
+ </div>
33
+ `;
34
+
35
+ overallStatusIconEl = modalContent.querySelector('#overallStatusIconContainer');
36
+ overallStatusTextEl = modalContent.querySelector('#overallStatusText');
37
+ installStepDetailsListEl = modalContent.querySelector('#installStepDetailsList');
38
+
39
+ const closeButton = modalContent.querySelector('#installLoadingModalCloseBtn');
40
+ if (closeButton) {
41
+ closeButton.addEventListener('click', hideInstallLoadingModal);
42
+ }
43
+
12
44
  loadingModal.style.display = 'block';
13
- loadingMsg.innerHTML = '';
14
- loadingTitle.textContent = `${operation}...`;
15
45
  } else {
16
- console.error('[LoadingModal] Required elements not found: installLoadingModal, installLoadingMessage, or loading-title');
46
+ console.error('[LoadingModal] Required elements not found: #installLoadingModal or .modal-content');
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Updates the overall status display in the loading modal.
52
+ * @param {'in-progress' | 'completed' | 'failed'} status - The overall status.
53
+ * @param {string} message - The message to display for the overall status.
54
+ */
55
+ export function updateOverallInstallStatus(status, message) {
56
+ if (!overallStatusIconEl || !overallStatusTextEl) {
57
+ // console.warn('[LoadingModal] Overall status elements not initialized or found.');
58
+ return;
59
+ }
60
+
61
+ let iconHtml = ICONS.IN_PROGRESS;
62
+ let statusClass = 'in-progress';
63
+
64
+ switch (status) {
65
+ case 'completed':
66
+ iconHtml = ICONS.COMPLETED;
67
+ statusClass = 'completed';
68
+ break;
69
+ case 'failed':
70
+ iconHtml = ICONS.FAILED;
71
+ statusClass = 'failed';
72
+ break;
73
+ case 'in-progress':
74
+ default:
75
+ // iconHtml and statusClass already set
76
+ break;
17
77
  }
78
+
79
+ overallStatusIconEl.innerHTML = iconHtml;
80
+ overallStatusIconEl.className = `overall-status-icon ${statusClass}`; // Update class for styling
81
+ overallStatusTextEl.textContent = message || (status.charAt(0).toUpperCase() + status.slice(1));
18
82
  }
19
83
 
20
84
  /**
21
- * Append a message to the loading modal display.
22
- * @param {string} message - The message to display
85
+ * Adds a step to the installation details list.
86
+ * @param {string} stepName - The name/title of the step.
87
+ * @param {string} message - The message associated with the step.
88
+ * @param {'pending'|'in-progress'|'completed'|'failed'|'canceled'} status - The status of the step.
89
+ * @param {string} timestamp - ISO timestamp string for the step.
23
90
  */
24
- export function appendInstallLoadingMessage(message) {
25
- console.log(`[LoadingModal] Message: ${message}`);
26
- delayedAppendInstallLoadingMessage(message);
91
+ export function addInstallationStep(stepName, message, status, timestamp) {
92
+ if (!installStepDetailsListEl) {
93
+ return;
94
+ }
95
+
96
+ // Use stepName as a unique identifier for the step
97
+ const stepId = `install-step-${stepName.replace(/[^a-zA-Z0-9_-]/g, '_')}`;
98
+ let listItem = document.getElementById(stepId);
99
+
100
+ let statusClass = '';
101
+ let stepIcon = '';
102
+
103
+ switch (status) {
104
+ case 'completed':
105
+ statusClass = 'success';
106
+ stepIcon = '<span class="step-icon icon-check-small">✓</span>';
107
+ break;
108
+ case 'failed':
109
+ statusClass = 'error';
110
+ stepIcon = '<span class="step-icon icon-cross-small">✗</span>';
111
+ break;
112
+ case 'canceled':
113
+ statusClass = 'canceled';
114
+ stepIcon = '<span class="step-icon icon-minus-small">–</span>';
115
+ break;
116
+ case 'in-progress':
117
+ case 'pending':
118
+ default:
119
+ statusClass = 'in-progress';
120
+ stepIcon = '<div class="status-spinner step-spinner"></div>';
121
+ break;
122
+ }
123
+
124
+ let timeString = '';
125
+ if (timestamp) {
126
+ try {
127
+ timeString = new Date(timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
128
+ } catch (e) { /* ignore */ }
129
+ }
130
+
131
+ const innerHTML = `
132
+ ${stepIcon}
133
+ <div class="step-info">
134
+ <span class="step-name">${stepName}</span>
135
+ <span class="step-message">${message || (
136
+ status === 'completed' ? 'Completed' :
137
+ status === 'failed' ? 'Failed' :
138
+ status === 'canceled' ? 'Canceled' :
139
+ 'In Progress'
140
+ )}</span>
141
+ </div>
142
+ <span class="step-timestamp">${timeString}</span>
143
+ `;
144
+
145
+ if (listItem) {
146
+ // Update existing step
147
+ listItem.className = `step-detail-item ${statusClass}`;
148
+ listItem.innerHTML = innerHTML;
149
+ } else {
150
+ // Create new step
151
+ listItem = document.createElement('li');
152
+ listItem.id = stepId;
153
+ listItem.className = `step-detail-item ${statusClass}`;
154
+ listItem.innerHTML = innerHTML;
155
+ installStepDetailsListEl.appendChild(listItem);
156
+ // Scroll to the bottom of the list
157
+ installStepDetailsListEl.scrollTop = installStepDetailsListEl.scrollHeight;
158
+ }
27
159
  }
28
160
 
161
+
29
162
  /**
30
163
  * Hide the install loading modal.
31
164
  */
@@ -35,17 +168,14 @@ export function hideInstallLoadingModal() {
35
168
  if (loadingModal) {
36
169
  loadingModal.style.display = 'none';
37
170
 
38
- // Get the last selected category
39
- const lastSelected = localStorage.getItem('lastSelectedCategory');
40
-
41
- // Refresh page while maintaining category selection
42
- setTimeout(() => {
43
- if (lastSelected) {
44
- window.location.href = window.location.pathname + '?category=' + encodeURIComponent(lastSelected);
45
- } else {
46
- location.reload();
47
- }
48
- }, 100);
171
+ // Clear references to avoid stale elements if modal is reshown
172
+ overallStatusIconEl = null;
173
+ overallStatusTextEl = null;
174
+ installStepDetailsListEl = null;
175
+
176
+ // Dispatch an event to signal that the main modal content should be refreshed
177
+ document.dispatchEvent(new CustomEvent('refreshMainModalContent'));
178
+ console.log('[LoadingModal] Dispatched refreshMainModalContent event.');
49
179
  } else {
50
180
  console.error('[LoadingModal] loading modal DOM not found');
51
181
  }