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.
- package/assets/antigravity/data-analyst.md +8 -4
- package/assets/claude/SKILL.md +7 -2
- package/assets/copilot/data-analyst.prompt.md +8 -4
- package/assets/cursor/data-analyst.md +8 -4
- package/assets/kiro/data-analyst.md +8 -4
- package/assets/windsurf/data-analyst.md +8 -4
- package/dist/commands/schema.d.ts +1 -0
- package/dist/commands/schema.js +54 -0
- package/dist/connections.d.ts +3 -1
- package/dist/connections.js +125 -0
- package/dist/connectors/additional/index.d.ts +42 -0
- package/dist/connectors/additional/index.js +268 -0
- package/dist/connectors/cloud/index.d.ts +8 -3
- package/dist/connectors/cloud/index.js +321 -10
- package/dist/connectors/csv/index.d.ts +1 -0
- package/dist/connectors/csv/index.js +27 -7
- package/dist/connectors/custom/index.d.ts +10 -0
- package/dist/connectors/custom/index.js +61 -0
- package/dist/connectors/index.d.ts +6 -0
- package/dist/connectors/mysql/index.d.ts +1 -0
- package/dist/connectors/mysql/index.js +162 -9
- package/dist/connectors/postgresql/index.d.ts +2 -0
- package/dist/connectors/postgresql/index.js +140 -8
- package/dist/connectors/shopify/index.d.ts +1 -0
- package/dist/connectors/shopify/index.js +44 -5
- package/dist/index.js +7 -0
- package/dist/routes/connections.js +2 -0
- package/dist/server.js +8 -0
- package/package.json +8 -3
- package/ui/assets/index-Ba1mRihD.js +40 -0
- package/ui/assets/index-rlcHFDJB.css +1 -0
- package/ui/favicon.svg +4 -18
- package/ui/index.html +7 -331
- package/ui/favicon-32x32.png +0 -0
- package/ui/main.js +0 -542
- 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();
|