imcp 0.0.18 → 0.1.1
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/.roo/rules-code/rules.md +88 -0
- package/dist/cli/index.js +0 -0
- package/dist/core/metadatas/constants.d.ts +7 -0
- package/dist/core/metadatas/constants.js +7 -0
- package/dist/core/onboard/FeedOnboardService.d.ts +7 -3
- package/dist/core/onboard/FeedOnboardService.js +52 -5
- package/dist/core/onboard/OnboardProcessor.js +22 -22
- package/dist/services/MCPManager.js +66 -6
- package/dist/services/TelemetryService.d.ts +15 -0
- package/dist/services/TelemetryService.js +54 -0
- package/dist/utils/githubAuth.js +65 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.js +78 -1
- package/dist/utils/versionUtils.d.ts +1 -0
- package/dist/utils/versionUtils.js +29 -0
- package/dist/web/public/css/serverCategoryList.css +120 -0
- package/dist/web/public/index.html +6 -3
- package/dist/web/public/js/flights/flights.js +0 -1
- package/dist/web/public/js/onboard/formProcessor.js +18 -11
- package/dist/web/public/js/onboard/publishHandler.js +30 -0
- package/dist/web/public/js/onboard/templates.js +5 -1
- package/dist/web/public/js/onboard/uiHandlers.js +266 -39
- package/dist/web/public/js/onboard/validationHandlers.js +71 -39
- package/dist/web/public/js/serverCategoryList.js +91 -7
- package/dist/web/public/onboard.html +2 -2
- package/dist/web/server.js +11 -1
- package/{src/web/public/js/onboard → docs}/ONBOARDING_PAGE_DESIGN.md +15 -125
- package/docs/Telemetry.md +136 -0
- package/memory-bank/activeContext.md +14 -0
- package/memory-bank/decisionLog.md +28 -0
- package/memory-bank/productContext.md +41 -0
- package/memory-bank/progress.md +5 -0
- package/memory-bank/systemPatterns.md +3 -0
- package/package.json +2 -1
- package/src/core/metadatas/constants.ts +9 -0
- package/src/core/onboard/FeedOnboardService.ts +59 -5
- package/src/core/onboard/OnboardProcessor.ts +25 -23
- package/src/services/MCPManager.ts +78 -8
- package/src/services/TelemetryService.ts +59 -0
- package/src/utils/githubAuth.ts +84 -1
- package/src/utils/logger.ts +83 -1
- package/src/utils/versionUtils.ts +33 -0
- package/src/web/public/css/serverCategoryList.css +120 -0
- package/src/web/public/index.html +6 -3
- package/src/web/public/js/onboard/formProcessor.js +18 -11
- package/src/web/public/js/onboard/publishHandler.js +30 -0
- package/src/web/public/js/onboard/templates.js +5 -1
- package/src/web/public/js/onboard/uiHandlers.js +266 -39
- package/src/web/public/js/onboard/validationHandlers.js +71 -39
- package/src/web/public/js/serverCategoryList.js +91 -7
- package/src/web/public/onboard.html +2 -2
- package/src/web/server.ts +11 -1
- package/dist/cli/commands/start.d.ts +0 -2
- package/dist/cli/commands/start.js +0 -32
- package/dist/cli/commands/sync.d.ts +0 -2
- package/dist/cli/commands/sync.js +0 -17
- package/dist/core/ConfigurationLoader.d.ts +0 -32
- package/dist/core/ConfigurationLoader.js +0 -236
- package/dist/core/ConfigurationProvider.d.ts +0 -35
- package/dist/core/ConfigurationProvider.js +0 -375
- package/dist/core/InstallationService.d.ts +0 -50
- package/dist/core/InstallationService.js +0 -350
- package/dist/core/MCPManager.d.ts +0 -28
- package/dist/core/MCPManager.js +0 -188
- package/dist/core/RequirementService.d.ts +0 -40
- package/dist/core/RequirementService.js +0 -110
- package/dist/core/ServerSchemaLoader.d.ts +0 -11
- package/dist/core/ServerSchemaLoader.js +0 -43
- package/dist/core/ServerSchemaProvider.d.ts +0 -17
- package/dist/core/ServerSchemaProvider.js +0 -120
- package/dist/core/constants.d.ts +0 -47
- package/dist/core/constants.js +0 -94
- package/dist/core/installers/BaseInstaller.d.ts +0 -74
- package/dist/core/installers/BaseInstaller.js +0 -253
- package/dist/core/installers/ClientInstaller.d.ts +0 -23
- package/dist/core/installers/ClientInstaller.js +0 -564
- package/dist/core/installers/CommandInstaller.d.ts +0 -37
- package/dist/core/installers/CommandInstaller.js +0 -173
- package/dist/core/installers/GeneralInstaller.d.ts +0 -33
- package/dist/core/installers/GeneralInstaller.js +0 -85
- package/dist/core/installers/InstallerFactory.d.ts +0 -54
- package/dist/core/installers/InstallerFactory.js +0 -97
- package/dist/core/installers/NpmInstaller.d.ts +0 -26
- package/dist/core/installers/NpmInstaller.js +0 -127
- package/dist/core/installers/PipInstaller.d.ts +0 -28
- package/dist/core/installers/PipInstaller.js +0 -127
- package/dist/core/installers/RequirementInstaller.d.ts +0 -33
- package/dist/core/installers/RequirementInstaller.js +0 -3
- package/dist/core/types.d.ts +0 -166
- package/dist/core/types.js +0 -16
- package/dist/services/InstallRequestValidator.d.ts +0 -21
- package/dist/services/InstallRequestValidator.js +0 -99
- package/dist/web/public/js/modal/installHandler.js +0 -227
- package/dist/web/public/js/modal/loadingUI.js +0 -74
- package/dist/web/public/js/modal/modalUI.js +0 -214
- package/dist/web/public/js/modal/version.js +0 -20
package/dist/utils/logger.js
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { SETTINGS_DIR, USER_INFO_PATH } from '../core/metadatas/constants.js';
|
|
5
|
+
import { TelemetryService } from '../services/TelemetryService.js';
|
|
6
|
+
import { getPackageVersion } from './versionUtils.js';
|
|
7
|
+
export var EventType;
|
|
8
|
+
(function (EventType) {
|
|
9
|
+
EventType["IMCP_SERVE"] = "imcp_serve";
|
|
10
|
+
EventType["SERVER_INSTALL"] = "server_install";
|
|
11
|
+
EventType["SERVER_UNINSTALL"] = "server_uninstall";
|
|
12
|
+
EventType["REQUIREMENT_UPDATE"] = "requirement_update";
|
|
13
|
+
EventType["FEED_ONBOARD"] = "feed_onboard";
|
|
14
|
+
EventType["FEED_VALIDATE"] = "feed_validate";
|
|
15
|
+
})(EventType || (EventType = {}));
|
|
16
|
+
export var EventStatus;
|
|
17
|
+
(function (EventStatus) {
|
|
18
|
+
EventStatus["SUCCESS"] = "success";
|
|
19
|
+
EventStatus["FAILED"] = "failed";
|
|
20
|
+
})(EventStatus || (EventStatus = {}));
|
|
4
21
|
export class Logger {
|
|
5
22
|
static verbose = false;
|
|
6
23
|
static fileLoggingEnabled = true;
|
|
7
24
|
static logsDir = path.join(SETTINGS_DIR, 'logs');
|
|
25
|
+
static isTestEnvironment = false;
|
|
26
|
+
static packageVersion = getPackageVersion();
|
|
8
27
|
static setVerbose(isVerbose) {
|
|
9
28
|
this.verbose = isVerbose;
|
|
10
29
|
}
|
|
@@ -96,5 +115,63 @@ export class Logger {
|
|
|
96
115
|
}
|
|
97
116
|
await this.writeToLogFile('ERROR', logMessage);
|
|
98
117
|
}
|
|
118
|
+
static getUsername() {
|
|
119
|
+
try {
|
|
120
|
+
// If on Windows, use os username directly
|
|
121
|
+
if (process.platform === 'win32') {
|
|
122
|
+
return os.userInfo().username;
|
|
123
|
+
}
|
|
124
|
+
// For Mac/Linux users
|
|
125
|
+
try {
|
|
126
|
+
const userInfoPath = USER_INFO_PATH;
|
|
127
|
+
if (fs.existsSync(userInfoPath)) {
|
|
128
|
+
const userInfo = JSON.parse(fs.readFileSync(userInfoPath, 'utf8'));
|
|
129
|
+
if (userInfo.alias) {
|
|
130
|
+
return userInfo.alias;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
this.error('Failed to get user info from file', error);
|
|
136
|
+
}
|
|
137
|
+
// Fall back to os username if all else fails
|
|
138
|
+
return os.userInfo().username;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
this.error('Failed to get username', error);
|
|
142
|
+
return 'unknown';
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
static trackEvent(eventType, dimensions) {
|
|
146
|
+
try {
|
|
147
|
+
const username = this.getUsername();
|
|
148
|
+
// Add username and package version to dimensions
|
|
149
|
+
const allDimensions = {
|
|
150
|
+
username,
|
|
151
|
+
packageVersion: this.packageVersion,
|
|
152
|
+
...dimensions
|
|
153
|
+
};
|
|
154
|
+
// Log to file
|
|
155
|
+
if (this.verbose) {
|
|
156
|
+
this.log(`Event: ${eventType}`);
|
|
157
|
+
this.log(JSON.stringify(allDimensions, null, 2));
|
|
158
|
+
}
|
|
159
|
+
// Skip AppInsights in test environment
|
|
160
|
+
if (Logger.isTestEnvironment) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// Log to Application Insights
|
|
164
|
+
try {
|
|
165
|
+
TelemetryService.trackEvent(eventType, allDimensions);
|
|
166
|
+
TelemetryService.flush();
|
|
167
|
+
}
|
|
168
|
+
catch (insightsError) {
|
|
169
|
+
this.error('Failed to track event in Application Insights', insightsError);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
this.error('Failed to log event', error);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
99
176
|
}
|
|
100
177
|
//# sourceMappingURL=logger.js.map
|
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
export function getPackageVersion() {
|
|
5
|
+
try {
|
|
6
|
+
// First try npm environment variable (available during npm scripts)
|
|
7
|
+
if (process.env.npm_package_version) {
|
|
8
|
+
return process.env.npm_package_version;
|
|
9
|
+
}
|
|
10
|
+
// Fall back to reading package.json
|
|
11
|
+
// Get directory name of current module
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
// Traverse up to find package.json (max 3 levels up)
|
|
14
|
+
let currentDir = __dirname;
|
|
15
|
+
for (let i = 0; i < 3; i++) {
|
|
16
|
+
const packagePath = path.join(currentDir, 'package.json');
|
|
17
|
+
if (fs.existsSync(packagePath)) {
|
|
18
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
19
|
+
return packageJson.version;
|
|
20
|
+
}
|
|
21
|
+
currentDir = path.join(currentDir, '..');
|
|
22
|
+
}
|
|
23
|
+
return 'unknown';
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.error('Failed to get package version:', error);
|
|
27
|
+
return 'unknown';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
1
30
|
/**
|
|
2
31
|
* Utility functions for version comparison and management
|
|
3
32
|
*/
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/* Server Category List Styles */
|
|
2
|
+
.category-toggle {
|
|
3
|
+
cursor: pointer;
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
color: #4b5563; /* gray-600 */
|
|
7
|
+
transition: all 0.2s;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.category-toggle:hover {
|
|
11
|
+
color: #1e40af; /* blue-800 */
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.category-toggle i {
|
|
15
|
+
font-size: 1.25rem;
|
|
16
|
+
transition: transform 0.2s;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.category-toggle.collapsed i {
|
|
20
|
+
transform: rotate(-90deg);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.server-list-container {
|
|
24
|
+
transition: height 0.3s ease, opacity 0.3s ease, margin 0.3s ease;
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.server-list-container.collapsed {
|
|
29
|
+
height: 0 !important;
|
|
30
|
+
opacity: 0;
|
|
31
|
+
margin-top: 0;
|
|
32
|
+
margin-bottom: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.category-section {
|
|
36
|
+
margin-bottom: 1rem;
|
|
37
|
+
border-bottom: 1px solid #e5e7eb; /* gray-200 */
|
|
38
|
+
padding-bottom: 0.5rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.category-section:last-child {
|
|
42
|
+
border-bottom: none;
|
|
43
|
+
margin-bottom: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.category-header {
|
|
47
|
+
display: flex;
|
|
48
|
+
justify-content: space-between;
|
|
49
|
+
align-items: center;
|
|
50
|
+
margin-bottom: 0.5rem;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Pin button styles */
|
|
54
|
+
.pin-button {
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
color: #9ca3af; /* gray-400 */
|
|
59
|
+
margin-right: 8px;
|
|
60
|
+
transition: all 0.2s ease;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.pin-button:hover {
|
|
64
|
+
color: #4b5563; /* gray-600 */
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.pin-button.pinned {
|
|
68
|
+
color: #2563eb; /* blue-600 */
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.pin-button.pinned:hover {
|
|
72
|
+
color: #1d4ed8; /* blue-700 */
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Pinned server section */
|
|
76
|
+
.server-item.pinned {
|
|
77
|
+
border-left: 3px solid #2563eb; /* blue-600 */
|
|
78
|
+
background-color: #f0f7ff; /* very light blue */
|
|
79
|
+
position: relative;
|
|
80
|
+
transition: all 0.2s ease-in-out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Pin animation */
|
|
84
|
+
.server-item {
|
|
85
|
+
transition: transform 0.3s ease, border-left 0.2s ease, background-color 0.2s ease;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Visual indicator for pinned items at the top */
|
|
89
|
+
.server-item.pinned::before {
|
|
90
|
+
content: "";
|
|
91
|
+
position: absolute;
|
|
92
|
+
top: -3px;
|
|
93
|
+
left: 0;
|
|
94
|
+
right: 0;
|
|
95
|
+
height: 3px;
|
|
96
|
+
background-color: #2563eb; /* blue-600 */
|
|
97
|
+
opacity: 0;
|
|
98
|
+
transition: opacity 0.2s ease;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Show top indicator for the first pinned item */
|
|
102
|
+
.server-item.pinned:first-child::before {
|
|
103
|
+
opacity: 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Add hover effect to pinned items */
|
|
107
|
+
.server-item.pinned:hover {
|
|
108
|
+
background-color: #e6f0ff; /* slightly darker on hover */
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Pin animation effect */
|
|
112
|
+
@keyframes pin-animation {
|
|
113
|
+
0% { transform: scale(1); }
|
|
114
|
+
50% { transform: scale(1.03); }
|
|
115
|
+
100% { transform: scale(1); }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.pin-animation {
|
|
119
|
+
animation: pin-animation 0.3s ease;
|
|
120
|
+
}
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
<link href="https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css" rel="stylesheet">
|
|
10
10
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
11
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">
|
|
12
|
+
<link rel="stylesheet" href="styles.css">
|
|
13
13
|
<link rel="stylesheet" href="css/modal.css">
|
|
14
14
|
<link rel="stylesheet" href="css/notifications.css">
|
|
15
15
|
<link rel="stylesheet" href="css/serverDetails.css">
|
|
16
16
|
<link rel="stylesheet" href="css/detailsWidget.css">
|
|
17
|
+
<link rel="stylesheet" href="css/serverCategoryList.css">
|
|
17
18
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
18
19
|
|
|
19
20
|
<!-- Alert container for notifications -->
|
|
@@ -25,8 +26,10 @@
|
|
|
25
26
|
<div class="container mx-auto px-4 py-6">
|
|
26
27
|
<div class="flex items-center justify-between mb-8">
|
|
27
28
|
<h1 class="text-3xl font-bold text-gray-900 flex items-center">
|
|
28
|
-
<
|
|
29
|
-
|
|
29
|
+
<a href="index.html" class="flex items-center text-gray-900 hover:text-blue-600 transition-colors">
|
|
30
|
+
<i class='bx bx-server mr-3 text-blue-600'></i>
|
|
31
|
+
IMCP Server Manager
|
|
32
|
+
</a>
|
|
30
33
|
</h1>
|
|
31
34
|
<div class="lg:w-2/3 flex justify-end items-center gap-4">
|
|
32
35
|
<div class="relative w-48 focus-within:w-96 transition-all duration-300 ease-in-out">
|
|
@@ -10,17 +10,7 @@ function setupRealTimeValidation(formId) {
|
|
|
10
10
|
const form = document.getElementById(formId);
|
|
11
11
|
if (!form) return;
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
const showValidationMessage = (element, message, isError = true) => {
|
|
15
|
-
let messageDiv = element.nextElementSibling;
|
|
16
|
-
if (!messageDiv || !messageDiv.classList.contains('validation-message')) {
|
|
17
|
-
messageDiv = document.createElement('div');
|
|
18
|
-
messageDiv.className = `validation-message text-xs mt-1 ${isError ? 'text-red-500' : 'text-green-500'}`;
|
|
19
|
-
element.insertAdjacentElement('afterend', messageDiv);
|
|
20
|
-
}
|
|
21
|
-
messageDiv.textContent = message;
|
|
22
|
-
messageDiv.className = `validation-message text-xs mt-1 ${isError ? 'text-red-500' : 'text-green-500'}`;
|
|
23
|
-
};
|
|
13
|
+
if (!form) return;
|
|
24
14
|
|
|
25
15
|
// Name input validation (category or server)
|
|
26
16
|
const nameInput = form.querySelector('input[name="name"]');
|
|
@@ -1026,5 +1016,22 @@ export function resetOnboardFormDynamicContent(formId = 'onboardForm', serversLi
|
|
|
1026
1016
|
setupRealTimeValidation(formId);
|
|
1027
1017
|
}
|
|
1028
1018
|
|
|
1019
|
+
/**
|
|
1020
|
+
* Shows validation message under an input field
|
|
1021
|
+
* @param {HTMLElement} element - The element to show validation message for
|
|
1022
|
+
* @param {string} message - The validation message to display
|
|
1023
|
+
* @param {boolean} isError - Whether this is an error message
|
|
1024
|
+
*/
|
|
1025
|
+
export function showValidationMessage(element, message, isError = true) {
|
|
1026
|
+
let messageDiv = element.nextElementSibling;
|
|
1027
|
+
if (!messageDiv || !messageDiv.classList.contains('validation-message')) {
|
|
1028
|
+
messageDiv = document.createElement('div');
|
|
1029
|
+
messageDiv.className = `validation-message text-xs mt-1 ${isError ? 'text-red-500' : 'text-green-500'}`;
|
|
1030
|
+
element.insertAdjacentElement('afterend', messageDiv);
|
|
1031
|
+
}
|
|
1032
|
+
messageDiv.textContent = message;
|
|
1033
|
+
messageDiv.className = `validation-message text-xs mt-1 ${isError ? 'text-red-500' : 'text-green-500'}`;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1029
1036
|
// Export setupRealTimeValidation for external use if needed
|
|
1030
1037
|
export { setupRealTimeValidation };
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { showToast } from '../notifications.js';
|
|
3
3
|
import { getFormData } from './formProcessor.js';
|
|
4
4
|
import {
|
|
5
|
+
validateFormFields,
|
|
5
6
|
pollOperationStatus,
|
|
6
7
|
updateOperationDisplay,
|
|
7
8
|
getElementIdsByTab,
|
|
@@ -37,6 +38,35 @@ export async function handlePublish(event, activeTab, currentSelectedCategoryDat
|
|
|
37
38
|
return;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
// Validate form fields
|
|
42
|
+
const validationResult = validateFormFields(onboardForm, activeTab);
|
|
43
|
+
if (!validationResult.isValid) {
|
|
44
|
+
showToast('Please fix all validation errors before proceeding.', 'error');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (activeTab === 'create-category') {
|
|
49
|
+
const formData = getFormData(onboardForm, false);
|
|
50
|
+
if (!formData.mcpServers || formData.mcpServers.length === 0) {
|
|
51
|
+
showToast('At least one MCP server must be configured for a new category.', 'error');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check category name format
|
|
56
|
+
const nameInput = onboardForm.querySelector('input[name="name"]');
|
|
57
|
+
if (nameInput && nameInput.value.trim()) {
|
|
58
|
+
if (!/^[a-zA-Z0-9-_]+$/.test(nameInput.value.trim())) {
|
|
59
|
+
showValidationMessage(nameInput, 'Only alphanumeric characters, hyphens, and underscores allowed', true);
|
|
60
|
+
hasErrors = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (hasErrors) {
|
|
66
|
+
showToast('Please fix all validation errors before proceeding.', 'error');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
40
70
|
publishButton.disabled = true;
|
|
41
71
|
publishButton.innerHTML = "<i class='bx bx-loader-alt bx-spin mr-2'></i>Publishing...";
|
|
42
72
|
validateButton.disabled = true; // Also disable validate button during publish
|
|
@@ -17,7 +17,7 @@ export const serverTemplate = (serverIndex, isReadOnly = false, serverData = nul
|
|
|
17
17
|
|
|
18
18
|
const disabledAttr = makeServerFullyEditable ? '' : 'disabled';
|
|
19
19
|
const readOnlyClasses = makeServerFullyEditable ? '' : 'bg-gray-100 cursor-not-allowed opacity-70';
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
// "Remove Server" button should be hidden if the server is NOT fully editable.
|
|
22
22
|
// A server is fully editable if the context isn't read-only OR if it's an adhoc server.
|
|
23
23
|
const hideRemoveServerButtonClass = !makeServerFullyEditable ? 'hidden' : '';
|
|
@@ -42,6 +42,10 @@ export const serverTemplate = (serverIndex, isReadOnly = false, serverData = nul
|
|
|
42
42
|
class="action-button-in-server p-1.5 text-sm text-red-600 hover:text-red-800 hover:bg-red-50 rounded-md flex items-center mr-2 ${hideRemoveServerButtonClass}" title="Remove Server">
|
|
43
43
|
<i class='bx bx-trash text-lg'></i>
|
|
44
44
|
</button>
|
|
45
|
+
<button type="button" onclick="event.stopPropagation(); window.duplicateServer(${serverIndex}, '${serversListId}')"
|
|
46
|
+
class="action-button-in-server duplicate-mcp-server-button p-1.5 text-sm text-blue-600 hover:text-blue-800 hover:bg-blue-50 rounded-md flex items-center mr-2" title="Duplicate Server">
|
|
47
|
+
<i class='bx bx-copy text-lg'></i>
|
|
48
|
+
</button>
|
|
45
49
|
<i class='bx bxs-chevron-down text-xl toggle-icon'></i>
|
|
46
50
|
</div>
|
|
47
51
|
</div>
|