crushdataai 1.2.6 → 1.2.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.
Files changed (36) hide show
  1. package/assets/antigravity/data-analyst.md +8 -4
  2. package/assets/claude/SKILL.md +7 -2
  3. package/assets/copilot/data-analyst.prompt.md +8 -4
  4. package/assets/cursor/data-analyst.md +8 -4
  5. package/assets/kiro/data-analyst.md +8 -4
  6. package/assets/windsurf/data-analyst.md +8 -4
  7. package/dist/commands/schema.d.ts +1 -0
  8. package/dist/commands/schema.js +54 -0
  9. package/dist/connections.d.ts +3 -1
  10. package/dist/connections.js +125 -0
  11. package/dist/connectors/additional/index.d.ts +42 -0
  12. package/dist/connectors/additional/index.js +268 -0
  13. package/dist/connectors/cloud/index.d.ts +8 -3
  14. package/dist/connectors/cloud/index.js +321 -10
  15. package/dist/connectors/csv/index.d.ts +1 -0
  16. package/dist/connectors/csv/index.js +27 -7
  17. package/dist/connectors/custom/index.d.ts +10 -0
  18. package/dist/connectors/custom/index.js +61 -0
  19. package/dist/connectors/index.d.ts +6 -0
  20. package/dist/connectors/mysql/index.d.ts +1 -0
  21. package/dist/connectors/mysql/index.js +162 -9
  22. package/dist/connectors/postgresql/index.d.ts +2 -0
  23. package/dist/connectors/postgresql/index.js +140 -8
  24. package/dist/connectors/shopify/index.d.ts +1 -0
  25. package/dist/connectors/shopify/index.js +44 -5
  26. package/dist/index.js +7 -0
  27. package/dist/routes/connections.js +2 -0
  28. package/dist/server.js +8 -0
  29. package/package.json +8 -3
  30. package/ui/assets/index-Ba1mRihD.js +40 -0
  31. package/ui/assets/index-rlcHFDJB.css +1 -0
  32. package/ui/favicon.svg +4 -18
  33. package/ui/index.html +7 -331
  34. package/ui/favicon-32x32.png +0 -0
  35. package/ui/main.js +0 -542
  36. package/ui/styles.css +0 -680
package/ui/main.js DELETED
@@ -1,542 +0,0 @@
1
- let selectedType = null;
2
- let selectedConnection = null;
3
-
4
- // Mock sample data for table previews
5
- const mockTableData = {
6
- users: {
7
- columns: ['id', 'name', 'email', 'created_at'],
8
- rows: [
9
- [1, 'John Doe', 'john@example.com', '2024-01-15'],
10
- [2, 'Jane Smith', 'jane@example.com', '2024-01-16'],
11
- [3, 'Bob Wilson', 'bob@example.com', '2024-01-17'],
12
- [4, 'Alice Brown', 'alice@example.com', '2024-01-18'],
13
- [5, 'Charlie Davis', 'charlie@example.com', '2024-01-19']
14
- ]
15
- },
16
- orders: {
17
- columns: ['order_id', 'customer', 'total', 'status', 'date'],
18
- rows: [
19
- ['ORD-001', 'John Doe', '$129.99', 'Completed', '2024-01-20'],
20
- ['ORD-002', 'Jane Smith', '$89.50', 'Shipped', '2024-01-21'],
21
- ['ORD-003', 'Bob Wilson', '$245.00', 'Processing', '2024-01-22'],
22
- ['ORD-004', 'Alice Brown', '$67.25', 'Completed', '2024-01-23'],
23
- ['ORD-005', 'Charlie Davis', '$199.99', 'Pending', '2024-01-24']
24
- ]
25
- },
26
- products: {
27
- columns: ['sku', 'name', 'price', 'stock', 'category'],
28
- rows: [
29
- ['SKU-001', 'Wireless Headphones', '$79.99', 150, 'Electronics'],
30
- ['SKU-002', 'USB-C Cable', '$12.99', 500, 'Accessories'],
31
- ['SKU-003', 'Laptop Stand', '$49.99', 75, 'Office'],
32
- ['SKU-004', 'Bluetooth Mouse', '$29.99', 200, 'Electronics'],
33
- ['SKU-005', 'Webcam HD', '$89.99', 80, 'Electronics']
34
- ]
35
- },
36
- customers: {
37
- columns: ['id', 'company', 'contact', 'country', 'orders'],
38
- rows: [
39
- [1, 'Acme Corp', 'John Smith', 'USA', 45],
40
- [2, 'Tech Inc', 'Sarah Johnson', 'Canada', 32],
41
- [3, 'Global Ltd', 'Mike Brown', 'UK', 28],
42
- [4, 'StartupXYZ', 'Lisa Davis', 'Germany', 15],
43
- [5, 'Enterprise Co', 'Tom Wilson', 'Australia', 52]
44
- ]
45
- },
46
- data: {
47
- columns: ['id', 'value', 'category', 'timestamp'],
48
- rows: [
49
- [1, 'Sample A', 'Type 1', '2024-01-20 10:30'],
50
- [2, 'Sample B', 'Type 2', '2024-01-20 11:45'],
51
- [3, 'Sample C', 'Type 1', '2024-01-20 14:20'],
52
- [4, 'Sample D', 'Type 3', '2024-01-21 09:15'],
53
- [5, 'Sample E', 'Type 2', '2024-01-21 16:30']
54
- ]
55
- }
56
- };
57
-
58
- // Default data for unknown tables
59
- const defaultTableData = {
60
- columns: ['id', 'name', 'value', 'updated_at'],
61
- rows: [
62
- [1, 'Record 1', 'Value A', '2024-01-20'],
63
- [2, 'Record 2', 'Value B', '2024-01-21'],
64
- [3, 'Record 3', 'Value C', '2024-01-22']
65
- ]
66
- };
67
-
68
- // Help instructions for each data source
69
- const helpInstructions = {
70
- mysql: {
71
- title: 'How to Connect to MySQL',
72
- steps: [
73
- '<b>1. Get your MySQL credentials</b><br>You need: host, port (default 3306), username, password, and database name.',
74
- '<b>2. Ensure remote access is enabled</b><br>If connecting to a remote server, make sure MySQL is configured to accept remote connections.',
75
- '<b>3. Check firewall settings</b><br>Port 3306 (or your custom port) must be open.',
76
- '<b>4. Test with a MySQL client</b><br>Try connecting with MySQL Workbench or command line first to verify credentials work.'
77
- ]
78
- },
79
- postgresql: {
80
- title: 'How to Connect to PostgreSQL',
81
- steps: [
82
- '<b>1. Get your PostgreSQL credentials</b><br>You need: host, port (default 5432), username, password, and database name.',
83
- '<b>2. Check pg_hba.conf</b><br>For remote connections, ensure your IP is allowed in pg_hba.conf.',
84
- '<b>3. Verify SSL requirements</b><br>Some PostgreSQL servers require SSL connections.',
85
- '<b>4. Test with psql</b><br>Try connecting with psql command line tool to verify credentials.'
86
- ]
87
- },
88
- bigquery: {
89
- title: 'How to Connect to BigQuery',
90
- steps: [
91
- '<b>1. Create a service account</b><br>Go to Google Cloud Console → IAM & Admin → Service Accounts.',
92
- '<b>2. Download JSON key</b><br>Create a key for your service account and download the JSON file.',
93
- '<b>3. Grant BigQuery permissions</b><br>Give the service account "BigQuery Data Viewer" and "BigQuery Job User" roles.',
94
- '<b>4. Save the key file path</b><br>Store the JSON file securely and enter its path here.'
95
- ]
96
- },
97
- snowflake: {
98
- title: 'How to Connect to Snowflake',
99
- steps: [
100
- '<b>1. Get your account identifier</b><br>Format: your_account.region (e.g., xy12345.us-east-1)',
101
- '<b>2. Get your credentials</b><br>You need: username, password, warehouse name, and database.',
102
- '<b>3. Ensure warehouse is running</b><br>Make sure your compute warehouse is started and you have access.',
103
- '<b>4. Check network access</b><br>If using Snowflake network policies, ensure your IP is allowed.'
104
- ]
105
- },
106
- shopify: {
107
- title: 'How to Connect to Shopify',
108
- steps: [
109
- '<b>1. Create a private app</b><br>Go to Shopify Admin → Apps → Develop apps → Create an app.',
110
- '<b>2. Configure API scopes</b><br>Enable read access for: Orders, Products, Customers, Analytics.',
111
- '<b>3. Get API credentials</b><br>Copy the API key and API secret from the app settings.',
112
- '<b>4. Find your store URL</b><br>Your store URL is: yourstore.myshopify.com'
113
- ]
114
- },
115
- csv: {
116
- title: 'How to Use CSV Files',
117
- steps: [
118
- '<b>1. Prepare your CSV file</b><br>Ensure your CSV has headers in the first row.',
119
- '<b>2. Get the absolute file path</b><br>Right-click the file and copy its full path.',
120
- '<b>3. Check file encoding</b><br>UTF-8 encoding works best. Save as UTF-8 if needed.',
121
- '<b>4. Verify file access</b><br>Make sure the file is not open in Excel when connecting.'
122
- ]
123
- }
124
- };
125
-
126
- // Mock tables for demo
127
- const mockTables = {
128
- mysql: ['users', 'orders', 'products', 'customers', 'sessions', 'analytics'],
129
- postgresql: ['accounts', 'transactions', 'logs', 'events', 'metrics'],
130
- bigquery: ['pageviews', 'conversions', 'user_events', 'revenue'],
131
- snowflake: ['sales', 'inventory', 'customers', 'suppliers'],
132
- shopify: ['orders', 'products', 'customers', 'collections'],
133
- csv: ['data']
134
- };
135
-
136
- // Load connections
137
- async function loadConnections() {
138
- try {
139
- const res = await fetch('/api/connections');
140
- const data = await res.json();
141
- const list = document.getElementById('connection-list');
142
-
143
- if (data.connections.length === 0) {
144
- list.innerHTML = '';
145
- showNoSelection();
146
- return;
147
- }
148
-
149
- list.innerHTML = data.connections.map(conn => `
150
- <li class="connection-item ${selectedConnection?.name === conn.name ? 'active' : ''}"
151
- data-name="${conn.name}" data-type="${conn.type}">
152
- <span class="icon">${getIcon(conn.type)}</span>
153
- <div class="info">
154
- <div class="name">${conn.name}</div>
155
- <div class="type">${conn.type}</div>
156
- </div>
157
- <span class="status"></span>
158
- </li>
159
- `).join('');
160
-
161
- // Add click handlers
162
- document.querySelectorAll('.connection-item').forEach(item => {
163
- item.addEventListener('click', () => selectConnection(item.dataset.name, item.dataset.type));
164
- });
165
-
166
- } catch (e) {
167
- console.error('Failed to load connections', e);
168
- }
169
- }
170
-
171
- function getIcon(type) {
172
- const icons = { mysql: '🐬', postgresql: '🐘', bigquery: '📊', snowflake: '❄️', shopify: '🛒', csv: '📄' };
173
- return icons[type] || '📁';
174
- }
175
-
176
- function showNoSelection() {
177
- document.getElementById('no-selection').classList.remove('hidden');
178
- document.getElementById('connection-details').classList.add('hidden');
179
- }
180
-
181
- async function selectConnection(name, type) {
182
- selectedConnection = { name, type };
183
-
184
- // Update sidebar active state
185
- document.querySelectorAll('.connection-item').forEach(item => {
186
- item.classList.toggle('active', item.dataset.name === name);
187
- });
188
-
189
- // Show connection details
190
- document.getElementById('no-selection').classList.add('hidden');
191
- document.getElementById('connection-details').classList.remove('hidden');
192
- document.getElementById('table-data-panel').classList.add('hidden');
193
- document.getElementById('tables-panel').classList.remove('hidden');
194
- document.getElementById('connection-panel').classList.remove('hidden');
195
-
196
- // Update header
197
- document.getElementById('selected-name').textContent = name;
198
- document.getElementById('selected-info').textContent = `${type} • Connected`;
199
-
200
- // Fetch real tables from API
201
- try {
202
- const res = await fetch(`/api/connections/${encodeURIComponent(name)}/tables`);
203
- const data = await res.json();
204
-
205
- let tables = [];
206
- if (data.success && data.tables && data.tables.length > 0) {
207
- tables = data.tables;
208
- } else {
209
- // Fallback to mock tables for non-CSV types
210
- tables = (mockTables[type] || []).map(t => ({ name: t, type: 'mock', rowCount: Math.floor(Math.random() * 100000) }));
211
- }
212
-
213
- document.getElementById('table-count').textContent = `${tables.length} table${tables.length !== 1 ? 's' : ''}`;
214
-
215
- document.getElementById('tables-grid').innerHTML = tables.map(table => {
216
- const tableName = typeof table === 'string' ? table : table.name;
217
- const rowCount = table.rowCount || '?';
218
- return `
219
- <div class="table-card" onclick="openTablePreview('${tableName}')" style="cursor: pointer;">
220
- <div class="table-icon">
221
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#64748B" stroke-width="2">
222
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/>
223
- </svg>
224
- </div>
225
- <div class="table-name">${tableName}</div>
226
- <div class="table-rows">${typeof rowCount === 'number' ? '~' + rowCount.toLocaleString() + ' rows' : 'Click to load'}</div>
227
- </div>
228
- `;
229
- }).join('');
230
- } catch (err) {
231
- console.error('Failed to fetch tables', err);
232
- document.getElementById('tables-grid').innerHTML = '<p style="color: var(--error);">Failed to load tables</p>';
233
- }
234
-
235
- // Connection info
236
- document.getElementById('connection-info-panel').innerHTML = `
237
- <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem;">
238
- <div><label>Type</label><p style="margin-top: 0.25rem;">${type}</p></div>
239
- <div><label>Status</label><p style="margin-top: 0.25rem; color: var(--success);">● Connected</p></div>
240
- <div><label>Created</label><p style="margin-top: 0.25rem;">Just now</p></div>
241
- <div><label>Last Used</label><p style="margin-top: 0.25rem;">Never</p></div>
242
- </div>
243
- `;
244
- }
245
-
246
- // Modal
247
- function openModal() {
248
- document.getElementById('modal-overlay').classList.add('active');
249
- selectedType = null;
250
- document.getElementById('form-fields').classList.add('hidden');
251
- document.querySelectorAll('.source-btn').forEach(b => b.classList.remove('selected'));
252
- }
253
-
254
- function closeModal() {
255
- document.getElementById('modal-overlay').classList.remove('active');
256
- document.getElementById('connection-form').reset();
257
- document.getElementById('message').innerHTML = '';
258
- }
259
-
260
- document.getElementById('add-connection-btn').addEventListener('click', openModal);
261
- document.getElementById('add-first-btn').addEventListener('click', openModal);
262
- document.getElementById('modal-close').addEventListener('click', closeModal);
263
- document.getElementById('modal-overlay').addEventListener('click', (e) => {
264
- if (e.target.id === 'modal-overlay') closeModal();
265
- });
266
-
267
- // Inline Table View with Pagination
268
- let currentTableName = '';
269
- let currentColumns = [];
270
- let currentTotalRows = 0;
271
- let currentPage = 1;
272
- let rowsPerPage = 25;
273
-
274
- async function openTablePreview(tableName) {
275
- currentTableName = tableName;
276
- currentPage = 1;
277
-
278
- // Hide tables panel, show table data panel
279
- document.getElementById('tables-panel').classList.add('hidden');
280
- document.getElementById('connection-panel').classList.add('hidden');
281
- document.getElementById('table-data-panel').classList.remove('hidden');
282
-
283
- document.getElementById('viewing-table-name').textContent = tableName;
284
- document.getElementById('viewing-table-rows').textContent = 'Loading...';
285
- document.getElementById('inline-table').innerHTML = '<tbody><tr><td style="padding: 2rem; text-align: center; color: var(--text-muted);">Loading data...</td></tr></tbody>';
286
- document.getElementById('pagination-controls').innerHTML = '';
287
-
288
- await loadTableData();
289
- }
290
-
291
- async function loadTableData() {
292
- if (!selectedConnection) return;
293
-
294
- try {
295
- const url = `/api/connections/${encodeURIComponent(selectedConnection.name)}/tables/${encodeURIComponent(currentTableName)}/data?page=${currentPage}&limit=${rowsPerPage}`;
296
- console.log('Fetching table data:', url);
297
-
298
- const res = await fetch(url);
299
-
300
- // Check for 404 or other HTTP errors
301
- if (!res.ok) {
302
- const contentType = res.headers.get('content-type');
303
- if (contentType && contentType.includes('text/html')) {
304
- throw new Error(`Server API not found (404 HTML response). Please restart the server.`);
305
- }
306
- const text = await res.text();
307
- throw new Error(`Server returned ${res.status}: ${res.statusText}`);
308
- }
309
-
310
- const data = await res.json();
311
-
312
- if (!data.success) {
313
- throw new Error(data.error || 'Failed to load data');
314
- }
315
-
316
- currentColumns = data.columns || [];
317
- currentTotalRows = data.pagination?.totalRows || 0;
318
- const rows = data.rows || [];
319
- const pagination = data.pagination || {};
320
-
321
- document.getElementById('viewing-table-rows').textContent = `${currentTotalRows.toLocaleString()} rows`;
322
-
323
- // Render table
324
- if (rows.length === 0) {
325
- document.getElementById('inline-table').innerHTML = '<tbody><tr><td style="padding: 2rem; text-align: center; color: var(--text-muted);">No data found</td></tr></tbody>';
326
- } else {
327
- const tableHtml = `
328
- <thead>
329
- <tr>${currentColumns.map(col => `<th>${col}</th>`).join('')}</tr>
330
- </thead>
331
- <tbody>
332
- ${rows.map(row => `
333
- <tr>${currentColumns.map(col => `<td>${row[col] !== null && row[col] !== undefined ? row[col] : ''}</td>`).join('')}</tr>
334
- `).join('')}
335
- </tbody>
336
- `;
337
- document.getElementById('inline-table').innerHTML = tableHtml;
338
- }
339
-
340
- // Render pagination
341
- const totalPages = pagination.totalPages || 1;
342
- const startIdx = pagination.startIdx || 1;
343
- const endIdx = pagination.endIdx || rows.length;
344
-
345
- const paginationHtml = `
346
- <div class="rows-per-page">
347
- Show
348
- <select onchange="changeRowsPerPage(this.value)">
349
- <option value="10" ${rowsPerPage === 10 ? 'selected' : ''}>10</option>
350
- <option value="25" ${rowsPerPage === 25 ? 'selected' : ''}>25</option>
351
- <option value="50" ${rowsPerPage === 50 ? 'selected' : ''}>50</option>
352
- <option value="100" ${rowsPerPage === 100 ? 'selected' : ''}>100</option>
353
- </select>
354
- rows
355
- </div>
356
- <div class="pagination">
357
- <button class="page-btn" onclick="goToPage(1)" ${currentPage === 1 ? 'disabled' : ''}>First</button>
358
- <button class="page-btn" onclick="goToPage(${currentPage - 1})" ${currentPage === 1 ? 'disabled' : ''}>Prev</button>
359
- <span class="page-info">Page ${currentPage} of ${totalPages} (${startIdx}-${endIdx} of ${currentTotalRows})</span>
360
- <button class="page-btn" onclick="goToPage(${currentPage + 1})" ${currentPage >= totalPages ? 'disabled' : ''}>Next</button>
361
- <button class="page-btn" onclick="goToPage(${totalPages})" ${currentPage >= totalPages ? 'disabled' : ''}>Last</button>
362
- </div>
363
- `;
364
- document.getElementById('pagination-controls').innerHTML = paginationHtml;
365
-
366
- } catch (err) {
367
- console.error('Failed to load table data', err);
368
- document.getElementById('inline-table').innerHTML = `<tbody><tr><td style="padding: 2rem; text-align: center; color: var(--error);">Error: ${err.message}</td></tr></tbody>`;
369
- document.getElementById('viewing-table-rows').textContent = 'Error';
370
- }
371
- }
372
-
373
- function goToPage(page) {
374
- currentPage = page;
375
- loadTableData();
376
- }
377
-
378
- function changeRowsPerPage(value) {
379
- rowsPerPage = parseInt(value);
380
- currentPage = 1;
381
- loadTableData();
382
- }
383
-
384
- function closeTablePreview() {
385
- document.getElementById('table-data-panel').classList.add('hidden');
386
- document.getElementById('tables-panel').classList.remove('hidden');
387
- document.getElementById('connection-panel').classList.remove('hidden');
388
- }
389
-
390
- document.getElementById('back-to-tables').addEventListener('click', closeTablePreview);
391
-
392
- // Help Modal
393
- function openHelpModal(type) {
394
- const help = helpInstructions[type];
395
- if (!help) return;
396
-
397
- document.getElementById('help-title').textContent = help.title;
398
- document.getElementById('help-content').innerHTML = `
399
- <div style="display: flex; flex-direction: column; gap: 1rem;">
400
- ${help.steps.map(step => `
401
- <div style="background: var(--border-light); padding: 0.875rem 1rem; border-radius: 8px; font-size: 0.875rem; line-height: 1.5;">
402
- ${step}
403
- </div>
404
- `).join('')}
405
- </div>
406
- `;
407
- document.getElementById('help-modal-overlay').classList.add('active');
408
- }
409
-
410
- function closeHelpModal() {
411
- document.getElementById('help-modal-overlay').classList.remove('active');
412
- }
413
-
414
- document.getElementById('help-modal-close').addEventListener('click', closeHelpModal);
415
- document.getElementById('help-modal-overlay').addEventListener('click', (e) => {
416
- if (e.target.id === 'help-modal-overlay') closeHelpModal();
417
- });
418
-
419
- // Help button clicks
420
- document.querySelectorAll('.help-btn').forEach(btn => {
421
- btn.addEventListener('click', (e) => {
422
- e.stopPropagation();
423
- openHelpModal(btn.dataset.type);
424
- });
425
- });
426
-
427
- // Source selection
428
- document.querySelectorAll('.source-btn').forEach(btn => {
429
- btn.addEventListener('click', () => {
430
- document.querySelectorAll('.source-btn').forEach(b => b.classList.remove('selected'));
431
- btn.classList.add('selected');
432
- selectedType = btn.dataset.type;
433
-
434
- document.getElementById('form-fields').classList.remove('hidden');
435
-
436
- // Hide all type-specific fields
437
- ['db-fields', 'shopify-fields', 'bigquery-fields', 'snowflake-fields', 'csv-fields'].forEach(id => {
438
- document.getElementById(id).classList.add('hidden');
439
- });
440
-
441
- // Show relevant fields
442
- if (['mysql', 'postgresql'].includes(selectedType)) {
443
- document.getElementById('db-fields').classList.remove('hidden');
444
- document.getElementById('conn-port').value = selectedType === 'mysql' ? '3306' : '5432';
445
- } else if (selectedType === 'shopify') {
446
- document.getElementById('shopify-fields').classList.remove('hidden');
447
- } else if (selectedType === 'bigquery') {
448
- document.getElementById('bigquery-fields').classList.remove('hidden');
449
- } else if (selectedType === 'snowflake') {
450
- document.getElementById('snowflake-fields').classList.remove('hidden');
451
- } else if (selectedType === 'csv') {
452
- document.getElementById('csv-fields').classList.remove('hidden');
453
- }
454
- });
455
- });
456
-
457
- // Test connection
458
- document.getElementById('test-btn').addEventListener('click', async () => {
459
- const msg = document.getElementById('message');
460
- msg.className = 'message';
461
- msg.innerHTML = 'Testing...';
462
-
463
- const res = await fetch('/api/connections/test', {
464
- method: 'POST',
465
- headers: { 'Content-Type': 'application/json' },
466
- body: JSON.stringify(getFormData())
467
- });
468
- const data = await res.json();
469
- msg.className = data.success ? 'message success' : 'message error';
470
- msg.innerHTML = data.success ? '✓ ' + data.message : '✗ ' + data.error;
471
- });
472
-
473
- // Save connection
474
- document.getElementById('connection-form').addEventListener('submit', async (e) => {
475
- e.preventDefault();
476
-
477
- const res = await fetch('/api/connections', {
478
- method: 'POST',
479
- headers: { 'Content-Type': 'application/json' },
480
- body: JSON.stringify(getFormData())
481
- });
482
- const data = await res.json();
483
-
484
- if (data.success) {
485
- closeModal();
486
- loadConnections();
487
- } else {
488
- const msg = document.getElementById('message');
489
- msg.className = 'message error';
490
- msg.innerHTML = '✗ ' + data.error;
491
- }
492
- });
493
-
494
- // Delete connection
495
- document.getElementById('delete-btn').addEventListener('click', async () => {
496
- if (!selectedConnection || !confirm(`Delete "${selectedConnection.name}"?`)) return;
497
-
498
- await fetch(`/api/connections/${selectedConnection.name}`, { method: 'DELETE' });
499
- selectedConnection = null;
500
- showNoSelection();
501
- loadConnections();
502
- });
503
-
504
- // Get form data
505
- function getFormData() {
506
- const data = { name: document.getElementById('conn-name').value, type: selectedType };
507
-
508
- if (['mysql', 'postgresql'].includes(selectedType)) {
509
- Object.assign(data, {
510
- host: document.getElementById('conn-host').value,
511
- port: parseInt(document.getElementById('conn-port').value) || undefined,
512
- user: document.getElementById('conn-user').value,
513
- password: document.getElementById('conn-password').value,
514
- database: document.getElementById('conn-database').value
515
- });
516
- } else if (selectedType === 'shopify') {
517
- Object.assign(data, {
518
- store: document.getElementById('conn-store').value,
519
- apiKey: document.getElementById('conn-apikey').value,
520
- apiSecret: document.getElementById('conn-apisecret').value
521
- });
522
- } else if (selectedType === 'bigquery') {
523
- Object.assign(data, {
524
- projectId: document.getElementById('conn-project').value,
525
- keyFile: document.getElementById('conn-keyfile').value
526
- });
527
- } else if (selectedType === 'snowflake') {
528
- Object.assign(data, {
529
- account: document.getElementById('conn-account').value,
530
- warehouse: document.getElementById('conn-warehouse').value,
531
- user: document.getElementById('conn-sf-user').value,
532
- password: document.getElementById('conn-sf-password').value
533
- });
534
- } else if (selectedType === 'csv') {
535
- data.filePath = document.getElementById('conn-filepath').value;
536
- }
537
-
538
- return data;
539
- }
540
-
541
- // Initial load
542
- loadConnections();