@softtechai/quickmcp 1.0.8
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/README.md +553 -0
- package/dist/client/MCPClient.d.ts +24 -0
- package/dist/client/MCPClient.d.ts.map +1 -0
- package/dist/client/MCPClient.js +211 -0
- package/dist/client/MCPClient.js.map +1 -0
- package/dist/client/MCPClientUnified.d.ts +31 -0
- package/dist/client/MCPClientUnified.d.ts.map +1 -0
- package/dist/client/MCPClientUnified.js +275 -0
- package/dist/client/MCPClientUnified.js.map +1 -0
- package/dist/client/MCPTestRunner.d.ts +44 -0
- package/dist/client/MCPTestRunner.d.ts.map +1 -0
- package/dist/client/MCPTestRunner.js +220 -0
- package/dist/client/MCPTestRunner.js.map +1 -0
- package/dist/client/MCPTestRunnerUnified.d.ts +48 -0
- package/dist/client/MCPTestRunnerUnified.d.ts.map +1 -0
- package/dist/client/MCPTestRunnerUnified.js +183 -0
- package/dist/client/MCPTestRunnerUnified.js.map +1 -0
- package/dist/database/json-manager.d.ts +55 -0
- package/dist/database/json-manager.d.ts.map +1 -0
- package/dist/database/json-manager.js +128 -0
- package/dist/database/json-manager.js.map +1 -0
- package/dist/database/sqlite-manager.d.ts +53 -0
- package/dist/database/sqlite-manager.d.ts.map +1 -0
- package/dist/database/sqlite-manager.js +193 -0
- package/dist/database/sqlite-manager.js.map +1 -0
- package/dist/dynamic-mcp-executor.d.ts +14 -0
- package/dist/dynamic-mcp-executor.d.ts.map +1 -0
- package/dist/dynamic-mcp-executor.js +274 -0
- package/dist/dynamic-mcp-executor.js.map +1 -0
- package/dist/generators/MCPServerGenerator-new.d.ts +37 -0
- package/dist/generators/MCPServerGenerator-new.d.ts.map +1 -0
- package/dist/generators/MCPServerGenerator-new.js +287 -0
- package/dist/generators/MCPServerGenerator-new.js.map +1 -0
- package/dist/generators/MCPServerGenerator.d.ts +42 -0
- package/dist/generators/MCPServerGenerator.d.ts.map +1 -0
- package/dist/generators/MCPServerGenerator.js +494 -0
- package/dist/generators/MCPServerGenerator.js.map +1 -0
- package/dist/generators/database/sqlite-manager.d.ts +52 -0
- package/dist/generators/database/sqlite-manager.js +143 -0
- package/dist/generators/generators/MCPServerGenerator.d.ts +37 -0
- package/dist/generators/generators/MCPServerGenerator.js +396 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/integrated-mcp-server-new.d.ts +12 -0
- package/dist/integrated-mcp-server-new.d.ts.map +1 -0
- package/dist/integrated-mcp-server-new.js +253 -0
- package/dist/integrated-mcp-server-new.js.map +1 -0
- package/dist/integrated-mcp-server.d.ts +25 -0
- package/dist/integrated-mcp-server.d.ts.map +1 -0
- package/dist/integrated-mcp-server.js +541 -0
- package/dist/integrated-mcp-server.js.map +1 -0
- package/dist/mcp-inspector-server.d.ts +3 -0
- package/dist/mcp-inspector-server.d.ts.map +1 -0
- package/dist/mcp-inspector-server.js +119 -0
- package/dist/mcp-inspector-server.js.map +1 -0
- package/dist/mcp-sdk-server.d.ts +3 -0
- package/dist/mcp-sdk-server.d.ts.map +1 -0
- package/dist/mcp-sdk-server.js +90 -0
- package/dist/mcp-sdk-server.js.map +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +300 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/parsers/CsvParser.d.ts +7 -0
- package/dist/parsers/CsvParser.d.ts.map +1 -0
- package/dist/parsers/CsvParser.js +98 -0
- package/dist/parsers/CsvParser.js.map +1 -0
- package/dist/parsers/DatabaseParser.d.ts +18 -0
- package/dist/parsers/DatabaseParser.d.ts.map +1 -0
- package/dist/parsers/DatabaseParser.js +372 -0
- package/dist/parsers/DatabaseParser.js.map +1 -0
- package/dist/parsers/ExcelParser.d.ts +8 -0
- package/dist/parsers/ExcelParser.d.ts.map +1 -0
- package/dist/parsers/ExcelParser.js +119 -0
- package/dist/parsers/ExcelParser.js.map +1 -0
- package/dist/parsers/index.d.ts +13 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +88 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/parsers/ExcelParser.js +118 -0
- package/dist/parsers/types/index.js +2 -0
- package/dist/quickmcp-unified-bridge.d.ts +13 -0
- package/dist/quickmcp-unified-bridge.d.ts.map +1 -0
- package/dist/quickmcp-unified-bridge.js +176 -0
- package/dist/quickmcp-unified-bridge.js.map +1 -0
- package/dist/server/ServerManager.d.ts +37 -0
- package/dist/server/ServerManager.d.ts.map +1 -0
- package/dist/server/ServerManager.js +376 -0
- package/dist/server/ServerManager.js.map +1 -0
- package/dist/sqlite-manager.js +145 -0
- package/dist/start-new-server.d.ts +3 -0
- package/dist/start-new-server.d.ts.map +1 -0
- package/dist/start-new-server.js +10 -0
- package/dist/start-new-server.js.map +1 -0
- package/dist/test-app.d.ts +2 -0
- package/dist/test-app.d.ts.map +1 -0
- package/dist/test-app.js +119 -0
- package/dist/test-app.js.map +1 -0
- package/dist/test-new-architecture.d.ts +3 -0
- package/dist/test-new-architecture.d.ts.map +1 -0
- package/dist/test-new-architecture.js +72 -0
- package/dist/test-new-architecture.js.map +1 -0
- package/dist/transport/base-transport.d.ts +21 -0
- package/dist/transport/base-transport.d.ts.map +1 -0
- package/dist/transport/base-transport.js +16 -0
- package/dist/transport/base-transport.js.map +1 -0
- package/dist/transport/index.d.ts +10 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +12 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/sse-transport.d.ts +13 -0
- package/dist/transport/sse-transport.d.ts.map +1 -0
- package/dist/transport/sse-transport.js +106 -0
- package/dist/transport/sse-transport.js.map +1 -0
- package/dist/transport/stdio-transport.d.ts +8 -0
- package/dist/transport/stdio-transport.d.ts.map +1 -0
- package/dist/transport/stdio-transport.js +53 -0
- package/dist/transport/stdio-transport.js.map +1 -0
- package/dist/transport/streamable-http-transport.d.ts +15 -0
- package/dist/transport/streamable-http-transport.d.ts.map +1 -0
- package/dist/transport/streamable-http-transport.js +151 -0
- package/dist/transport/streamable-http-transport.js.map +1 -0
- package/dist/types/index.d.ts +64 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/web/client/MCPClient.js +348 -0
- package/dist/web/client/MCPTestRunner.js +317 -0
- package/dist/web/database/json-manager.js +124 -0
- package/dist/web/database/sqlite-manager.js +146 -0
- package/dist/web/dynamic-mcp-executor.js +443 -0
- package/dist/web/generators/MCPServerGenerator-new.js +284 -0
- package/dist/web/generators/MCPServerGenerator.js +566 -0
- package/dist/web/integrated-mcp-server-new.js +394 -0
- package/dist/web/parsers/CsvParser.js +144 -0
- package/dist/web/parsers/DatabaseParser.js +637 -0
- package/dist/web/parsers/ExcelParser.js +180 -0
- package/dist/web/parsers/index.js +152 -0
- package/dist/web/server.d.ts +3 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +790 -0
- package/dist/web/server.js.map +1 -0
- package/dist/web/types/index.js +2 -0
- package/dist/web/web/server.js +860 -0
- package/package.json +68 -0
- package/quickmcp-direct-stdio.js +328 -0
- package/src/web/public/app.js +1795 -0
- package/src/web/public/database-tables.html +711 -0
- package/src/web/public/how-to-use.html +571 -0
- package/src/web/public/how-to-use.js +255 -0
- package/src/web/public/images/1-claude-quickmcp-stdio.png +0 -0
- package/src/web/public/images/2-claude-tools.png +0 -0
- package/src/web/public/images/3-claude-developer-settings.png +0 -0
- package/src/web/public/images/4-claude-config.png +0 -0
- package/src/web/public/images/5-claude-config-edit.png +0 -0
- package/src/web/public/index.html +626 -0
- package/src/web/public/manage-servers.html +198 -0
- package/src/web/public/modern-styles.css +946 -0
- package/src/web/public/shared-styles.css +2091 -0
- package/src/web/public/shared.js +93 -0
- package/src/web/public/test-servers.html +302 -0
|
@@ -0,0 +1,1795 @@
|
|
|
1
|
+
let currentParsedData = null;
|
|
2
|
+
let currentDataSource = null;
|
|
3
|
+
let currentWizardStep = 1;
|
|
4
|
+
|
|
5
|
+
// Initialize the app
|
|
6
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
7
|
+
setupEventListeners();
|
|
8
|
+
setupFileUpload();
|
|
9
|
+
setupRouting();
|
|
10
|
+
handleInitialRoute();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Setup event listeners
|
|
14
|
+
function setupEventListeners() {
|
|
15
|
+
// Sidebar controls
|
|
16
|
+
document.getElementById('openSidebar')?.addEventListener('click', openSidebar);
|
|
17
|
+
document.getElementById('closeSidebar')?.addEventListener('click', closeSidebar);
|
|
18
|
+
document.getElementById('sidebarOverlay')?.addEventListener('click', closeSidebar);
|
|
19
|
+
|
|
20
|
+
// Navigation
|
|
21
|
+
document.querySelectorAll('.nav-item').forEach(item => {
|
|
22
|
+
item.addEventListener('click', (e) => {
|
|
23
|
+
const tabName = item.getAttribute('data-tab');
|
|
24
|
+
// Only prevent default for tab navigation, allow normal URL navigation
|
|
25
|
+
if (tabName) {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
switchTab(tabName);
|
|
28
|
+
}
|
|
29
|
+
// If no data-tab, allow normal link navigation to href
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Data source type selection
|
|
34
|
+
document.querySelectorAll('input[name="dataSourceType"]').forEach(radio => {
|
|
35
|
+
radio.addEventListener('change', toggleDataSourceFields);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Database type change
|
|
39
|
+
document.getElementById('dbType')?.addEventListener('change', updateDefaultPort);
|
|
40
|
+
|
|
41
|
+
// Database field changes to update navigation
|
|
42
|
+
document.getElementById('dbType')?.addEventListener('change', updateWizardNavigation);
|
|
43
|
+
document.getElementById('dbHost')?.addEventListener('input', updateWizardNavigation);
|
|
44
|
+
document.getElementById('dbPort')?.addEventListener('input', updateWizardNavigation);
|
|
45
|
+
document.getElementById('dbName')?.addEventListener('input', updateWizardNavigation);
|
|
46
|
+
document.getElementById('dbUser')?.addEventListener('input', updateWizardNavigation);
|
|
47
|
+
document.getElementById('dbPassword')?.addEventListener('input', updateWizardNavigation);
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
// Generate button
|
|
51
|
+
document.getElementById('generateBtn')?.addEventListener('click', generateServer);
|
|
52
|
+
|
|
53
|
+
// Server name validation
|
|
54
|
+
document.getElementById('serverName')?.addEventListener('input', checkServerName);
|
|
55
|
+
|
|
56
|
+
// Test buttons
|
|
57
|
+
document.getElementById('runQuickTestBtn')?.addEventListener('click', () => runAutoTests(false));
|
|
58
|
+
document.getElementById('runFullTestBtn')?.addEventListener('click', () => runAutoTests(true));
|
|
59
|
+
document.getElementById('runAutoTestsBtn')?.addEventListener('click', runAutoTests);
|
|
60
|
+
document.getElementById('runCustomTestBtn')?.addEventListener('click', runCustomTest);
|
|
61
|
+
|
|
62
|
+
// Test type change handler
|
|
63
|
+
document.getElementById('testType')?.addEventListener('change', handleTestTypeChange);
|
|
64
|
+
|
|
65
|
+
// Test server select change handler for loading tools
|
|
66
|
+
document.getElementById('testServerSelect')?.addEventListener('change', handleTestServerChange);
|
|
67
|
+
|
|
68
|
+
// Wizard navigation
|
|
69
|
+
document.getElementById('next-to-step-2')?.addEventListener('click', handleNextToStep2);
|
|
70
|
+
document.getElementById('back-to-step-1')?.addEventListener('click', () => goToWizardStep(1));
|
|
71
|
+
document.getElementById('next-to-step-3')?.addEventListener('click', () => goToWizardStep(3));
|
|
72
|
+
document.getElementById('back-to-step-2')?.addEventListener('click', () => goToWizardStep(2));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Sidebar functions
|
|
76
|
+
function openSidebar() {
|
|
77
|
+
const sidebar = document.getElementById('sidebar');
|
|
78
|
+
const overlay = document.getElementById('sidebarOverlay');
|
|
79
|
+
|
|
80
|
+
sidebar.classList.remove('-translate-x-full');
|
|
81
|
+
overlay.classList.remove('opacity-0', 'invisible');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function closeSidebar() {
|
|
85
|
+
const sidebar = document.getElementById('sidebar');
|
|
86
|
+
const overlay = document.getElementById('sidebarOverlay');
|
|
87
|
+
|
|
88
|
+
sidebar.classList.add('-translate-x-full');
|
|
89
|
+
overlay.classList.add('opacity-0', 'invisible');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Setup URL routing
|
|
93
|
+
function setupRouting() {
|
|
94
|
+
window.addEventListener('popstate', function(event) {
|
|
95
|
+
handleRoute();
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Handle initial route
|
|
100
|
+
function handleInitialRoute() {
|
|
101
|
+
handleRoute();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Route handler
|
|
105
|
+
function handleRoute() {
|
|
106
|
+
const path = window.location.pathname;
|
|
107
|
+
let tabName = 'generate'; // default tab
|
|
108
|
+
|
|
109
|
+
if (path === '/manage-servers') {
|
|
110
|
+
tabName = 'manage';
|
|
111
|
+
} else if (path === '/test-servers') {
|
|
112
|
+
tabName = 'test';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
switchTabByRoute(tabName);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Switch tab and update URL
|
|
119
|
+
function switchTabByRoute(tabName) {
|
|
120
|
+
// Hide all tab contents
|
|
121
|
+
const tabs = document.querySelectorAll('.tab-content');
|
|
122
|
+
tabs.forEach(tab => tab.classList.add('hidden'));
|
|
123
|
+
|
|
124
|
+
// Remove active state from all nav items
|
|
125
|
+
const navItems = document.querySelectorAll('.nav-item');
|
|
126
|
+
navItems.forEach(item => {
|
|
127
|
+
item.classList.remove('active', 'bg-gradient-to-r', 'from-blue-50', 'to-cyan-50', 'text-blue-700', 'border', 'border-blue-200');
|
|
128
|
+
item.classList.add('text-gray-700', 'hover:bg-gray-50');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Show selected tab
|
|
132
|
+
const selectedTab = document.getElementById(`${tabName}-tab`);
|
|
133
|
+
if (selectedTab) {
|
|
134
|
+
selectedTab.classList.remove('hidden');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Set active nav item
|
|
138
|
+
const activeItem = document.querySelector(`[data-tab="${tabName}"]`);
|
|
139
|
+
if (activeItem) {
|
|
140
|
+
activeItem.classList.remove('text-gray-700', 'hover:bg-gray-50');
|
|
141
|
+
activeItem.classList.add('active', 'bg-gradient-to-r', 'from-blue-50', 'to-cyan-50', 'text-blue-700', 'border', 'border-blue-200');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Update page title
|
|
145
|
+
const pageTitle = document.getElementById('pageTitle');
|
|
146
|
+
const pageSubtitle = pageTitle?.nextElementSibling;
|
|
147
|
+
|
|
148
|
+
if (pageTitle) {
|
|
149
|
+
switch(tabName) {
|
|
150
|
+
case 'generate':
|
|
151
|
+
pageTitle.textContent = 'Generate Server';
|
|
152
|
+
if (pageSubtitle) pageSubtitle.textContent = 'Create powerful MCP servers from your data';
|
|
153
|
+
break;
|
|
154
|
+
case 'manage':
|
|
155
|
+
pageTitle.textContent = 'Manage Servers';
|
|
156
|
+
if (pageSubtitle) pageSubtitle.textContent = 'Manage and deploy your created MCP servers';
|
|
157
|
+
break;
|
|
158
|
+
case 'test':
|
|
159
|
+
pageTitle.textContent = 'Test Servers';
|
|
160
|
+
if (pageSubtitle) pageSubtitle.textContent = 'Run automated tests or create custom test scenarios';
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Load data for specific tabs
|
|
166
|
+
if (tabName === 'manage') {
|
|
167
|
+
loadServers();
|
|
168
|
+
} else if (tabName === 'test') {
|
|
169
|
+
loadTestServers();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Tab management with URL update
|
|
174
|
+
function switchTab(tabName) {
|
|
175
|
+
// Update URL
|
|
176
|
+
let newPath = '/';
|
|
177
|
+
if (tabName === 'manage') {
|
|
178
|
+
newPath = '/manage-servers';
|
|
179
|
+
} else if (tabName === 'test') {
|
|
180
|
+
newPath = '/test-servers';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Update URL without triggering popstate
|
|
184
|
+
window.history.pushState(null, '', newPath);
|
|
185
|
+
|
|
186
|
+
// Switch the tab
|
|
187
|
+
switchTabByRoute(tabName);
|
|
188
|
+
|
|
189
|
+
// Close sidebar on mobile
|
|
190
|
+
if (window.innerWidth < 1024) {
|
|
191
|
+
closeSidebar();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// File upload setup
|
|
196
|
+
function setupFileUpload() {
|
|
197
|
+
const fileUpload = document.getElementById('fileUpload');
|
|
198
|
+
const fileInput = document.getElementById('fileInput');
|
|
199
|
+
|
|
200
|
+
if (!fileUpload || !fileInput) return;
|
|
201
|
+
|
|
202
|
+
fileUpload.addEventListener('click', () => fileInput.click());
|
|
203
|
+
|
|
204
|
+
fileUpload.addEventListener('dragover', (e) => {
|
|
205
|
+
e.preventDefault();
|
|
206
|
+
fileUpload.classList.add('border-blue-400', 'bg-blue-50');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
fileUpload.addEventListener('dragleave', () => {
|
|
210
|
+
fileUpload.classList.remove('border-blue-400', 'bg-blue-50');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
fileUpload.addEventListener('drop', (e) => {
|
|
214
|
+
e.preventDefault();
|
|
215
|
+
fileUpload.classList.remove('border-blue-400', 'bg-blue-50');
|
|
216
|
+
|
|
217
|
+
const files = e.dataTransfer.files;
|
|
218
|
+
if (files.length > 0) {
|
|
219
|
+
fileInput.files = files;
|
|
220
|
+
updateFileUploadDisplay(files[0].name);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
fileInput.addEventListener('change', (e) => {
|
|
225
|
+
if (e.target.files.length > 0) {
|
|
226
|
+
updateFileUploadDisplay(e.target.files[0].name);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function updateFileUploadDisplay(fileName) {
|
|
232
|
+
const fileUpload = document.getElementById('fileUpload');
|
|
233
|
+
if (fileUpload) {
|
|
234
|
+
fileUpload.innerHTML = `
|
|
235
|
+
<div class="space-y-4">
|
|
236
|
+
<div class="w-16 h-16 mx-auto bg-green-50 rounded-xl flex items-center justify-center">
|
|
237
|
+
<i class="fas fa-check text-2xl text-green-500"></i>
|
|
238
|
+
</div>
|
|
239
|
+
<div>
|
|
240
|
+
<p class="text-lg font-medium text-gray-900">File Selected</p>
|
|
241
|
+
<p class="text-sm text-gray-500">${fileName}</p>
|
|
242
|
+
</div>
|
|
243
|
+
<button type="button" onclick="resetFileUpload()" class="text-xs text-blue-500 hover:text-blue-600">Choose different file</button>
|
|
244
|
+
</div>
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function resetFileUpload() {
|
|
250
|
+
const fileUpload = document.getElementById('fileUpload');
|
|
251
|
+
const fileInput = document.getElementById('fileInput');
|
|
252
|
+
|
|
253
|
+
if (fileInput) fileInput.value = '';
|
|
254
|
+
|
|
255
|
+
if (fileUpload) {
|
|
256
|
+
fileUpload.innerHTML = `
|
|
257
|
+
<div class="space-y-4">
|
|
258
|
+
<div class="w-16 h-16 mx-auto bg-blue-50 rounded-xl flex items-center justify-center group-hover:bg-blue-100 transition-colors">
|
|
259
|
+
<i class="fas fa-cloud-upload-alt text-2xl text-blue-500"></i>
|
|
260
|
+
</div>
|
|
261
|
+
<div>
|
|
262
|
+
<p class="text-lg font-medium text-gray-900">Drop your file here</p>
|
|
263
|
+
<p class="text-sm text-gray-500">or click to browse files</p>
|
|
264
|
+
</div>
|
|
265
|
+
<p class="text-xs text-gray-400">Supports CSV, Excel files up to 10MB</p>
|
|
266
|
+
</div>
|
|
267
|
+
`;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
// Update default port based on database type
|
|
273
|
+
function updateDefaultPort() {
|
|
274
|
+
const dbType = document.getElementById('dbType')?.value;
|
|
275
|
+
const dbPort = document.getElementById('dbPort');
|
|
276
|
+
|
|
277
|
+
const defaultPorts = {
|
|
278
|
+
'mysql': 3306,
|
|
279
|
+
'postgresql': 5432,
|
|
280
|
+
'sqlite': '',
|
|
281
|
+
'mssql': 1433
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
if (dbPort) {
|
|
285
|
+
dbPort.placeholder = defaultPorts[dbType] || '';
|
|
286
|
+
if (!dbPort.value && defaultPorts[dbType]) {
|
|
287
|
+
dbPort.value = defaultPorts[dbType];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
// Display data preview
|
|
294
|
+
function displayDataPreview(parsedData) {
|
|
295
|
+
const preview = document.getElementById('data-preview');
|
|
296
|
+
if (!preview) return;
|
|
297
|
+
|
|
298
|
+
let html = '<div class="space-y-4">';
|
|
299
|
+
|
|
300
|
+
// Header with instructions
|
|
301
|
+
html += `
|
|
302
|
+
<div class="bg-blue-50 border border-blue-200 rounded-xl p-4 mb-6">
|
|
303
|
+
<div class="flex items-start space-x-3">
|
|
304
|
+
<i class="fas fa-info-circle text-blue-500 mt-1"></i>
|
|
305
|
+
<div>
|
|
306
|
+
<h3 class="font-semibold text-blue-900 mb-1">Configure Your MCP Server</h3>
|
|
307
|
+
<p class="text-blue-800 text-sm">Select which tables to include and choose which tools to generate for each table. All tools are enabled by default.</p>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
`;
|
|
312
|
+
|
|
313
|
+
parsedData.forEach((data, index) => {
|
|
314
|
+
const tableName = data.tableName || `Table ${index + 1}`;
|
|
315
|
+
const panelId = `table-panel-${index}`;
|
|
316
|
+
const cleanTableName = tableName.replace(/[^a-zA-Z0-9]/g, '_');
|
|
317
|
+
|
|
318
|
+
// Check if table has numeric columns
|
|
319
|
+
const numericColumns = data.headers.filter(header => {
|
|
320
|
+
const dataType = data.metadata.dataTypes[header]?.toLowerCase() || '';
|
|
321
|
+
return dataType.includes('int') || dataType.includes('float') || dataType.includes('decimal') ||
|
|
322
|
+
dataType.includes('numeric') || dataType.includes('real') || dataType.includes('double') ||
|
|
323
|
+
dataType === 'number';
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
html += `
|
|
327
|
+
<div class="bg-white rounded-xl border-2 border-gray-200 shadow-sm overflow-hidden mb-4 table-selection-panel">
|
|
328
|
+
<!-- Table Header with Selection -->
|
|
329
|
+
<div class="bg-gradient-to-r from-gray-50 to-gray-100 p-4 border-b border-gray-200">
|
|
330
|
+
<div class="flex items-center justify-between">
|
|
331
|
+
<div class="flex items-center space-x-3">
|
|
332
|
+
<label class="flex items-center space-x-2 cursor-pointer">
|
|
333
|
+
<input type="checkbox"
|
|
334
|
+
id="table-select-${index}"
|
|
335
|
+
class="w-5 h-5 text-blue-600 border-2 border-gray-300 rounded focus:ring-2 focus:ring-blue-500"
|
|
336
|
+
checked
|
|
337
|
+
onchange="toggleTableSelection(${index})">
|
|
338
|
+
<div>
|
|
339
|
+
<h4 class="font-semibold text-gray-900 text-lg">${tableName}</h4>
|
|
340
|
+
<p class="text-sm text-gray-600">${data.metadata.rowCount} rows, ${data.metadata.columnCount} columns</p>
|
|
341
|
+
</div>
|
|
342
|
+
</label>
|
|
343
|
+
</div>
|
|
344
|
+
<button class="text-gray-400 hover:text-gray-600 transition-colors" onclick="toggleTableDetails('${panelId}')">
|
|
345
|
+
<i id="${panelId}-icon" class="fas fa-chevron-down transition-transform"></i>
|
|
346
|
+
</button>
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
|
|
350
|
+
<!-- Tool Selection Panel -->
|
|
351
|
+
<div id="table-tools-${index}" class="bg-blue-50 p-4 border-b border-gray-200">
|
|
352
|
+
<h5 class="font-medium text-gray-900 mb-3">
|
|
353
|
+
<i class="fas fa-tools mr-2 text-blue-500"></i>
|
|
354
|
+
Select Tools to Generate
|
|
355
|
+
</h5>
|
|
356
|
+
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-3">
|
|
357
|
+
<!-- Basic CRUD Tools -->
|
|
358
|
+
<label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
|
|
359
|
+
<input type="checkbox"
|
|
360
|
+
id="tool-get-${index}"
|
|
361
|
+
class="w-4 h-4 text-blue-600 border border-gray-300 rounded focus:ring-2 focus:ring-blue-500"
|
|
362
|
+
checked>
|
|
363
|
+
<span class="text-sm font-medium text-gray-700">GET</span>
|
|
364
|
+
</label>
|
|
365
|
+
|
|
366
|
+
<label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
|
|
367
|
+
<input type="checkbox"
|
|
368
|
+
id="tool-create-${index}"
|
|
369
|
+
class="w-4 h-4 text-green-600 border border-gray-300 rounded focus:ring-2 focus:ring-green-500"
|
|
370
|
+
checked>
|
|
371
|
+
<span class="text-sm font-medium text-gray-700">CREATE</span>
|
|
372
|
+
</label>
|
|
373
|
+
|
|
374
|
+
<label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
|
|
375
|
+
<input type="checkbox"
|
|
376
|
+
id="tool-update-${index}"
|
|
377
|
+
class="w-4 h-4 text-yellow-600 border border-gray-300 rounded focus:ring-2 focus:ring-yellow-500"
|
|
378
|
+
checked>
|
|
379
|
+
<span class="text-sm font-medium text-gray-700">UPDATE</span>
|
|
380
|
+
</label>
|
|
381
|
+
|
|
382
|
+
<label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
|
|
383
|
+
<input type="checkbox"
|
|
384
|
+
id="tool-delete-${index}"
|
|
385
|
+
class="w-4 h-4 text-red-600 border border-gray-300 rounded focus:ring-2 focus:ring-red-500"
|
|
386
|
+
checked>
|
|
387
|
+
<span class="text-sm font-medium text-gray-700">DELETE</span>
|
|
388
|
+
</label>
|
|
389
|
+
|
|
390
|
+
<label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
|
|
391
|
+
<input type="checkbox"
|
|
392
|
+
id="tool-count-${index}"
|
|
393
|
+
class="w-4 h-4 text-purple-600 border border-gray-300 rounded focus:ring-2 focus:ring-purple-500"
|
|
394
|
+
checked>
|
|
395
|
+
<span class="text-sm font-medium text-gray-700">COUNT</span>
|
|
396
|
+
</label>
|
|
397
|
+
|
|
398
|
+
<!-- Aggregate Tools (only if numeric columns exist) -->
|
|
399
|
+
${numericColumns.length > 0 ? `
|
|
400
|
+
<label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
|
|
401
|
+
<input type="checkbox"
|
|
402
|
+
id="tool-min-${index}"
|
|
403
|
+
class="w-4 h-4 text-indigo-600 border border-gray-300 rounded focus:ring-2 focus:ring-indigo-500"
|
|
404
|
+
checked>
|
|
405
|
+
<span class="text-sm font-medium text-gray-700">MIN</span>
|
|
406
|
+
</label>
|
|
407
|
+
|
|
408
|
+
<label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
|
|
409
|
+
<input type="checkbox"
|
|
410
|
+
id="tool-max-${index}"
|
|
411
|
+
class="w-4 h-4 text-pink-600 border border-gray-300 rounded focus:ring-2 focus:ring-pink-500"
|
|
412
|
+
checked>
|
|
413
|
+
<span class="text-sm font-medium text-gray-700">MAX</span>
|
|
414
|
+
</label>
|
|
415
|
+
|
|
416
|
+
<label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
|
|
417
|
+
<input type="checkbox"
|
|
418
|
+
id="tool-sum-${index}"
|
|
419
|
+
class="w-4 h-4 text-teal-600 border border-gray-300 rounded focus:ring-2 focus:ring-teal-500"
|
|
420
|
+
checked>
|
|
421
|
+
<span class="text-sm font-medium text-gray-700">SUM</span>
|
|
422
|
+
</label>
|
|
423
|
+
|
|
424
|
+
<label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
|
|
425
|
+
<input type="checkbox"
|
|
426
|
+
id="tool-avg-${index}"
|
|
427
|
+
class="w-4 h-4 text-orange-600 border border-gray-300 rounded focus:ring-2 focus:ring-orange-500"
|
|
428
|
+
checked>
|
|
429
|
+
<span class="text-sm font-medium text-gray-700">AVG</span>
|
|
430
|
+
</label>
|
|
431
|
+
` : ''}
|
|
432
|
+
</div>
|
|
433
|
+
${numericColumns.length > 0 ? `
|
|
434
|
+
<div class="mt-3 p-3 bg-green-50 border border-green-200 rounded-lg">
|
|
435
|
+
<p class="text-sm text-green-800">
|
|
436
|
+
<i class="fas fa-calculator mr-2"></i>
|
|
437
|
+
<strong>Aggregate tools available:</strong> This table has ${numericColumns.length} numeric column(s):
|
|
438
|
+
<span class="font-mono">${numericColumns.join(', ')}</span>
|
|
439
|
+
</p>
|
|
440
|
+
</div>
|
|
441
|
+
` : ''}
|
|
442
|
+
|
|
443
|
+
<!-- Quick Actions -->
|
|
444
|
+
<div class="mt-4 flex items-center space-x-4">
|
|
445
|
+
<button onclick="selectAllTools(${index})" class="text-sm text-blue-600 hover:text-blue-800 font-medium">
|
|
446
|
+
<i class="fas fa-check-square mr-1"></i>Select All
|
|
447
|
+
</button>
|
|
448
|
+
<button onclick="deselectAllTools(${index})" class="text-sm text-gray-600 hover:text-gray-800 font-medium">
|
|
449
|
+
<i class="fas fa-square mr-1"></i>Deselect All
|
|
450
|
+
</button>
|
|
451
|
+
<button onclick="selectOnlyBasicTools(${index})" class="text-sm text-green-600 hover:text-green-800 font-medium">
|
|
452
|
+
<i class="fas fa-check mr-1"></i>Basic Only
|
|
453
|
+
</button>
|
|
454
|
+
</div>
|
|
455
|
+
</div>
|
|
456
|
+
|
|
457
|
+
<!-- Table Data Preview (Collapsible) -->
|
|
458
|
+
<div id="${panelId}" class="p-4 hidden">
|
|
459
|
+
<div class="overflow-x-auto">
|
|
460
|
+
<table class="min-w-full text-sm">
|
|
461
|
+
<thead>
|
|
462
|
+
<tr class="bg-gray-100">`;
|
|
463
|
+
|
|
464
|
+
data.headers.forEach(header => {
|
|
465
|
+
const dataType = data.metadata.dataTypes[header];
|
|
466
|
+
html += `<th class="px-3 py-2 text-left font-medium text-gray-700">${header} <span class="text-xs text-gray-500">(${dataType})</span></th>`;
|
|
467
|
+
});
|
|
468
|
+
html += '</tr></thead><tbody>';
|
|
469
|
+
|
|
470
|
+
data.rows.slice(0, 5).forEach((row, rowIndex) => {
|
|
471
|
+
html += `<tr class="${rowIndex % 2 === 0 ? 'bg-white' : 'bg-gray-50'}">`;
|
|
472
|
+
row.forEach(cell => {
|
|
473
|
+
html += `<td class="px-3 py-2 text-gray-900">${cell || ''}</td>`;
|
|
474
|
+
});
|
|
475
|
+
html += '</tr>';
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
if (data.rows.length > 5) {
|
|
479
|
+
html += `<tr><td colspan="${data.headers.length}" class="px-3 py-2 text-center text-gray-500 italic">... and ${data.rows.length - 5} more rows</td></tr>`;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
html += `</tbody></table>
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
</div>`;
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
html += '</div>';
|
|
489
|
+
preview.innerHTML = html;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Toggle table details panel
|
|
493
|
+
function toggleTableDetails(panelId) {
|
|
494
|
+
const panel = document.getElementById(panelId);
|
|
495
|
+
const icon = document.getElementById(`${panelId}-icon`);
|
|
496
|
+
|
|
497
|
+
if (panel?.classList.contains('hidden')) {
|
|
498
|
+
panel.classList.remove('hidden');
|
|
499
|
+
icon?.classList.add('rotate-180');
|
|
500
|
+
} else {
|
|
501
|
+
panel?.classList.add('hidden');
|
|
502
|
+
icon?.classList.remove('rotate-180');
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Legacy function for backward compatibility
|
|
507
|
+
function togglePanel(panelId) {
|
|
508
|
+
toggleTableDetails(panelId);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Toggle table selection
|
|
512
|
+
function toggleTableSelection(tableIndex) {
|
|
513
|
+
const checkbox = document.getElementById(`table-select-${tableIndex}`);
|
|
514
|
+
const toolsPanel = document.getElementById(`table-tools-${tableIndex}`);
|
|
515
|
+
|
|
516
|
+
if (checkbox?.checked) {
|
|
517
|
+
toolsPanel?.classList.remove('opacity-50');
|
|
518
|
+
toolsPanel?.querySelectorAll('input[type="checkbox"]').forEach(input => {
|
|
519
|
+
input.disabled = false;
|
|
520
|
+
});
|
|
521
|
+
} else {
|
|
522
|
+
toolsPanel?.classList.add('opacity-50');
|
|
523
|
+
toolsPanel?.querySelectorAll('input[type="checkbox"]').forEach(input => {
|
|
524
|
+
input.disabled = true;
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Tool selection helper functions
|
|
530
|
+
function selectAllTools(tableIndex) {
|
|
531
|
+
const toolInputs = document.querySelectorAll(`#table-tools-${tableIndex} input[type="checkbox"]`);
|
|
532
|
+
toolInputs.forEach(input => {
|
|
533
|
+
if (!input.disabled) input.checked = true;
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function deselectAllTools(tableIndex) {
|
|
538
|
+
const toolInputs = document.querySelectorAll(`#table-tools-${tableIndex} input[type="checkbox"]`);
|
|
539
|
+
toolInputs.forEach(input => {
|
|
540
|
+
if (!input.disabled) input.checked = false;
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function selectOnlyBasicTools(tableIndex) {
|
|
545
|
+
// First deselect all
|
|
546
|
+
deselectAllTools(tableIndex);
|
|
547
|
+
|
|
548
|
+
// Then select only basic CRUD tools
|
|
549
|
+
const basicTools = ['get', 'create', 'update', 'delete'];
|
|
550
|
+
basicTools.forEach(tool => {
|
|
551
|
+
const input = document.getElementById(`tool-${tool}-${tableIndex}`);
|
|
552
|
+
if (input && !input.disabled) input.checked = true;
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Get selected tables and their tools configuration
|
|
557
|
+
function getSelectedTablesAndTools() {
|
|
558
|
+
const selectedTables = [];
|
|
559
|
+
|
|
560
|
+
// Find all table selection checkboxes
|
|
561
|
+
document.querySelectorAll('[id^="table-select-"]').forEach((checkbox, index) => {
|
|
562
|
+
if (checkbox.checked) {
|
|
563
|
+
const toolsConfig = {
|
|
564
|
+
get: document.getElementById(`tool-get-${index}`)?.checked || false,
|
|
565
|
+
create: document.getElementById(`tool-create-${index}`)?.checked || false,
|
|
566
|
+
update: document.getElementById(`tool-update-${index}`)?.checked || false,
|
|
567
|
+
delete: document.getElementById(`tool-delete-${index}`)?.checked || false,
|
|
568
|
+
count: document.getElementById(`tool-count-${index}`)?.checked || false,
|
|
569
|
+
min: document.getElementById(`tool-min-${index}`)?.checked || false,
|
|
570
|
+
max: document.getElementById(`tool-max-${index}`)?.checked || false,
|
|
571
|
+
sum: document.getElementById(`tool-sum-${index}`)?.checked || false,
|
|
572
|
+
avg: document.getElementById(`tool-avg-${index}`)?.checked || false
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
selectedTables.push({
|
|
576
|
+
index: index,
|
|
577
|
+
tools: toolsConfig
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
return selectedTables;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Generate server
|
|
586
|
+
async function generateServer() {
|
|
587
|
+
const name = document.getElementById('serverName')?.value;
|
|
588
|
+
const description = document.getElementById('serverDescription')?.value;
|
|
589
|
+
const version = document.getElementById('serverVersion')?.value;
|
|
590
|
+
|
|
591
|
+
if (!name) {
|
|
592
|
+
showError('generate-error', 'Please provide server name');
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (!currentDataSource || !currentParsedData) {
|
|
597
|
+
showError('generate-error', 'Please parse a data source first');
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Get selected tables and their tool configurations
|
|
602
|
+
const selectedTablesConfig = getSelectedTablesAndTools();
|
|
603
|
+
|
|
604
|
+
if (selectedTablesConfig.length === 0) {
|
|
605
|
+
showError('generate-error', 'Please select at least one table to generate server for');
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
console.log('🔍 Selected tables and tools:', selectedTablesConfig);
|
|
610
|
+
|
|
611
|
+
const loading = document.getElementById('generate-loading');
|
|
612
|
+
const successDiv = document.getElementById('generate-success');
|
|
613
|
+
const errorDiv = document.getElementById('generate-error');
|
|
614
|
+
const generateBtn = document.getElementById('generateBtn');
|
|
615
|
+
|
|
616
|
+
loading?.classList.remove('hidden');
|
|
617
|
+
successDiv?.classList.add('hidden');
|
|
618
|
+
errorDiv?.classList.add('hidden');
|
|
619
|
+
if (generateBtn) generateBtn.disabled = true;
|
|
620
|
+
|
|
621
|
+
try {
|
|
622
|
+
const response = await fetch('/api/generate', {
|
|
623
|
+
method: 'POST',
|
|
624
|
+
headers: {
|
|
625
|
+
'Content-Type': 'application/json'
|
|
626
|
+
},
|
|
627
|
+
body: JSON.stringify({
|
|
628
|
+
name,
|
|
629
|
+
description: description || '',
|
|
630
|
+
version: version || '1.0.0',
|
|
631
|
+
dataSource: currentDataSource,
|
|
632
|
+
selectedTables: selectedTablesConfig,
|
|
633
|
+
parsedData: currentParsedData
|
|
634
|
+
})
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
const result = await response.json();
|
|
638
|
+
|
|
639
|
+
if (result.success) {
|
|
640
|
+
showSuccessModal(name, result.data);
|
|
641
|
+
resetForm();
|
|
642
|
+
} else {
|
|
643
|
+
throw new Error(result.error);
|
|
644
|
+
}
|
|
645
|
+
} catch (error) {
|
|
646
|
+
showError('generate-error', error.message);
|
|
647
|
+
} finally {
|
|
648
|
+
loading?.classList.add('hidden');
|
|
649
|
+
if (generateBtn) generateBtn.disabled = false;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function resetForm() {
|
|
654
|
+
document.getElementById('serverName').value = '';
|
|
655
|
+
document.getElementById('serverDescription').value = '';
|
|
656
|
+
document.getElementById('serverVersion').value = '1.0.0';
|
|
657
|
+
|
|
658
|
+
// Reset data source selection
|
|
659
|
+
document.querySelectorAll('input[name="dataSourceType"]').forEach(radio => {
|
|
660
|
+
radio.checked = false;
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
document.getElementById('file-upload-section')?.classList.add('hidden');
|
|
664
|
+
document.getElementById('database-section')?.classList.add('hidden');
|
|
665
|
+
|
|
666
|
+
resetFileUpload();
|
|
667
|
+
|
|
668
|
+
currentParsedData = null;
|
|
669
|
+
currentDataSource = null;
|
|
670
|
+
currentWizardStep = 1;
|
|
671
|
+
|
|
672
|
+
// Reset wizard to step 1
|
|
673
|
+
goToWizardStep(1);
|
|
674
|
+
|
|
675
|
+
const generateBtn = document.getElementById('generateBtn');
|
|
676
|
+
if (generateBtn) generateBtn.disabled = true;
|
|
677
|
+
|
|
678
|
+
const nextBtn = document.getElementById('next-to-step-2');
|
|
679
|
+
if (nextBtn) nextBtn.disabled = true;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Load servers list
|
|
683
|
+
async function loadServers() {
|
|
684
|
+
try {
|
|
685
|
+
const response = await fetch('/api/servers');
|
|
686
|
+
const result = await response.json();
|
|
687
|
+
|
|
688
|
+
if (result.success) {
|
|
689
|
+
displayServers(result.data);
|
|
690
|
+
} else {
|
|
691
|
+
displayServers([]);
|
|
692
|
+
}
|
|
693
|
+
} catch (error) {
|
|
694
|
+
console.error('Failed to load servers:', error);
|
|
695
|
+
displayServers([]);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Display servers
|
|
700
|
+
function displayServers(servers) {
|
|
701
|
+
const serverList = document.getElementById('server-list');
|
|
702
|
+
if (!serverList) return;
|
|
703
|
+
|
|
704
|
+
if (!servers || servers.length === 0) {
|
|
705
|
+
serverList.innerHTML = `
|
|
706
|
+
<div class="col-span-full">
|
|
707
|
+
<div class="text-center py-12">
|
|
708
|
+
<div class="w-24 h-24 mx-auto bg-gray-100 rounded-2xl flex items-center justify-center mb-4">
|
|
709
|
+
<i class="fas fa-server text-3xl text-gray-400"></i>
|
|
710
|
+
</div>
|
|
711
|
+
<h3 class="text-xl font-semibold text-gray-900 mb-2">No Servers Generated Yet</h3>
|
|
712
|
+
<p class="text-gray-600 mb-6">Create your first MCP server by uploading data or connecting to a database.</p>
|
|
713
|
+
<button class="bg-gradient-to-r from-blue-500 to-cyan-500 text-white px-6 py-3 rounded-xl font-medium hover:from-blue-600 hover:to-cyan-600 transition-all duration-200 transform hover:scale-[1.02]" onclick="window.location.href='/'">
|
|
714
|
+
<i class="fas fa-rocket mr-2"></i>
|
|
715
|
+
Generate Your First Server
|
|
716
|
+
</button>
|
|
717
|
+
</div>
|
|
718
|
+
</div>
|
|
719
|
+
`;
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
let html = '';
|
|
724
|
+
servers.forEach(server => {
|
|
725
|
+
html += `
|
|
726
|
+
<div class="bg-white rounded-2xl shadow-lg border border-gray-200/50 overflow-hidden hover:shadow-xl transition-all duration-200 transform hover:scale-[1.02]">
|
|
727
|
+
<div class="bg-gradient-to-r from-blue-50 to-cyan-50 p-6 border-b border-gray-200/50">
|
|
728
|
+
<div class="flex items-center justify-between">
|
|
729
|
+
<div>
|
|
730
|
+
<h3 class="text-lg font-semibold text-gray-900">${server.name}</h3>
|
|
731
|
+
<p class="text-sm text-gray-600">${server.description}</p>
|
|
732
|
+
</div>
|
|
733
|
+
<div class="w-10 h-10 bg-gradient-to-r from-blue-500 to-cyan-500 rounded-xl flex items-center justify-center">
|
|
734
|
+
<i class="fas fa-server text-white"></i>
|
|
735
|
+
</div>
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
738
|
+
|
|
739
|
+
<div class="p-6">
|
|
740
|
+
<div class="grid grid-cols-2 gap-4 mb-6">
|
|
741
|
+
<div class="text-center">
|
|
742
|
+
<div class="text-2xl font-bold text-blue-600">${server.toolsCount}</div>
|
|
743
|
+
<div class="text-sm text-gray-500">Tools</div>
|
|
744
|
+
</div>
|
|
745
|
+
<div class="text-center">
|
|
746
|
+
<div class="text-2xl font-bold text-green-600">${server.resourcesCount}</div>
|
|
747
|
+
<div class="text-sm text-gray-500">Resources</div>
|
|
748
|
+
</div>
|
|
749
|
+
</div>
|
|
750
|
+
|
|
751
|
+
<div class="flex flex-wrap gap-2 mb-6">
|
|
752
|
+
<span class="bg-blue-100 text-blue-800 text-xs font-medium px-2.5 py-0.5 rounded-full">v${server.version}</span>
|
|
753
|
+
<span class="bg-purple-100 text-purple-800 text-xs font-medium px-2.5 py-0.5 rounded-full">${server.promptsCount} prompts</span>
|
|
754
|
+
</div>
|
|
755
|
+
|
|
756
|
+
<div class="space-y-2">
|
|
757
|
+
<button class="w-full bg-gradient-to-r from-blue-500 to-cyan-500 text-white px-4 py-2 rounded-lg font-medium hover:from-blue-600 hover:to-cyan-600 transition-all duration-200" onclick="viewServer('${server.id}')">
|
|
758
|
+
<i class="fas fa-eye mr-2"></i>
|
|
759
|
+
View Details
|
|
760
|
+
</button>
|
|
761
|
+
|
|
762
|
+
<div class="grid grid-cols-3 gap-2">
|
|
763
|
+
<button class="bg-gray-100 text-gray-700 px-3 py-2 rounded-lg text-sm font-medium hover:bg-gray-200 transition-all duration-200" onclick="testServer('${server.id}')">
|
|
764
|
+
<i class="fas fa-vial text-xs mr-1"></i>
|
|
765
|
+
Test
|
|
766
|
+
</button>
|
|
767
|
+
<button class="bg-gray-100 text-gray-700 px-3 py-2 rounded-lg text-sm font-medium hover:bg-gray-200 transition-all duration-200" onclick="exportServer('${server.id}')">
|
|
768
|
+
<i class="fas fa-download text-xs mr-1"></i>
|
|
769
|
+
Export
|
|
770
|
+
</button>
|
|
771
|
+
<button class="bg-red-100 text-red-700 px-3 py-2 rounded-lg text-sm font-medium hover:bg-red-200 transition-all duration-200" onclick="deleteServer('${server.id}')">
|
|
772
|
+
<i class="fas fa-trash text-xs mr-1"></i>
|
|
773
|
+
Delete
|
|
774
|
+
</button>
|
|
775
|
+
</div>
|
|
776
|
+
</div>
|
|
777
|
+
</div>
|
|
778
|
+
</div>
|
|
779
|
+
`;
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
serverList.innerHTML = html;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Load test servers
|
|
786
|
+
async function loadTestServers() {
|
|
787
|
+
try {
|
|
788
|
+
const response = await fetch('/api/servers');
|
|
789
|
+
const result = await response.json();
|
|
790
|
+
|
|
791
|
+
const select = document.getElementById('testServerSelect');
|
|
792
|
+
if (!select) return;
|
|
793
|
+
|
|
794
|
+
if (result.success && result.data.length > 0) {
|
|
795
|
+
select.innerHTML = '<option value="">Select a server to test</option>';
|
|
796
|
+
result.data.forEach(server => {
|
|
797
|
+
select.innerHTML += `<option value="${server.id}">${server.name}</option>`;
|
|
798
|
+
});
|
|
799
|
+
} else {
|
|
800
|
+
select.innerHTML = '<option value="">No servers available - Generate a server first</option>';
|
|
801
|
+
}
|
|
802
|
+
} catch (error) {
|
|
803
|
+
console.error('Failed to load test servers:', error);
|
|
804
|
+
const select = document.getElementById('testServerSelect');
|
|
805
|
+
if (select) {
|
|
806
|
+
select.innerHTML = '<option value="">Error loading servers</option>';
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Handle test type change - switch between input and dropdown for test name
|
|
812
|
+
function handleTestTypeChange() {
|
|
813
|
+
const testType = document.getElementById('testType')?.value;
|
|
814
|
+
const container = document.getElementById('testNameContainer');
|
|
815
|
+
const serverId = document.getElementById('testServerSelect')?.value;
|
|
816
|
+
|
|
817
|
+
if (!container) return;
|
|
818
|
+
|
|
819
|
+
if (testType === 'tools/call' && serverId) {
|
|
820
|
+
// Load tools dropdown
|
|
821
|
+
loadToolsDropdown(serverId);
|
|
822
|
+
} else {
|
|
823
|
+
// Show regular input
|
|
824
|
+
container.innerHTML = '<input type="text" id="testName" placeholder="Enter test name" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent">';
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Handle test server change - reload tools if Tool Call is selected
|
|
829
|
+
async function handleTestServerChange() {
|
|
830
|
+
const testType = document.getElementById('testType')?.value;
|
|
831
|
+
const serverId = document.getElementById('testServerSelect')?.value;
|
|
832
|
+
|
|
833
|
+
if (testType === 'tools/call' && serverId) {
|
|
834
|
+
await loadToolsDropdown(serverId);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Load tools dropdown for custom test
|
|
839
|
+
async function loadToolsDropdown(serverId) {
|
|
840
|
+
const container = document.getElementById('testNameContainer');
|
|
841
|
+
if (!container) return;
|
|
842
|
+
|
|
843
|
+
try {
|
|
844
|
+
const response = await fetch(`/api/servers/${serverId}`);
|
|
845
|
+
const result = await response.json();
|
|
846
|
+
|
|
847
|
+
if (result.success && result.data) {
|
|
848
|
+
const tools = result.data.config.tools || [];
|
|
849
|
+
|
|
850
|
+
// Create dropdown with tools
|
|
851
|
+
let dropdownHTML = '<select id="testName" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent" onchange="updateParametersExample()">';
|
|
852
|
+
dropdownHTML += '<option value="">Select a tool to test</option>';
|
|
853
|
+
|
|
854
|
+
tools.forEach(tool => {
|
|
855
|
+
// Store tool data in data attributes for parameter generation
|
|
856
|
+
dropdownHTML += `<option value="${tool.name}" data-schema='${JSON.stringify(tool.inputSchema || {})}'>${tool.name} - ${tool.description}</option>`;
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
dropdownHTML += '</select>';
|
|
860
|
+
container.innerHTML = dropdownHTML;
|
|
861
|
+
|
|
862
|
+
// Store tools data globally for parameter generation
|
|
863
|
+
window.testTools = tools;
|
|
864
|
+
}
|
|
865
|
+
} catch (error) {
|
|
866
|
+
console.error('Failed to load tools:', error);
|
|
867
|
+
container.innerHTML = '<input type="text" id="testName" placeholder="Error loading tools" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent">';
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Update parameters example when tool is selected
|
|
872
|
+
function updateParametersExample() {
|
|
873
|
+
const select = document.getElementById('testName');
|
|
874
|
+
const paramsTextarea = document.getElementById('testParams');
|
|
875
|
+
|
|
876
|
+
if (!select || !paramsTextarea) return;
|
|
877
|
+
|
|
878
|
+
const selectedOption = select.options[select.selectedIndex];
|
|
879
|
+
if (!selectedOption || !selectedOption.value) {
|
|
880
|
+
paramsTextarea.placeholder = '{"param1": "value1"}';
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
try {
|
|
885
|
+
const schema = JSON.parse(selectedOption.getAttribute('data-schema') || '{}');
|
|
886
|
+
const exampleParams = {};
|
|
887
|
+
|
|
888
|
+
// Generate example parameters based on schema
|
|
889
|
+
if (schema.properties) {
|
|
890
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
891
|
+
if (key === 'limit') {
|
|
892
|
+
exampleParams[key] = 10;
|
|
893
|
+
} else if (key === 'offset') {
|
|
894
|
+
exampleParams[key] = 0;
|
|
895
|
+
} else if (prop.type === 'string') {
|
|
896
|
+
exampleParams[key] = prop.description || "example_value";
|
|
897
|
+
} else if (prop.type === 'number' || prop.type === 'integer') {
|
|
898
|
+
exampleParams[key] = 0;
|
|
899
|
+
} else if (prop.type === 'boolean') {
|
|
900
|
+
exampleParams[key] = false;
|
|
901
|
+
} else {
|
|
902
|
+
exampleParams[key] = "value";
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Set the example in the textarea
|
|
908
|
+
if (Object.keys(exampleParams).length > 0) {
|
|
909
|
+
paramsTextarea.value = JSON.stringify(exampleParams, null, 2);
|
|
910
|
+
} else {
|
|
911
|
+
paramsTextarea.value = '{}';
|
|
912
|
+
}
|
|
913
|
+
} catch (error) {
|
|
914
|
+
console.error('Error generating parameter example:', error);
|
|
915
|
+
paramsTextarea.value = '{}';
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// Test functions
|
|
920
|
+
async function runAutoTests(runAll = false) {
|
|
921
|
+
const serverId = document.getElementById('testServerSelect')?.value;
|
|
922
|
+
if (!serverId) {
|
|
923
|
+
showError('test-error', 'Please select a server to test');
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const loading = document.getElementById('test-loading');
|
|
928
|
+
const loadingText = document.getElementById('loadingText');
|
|
929
|
+
const resultsDiv = document.getElementById('test-results');
|
|
930
|
+
const noResults = document.getElementById('no-results');
|
|
931
|
+
const errorDiv = document.getElementById('test-error');
|
|
932
|
+
|
|
933
|
+
// Update button states
|
|
934
|
+
const quickBtn = document.getElementById('runQuickTestBtn');
|
|
935
|
+
const fullBtn = document.getElementById('runFullTestBtn');
|
|
936
|
+
const fullBtnIcon = document.getElementById('fullTestIcon');
|
|
937
|
+
const fullBtnText = document.getElementById('fullTestText');
|
|
938
|
+
|
|
939
|
+
if (runAll) {
|
|
940
|
+
// Show loading spinner in button
|
|
941
|
+
fullBtnIcon.className = 'fas fa-spinner fa-spin mr-2';
|
|
942
|
+
fullBtnText.textContent = 'Testing All Tools...';
|
|
943
|
+
fullBtn.disabled = true;
|
|
944
|
+
quickBtn.disabled = true;
|
|
945
|
+
loadingText.textContent = 'Running all tests... This may take a while.';
|
|
946
|
+
} else {
|
|
947
|
+
quickBtn.disabled = true;
|
|
948
|
+
fullBtn.disabled = true;
|
|
949
|
+
loadingText.textContent = 'Running quick tests...';
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
loading?.classList.remove('hidden');
|
|
953
|
+
resultsDiv?.classList.add('hidden');
|
|
954
|
+
noResults?.classList.add('hidden');
|
|
955
|
+
errorDiv?.classList.add('hidden');
|
|
956
|
+
|
|
957
|
+
try {
|
|
958
|
+
const response = await fetch(`/api/servers/${serverId}/test`, {
|
|
959
|
+
method: 'POST',
|
|
960
|
+
headers: {
|
|
961
|
+
'Content-Type': 'application/json'
|
|
962
|
+
},
|
|
963
|
+
body: JSON.stringify({ runAll: runAll })
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
const result = await response.json();
|
|
967
|
+
|
|
968
|
+
if (result.success) {
|
|
969
|
+
displayTestResults(result.data);
|
|
970
|
+
} else {
|
|
971
|
+
throw new Error(result.error);
|
|
972
|
+
}
|
|
973
|
+
} catch (error) {
|
|
974
|
+
showError('test-error', error.message);
|
|
975
|
+
noResults?.classList.remove('hidden');
|
|
976
|
+
} finally {
|
|
977
|
+
loading?.classList.add('hidden');
|
|
978
|
+
|
|
979
|
+
// Reset button states
|
|
980
|
+
if (quickBtn) quickBtn.disabled = false;
|
|
981
|
+
if (fullBtn) {
|
|
982
|
+
fullBtn.disabled = false;
|
|
983
|
+
fullBtnIcon.className = 'fas fa-play-circle mr-2';
|
|
984
|
+
fullBtnText.textContent = 'Run Auto Tests';
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
async function runCustomTest() {
|
|
990
|
+
const serverId = document.getElementById('testServerSelect')?.value;
|
|
991
|
+
const testType = document.getElementById('testType')?.value;
|
|
992
|
+
const testName = document.getElementById('testName')?.value;
|
|
993
|
+
const testParams = document.getElementById('testParams')?.value;
|
|
994
|
+
|
|
995
|
+
if (!serverId || !testName) {
|
|
996
|
+
showError('test-error', 'Please select a server and enter test name');
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
let params = {};
|
|
1001
|
+
if (testParams) {
|
|
1002
|
+
try {
|
|
1003
|
+
params = JSON.parse(testParams);
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
showError('test-error', 'Invalid JSON in parameters');
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const loading = document.getElementById('test-loading');
|
|
1011
|
+
const resultsDiv = document.getElementById('test-results');
|
|
1012
|
+
const noResults = document.getElementById('no-results');
|
|
1013
|
+
const errorDiv = document.getElementById('test-error');
|
|
1014
|
+
|
|
1015
|
+
loading?.classList.remove('hidden');
|
|
1016
|
+
resultsDiv?.classList.add('hidden');
|
|
1017
|
+
noResults?.classList.add('hidden');
|
|
1018
|
+
errorDiv?.classList.add('hidden');
|
|
1019
|
+
|
|
1020
|
+
try {
|
|
1021
|
+
const response = await fetch(`/api/servers/${serverId}/test`, {
|
|
1022
|
+
method: 'POST',
|
|
1023
|
+
headers: {
|
|
1024
|
+
'Content-Type': 'application/json'
|
|
1025
|
+
},
|
|
1026
|
+
body: JSON.stringify({
|
|
1027
|
+
testType: testType,
|
|
1028
|
+
toolName: testName,
|
|
1029
|
+
parameters: params
|
|
1030
|
+
})
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
const result = await response.json();
|
|
1034
|
+
|
|
1035
|
+
if (result.success) {
|
|
1036
|
+
displaySingleTestResult(result.data);
|
|
1037
|
+
} else {
|
|
1038
|
+
throw new Error(result.error);
|
|
1039
|
+
}
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
showError('test-error', error.message);
|
|
1042
|
+
noResults?.classList.remove('hidden');
|
|
1043
|
+
} finally {
|
|
1044
|
+
loading?.classList.add('hidden');
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Display test results
|
|
1049
|
+
function displayTestResults(testData) {
|
|
1050
|
+
const resultsDiv = document.getElementById('test-results');
|
|
1051
|
+
const noResults = document.getElementById('no-results');
|
|
1052
|
+
|
|
1053
|
+
if (!resultsDiv) return;
|
|
1054
|
+
|
|
1055
|
+
// Check if this is the new format from SQLite-based test
|
|
1056
|
+
if (testData.serverName && testData.results) {
|
|
1057
|
+
// Count success and failed tests
|
|
1058
|
+
const successCount = testData.results.filter(r => r.status === 'success').length;
|
|
1059
|
+
const failedCount = testData.results.filter(r => r.status === 'error').length;
|
|
1060
|
+
|
|
1061
|
+
let output = `=== Test Results for ${testData.serverName} ===\n`;
|
|
1062
|
+
output += `Total Tools: ${testData.toolsCount}\n`;
|
|
1063
|
+
output += `Tests Run: ${testData.testsRun}\n`;
|
|
1064
|
+
output += `✅ Success: ${successCount} | ❌ Failed: ${failedCount}\n\n`;
|
|
1065
|
+
|
|
1066
|
+
testData.results.forEach(result => {
|
|
1067
|
+
const status = result.status === 'success' ? '✅ PASS' : '❌ FAIL';
|
|
1068
|
+
output += `${status} ${result.tool}\n`;
|
|
1069
|
+
output += ` Description: ${result.description}\n`;
|
|
1070
|
+
if (result.parameters && Object.keys(result.parameters).length > 0) {
|
|
1071
|
+
output += ` Parameters: ${JSON.stringify(result.parameters)}\n`;
|
|
1072
|
+
}
|
|
1073
|
+
if (result.status === 'success') {
|
|
1074
|
+
output += ` Result: ${result.result}\n`;
|
|
1075
|
+
if (result.rowCount !== undefined) {
|
|
1076
|
+
output += ` Rows: ${result.rowCount}\n`;
|
|
1077
|
+
}
|
|
1078
|
+
} else if (result.error) {
|
|
1079
|
+
output += ` Error: ${result.error}\n`;
|
|
1080
|
+
}
|
|
1081
|
+
output += '\n';
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
resultsDiv.textContent = output;
|
|
1085
|
+
resultsDiv.classList.remove('hidden');
|
|
1086
|
+
noResults?.classList.add('hidden');
|
|
1087
|
+
} else if (testData.testSuite) {
|
|
1088
|
+
// Old format for compatibility
|
|
1089
|
+
let output = `=== Test Suite: ${testData.testSuite.name} ===\n`;
|
|
1090
|
+
output += `Description: ${testData.testSuite.description}\n`;
|
|
1091
|
+
output += `Duration: ${testData.duration}ms\n`;
|
|
1092
|
+
output += `Results: ${testData.passedTests}/${testData.totalTests} tests passed\n\n`;
|
|
1093
|
+
|
|
1094
|
+
testData.results.forEach(testResult => {
|
|
1095
|
+
const status = testResult.passed ? '✅ PASS' : '❌ FAIL';
|
|
1096
|
+
output += `${status} ${testResult.testCase.name} (${testResult.duration}ms)\n`;
|
|
1097
|
+
|
|
1098
|
+
if (!testResult.passed) {
|
|
1099
|
+
output += ` Error: ${testResult.error || testResult.response.error || 'Test assertion failed'}\n`;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
if (testResult.response && testResult.response.data) {
|
|
1103
|
+
const dataPreview = JSON.stringify(testResult.response.data, null, 2).slice(0, 200);
|
|
1104
|
+
output += ` Response: ${dataPreview}${dataPreview.length >= 200 ? '...' : ''}\n`;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
output += '\n';
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
resultsDiv.textContent = output;
|
|
1111
|
+
resultsDiv.classList.remove('hidden');
|
|
1112
|
+
noResults?.classList.add('hidden');
|
|
1113
|
+
} else {
|
|
1114
|
+
// Fallback for unknown format - just display the raw JSON
|
|
1115
|
+
resultsDiv.textContent = JSON.stringify(testData, null, 2);
|
|
1116
|
+
resultsDiv.classList.remove('hidden');
|
|
1117
|
+
noResults?.classList.add('hidden');
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function displaySingleTestResult(testData) {
|
|
1122
|
+
const resultsDiv = document.getElementById('test-results');
|
|
1123
|
+
const noResults = document.getElementById('no-results');
|
|
1124
|
+
|
|
1125
|
+
if (!resultsDiv) return;
|
|
1126
|
+
|
|
1127
|
+
// Check if this is the new format from custom tool test
|
|
1128
|
+
if (testData && testData.tool) {
|
|
1129
|
+
let output = `=== Custom Test Result ===\n`;
|
|
1130
|
+
output += `Tool: ${testData.tool}\n`;
|
|
1131
|
+
output += `Status: ${testData.status === 'success' ? '✅ PASS' : '❌ FAIL'}\n`;
|
|
1132
|
+
if (testData.description) {
|
|
1133
|
+
output += `Description: ${testData.description}\n`;
|
|
1134
|
+
}
|
|
1135
|
+
if (testData.parameters && Object.keys(testData.parameters).length > 0) {
|
|
1136
|
+
output += `Parameters: ${JSON.stringify(testData.parameters)}\n`;
|
|
1137
|
+
}
|
|
1138
|
+
output += '\n';
|
|
1139
|
+
|
|
1140
|
+
if (testData.status === 'success') {
|
|
1141
|
+
if (testData.result) {
|
|
1142
|
+
output += `Result: ${typeof testData.result === 'object' ? JSON.stringify(testData.result, null, 2) : testData.result}\n`;
|
|
1143
|
+
}
|
|
1144
|
+
if (testData.rowCount !== undefined) {
|
|
1145
|
+
output += `Rows: ${testData.rowCount}\n`;
|
|
1146
|
+
}
|
|
1147
|
+
} else if (testData.error) {
|
|
1148
|
+
output += `Error: ${testData.error}\n`;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
resultsDiv.textContent = output;
|
|
1152
|
+
resultsDiv.classList.remove('hidden');
|
|
1153
|
+
noResults?.classList.add('hidden');
|
|
1154
|
+
} else if (testData && testData.testCase) {
|
|
1155
|
+
// Old format for compatibility
|
|
1156
|
+
let output = `=== Custom Test Result ===\n`;
|
|
1157
|
+
output += `Test: ${testData.testCase.name}\n`;
|
|
1158
|
+
output += `Duration: ${testData.duration}ms\n`;
|
|
1159
|
+
output += `Status: ${testData.passed ? '✅ PASS' : '❌ FAIL'}\n\n`;
|
|
1160
|
+
|
|
1161
|
+
if (!testData.passed) {
|
|
1162
|
+
output += `Error: ${testData.error || testData.response?.error || 'Test assertion failed'}\n\n`;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
if (testData.response?.data) {
|
|
1166
|
+
output += `Response:\n${JSON.stringify(testData.response.data, null, 2)}\n`;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
resultsDiv.textContent = output;
|
|
1170
|
+
resultsDiv.classList.remove('hidden');
|
|
1171
|
+
noResults?.classList.add('hidden');
|
|
1172
|
+
} else {
|
|
1173
|
+
// Fallback for unknown format
|
|
1174
|
+
resultsDiv.textContent = JSON.stringify(testData, null, 2);
|
|
1175
|
+
resultsDiv.classList.remove('hidden');
|
|
1176
|
+
noResults?.classList.add('hidden');
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// Server management functions
|
|
1181
|
+
async function viewServer(serverId) {
|
|
1182
|
+
console.log('🔍 viewServer called with serverId:', serverId);
|
|
1183
|
+
|
|
1184
|
+
try {
|
|
1185
|
+
console.log('🔍 Fetching server details from:', `/api/servers/${serverId}`);
|
|
1186
|
+
const response = await fetch(`/api/servers/${serverId}`);
|
|
1187
|
+
console.log('🔍 Response status:', response.status);
|
|
1188
|
+
|
|
1189
|
+
const result = await response.json();
|
|
1190
|
+
console.log('🔍 Response result:', result);
|
|
1191
|
+
|
|
1192
|
+
if (result.success) {
|
|
1193
|
+
console.log('🔍 Server data structure:', result.data);
|
|
1194
|
+
console.log('🔍 Config tools:', result.data?.config?.tools);
|
|
1195
|
+
console.log('🔍 Config resources:', result.data?.config?.resources);
|
|
1196
|
+
showServerDetailsModal(result.data);
|
|
1197
|
+
} else {
|
|
1198
|
+
console.error('❌ Failed to load server details:', result.error);
|
|
1199
|
+
alert('Failed to load server details: ' + result.error);
|
|
1200
|
+
}
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
console.error('❌ Error loading server details:', error);
|
|
1203
|
+
alert('Error loading server details: ' + error.message);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function showServerDetailsModal(serverData) {
|
|
1208
|
+
console.log('🔍 showServerDetailsModal called with:', serverData);
|
|
1209
|
+
|
|
1210
|
+
// Safely extract data with defaults
|
|
1211
|
+
const config = serverData?.config || {};
|
|
1212
|
+
const tools = config.tools || [];
|
|
1213
|
+
const resources = config.resources || [];
|
|
1214
|
+
const serverName = config.name || 'Unknown Server';
|
|
1215
|
+
const serverDescription = config.description || 'No description available';
|
|
1216
|
+
|
|
1217
|
+
console.log('🔍 Modal data:', { tools: tools.length, resources: resources.length, serverName });
|
|
1218
|
+
|
|
1219
|
+
const modalHtml = `
|
|
1220
|
+
<div id="server-details-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
1221
|
+
<div class="bg-white rounded-2xl shadow-xl max-w-4xl w-full mx-4 max-h-[90vh] overflow-y-auto">
|
|
1222
|
+
<div class="p-6 border-b border-gray-200">
|
|
1223
|
+
<div class="flex items-center justify-between">
|
|
1224
|
+
<h2 class="text-2xl font-bold text-gray-900">${serverName}</h2>
|
|
1225
|
+
<button onclick="closeServerDetailsModal()" class="text-gray-400 hover:text-gray-600">
|
|
1226
|
+
<i class="fas fa-times text-xl"></i>
|
|
1227
|
+
</button>
|
|
1228
|
+
</div>
|
|
1229
|
+
<p class="text-gray-600 mt-2">${serverDescription}</p>
|
|
1230
|
+
</div>
|
|
1231
|
+
|
|
1232
|
+
<div class="p-6">
|
|
1233
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
1234
|
+
<!-- Tools Section -->
|
|
1235
|
+
<div>
|
|
1236
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-4">
|
|
1237
|
+
<i class="fas fa-tools mr-2 text-blue-500"></i>
|
|
1238
|
+
Tools (${tools.length})
|
|
1239
|
+
</h3>
|
|
1240
|
+
<div class="space-y-3 max-h-60 overflow-y-auto">
|
|
1241
|
+
${tools.length > 0 ? tools.map(tool => `
|
|
1242
|
+
<div class="bg-gray-50 rounded-lg p-3">
|
|
1243
|
+
<div class="font-medium text-gray-900">${tool.name || 'Unnamed Tool'}</div>
|
|
1244
|
+
<div class="text-sm text-gray-600 mt-1">${tool.description || 'No description'}</div>
|
|
1245
|
+
<div class="text-xs text-gray-500 mt-1">Operation: ${tool.operation || 'Unknown'}</div>
|
|
1246
|
+
</div>
|
|
1247
|
+
`).join('') : '<div class="text-gray-500 text-sm">No tools available</div>'}
|
|
1248
|
+
</div>
|
|
1249
|
+
</div>
|
|
1250
|
+
|
|
1251
|
+
<!-- Resources Section -->
|
|
1252
|
+
<div>
|
|
1253
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-4">
|
|
1254
|
+
<i class="fas fa-database mr-2 text-green-500"></i>
|
|
1255
|
+
Resources (${resources.length})
|
|
1256
|
+
</h3>
|
|
1257
|
+
<div class="space-y-3 max-h-60 overflow-y-auto">
|
|
1258
|
+
${resources.length > 0 ? resources.map(resource => `
|
|
1259
|
+
<div class="bg-gray-50 rounded-lg p-3">
|
|
1260
|
+
<div class="font-medium text-gray-900">${resource.name || 'Unnamed Resource'}</div>
|
|
1261
|
+
<div class="text-sm text-gray-600 mt-1">${resource.description || 'No description'}</div>
|
|
1262
|
+
<div class="text-xs text-gray-500 mt-1 font-mono">${resource.uri_template || resource.uri || 'No URI'}</div>
|
|
1263
|
+
</div>
|
|
1264
|
+
`).join('') : '<div class="text-gray-500 text-sm">No resources available</div>'}
|
|
1265
|
+
</div>
|
|
1266
|
+
</div>
|
|
1267
|
+
</div>
|
|
1268
|
+
|
|
1269
|
+
<div class="mt-6 pt-6 border-t border-gray-200">
|
|
1270
|
+
<div class="flex flex-wrap gap-4">
|
|
1271
|
+
<button onclick="testServer('${serverData.id || serverData.config?.id || 'unknown'}')" class="bg-blue-500 text-white px-4 py-2 rounded-lg font-medium hover:bg-blue-600 transition-all duration-200">
|
|
1272
|
+
<i class="fas fa-vial mr-2"></i>
|
|
1273
|
+
Test Server
|
|
1274
|
+
</button>
|
|
1275
|
+
<button onclick="exportServer('${serverData.id || serverData.config?.id || 'unknown'}')" class="bg-green-500 text-white px-4 py-2 rounded-lg font-medium hover:bg-green-600 transition-all duration-200">
|
|
1276
|
+
<i class="fas fa-download mr-2"></i>
|
|
1277
|
+
Export Server
|
|
1278
|
+
</button>
|
|
1279
|
+
<button onclick="deleteServer('${serverData.id || serverData.config?.id || 'unknown'}')" class="bg-red-500 text-white px-4 py-2 rounded-lg font-medium hover:bg-red-600 transition-all duration-200">
|
|
1280
|
+
<i class="fas fa-trash mr-2"></i>
|
|
1281
|
+
Delete Server
|
|
1282
|
+
</button>
|
|
1283
|
+
</div>
|
|
1284
|
+
</div>
|
|
1285
|
+
</div>
|
|
1286
|
+
</div>
|
|
1287
|
+
</div>
|
|
1288
|
+
`;
|
|
1289
|
+
|
|
1290
|
+
// Add modal to body
|
|
1291
|
+
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
1292
|
+
console.log('🔍 Modal added to DOM');
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
function closeServerDetailsModal() {
|
|
1296
|
+
const modal = document.getElementById('server-details-modal');
|
|
1297
|
+
if (modal) {
|
|
1298
|
+
modal.remove();
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
function testServer(serverId) {
|
|
1303
|
+
// Close modal if open
|
|
1304
|
+
closeServerDetailsModal();
|
|
1305
|
+
|
|
1306
|
+
switchTab('test');
|
|
1307
|
+
setTimeout(() => {
|
|
1308
|
+
const select = document.getElementById('testServerSelect');
|
|
1309
|
+
if (select) select.value = serverId;
|
|
1310
|
+
}, 100);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
async function exportServer(serverId) {
|
|
1314
|
+
try {
|
|
1315
|
+
const response = await fetch(`/api/servers/${serverId}/export`);
|
|
1316
|
+
const result = await response.json();
|
|
1317
|
+
|
|
1318
|
+
if (result.success) {
|
|
1319
|
+
// Create download link
|
|
1320
|
+
const link = document.createElement('a');
|
|
1321
|
+
link.href = result.data.downloadUrl;
|
|
1322
|
+
link.download = result.data.filename;
|
|
1323
|
+
link.click();
|
|
1324
|
+
}
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
console.error('Export failed:', error);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
async function deleteServer(serverId) {
|
|
1331
|
+
// Get server name for modal display
|
|
1332
|
+
const serverName = await getServerName(serverId);
|
|
1333
|
+
showDeleteConfirmModal(serverId, serverName || 'Unknown Server');
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
async function getServerName(serverId) {
|
|
1337
|
+
try {
|
|
1338
|
+
const response = await fetch(`/api/servers/${serverId}`);
|
|
1339
|
+
const result = await response.json();
|
|
1340
|
+
return result.success ? result.data.config.name : null;
|
|
1341
|
+
} catch (error) {
|
|
1342
|
+
return null;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function showDeleteConfirmModal(serverId, serverName) {
|
|
1347
|
+
const modalHtml = `
|
|
1348
|
+
<div id="delete-confirm-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
1349
|
+
<div class="bg-white rounded-2xl shadow-xl max-w-md w-full mx-4">
|
|
1350
|
+
<div class="p-6">
|
|
1351
|
+
<div class="flex items-center justify-center w-16 h-16 mx-auto bg-red-100 rounded-full mb-4">
|
|
1352
|
+
<i class="fas fa-trash text-2xl text-red-600"></i>
|
|
1353
|
+
</div>
|
|
1354
|
+
|
|
1355
|
+
<h3 class="text-lg font-semibold text-gray-900 text-center mb-2">Delete Server</h3>
|
|
1356
|
+
<p class="text-gray-600 text-center mb-6">
|
|
1357
|
+
Are you sure you want to delete <strong>"${serverName}"</strong>?
|
|
1358
|
+
<br><br>
|
|
1359
|
+
This action cannot be undone and will permanently remove all server files and configurations.
|
|
1360
|
+
</p>
|
|
1361
|
+
|
|
1362
|
+
<div class="flex gap-3">
|
|
1363
|
+
<button onclick="closeDeleteConfirmModal()" class="flex-1 bg-gray-100 text-gray-700 px-4 py-2 rounded-lg font-medium hover:bg-gray-200 transition-all duration-200">
|
|
1364
|
+
Cancel
|
|
1365
|
+
</button>
|
|
1366
|
+
<button onclick="confirmDeleteServer('${serverId}')" class="flex-1 bg-red-500 text-white px-4 py-2 rounded-lg font-medium hover:bg-red-600 transition-all duration-200">
|
|
1367
|
+
<i class="fas fa-trash mr-2"></i>
|
|
1368
|
+
Delete
|
|
1369
|
+
</button>
|
|
1370
|
+
</div>
|
|
1371
|
+
</div>
|
|
1372
|
+
</div>
|
|
1373
|
+
</div>
|
|
1374
|
+
`;
|
|
1375
|
+
|
|
1376
|
+
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
function closeDeleteConfirmModal() {
|
|
1380
|
+
const modal = document.getElementById('delete-confirm-modal');
|
|
1381
|
+
if (modal) {
|
|
1382
|
+
modal.remove();
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
async function confirmDeleteServer(serverId) {
|
|
1387
|
+
// Close confirmation modal
|
|
1388
|
+
closeDeleteConfirmModal();
|
|
1389
|
+
|
|
1390
|
+
// Show loading modal
|
|
1391
|
+
showDeleteLoadingModal();
|
|
1392
|
+
|
|
1393
|
+
try {
|
|
1394
|
+
const response = await fetch(`/api/servers/${serverId}`, {
|
|
1395
|
+
method: 'DELETE'
|
|
1396
|
+
});
|
|
1397
|
+
|
|
1398
|
+
const result = await response.json();
|
|
1399
|
+
|
|
1400
|
+
// Close loading modal
|
|
1401
|
+
closeDeleteLoadingModal();
|
|
1402
|
+
|
|
1403
|
+
if (result.success) {
|
|
1404
|
+
// Close server details modal if open
|
|
1405
|
+
closeServerDetailsModal();
|
|
1406
|
+
|
|
1407
|
+
// Show success modal
|
|
1408
|
+
showDeleteSuccessModal();
|
|
1409
|
+
|
|
1410
|
+
// Reload server lists
|
|
1411
|
+
loadServers();
|
|
1412
|
+
loadTestServers();
|
|
1413
|
+
} else {
|
|
1414
|
+
throw new Error(result.error);
|
|
1415
|
+
}
|
|
1416
|
+
} catch (error) {
|
|
1417
|
+
closeDeleteLoadingModal();
|
|
1418
|
+
showDeleteErrorModal(error.message);
|
|
1419
|
+
console.error('Delete failed:', error);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
function showDeleteLoadingModal() {
|
|
1424
|
+
const modalHtml = `
|
|
1425
|
+
<div id="delete-loading-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
1426
|
+
<div class="bg-white rounded-2xl shadow-xl max-w-md w-full mx-4">
|
|
1427
|
+
<div class="p-6 text-center">
|
|
1428
|
+
<div class="animate-spin rounded-full h-16 w-16 border-b-2 border-red-500 mx-auto mb-4"></div>
|
|
1429
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-2">Deleting Server</h3>
|
|
1430
|
+
<p class="text-gray-600">Please wait while we remove the server and all its files...</p>
|
|
1431
|
+
</div>
|
|
1432
|
+
</div>
|
|
1433
|
+
</div>
|
|
1434
|
+
`;
|
|
1435
|
+
|
|
1436
|
+
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
function closeDeleteLoadingModal() {
|
|
1440
|
+
const modal = document.getElementById('delete-loading-modal');
|
|
1441
|
+
if (modal) {
|
|
1442
|
+
modal.remove();
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
function showDeleteSuccessModal() {
|
|
1447
|
+
const modalHtml = `
|
|
1448
|
+
<div id="delete-success-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
1449
|
+
<div class="bg-white rounded-2xl shadow-xl max-w-md w-full mx-4">
|
|
1450
|
+
<div class="p-6">
|
|
1451
|
+
<div class="flex items-center justify-center w-16 h-16 mx-auto bg-green-100 rounded-full mb-4">
|
|
1452
|
+
<i class="fas fa-check text-2xl text-green-600"></i>
|
|
1453
|
+
</div>
|
|
1454
|
+
|
|
1455
|
+
<h3 class="text-lg font-semibold text-gray-900 text-center mb-2">Server Deleted Successfully</h3>
|
|
1456
|
+
<p class="text-gray-600 text-center mb-6">
|
|
1457
|
+
The server has been permanently removed from your system.
|
|
1458
|
+
</p>
|
|
1459
|
+
|
|
1460
|
+
<button onclick="closeDeleteSuccessModal()" class="w-full bg-green-500 text-white px-4 py-2 rounded-lg font-medium hover:bg-green-600 transition-all duration-200">
|
|
1461
|
+
<i class="fas fa-check mr-2"></i>
|
|
1462
|
+
OK
|
|
1463
|
+
</button>
|
|
1464
|
+
</div>
|
|
1465
|
+
</div>
|
|
1466
|
+
</div>
|
|
1467
|
+
`;
|
|
1468
|
+
|
|
1469
|
+
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
function closeDeleteSuccessModal() {
|
|
1473
|
+
const modal = document.getElementById('delete-success-modal');
|
|
1474
|
+
if (modal) {
|
|
1475
|
+
modal.remove();
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
function showDeleteErrorModal(errorMessage) {
|
|
1480
|
+
const modalHtml = `
|
|
1481
|
+
<div id="delete-error-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
1482
|
+
<div class="bg-white rounded-2xl shadow-xl max-w-md w-full mx-4">
|
|
1483
|
+
<div class="p-6">
|
|
1484
|
+
<div class="flex items-center justify-center w-16 h-16 mx-auto bg-red-100 rounded-full mb-4">
|
|
1485
|
+
<i class="fas fa-exclamation-triangle text-2xl text-red-600"></i>
|
|
1486
|
+
</div>
|
|
1487
|
+
|
|
1488
|
+
<h3 class="text-lg font-semibold text-gray-900 text-center mb-2">Delete Failed</h3>
|
|
1489
|
+
<p class="text-gray-600 text-center mb-6">
|
|
1490
|
+
Failed to delete the server: ${errorMessage}
|
|
1491
|
+
</p>
|
|
1492
|
+
|
|
1493
|
+
<button onclick="closeDeleteErrorModal()" class="w-full bg-red-500 text-white px-4 py-2 rounded-lg font-medium hover:bg-red-600 transition-all duration-200">
|
|
1494
|
+
<i class="fas fa-times mr-2"></i>
|
|
1495
|
+
Close
|
|
1496
|
+
</button>
|
|
1497
|
+
</div>
|
|
1498
|
+
</div>
|
|
1499
|
+
</div>
|
|
1500
|
+
`;
|
|
1501
|
+
|
|
1502
|
+
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
function closeDeleteErrorModal() {
|
|
1506
|
+
const modal = document.getElementById('delete-error-modal');
|
|
1507
|
+
if (modal) {
|
|
1508
|
+
modal.remove();
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
// Success Modal Functions
|
|
1513
|
+
function showSuccessModal(serverName, serverData) {
|
|
1514
|
+
const modal = document.getElementById("success-modal");
|
|
1515
|
+
const messageElement = document.getElementById("success-message");
|
|
1516
|
+
|
|
1517
|
+
let message = `Your MCP server "${serverName}" has been successfully generated and is ready to use.`;
|
|
1518
|
+
|
|
1519
|
+
if (serverData) {
|
|
1520
|
+
message += ` Generated ${serverData.toolsCount || 0} tools, ${serverData.resourcesCount || 0} resources, and ${serverData.promptsCount || 0} prompts.`;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
if (messageElement) messageElement.textContent = message;
|
|
1524
|
+
|
|
1525
|
+
if (modal) {
|
|
1526
|
+
modal.classList.remove('opacity-0', 'invisible');
|
|
1527
|
+
modal.querySelector('.bg-white').classList.remove('scale-95');
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
function closeSuccessModal() {
|
|
1532
|
+
const modal = document.getElementById("success-modal");
|
|
1533
|
+
if (modal) {
|
|
1534
|
+
modal.classList.add('opacity-0', 'invisible');
|
|
1535
|
+
modal.querySelector('.bg-white').classList.add('scale-95');
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
function goToManageServers() {
|
|
1540
|
+
closeSuccessModal();
|
|
1541
|
+
switchTab('manage');
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// Server name validation
|
|
1545
|
+
let nameCheckTimeout;
|
|
1546
|
+
|
|
1547
|
+
async function checkServerName() {
|
|
1548
|
+
const nameInput = document.getElementById('serverName');
|
|
1549
|
+
const validationDiv = document.getElementById('name-validation');
|
|
1550
|
+
const serverName = nameInput?.value.trim();
|
|
1551
|
+
|
|
1552
|
+
// Clear previous timeout
|
|
1553
|
+
if (nameCheckTimeout) {
|
|
1554
|
+
clearTimeout(nameCheckTimeout);
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
// Hide validation if empty
|
|
1558
|
+
if (!serverName) {
|
|
1559
|
+
if (validationDiv) validationDiv.style.display = 'none';
|
|
1560
|
+
if (nameInput) nameInput.classList.remove('border-green-300', 'border-red-300');
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
// Debounce API calls
|
|
1565
|
+
nameCheckTimeout = setTimeout(async () => {
|
|
1566
|
+
try {
|
|
1567
|
+
const response = await fetch(`/api/servers/check-name/${encodeURIComponent(serverName)}`);
|
|
1568
|
+
const result = await response.json();
|
|
1569
|
+
|
|
1570
|
+
if (result.success && validationDiv && nameInput) {
|
|
1571
|
+
validationDiv.style.display = 'block';
|
|
1572
|
+
if (result.available) {
|
|
1573
|
+
validationDiv.textContent = '✓ Server name is available';
|
|
1574
|
+
validationDiv.className = 'mt-2 text-sm text-green-600';
|
|
1575
|
+
nameInput.classList.remove('border-red-300');
|
|
1576
|
+
nameInput.classList.add('border-green-300');
|
|
1577
|
+
} else {
|
|
1578
|
+
validationDiv.textContent = '✗ Server name already exists';
|
|
1579
|
+
validationDiv.className = 'mt-2 text-sm text-red-600';
|
|
1580
|
+
nameInput.classList.remove('border-green-300');
|
|
1581
|
+
nameInput.classList.add('border-red-300');
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
} catch (error) {
|
|
1585
|
+
console.error('Error checking server name:', error);
|
|
1586
|
+
}
|
|
1587
|
+
}, 500);
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// Handle next to step 2 - parse data first
|
|
1591
|
+
async function handleNextToStep2() {
|
|
1592
|
+
const selectedType = document.querySelector('input[name="dataSourceType"]:checked')?.value;
|
|
1593
|
+
|
|
1594
|
+
if (!selectedType) {
|
|
1595
|
+
showError('parse-error', 'Please select a data source type');
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
// If we already have parsed data, just go to step 2
|
|
1600
|
+
if (currentParsedData) {
|
|
1601
|
+
goToWizardStep(2);
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
const loading = document.getElementById('parse-loading');
|
|
1606
|
+
const errorDiv = document.getElementById('parse-error');
|
|
1607
|
+
const nextBtn = document.getElementById('next-to-step-2');
|
|
1608
|
+
|
|
1609
|
+
loading?.classList.remove('hidden');
|
|
1610
|
+
errorDiv?.classList.add('hidden');
|
|
1611
|
+
if (nextBtn) nextBtn.disabled = true;
|
|
1612
|
+
|
|
1613
|
+
try {
|
|
1614
|
+
const formData = new FormData();
|
|
1615
|
+
formData.append('type', selectedType);
|
|
1616
|
+
|
|
1617
|
+
if (selectedType === 'csv' || selectedType === 'excel') {
|
|
1618
|
+
const fileInput = document.getElementById('fileInput');
|
|
1619
|
+
if (!fileInput?.files[0]) {
|
|
1620
|
+
throw new Error('Please select a file');
|
|
1621
|
+
}
|
|
1622
|
+
formData.append('file', fileInput.files[0]);
|
|
1623
|
+
} else if (selectedType === 'database') {
|
|
1624
|
+
const connection = {
|
|
1625
|
+
type: document.getElementById('dbType')?.value,
|
|
1626
|
+
host: document.getElementById('dbHost')?.value,
|
|
1627
|
+
port: parseInt(document.getElementById('dbPort')?.value),
|
|
1628
|
+
database: document.getElementById('dbName')?.value,
|
|
1629
|
+
username: document.getElementById('dbUser')?.value,
|
|
1630
|
+
password: document.getElementById('dbPassword')?.value
|
|
1631
|
+
};
|
|
1632
|
+
formData.append('connection', JSON.stringify(connection));
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
const response = await fetch('/api/parse', {
|
|
1636
|
+
method: 'POST',
|
|
1637
|
+
body: formData
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
const result = await response.json();
|
|
1641
|
+
|
|
1642
|
+
if (result.success) {
|
|
1643
|
+
currentParsedData = result.data.parsedData;
|
|
1644
|
+
currentDataSource = result.data.dataSource;
|
|
1645
|
+
displayDataPreview(result.data.parsedData);
|
|
1646
|
+
|
|
1647
|
+
// Go to step 2 after successful parse
|
|
1648
|
+
goToWizardStep(2);
|
|
1649
|
+
} else {
|
|
1650
|
+
throw new Error(result.error);
|
|
1651
|
+
}
|
|
1652
|
+
} catch (error) {
|
|
1653
|
+
showError('parse-error', error.message);
|
|
1654
|
+
} finally {
|
|
1655
|
+
loading?.classList.add('hidden');
|
|
1656
|
+
if (nextBtn) nextBtn.disabled = false;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
// Wizard Navigation Functions
|
|
1661
|
+
function goToWizardStep(stepNumber) {
|
|
1662
|
+
// Hide all steps
|
|
1663
|
+
document.querySelectorAll('.wizard-step').forEach(step => {
|
|
1664
|
+
step.classList.add('hidden');
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
// Show target step
|
|
1668
|
+
const targetStep = document.getElementById(`wizard-step-${stepNumber}`);
|
|
1669
|
+
if (targetStep) {
|
|
1670
|
+
targetStep.classList.remove('hidden');
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
// Update progress indicators
|
|
1674
|
+
updateWizardProgress(stepNumber);
|
|
1675
|
+
|
|
1676
|
+
currentWizardStep = stepNumber;
|
|
1677
|
+
|
|
1678
|
+
// Enable/disable navigation based on step and data state
|
|
1679
|
+
updateWizardNavigation();
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
function updateWizardProgress(activeStep) {
|
|
1683
|
+
// Reset all step indicators
|
|
1684
|
+
for (let i = 1; i <= 3; i++) {
|
|
1685
|
+
const indicator = document.getElementById(`step-${i}-indicator`);
|
|
1686
|
+
const stepText = indicator?.parentElement.nextElementSibling.querySelector('p');
|
|
1687
|
+
|
|
1688
|
+
if (i < activeStep) {
|
|
1689
|
+
// Completed step
|
|
1690
|
+
indicator?.classList.remove('bg-gray-300', 'text-gray-600', 'bg-blue-500');
|
|
1691
|
+
indicator?.classList.add('bg-green-500', 'text-white');
|
|
1692
|
+
stepText?.classList.remove('text-gray-500', 'text-blue-600');
|
|
1693
|
+
stepText?.classList.add('text-green-600');
|
|
1694
|
+
} else if (i === activeStep) {
|
|
1695
|
+
// Current step
|
|
1696
|
+
indicator?.classList.remove('bg-gray-300', 'text-gray-600', 'bg-green-500');
|
|
1697
|
+
indicator?.classList.add('bg-blue-500', 'text-white');
|
|
1698
|
+
stepText?.classList.remove('text-gray-500', 'text-green-600');
|
|
1699
|
+
stepText?.classList.add('text-blue-600');
|
|
1700
|
+
} else {
|
|
1701
|
+
// Future step
|
|
1702
|
+
indicator?.classList.remove('bg-blue-500', 'bg-green-500', 'text-white');
|
|
1703
|
+
indicator?.classList.add('bg-gray-300', 'text-gray-600');
|
|
1704
|
+
stepText?.classList.remove('text-blue-600', 'text-green-600');
|
|
1705
|
+
stepText?.classList.add('text-gray-500');
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// Update progress bars
|
|
1710
|
+
const progress12 = document.getElementById('progress-1-2');
|
|
1711
|
+
const progress23 = document.getElementById('progress-2-3');
|
|
1712
|
+
|
|
1713
|
+
if (activeStep >= 2) {
|
|
1714
|
+
progress12?.classList.remove('bg-gray-200');
|
|
1715
|
+
progress12?.classList.add('bg-green-500');
|
|
1716
|
+
} else {
|
|
1717
|
+
progress12?.classList.remove('bg-green-500');
|
|
1718
|
+
progress12?.classList.add('bg-gray-200');
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
if (activeStep >= 3) {
|
|
1722
|
+
progress23?.classList.remove('bg-gray-200');
|
|
1723
|
+
progress23?.classList.add('bg-green-500');
|
|
1724
|
+
} else {
|
|
1725
|
+
progress23?.classList.remove('bg-green-500');
|
|
1726
|
+
progress23?.classList.add('bg-gray-200');
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
function updateWizardNavigation() {
|
|
1731
|
+
const nextToStep2 = document.getElementById('next-to-step-2');
|
|
1732
|
+
|
|
1733
|
+
// Only enable step 2 if data source is configured and parsed, or if database connection is ready
|
|
1734
|
+
if (nextToStep2) {
|
|
1735
|
+
const hasDataSource = document.querySelector('input[name="dataSourceType"]:checked');
|
|
1736
|
+
const selectedType = hasDataSource?.value;
|
|
1737
|
+
const hasParsedData = currentParsedData !== null;
|
|
1738
|
+
|
|
1739
|
+
let canProceed = false;
|
|
1740
|
+
|
|
1741
|
+
if (selectedType === 'csv' || selectedType === 'excel') {
|
|
1742
|
+
// For file uploads, need parsed data
|
|
1743
|
+
canProceed = hasParsedData;
|
|
1744
|
+
} else if (selectedType === 'database') {
|
|
1745
|
+
// For database, check if all required fields are filled
|
|
1746
|
+
const dbType = document.getElementById('dbType')?.value;
|
|
1747
|
+
const dbHost = document.getElementById('dbHost')?.value;
|
|
1748
|
+
const dbName = document.getElementById('dbName')?.value;
|
|
1749
|
+
const dbUser = document.getElementById('dbUser')?.value;
|
|
1750
|
+
const dbPassword = document.getElementById('dbPassword')?.value;
|
|
1751
|
+
|
|
1752
|
+
canProceed = dbType && dbHost && dbName && dbUser && dbPassword;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
nextToStep2.disabled = !hasDataSource || !canProceed;
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
// Toggle data source fields (updated to enable navigation)
|
|
1760
|
+
function toggleDataSourceFields() {
|
|
1761
|
+
const selectedType = document.querySelector('input[name="dataSourceType"]:checked')?.value;
|
|
1762
|
+
const fileSection = document.getElementById('file-upload-section');
|
|
1763
|
+
const dbSection = document.getElementById('database-section');
|
|
1764
|
+
|
|
1765
|
+
// Hide all sections first
|
|
1766
|
+
fileSection?.classList.add('hidden');
|
|
1767
|
+
dbSection?.classList.add('hidden');
|
|
1768
|
+
|
|
1769
|
+
if (selectedType === 'csv' || selectedType === 'excel') {
|
|
1770
|
+
fileSection?.classList.remove('hidden');
|
|
1771
|
+
} else if (selectedType === 'database') {
|
|
1772
|
+
dbSection?.classList.remove('hidden');
|
|
1773
|
+
updateDefaultPort();
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
// Update wizard navigation state
|
|
1777
|
+
updateWizardNavigation();
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
// Utility functions
|
|
1781
|
+
function showError(elementId, message) {
|
|
1782
|
+
const errorDiv = document.getElementById(elementId);
|
|
1783
|
+
if (errorDiv) {
|
|
1784
|
+
errorDiv.textContent = message;
|
|
1785
|
+
errorDiv.classList.remove('hidden');
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
function showSuccess(elementId, message) {
|
|
1790
|
+
const successDiv = document.getElementById(elementId);
|
|
1791
|
+
if (successDiv) {
|
|
1792
|
+
successDiv.textContent = message;
|
|
1793
|
+
successDiv.classList.remove('hidden');
|
|
1794
|
+
}
|
|
1795
|
+
}
|