imcp 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/ConfigurationProvider.d.ts +1 -0
- package/dist/core/ConfigurationProvider.js +15 -0
- package/dist/core/InstallationService.js +2 -7
- package/dist/core/MCPManager.d.ts +11 -2
- package/dist/core/MCPManager.js +24 -1
- package/dist/core/RequirementService.js +2 -8
- package/dist/core/installers/clients/BaseClientInstaller.d.ts +51 -0
- package/dist/core/installers/clients/BaseClientInstaller.js +160 -0
- package/dist/core/installers/clients/ClientInstaller.d.ts +16 -8
- package/dist/core/installers/clients/ClientInstaller.js +77 -504
- package/dist/core/installers/clients/ClientInstallerFactory.d.ts +19 -0
- package/dist/core/installers/clients/ClientInstallerFactory.js +41 -0
- package/dist/core/installers/clients/ClineInstaller.d.ts +18 -0
- package/dist/core/installers/clients/ClineInstaller.js +124 -0
- package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +34 -0
- package/dist/core/installers/clients/GithubCopilotInstaller.js +162 -0
- package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +15 -0
- package/dist/core/installers/clients/MSRooCodeInstaller.js +122 -0
- package/dist/core/installers/requirements/BaseInstaller.d.ts +11 -34
- package/dist/core/installers/requirements/BaseInstaller.js +5 -116
- package/dist/core/installers/requirements/CommandInstaller.d.ts +6 -1
- package/dist/core/installers/requirements/CommandInstaller.js +7 -0
- package/dist/core/installers/requirements/GeneralInstaller.d.ts +6 -1
- package/dist/core/installers/requirements/GeneralInstaller.js +9 -4
- package/dist/core/installers/requirements/NpmInstaller.d.ts +46 -7
- package/dist/core/installers/requirements/NpmInstaller.js +150 -58
- package/dist/core/installers/requirements/PipInstaller.d.ts +9 -0
- package/dist/core/installers/requirements/PipInstaller.js +66 -28
- package/dist/core/onboard/FeedOnboardService.d.ts +72 -0
- package/dist/core/onboard/FeedOnboardService.js +312 -0
- package/dist/core/onboard/OnboardProcessor.d.ts +79 -0
- package/dist/core/onboard/OnboardProcessor.js +290 -0
- package/dist/core/onboard/OnboardStatus.d.ts +49 -0
- package/dist/core/onboard/OnboardStatus.js +10 -0
- package/dist/core/onboard/OnboardStatusManager.d.ts +57 -0
- package/dist/core/onboard/OnboardStatusManager.js +176 -0
- package/dist/core/types.d.ts +6 -6
- package/dist/core/validators/FeedValidator.d.ts +20 -0
- package/dist/core/validators/FeedValidator.js +80 -0
- package/dist/core/validators/IServerValidator.d.ts +19 -0
- package/dist/core/validators/IServerValidator.js +2 -0
- package/dist/core/validators/SSEServerValidator.d.ts +15 -0
- package/dist/core/validators/SSEServerValidator.js +39 -0
- package/dist/core/validators/ServerValidatorFactory.d.ts +24 -0
- package/dist/core/validators/ServerValidatorFactory.js +45 -0
- package/dist/core/validators/StdioServerValidator.d.ts +46 -0
- package/dist/core/validators/StdioServerValidator.js +229 -0
- package/dist/services/InstallRequestValidator.d.ts +1 -1
- package/dist/services/ServerService.d.ts +9 -6
- package/dist/services/ServerService.js +18 -7
- package/dist/utils/adoUtils.d.ts +29 -0
- package/dist/utils/adoUtils.js +252 -0
- package/dist/utils/clientUtils.d.ts +0 -7
- package/dist/utils/clientUtils.js +0 -42
- package/dist/utils/githubUtils.d.ts +10 -0
- package/dist/utils/githubUtils.js +22 -0
- package/dist/utils/macroExpressionUtils.d.ts +38 -0
- package/dist/utils/macroExpressionUtils.js +116 -0
- package/dist/utils/osUtils.d.ts +4 -20
- package/dist/utils/osUtils.js +78 -23
- package/dist/web/contract/serverContract.d.ts +66 -0
- package/dist/web/contract/serverContract.js +2 -0
- package/dist/web/public/css/notifications.css +48 -17
- package/dist/web/public/css/onboard.css +107 -0
- package/dist/web/public/index.html +90 -18
- package/dist/web/public/js/api.js +3 -6
- package/dist/web/public/js/flights/flights.js +127 -0
- package/dist/web/public/js/modal/index.js +58 -0
- package/dist/web/public/js/modal/installHandler.js +227 -0
- package/dist/web/public/js/modal/installModal.js +163 -0
- package/dist/web/public/js/modal/installation.js +281 -0
- package/dist/web/public/js/modal/loadingModal.js +52 -0
- package/dist/web/public/js/modal/loadingUI.js +74 -0
- package/dist/web/public/js/modal/messageQueue.js +112 -0
- package/dist/web/public/js/modal/modalSetup.js +513 -0
- package/dist/web/public/js/modal/modalUI.js +214 -0
- package/dist/web/public/js/modal/modalUtils.js +49 -0
- package/dist/web/public/js/modal/version.js +20 -0
- package/dist/web/public/js/modal/versionUtils.js +20 -0
- package/dist/web/public/js/modal.js +25 -1041
- package/dist/web/public/js/notifications.js +66 -27
- package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
- package/dist/web/public/js/onboard/formProcessor.js +864 -0
- package/dist/web/public/js/onboard/index.js +374 -0
- package/dist/web/public/js/onboard/publishHandler.js +132 -0
- package/dist/web/public/js/onboard/state.js +76 -0
- package/dist/web/public/js/onboard/templates.js +343 -0
- package/dist/web/public/js/onboard/uiHandlers.js +758 -0
- package/dist/web/public/js/onboard/validationHandlers.js +378 -0
- package/dist/web/public/js/serverCategoryDetails.js +43 -17
- package/dist/web/public/js/serverCategoryList.js +15 -2
- package/dist/web/public/onboard.html +296 -0
- package/dist/web/public/styles.css +91 -1
- package/dist/web/server.d.ts +0 -10
- package/dist/web/server.js +131 -22
- package/package.json +2 -2
- package/src/core/ConfigurationProvider.ts +15 -0
- package/src/core/InstallationService.ts +2 -7
- package/src/core/MCPManager.ts +26 -1
- package/src/core/RequirementService.ts +2 -9
- package/src/core/installers/clients/BaseClientInstaller.ts +196 -0
- package/src/core/installers/clients/ClientInstaller.ts +97 -589
- package/src/core/installers/clients/ClientInstallerFactory.ts +46 -0
- package/src/core/installers/clients/ClineInstaller.ts +135 -0
- package/src/core/installers/clients/GithubCopilotInstaller.ts +179 -0
- package/src/core/installers/clients/MSRooCodeInstaller.ts +133 -0
- package/src/core/installers/requirements/BaseInstaller.ts +13 -136
- package/src/core/installers/requirements/CommandInstaller.ts +9 -1
- package/src/core/installers/requirements/GeneralInstaller.ts +11 -4
- package/src/core/installers/requirements/NpmInstaller.ts +178 -61
- package/src/core/installers/requirements/PipInstaller.ts +68 -29
- package/src/core/onboard/FeedOnboardService.ts +346 -0
- package/src/core/onboard/OnboardProcessor.ts +305 -0
- package/src/core/onboard/OnboardStatus.ts +55 -0
- package/src/core/onboard/OnboardStatusManager.ts +188 -0
- package/src/core/types.ts +6 -6
- package/src/core/validators/FeedValidator.ts +79 -0
- package/src/core/validators/IServerValidator.ts +21 -0
- package/src/core/validators/SSEServerValidator.ts +43 -0
- package/src/core/validators/ServerValidatorFactory.ts +51 -0
- package/src/core/validators/StdioServerValidator.ts +259 -0
- package/src/services/InstallRequestValidator.ts +1 -1
- package/src/services/ServerService.ts +22 -7
- package/src/utils/adoUtils.ts +291 -0
- package/src/utils/clientUtils.ts +0 -44
- package/src/utils/githubUtils.ts +24 -0
- package/src/utils/macroExpressionUtils.ts +121 -0
- package/src/utils/osUtils.ts +89 -24
- package/src/web/contract/serverContract.ts +74 -0
- package/src/web/public/css/notifications.css +48 -17
- package/src/web/public/css/onboard.css +107 -0
- package/src/web/public/index.html +90 -18
- package/src/web/public/js/api.js +3 -6
- package/src/web/public/js/flights/flights.js +127 -0
- package/src/web/public/js/modal/index.js +58 -0
- package/src/web/public/js/modal/installModal.js +163 -0
- package/src/web/public/js/modal/installation.js +281 -0
- package/src/web/public/js/modal/loadingModal.js +52 -0
- package/src/web/public/js/modal/messageQueue.js +112 -0
- package/src/web/public/js/modal/modalSetup.js +513 -0
- package/src/web/public/js/modal/modalUtils.js +49 -0
- package/src/web/public/js/modal/versionUtils.js +20 -0
- package/src/web/public/js/modal.js +25 -1041
- package/src/web/public/js/notifications.js +66 -27
- package/src/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
- package/src/web/public/js/onboard/formProcessor.js +864 -0
- package/src/web/public/js/onboard/index.js +374 -0
- package/src/web/public/js/onboard/publishHandler.js +132 -0
- package/src/web/public/js/onboard/state.js +76 -0
- package/src/web/public/js/onboard/templates.js +343 -0
- package/src/web/public/js/onboard/uiHandlers.js +758 -0
- package/src/web/public/js/onboard/validationHandlers.js +378 -0
- package/src/web/public/js/serverCategoryDetails.js +43 -17
- package/src/web/public/js/serverCategoryList.js +15 -2
- package/src/web/public/onboard.html +296 -0
- package/src/web/public/styles.css +91 -1
- package/src/web/server.ts +167 -58
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
// src/web/public/js/onboard/validationHandlers.js
|
|
2
|
+
import { showToast } from '../notifications.js';
|
|
3
|
+
import { getFormData } from './formProcessor.js';
|
|
4
|
+
import { toggleSectionContent } from './uiHandlers.js'; // Assuming this is correctly in uiHandlers.js and exported
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Shows validation message under an input field.
|
|
8
|
+
* @param {HTMLElement} element - The input element to show validation message for.
|
|
9
|
+
* @param {string} message - The validation message to display.
|
|
10
|
+
* @param {boolean} isError - Whether this is an error message (true) or success message (false).
|
|
11
|
+
*/
|
|
12
|
+
function showValidationMessage(element, message, isError = true) {
|
|
13
|
+
// Remove any existing validation message
|
|
14
|
+
const existingMessage = element.nextElementSibling;
|
|
15
|
+
if (existingMessage && existingMessage.classList.contains('validation-message')) {
|
|
16
|
+
existingMessage.remove();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Create new validation message element
|
|
20
|
+
const messageDiv = document.createElement('div');
|
|
21
|
+
messageDiv.className = `validation-message text-xs mt-1 ${isError ? 'text-red-500' : 'text-green-500'}`;
|
|
22
|
+
messageDiv.textContent = message;
|
|
23
|
+
|
|
24
|
+
// Insert the message after the input element
|
|
25
|
+
element.insertAdjacentElement('afterend', messageDiv);
|
|
26
|
+
|
|
27
|
+
// Add visual feedback to the input field
|
|
28
|
+
if (isError) {
|
|
29
|
+
element.classList.add('border-red-500');
|
|
30
|
+
element.classList.remove('border-green-500');
|
|
31
|
+
} else {
|
|
32
|
+
element.classList.add('border-green-500');
|
|
33
|
+
element.classList.remove('border-red-500');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const POLLING_INTERVAL = 3000; // 3 seconds
|
|
38
|
+
let pollingIntervalId = null;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Updates the operation status display.
|
|
42
|
+
* @param {object} data - The data object from the API response.
|
|
43
|
+
* @param {HTMLElement} contentElement - The HTML element to update.
|
|
44
|
+
*/
|
|
45
|
+
export function updateOperationDisplay(data, contentElement) { // Renamed from updateValidationDisplay
|
|
46
|
+
const { status, message, validationStatus, prInfo, errorMessage: operationErrorMessage, operationType } = data;
|
|
47
|
+
let htmlContent = `<p><strong>Overall Status:</strong> <span class="${status === 'COMPLETED' || status === 'succeeded' ? 'text-green-600' : (status === 'FAILED' ? 'text-red-600' : 'text-yellow-600')}">${status} ${(status === 'IN_PROGRESS' || status === 'PENDING' || status === 'VALIDATING' || status === 'PUBLISHING') ? "<i class='bx bx-loader-alt bx-spin ml-2'></i>" : ""}</span></p>`;
|
|
48
|
+
if (operationType) htmlContent += `<p><strong>Operation Type:</strong> ${operationType}</p>`;
|
|
49
|
+
if (message) htmlContent += `<p><strong>Message:</strong> ${message}</p>`;
|
|
50
|
+
if (operationErrorMessage) htmlContent += `<p class="text-red-500"><strong>Error:</strong> ${operationErrorMessage}</p>`;
|
|
51
|
+
if (validationStatus) {
|
|
52
|
+
htmlContent += '<h4 class="font-semibold mt-3 mb-1">Detailed Validation:</h4>';
|
|
53
|
+
htmlContent += '<ul class="list-disc list-inside space-y-1">';
|
|
54
|
+
let hasDetailedItems = false;
|
|
55
|
+
// Check for serverResults array first
|
|
56
|
+
if (Array.isArray(validationStatus.serverResults)) {
|
|
57
|
+
validationStatus.serverResults.forEach(serverResult => {
|
|
58
|
+
if (serverResult && typeof serverResult.serverName === 'string' && typeof serverResult.isValid === 'boolean') {
|
|
59
|
+
hasDetailedItems = true;
|
|
60
|
+
const icon = serverResult.isValid ? "<i class='bx bx-check-circle text-green-500'></i>" : "<i class='bx bx-x-circle text-red-500'></i>";
|
|
61
|
+
let serverMessage = serverResult.message ? `: ${serverResult.message}` : (serverResult.isValid ? ': Valid' : ': Invalid');
|
|
62
|
+
htmlContent += `<li>${icon} <strong>Server "${serverResult.serverName}"</strong>${serverMessage}</li>`;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Fallback for other keys in validationStatus or if serverResults is not the primary detail
|
|
68
|
+
for (const key in validationStatus) {
|
|
69
|
+
if (key === 'serverResults') continue; // Already processed
|
|
70
|
+
|
|
71
|
+
const item = validationStatus[key];
|
|
72
|
+
// Handle simple {isValid, message} structure directly under validationStatus
|
|
73
|
+
if (key === 'isValid' && typeof item === 'boolean' && typeof validationStatus.message === 'string' && !hasDetailedItems) {
|
|
74
|
+
hasDetailedItems = true;
|
|
75
|
+
const icon = item ? "<i class='bx bx-check-circle text-green-500'></i>" : "<i class='bx bx-x-circle text-red-500'></i>";
|
|
76
|
+
htmlContent += `<li>${icon} <strong>Overall Validation:</strong> ${validationStatus.message}</li>`;
|
|
77
|
+
break; // Assume this is the main summary if serverResults wasn't present or detailed
|
|
78
|
+
} else if (typeof item === 'object' && item !== null && 'isValid' in item && 'message' in item) {
|
|
79
|
+
// Handle other named detailed items
|
|
80
|
+
hasDetailedItems = true;
|
|
81
|
+
const icon = item.isValid ? "<i class='bx bx-check-circle text-green-500'></i>" : "<i class='bx bx-x-circle text-red-500'></i>";
|
|
82
|
+
htmlContent += `<li>${icon} <strong>${key.replace(/([A-Z])/g, ' $1').trim()}:</strong> ${item.message}</li>`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!hasDetailedItems && validationStatus.isValid === true && validationStatus.message) {
|
|
87
|
+
// Catch-all for a simple success message if no other details were parsed
|
|
88
|
+
hasDetailedItems = true;
|
|
89
|
+
const icon = "<i class='bx bx-check-circle text-green-500'></i>";
|
|
90
|
+
htmlContent += `<li>${icon} <strong>Overall:</strong> ${validationStatus.message}</li>`;
|
|
91
|
+
} else if (!hasDetailedItems) {
|
|
92
|
+
htmlContent += '<li>No detailed validation information available or format is unexpected.</li>';
|
|
93
|
+
}
|
|
94
|
+
htmlContent += '</ul>';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (prInfo && prInfo.url) {
|
|
98
|
+
htmlContent += `<p class="mt-3"><strong>PR Info:</strong> <a href="${prInfo.url}" target="_blank" class="text-blue-600 hover:underline">${prInfo.url}</a></p>`;
|
|
99
|
+
}
|
|
100
|
+
contentElement.innerHTML = htmlContent;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Polls the server for operation status.
|
|
105
|
+
* @param {string} categoryName - The name of the category.
|
|
106
|
+
* @param {string} contentId - The ID of the status content element.
|
|
107
|
+
* @param {string} validateBtnId - The ID of the validate button.
|
|
108
|
+
* @param {string} publishBtnId - The ID of the publish button.
|
|
109
|
+
* @param {string} operationInitiator - 'validate' or 'publish' to restore correct button text.
|
|
110
|
+
*/
|
|
111
|
+
export async function pollOperationStatus(categoryName, contentId, validateBtnId, publishBtnId, operationInitiator) {
|
|
112
|
+
const statusContentElement = document.getElementById(contentId);
|
|
113
|
+
const validateButton = document.getElementById(validateBtnId);
|
|
114
|
+
const publishButton = document.getElementById(publishBtnId);
|
|
115
|
+
|
|
116
|
+
// Helper function to update button states based on operation status
|
|
117
|
+
const updateButtonStates = (isCompleted = false) => {
|
|
118
|
+
if (validateButton && publishButton) {
|
|
119
|
+
if (isCompleted) {
|
|
120
|
+
// On completion, enable both buttons and restore their original state
|
|
121
|
+
validateButton.disabled = false;
|
|
122
|
+
validateButton.innerHTML = "<i class='bx bx-check-shield mr-2'></i>Validate";
|
|
123
|
+
validateButton.classList.remove('opacity-50');
|
|
124
|
+
publishButton.disabled = false;
|
|
125
|
+
publishButton.innerHTML = "<i class='bx bx-cloud-upload mr-2'></i>Publish";
|
|
126
|
+
publishButton.classList.remove('opacity-50');
|
|
127
|
+
} else {
|
|
128
|
+
// During operation, handle buttons based on which operation is running
|
|
129
|
+
if (operationInitiator === 'validate') {
|
|
130
|
+
validateButton.disabled = true;
|
|
131
|
+
validateButton.innerHTML = "<i class='bx bx-loader-alt bx-spin mr-2'></i>Validating...";
|
|
132
|
+
publishButton.disabled = true;
|
|
133
|
+
publishButton.innerHTML = "<i class='bx bx-cloud-upload mr-2'></i>Publish";
|
|
134
|
+
publishButton.classList.add('opacity-50');
|
|
135
|
+
} else if (operationInitiator === 'publish') {
|
|
136
|
+
publishButton.disabled = true;
|
|
137
|
+
publishButton.innerHTML = "<i class='bx bx-loader-alt bx-spin mr-2'></i>Publishing...";
|
|
138
|
+
validateButton.disabled = true;
|
|
139
|
+
validateButton.innerHTML = "<i class='bx bx-check-shield mr-2'></i>Validate";
|
|
140
|
+
validateButton.classList.add('opacity-50');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const response = await fetch(`/api/categories/${categoryName}/onboard/status`);
|
|
148
|
+
const result = await response.json();
|
|
149
|
+
|
|
150
|
+
if (result.success && result.data) {
|
|
151
|
+
updateOperationDisplay(result.data, statusContentElement);
|
|
152
|
+
|
|
153
|
+
const currentStatus = result.data.status ? String(result.data.status).toUpperCase() : ''; // Ensure uppercase for comparison
|
|
154
|
+
// Use string literals for status comparison, include 'succeeded'
|
|
155
|
+
if (currentStatus === 'COMPLETED' || currentStatus === 'FAILED' || currentStatus === 'SUCCEEDED') {
|
|
156
|
+
clearInterval(pollingIntervalId);
|
|
157
|
+
pollingIntervalId = null;
|
|
158
|
+
updateButtonStates(true);
|
|
159
|
+
} else {
|
|
160
|
+
// Operation is still in progress
|
|
161
|
+
updateButtonStates(false);
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
statusContentElement.innerHTML = `<p class="text-red-500">Error polling status: ${result.error || 'Unknown error'}</p>`;
|
|
165
|
+
clearInterval(pollingIntervalId);
|
|
166
|
+
pollingIntervalId = null;
|
|
167
|
+
updateButtonStates(true);
|
|
168
|
+
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('Error polling operation status:', error);
|
|
171
|
+
statusContentElement.innerHTML = `<p class="text-red-500">An error occurred while polling status. Please check the console.</p>`;
|
|
172
|
+
clearInterval(pollingIntervalId);
|
|
173
|
+
pollingIntervalId = null;
|
|
174
|
+
updateButtonStates(true);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Handles the validation process for the onboarding form.
|
|
180
|
+
* It initiates validation, displays status, and polls for updates.
|
|
181
|
+
* @param {Event} event - The click event.
|
|
182
|
+
* @param {string} activeTab - Identifier for the active tab ('create-category' or 'create-server').
|
|
183
|
+
* @param {object|null} currentSelectedCategoryData - Data for the currently selected category if activeTab is 'create-server'.
|
|
184
|
+
*/
|
|
185
|
+
export async function handleValidation(event, activeTab, currentSelectedCategoryData = null) {
|
|
186
|
+
const { panelId, contentId, formId, validateButtonId, publishButtonId } = getElementIdsByTab(activeTab);
|
|
187
|
+
|
|
188
|
+
const statusContentElement = document.getElementById(contentId);
|
|
189
|
+
const onboardForm = document.getElementById(formId);
|
|
190
|
+
const validateButton = document.getElementById(validateButtonId);
|
|
191
|
+
const publishButton = document.getElementById(publishButtonId);
|
|
192
|
+
|
|
193
|
+
if (!onboardForm || !statusContentElement || !validateButton || !publishButton) {
|
|
194
|
+
console.error('Required form or panel elements not found for validation.');
|
|
195
|
+
showToast('Required form or panel elements not found for validation.', 'error');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// First clear any existing validation messages
|
|
200
|
+
onboardForm.querySelectorAll('.validation-message').forEach(el => el.remove());
|
|
201
|
+
|
|
202
|
+
// Check for empty required fields and show validation messages
|
|
203
|
+
const emptyRequiredFields = Array.from(onboardForm.querySelectorAll('[required]'));
|
|
204
|
+
let hasErrors = false;
|
|
205
|
+
|
|
206
|
+
emptyRequiredFields.forEach(field => {
|
|
207
|
+
if (!field.value.trim()) {
|
|
208
|
+
showValidationMessage(field, 'This field is required', true);
|
|
209
|
+
hasErrors = true;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Check for other validation errors
|
|
214
|
+
const envNameFields = onboardForm.querySelectorAll('input[name$=".env\\[\\].name"]');
|
|
215
|
+
envNameFields.forEach(field => {
|
|
216
|
+
const value = field.value.trim();
|
|
217
|
+
if (value && !/^[A-Z_][A-Z0-9_]*$/.test(value)) {
|
|
218
|
+
showValidationMessage(field, 'Must be uppercase with only letters, numbers, and underscores', true);
|
|
219
|
+
hasErrors = true;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const urlFields = onboardForm.querySelectorAll('input[name$=".url"]');
|
|
224
|
+
urlFields.forEach(field => {
|
|
225
|
+
const value = field.value.trim();
|
|
226
|
+
if (value) {
|
|
227
|
+
try {
|
|
228
|
+
new URL(value);
|
|
229
|
+
} catch (e) {
|
|
230
|
+
showValidationMessage(field, 'Invalid URL format', true);
|
|
231
|
+
hasErrors = true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
if (hasErrors) {
|
|
237
|
+
showToast('Please fix all validation errors before proceeding.', 'error');
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Additional validation for new category form
|
|
242
|
+
if (activeTab === 'create-category') {
|
|
243
|
+
const formData = getFormData(onboardForm, false);
|
|
244
|
+
if (!formData.mcpServers || formData.mcpServers.length === 0) {
|
|
245
|
+
showToast('At least one MCP server must be configured for a new category.', 'error');
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Disable buttons and show appropriate states
|
|
251
|
+
validateButton.disabled = true;
|
|
252
|
+
validateButton.innerHTML = "<i class='bx bx-loader-alt bx-spin mr-2'></i>Validating...";
|
|
253
|
+
publishButton.disabled = true;
|
|
254
|
+
publishButton.innerHTML = "<i class='bx bx-cloud-upload mr-2'></i>Publish";
|
|
255
|
+
publishButton.classList.add('opacity-50'); // Add gray state to inactive button
|
|
256
|
+
|
|
257
|
+
if (pollingIntervalId) {
|
|
258
|
+
clearInterval(pollingIntervalId); // Clear any existing polling
|
|
259
|
+
pollingIntervalId = null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const forExistingCategoryTab = activeTab === 'create-server';
|
|
263
|
+
const newServersData = getFormData(onboardForm, forExistingCategoryTab, currentSelectedCategoryData);
|
|
264
|
+
let finalFeedConfiguration;
|
|
265
|
+
|
|
266
|
+
if (forExistingCategoryTab && currentSelectedCategoryData) {
|
|
267
|
+
finalFeedConfiguration = JSON.parse(JSON.stringify(currentSelectedCategoryData)); // Deep clone
|
|
268
|
+
|
|
269
|
+
// Merge MCP Servers
|
|
270
|
+
finalFeedConfiguration.mcpServers = (finalFeedConfiguration.mcpServers || []).concat(newServersData.mcpServers || []);
|
|
271
|
+
|
|
272
|
+
// Merge Requirements (ensuring uniqueness)
|
|
273
|
+
const existingReqKeys = new Set((finalFeedConfiguration.requirements || []).map(r => `${r.type}|${r.name}|${r.version}`));
|
|
274
|
+
(newServersData.requirements || []).forEach(newReq => {
|
|
275
|
+
const reqKey = `${newReq.type}|${newReq.name}|${newReq.version}`;
|
|
276
|
+
if (!existingReqKeys.has(reqKey)) {
|
|
277
|
+
if (!finalFeedConfiguration.requirements) {
|
|
278
|
+
finalFeedConfiguration.requirements = [];
|
|
279
|
+
}
|
|
280
|
+
finalFeedConfiguration.requirements.push(newReq);
|
|
281
|
+
existingReqKeys.add(reqKey);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
} else {
|
|
285
|
+
finalFeedConfiguration = newServersData;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
statusContentElement.innerHTML = '<p class="text-gray-500">Initiating validation...</p>';
|
|
289
|
+
ensureStatusPanelVisible(panelId, contentId);
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
const response = await fetch('/api/categories/onboard/validate', {
|
|
294
|
+
method: 'POST',
|
|
295
|
+
headers: {
|
|
296
|
+
'Content-Type': 'application/json',
|
|
297
|
+
},
|
|
298
|
+
body: JSON.stringify({
|
|
299
|
+
categoryData: finalFeedConfiguration,
|
|
300
|
+
forExistingCategory: forExistingCategoryTab
|
|
301
|
+
}),
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const result = await response.json();
|
|
305
|
+
|
|
306
|
+
if (result.success && result.data) {
|
|
307
|
+
updateOperationDisplay(result.data, statusContentElement);
|
|
308
|
+
const categoryName = result.data.onboardingId; // This is the category name used for polling
|
|
309
|
+
const initialStatus = result.data.status;
|
|
310
|
+
|
|
311
|
+
// Use string literals for status comparison, include 'succeeded'
|
|
312
|
+
if (categoryName && initialStatus !== 'COMPLETED' && initialStatus !== 'FAILED' && initialStatus !== 'succeeded') {
|
|
313
|
+
pollingIntervalId = setInterval(() => pollOperationStatus(categoryName, contentId, validateButtonId, publishButtonId, 'validate'), POLLING_INTERVAL);
|
|
314
|
+
} else {
|
|
315
|
+
validateButton.disabled = false;
|
|
316
|
+
validateButton.innerHTML = "<i class='bx bx-check-shield mr-2'></i>Validate";
|
|
317
|
+
publishButton.disabled = false;
|
|
318
|
+
publishButton.classList.remove('opacity-50'); // Remove gray state
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
statusContentElement.innerHTML = `<p class="text-red-500">Validation request failed: ${result.error || 'Unknown error'}</p>`;
|
|
322
|
+
validateButton.disabled = false;
|
|
323
|
+
validateButton.innerHTML = "<i class='bx bx-check-shield mr-2'></i>Validate";
|
|
324
|
+
publishButton.disabled = false;
|
|
325
|
+
publishButton.classList.remove('opacity-50'); // Remove gray state on error
|
|
326
|
+
}
|
|
327
|
+
} catch (error) {
|
|
328
|
+
console.error('Error during validation:', error);
|
|
329
|
+
statusContentElement.innerHTML = `<p class="text-red-500">An error occurred while initiating validation. Please check the console.</p>`;
|
|
330
|
+
validateButton.disabled = false;
|
|
331
|
+
validateButton.innerHTML = "<i class='bx bx-check-shield mr-2'></i>Validate";
|
|
332
|
+
publishButton.disabled = false;
|
|
333
|
+
publishButton.classList.remove('opacity-50'); // Remove gray state on error
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Gets common element IDs based on the active tab.
|
|
339
|
+
* @param {string} activeTab - Identifier for the active tab.
|
|
340
|
+
* @returns {object} Object containing element IDs.
|
|
341
|
+
*/
|
|
342
|
+
export function getElementIdsByTab(activeTab) {
|
|
343
|
+
const isCreateCategoryTab = activeTab === 'create-category';
|
|
344
|
+
return {
|
|
345
|
+
panelId: isCreateCategoryTab ? 'validationStatusPanelNewCategory' : 'validationStatusPanelExistingCategory',
|
|
346
|
+
contentId: isCreateCategoryTab ? 'validationStatusContentNewCategory' : 'validationStatusContentExistingCategoryTab',
|
|
347
|
+
formId: isCreateCategoryTab ? 'onboardForm' : 'onboardServerForm',
|
|
348
|
+
validateButtonId: isCreateCategoryTab ? 'validateButtonNewCategory' : 'validateButtonExistingCategory',
|
|
349
|
+
publishButtonId: isCreateCategoryTab ? 'publishButtonNewCategory' : 'publishButtonExistingCategory',
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Ensures the status panel is visible and expanded.
|
|
355
|
+
* @param {string} panelId - The ID of the status panel.
|
|
356
|
+
* @param {string} contentId - The ID of the status content area.
|
|
357
|
+
*/
|
|
358
|
+
export function ensureStatusPanelVisible(panelId, contentId) {
|
|
359
|
+
const statusPanel = document.getElementById(panelId);
|
|
360
|
+
const statusContent = document.getElementById(contentId);
|
|
361
|
+
|
|
362
|
+
if (!statusPanel || !statusContent) return;
|
|
363
|
+
|
|
364
|
+
statusPanel.classList.remove('hidden');
|
|
365
|
+
if (statusContent.classList.contains('hidden')) {
|
|
366
|
+
const headerIcon = statusPanel.querySelector('.toggle-icon');
|
|
367
|
+
if (headerIcon && typeof toggleSectionContent === 'function') {
|
|
368
|
+
toggleSectionContent(contentId, headerIcon);
|
|
369
|
+
} else if (headerIcon) {
|
|
370
|
+
console.warn('toggleSectionContent function not available for status panel.');
|
|
371
|
+
statusContent.classList.remove('hidden');
|
|
372
|
+
headerIcon.classList.remove('bx-chevron-down');
|
|
373
|
+
headerIcon.classList.add('bx-chevron-up');
|
|
374
|
+
} else {
|
|
375
|
+
statusContent.classList.remove('hidden');
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
@@ -6,6 +6,8 @@ import { DetailsWidget } from './detailsWidget.js';
|
|
|
6
6
|
const REFRESH_INTERVAL = 2000; // 2 seconds
|
|
7
7
|
let refreshTimer = null;
|
|
8
8
|
let activeDetailsWidget = null;
|
|
9
|
+
const MAX_RETRIES = 3;
|
|
10
|
+
const RETRY_DELAY = 1000;
|
|
9
11
|
|
|
10
12
|
// Start refresh timer for installation status
|
|
11
13
|
function startRefreshTimer(serverName) {
|
|
@@ -37,16 +39,28 @@ function startRefreshTimer(serverName) {
|
|
|
37
39
|
}, REFRESH_INTERVAL);
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
// Show server details
|
|
41
|
-
async function showServerDetails(serverName) {
|
|
42
|
+
// Show server details with retry mechanism
|
|
43
|
+
async function showServerDetails(serverName, retryCount = 0) {
|
|
42
44
|
console.log("Showing details for:", serverName);
|
|
43
45
|
try {
|
|
44
46
|
localStorage.setItem('lastSelectedCategory', serverName);
|
|
47
|
+
|
|
48
|
+
// If server data is not available, attempt to fetch it
|
|
49
|
+
if (allServerCategoriesData.length === 0) {
|
|
50
|
+
await fetchServerCategories();
|
|
51
|
+
}
|
|
52
|
+
|
|
45
53
|
const server = allServerCategoriesData.find(s => s.name === serverName);
|
|
46
54
|
const detailsDiv = document.getElementById('serverCategoryDetails');
|
|
47
55
|
|
|
48
56
|
if (!server) {
|
|
49
|
-
|
|
57
|
+
if (retryCount < MAX_RETRIES) {
|
|
58
|
+
console.log(`Server data not found, retrying (${retryCount + 1}/${MAX_RETRIES})...`);
|
|
59
|
+
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
|
|
60
|
+
await fetchServerCategories(); // Refresh the data
|
|
61
|
+
return showServerDetails(serverName, retryCount + 1);
|
|
62
|
+
}
|
|
63
|
+
throw new Error('Server data not found after retries');
|
|
50
64
|
}
|
|
51
65
|
|
|
52
66
|
detailsDiv.innerHTML = `
|
|
@@ -168,12 +182,24 @@ async function renderServersList(serverCategory) {
|
|
|
168
182
|
<div class="server-item-header">
|
|
169
183
|
<div class="flex items-center">
|
|
170
184
|
<h5 class="font-semibold text-gray-800">${mcpServer.displayName || mcpServer.name}</h5>
|
|
171
|
-
${mcpServer.repository || serverCategory.feedConfiguration?.repository ?
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
185
|
+
${mcpServer.repository || serverCategory.feedConfiguration?.repository ? (() => {
|
|
186
|
+
const repoUrl = mcpServer.repository || serverCategory.feedConfiguration?.repository;
|
|
187
|
+
const isGithub = repoUrl.toLowerCase().includes('github.com');
|
|
188
|
+
return `
|
|
189
|
+
<a href="${repoUrl}" target="_blank" class="ml-2 flex items-center">
|
|
190
|
+
<span class="text-xs px-2 py-1 bg-gray-100 rounded-md flex items-center text-gray-700 hover:bg-gray-200 transition-colors duration-200">
|
|
191
|
+
${isGithub ? `
|
|
192
|
+
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
193
|
+
<path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"/>
|
|
194
|
+
</svg>
|
|
195
|
+
GitHub` : `
|
|
196
|
+
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
197
|
+
<path d="M21.721 12.752a9.711 9.711 0 00-.945-5.003 1.743 1.743 0 01-1.339-1.647c0-.522.236-1.01.62-1.341a9.707 9.707 0 00-3.62-2.087 1.744 1.744 0 01-2.113-1.334A9.721 9.721 0 0012 1.016 9.721 9.721 0 009.676 1.3 1.744 1.744 0 017.562 2.634a9.707 9.707 0 00-3.62 2.087 1.744 1.744 0 00-.048 2.32 9.711 9.711 0 00-.945 5.003 9.712 9.712 0 00.945 5.003 1.744 1.744 0 01.048 2.32 9.707 9.707 0 003.62 2.087 1.744 1.744 0 012.114 1.334A9.721 9.721 0 0012 22.982a9.721 9.721 0 002.324-.284 1.744 1.744 0 012.114-1.334 9.707 9.707 0 003.62-2.087 1.744 1.744 0 01.048-2.32 9.711 9.711 0 00.945-5.003z"/>
|
|
198
|
+
</svg>
|
|
199
|
+
Website`}
|
|
200
|
+
</span>
|
|
201
|
+
</a>`
|
|
202
|
+
})() : ''}
|
|
177
203
|
</div>
|
|
178
204
|
<p class="text-sm text-gray-600 mb-1">${mcpServer.description || 'No description'}</p>
|
|
179
205
|
</div>
|
|
@@ -181,20 +207,20 @@ async function renderServersList(serverCategory) {
|
|
|
181
207
|
<span class="text-xs font-semibold mb-2">Client Status:</span>
|
|
182
208
|
<div class="flex flex-wrap gap-2">
|
|
183
209
|
${availableTargets.map(client => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
210
|
+
const status = (serverCategory.installationStatus?.serversStatus[mcpServer.name]?.installedStatus || {})[client];
|
|
211
|
+
const isInstalled = status?.status === 'completed';
|
|
212
|
+
return `
|
|
187
213
|
<span class="text-xs flex items-center ${isInstalled ? 'text-green-600 bg-green-50' : 'text-gray-600 bg-gray-50'} px-3 py-1 rounded-full shadow">
|
|
188
214
|
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
|
189
215
|
${isInstalled
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
216
|
+
? '<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>'
|
|
217
|
+
: '<path d="M7 3.5A1.5 1.5 0 018.5 2h3.879a1.5 1.5 0 011.06.44l3.122 3.12A1.5 1.5 0 0117 6.622V12.5a1.5 1.5 0 01-1.5 1.5h-1v-3.379a1.5 1.5 0 00-.44-1.06L10.94 6.44A1.5 1.5 0 009.879 6H7V3.5z M6 6h2.879A1.5 1.5 0 0110 7.5v1.379a1.5 1.5 0 01-.44 1.06L6.44 13.06A1.5 1.5 0 015.379 13H4.5A1.5 1.5 0 013 11.5V7.5A1.5 1.5 0 014.5 6H6z"></path>'
|
|
218
|
+
}
|
|
193
219
|
</svg>
|
|
194
220
|
${client}
|
|
195
221
|
</span>
|
|
196
222
|
`;
|
|
197
|
-
|
|
223
|
+
}).join('')}
|
|
198
224
|
</div>
|
|
199
225
|
</div>
|
|
200
226
|
<div class="action-buttons">
|
|
@@ -271,4 +297,4 @@ window.showServerDetails = async function (serverName) {
|
|
|
271
297
|
}
|
|
272
298
|
};
|
|
273
299
|
|
|
274
|
-
export { showServerDetails };
|
|
300
|
+
export { showServerDetails };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { allServerCategoriesData, fetchServerCategories } from './api.js';
|
|
2
|
-
import { showServerDetails } from './serverCategoryDetails.js';
|
|
2
|
+
import { showServerDetails } from './serverCategoryDetails.js'; // Still needed for initial load and direct calls
|
|
3
3
|
import { showToast } from './notifications.js';
|
|
4
|
+
import { buildUrlWithFlights } from './flights/flights.js';
|
|
4
5
|
|
|
5
6
|
// Wait for data to be loaded
|
|
6
7
|
async function waitForData() {
|
|
@@ -73,7 +74,7 @@ function renderServerCategoryList(servers) {
|
|
|
73
74
|
|
|
74
75
|
return `
|
|
75
76
|
<div class="server-item border border-gray-200 p-3 rounded hover:bg-gray-50 cursor-pointer transition duration-150 ease-in-out"
|
|
76
|
-
onclick="
|
|
77
|
+
onclick="navigateToCategory('${server.name}')"
|
|
77
78
|
data-server-name="${server.name}">
|
|
78
79
|
<h3 class="font-semibold text-gray-800">${server.displayName || server.name}</h3>
|
|
79
80
|
<p class="text-sm text-gray-500">${statusHtml}</p>
|
|
@@ -128,5 +129,17 @@ function setupSearch() {
|
|
|
128
129
|
});
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
function navigateToCategory(categoryName) {
|
|
133
|
+
// This function will now handle the navigation, ensuring flight params are preserved.
|
|
134
|
+
// It constructs the new URL with the selected category and existing flight params.
|
|
135
|
+
window.location.href = buildUrlWithFlights('index.html', { category: categoryName });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Make navigateToCategory globally accessible if it's directly used in HTML onclick
|
|
139
|
+
// Alternatively, attach event listeners dynamically after rendering the list.
|
|
140
|
+
// For simplicity with current structure, we'll make it global.
|
|
141
|
+
window.navigateToCategory = navigateToCategory;
|
|
142
|
+
|
|
143
|
+
|
|
131
144
|
// Export functions
|
|
132
145
|
export { renderServerCategoryList, setupSearch, loadLastSelectedCategory };
|