amaye15 commited on
Commit
07f54c0
·
1 Parent(s): 3590e8f

UI Updated

Browse files
Files changed (1) hide show
  1. index.html +108 -339
index.html CHANGED
@@ -30,8 +30,8 @@
30
  --status-error-bg: #f8d7da;
31
  --status-error-text: #842029;
32
  --status-error-border: #f5c2c7;
33
- --loader-border: #f3f3f3;
34
- --loader-spinner: #007bff; /* Use primary blue for spinner */
35
  --input-border: #ced4da;
36
  --input-bg: #fff;
37
  --link-color: #007bff;
@@ -39,21 +39,21 @@
39
 
40
  @media (prefers-color-scheme: dark) {
41
  :root {
42
- --bg-color: #1a1a1a; /* Darker background */
43
- --text-color: #e8e8e8; /* Lighter text */
44
- --header-bg: #2a622d; /* Darker header */
45
  --header-text: #e8e8e8;
46
- --sidebar-bg: #2c2c2c; /* Dark sidebar */
47
  --sidebar-text: #adb5bd;
48
  --sidebar-hover-bg: #444;
49
- --sidebar-active-bg: #0056b3; /* Darker blue */
50
  --sidebar-active-text: white;
51
- --content-bg: #212121; /* Dark content area */
52
  --border-color: #444;
53
  --border-color-light: #333;
54
  --table-header-bg: #2c2c2c;
55
  --table-row-even-bg: #252525;
56
- --button-primary-bg: #218838; /* Keep green accessible */
57
  --button-primary-hover-bg: #1e7e34;
58
  --button-primary-text: white;
59
  --status-success-bg: #143620;
@@ -62,285 +62,84 @@
62
  --status-error-bg: #58151c;
63
  --status-error-text: #f5c6cb;
64
  --status-error-border: #842029;
65
- --loader-border: #444;
66
- --loader-spinner: #4dabf7; /* Lighter blue for dark */
67
  --input-border: #555;
68
  --input-bg: #333;
69
  --link-color: #4dabf7;
70
  }
71
- /* Ensure inputs inherit text color */
72
  input[type="text"], textarea {
73
  color: var(--text-color);
74
  background-color: var(--input-bg);
75
  }
76
- /* Ensure table cells inherit text color */
77
  th, td {
78
  color: var(--text-color);
79
  }
80
- /* Placeholder text color for dark mode */
81
- ::placeholder {
82
- color: #888;
83
- opacity: 1; /* Firefox */
84
- }
85
- :-ms-input-placeholder { /* Internet Explorer 10-11 */
86
- color: #888;
87
- }
88
- ::-ms-input-placeholder { /* Microsoft Edge */
89
- color: #888;
90
- }
91
  }
92
 
93
  /* --- General Styles --- */
94
  body {
95
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
96
- margin: 0;
97
- padding: 0;
98
- display: flex;
99
- flex-direction: column;
100
- height: 100vh;
101
- background-color: var(--bg-color);
102
- color: var(--text-color);
103
- transition: background-color 0.3s, color 0.3s; /* Smooth theme transition */
104
- }
105
- a {
106
- color: var(--link-color);
107
  }
 
108
 
109
  /* --- Header --- */
110
  header {
111
- background-color: var(--header-bg);
112
- color: var(--header-text);
113
- padding: 15px 20px;
114
- display: flex;
115
- align-items: center;
116
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
117
- font-size: 1.2em;
118
- font-weight: bold;
119
- transition: background-color 0.3s, color 0.3s;
120
- }
121
- header .loader {
122
- border: 3px solid var(--loader-border);
123
- border-top: 3px solid var(--header-text); /* Match header text */
124
- border-radius: 50%;
125
- width: 18px;
126
- height: 18px;
127
- animation: spin 1s linear infinite;
128
- display: none;
129
- margin-left: 15px;
130
- }
131
-
132
- /* --- Container --- */
133
- .container {
134
- display: flex;
135
- flex: 1;
136
- overflow: hidden;
137
- }
138
-
139
- /* --- Sidebar --- */
140
- #sidebar {
141
- width: 220px;
142
- background-color: var(--sidebar-bg);
143
- padding: 15px;
144
- overflow-y: auto;
145
- border-right: 1px solid var(--border-color);
146
- transition: background-color 0.3s;
147
- }
148
- #sidebar h3 {
149
- margin-top: 0;
150
- margin-bottom: 15px;
151
- color: var(--sidebar-text);
152
- border-bottom: 1px solid var(--border-color);
153
- padding-bottom: 10px;
154
- }
155
- #tableList {
156
- list-style: none;
157
- padding: 0;
158
- margin: 0;
159
- }
160
- #tableList li {
161
- padding: 8px 10px;
162
- cursor: pointer;
163
- border-radius: 4px;
164
- margin-bottom: 5px;
165
- transition: background-color 0.2s, color 0.2s;
166
- font-size: 0.95em;
167
- color: var(--sidebar-text); /* Use variable */
168
- }
169
- #tableList li:hover {
170
- background-color: var(--sidebar-hover-bg);
171
- }
172
- #tableList li.active {
173
- background-color: var(--sidebar-active-bg);
174
- color: var(--sidebar-active-text);
175
- font-weight: bold;
176
- }
177
-
178
- /* --- Main Content --- */
179
- #mainContent {
180
- flex: 1;
181
- display: flex;
182
- flex-direction: column;
183
- padding: 20px;
184
- overflow: hidden;
185
- }
186
- .content-area {
187
- flex: 1;
188
- display: flex;
189
- flex-direction: column;
190
- overflow: hidden;
191
- gap: 15px;
192
- }
193
- #schemaDisplay, #dataDisplayContainer, #queryResultContainer {
194
- background-color: var(--content-bg);
195
- border: 1px solid var(--border-color);
196
- border-radius: 5px;
197
- padding: 15px;
198
- margin-bottom: 0;
199
- overflow: auto;
200
- box-shadow: 0 1px 3px rgba(0,0,0,0.05);
201
- transition: background-color 0.3s, border-color 0.3s;
202
- }
203
- #schemaDisplay h4, #dataDisplayContainer h4, #queryResultContainer h4 {
204
- margin-top: 0;
205
- color: var(--sidebar-text); /* Match sidebar heading color */
206
- border-bottom: 1px solid var(--border-color-light);
207
- padding-bottom: 10px;
208
- margin-bottom: 15px;
209
- }
210
- #dataDisplayContainer {
211
- flex: 1;
212
- display: flex;
213
- flex-direction: column;
214
- }
215
- #dataDisplay {
216
- flex: 1;
217
- overflow: auto;
218
- min-height: 100px;
219
- }
220
- #schemaDisplay p, #dataDisplayContainer p {
221
- color: #888; /* Placeholder text color */
222
- }
223
-
224
-
225
- /* --- Query Area --- */
226
- #queryArea {
227
- padding-top: 20px;
228
- border-top: 1px solid var(--border-color);
229
- background-color: var(--content-bg); /* Match content bg */
230
- padding: 15px;
231
- border-radius: 5px;
232
- box-shadow: 0 -1px 3px rgba(0,0,0,0.05);
233
- margin-top: 15px; /* Add margin instead of relying on flex */
234
- transition: background-color 0.3s, border-color 0.3s;
235
- }
236
- #queryArea h4 {
237
- margin-top: 0;
238
- margin-bottom: 10px;
239
- color: var(--sidebar-text);
240
- }
241
- #queryArea textarea {
242
- width: 100%;
243
- min-height: 80px;
244
- padding: 10px;
245
- border: 1px solid var(--input-border);
246
- border-radius: 4px;
247
- box-sizing: border-box;
248
- font-family: monospace;
249
- margin-bottom: 10px;
250
- resize: vertical;
251
- background-color: var(--input-bg); /* Input background */
252
- color: var(--text-color); /* Input text color */
253
- transition: background-color 0.3s, border-color 0.3s, color 0.3s;
254
- }
255
- #queryArea button {
256
- padding: 10px 20px;
257
- background-color: var(--button-primary-bg);
258
- color: var(--button-primary-text);
259
- border: none;
260
- border-radius: 4px;
261
- cursor: pointer;
262
- transition: background-color 0.2s;
263
- font-weight: bold;
264
- }
265
- #queryArea button:hover {
266
- background-color: var(--button-primary-hover-bg);
267
- }
268
-
269
- /* --- Tables --- */
270
- table {
271
- width: 100%;
272
- border-collapse: collapse;
273
- margin-top: 0;
274
- font-size: 0.9em;
275
- }
276
- th, td {
277
- border: 1px solid var(--border-color-light);
278
- padding: 10px 12px;
279
- text-align: left;
280
- white-space: nowrap;
281
- max-width: 250px;
282
- overflow: hidden;
283
- text-overflow: ellipsis;
284
- color: var(--text-color); /* Inherit text color */
285
- transition: border-color 0.3s;
286
- }
287
- th {
288
- background-color: var(--table-header-bg);
289
- font-weight: 600;
290
- position: sticky;
291
- top: 0;
292
- z-index: 1;
293
- transition: background-color 0.3s;
294
- }
295
- tr:nth-child(even) {
296
- background-color: var(--table-row-even-bg);
297
- transition: background-color 0.3s;
298
- }
299
- tr:nth-child(odd) {
300
- background-color: var(--content-bg); /* Match content background */
301
- transition: background-color 0.3s;
302
- }
303
-
304
- /* --- Status Message --- */
305
- #statusMessage {
306
- padding: 10px 15px;
307
- margin-top: 15px;
308
- border-radius: 4px;
309
- display: none;
310
- font-size: 0.9em;
311
- transition: background-color 0.3s, color 0.3s, border-color 0.3s;
312
- }
313
- #statusMessage.success {
314
- background-color: var(--status-success-bg);
315
- color: var(--status-success-text);
316
- border: 1px solid var(--status-success-border);
317
- }
318
- #statusMessage.error {
319
- background-color: var(--status-error-bg);
320
- color: var(--status-error-text);
321
- border: 1px solid var(--status-error-border);
322
- }
323
- /* --- Loader --- */
324
- .loader {
325
- border: 4px solid var(--loader-border);
326
- border-top: 4px solid var(--loader-spinner);
327
- border-radius: 50%;
328
- width: 20px;
329
- height: 20px;
330
- animation: spin 1s linear infinite;
331
- display: none;
332
- }
333
- @keyframes spin {
334
- 0% { transform: rotate(0deg); }
335
- 100% { transform: rotate(360deg); }
336
- }
337
  </style>
338
  </head>
339
  <body>
340
 
341
  <header>
342
  <span>🦆 DuckDB Explorer</span>
343
- <div class="loader" id="loadingIndicator"></div>
344
  </header>
345
 
346
  <div class="container">
@@ -384,15 +183,15 @@
384
  </div>
385
 
386
  <script>
387
- // --- Keep the existing element variables ---
388
  const tableList = document.getElementById('tableList');
389
  const schemaDisplay = document.getElementById('schemaDisplay');
390
  const schemaTable = document.getElementById('schemaTable');
391
- const schemaPlaceholder = document.getElementById('schemaPlaceholder'); // Added
392
  const dataDisplayContainer = document.getElementById('dataDisplayContainer');
393
  const dataDisplay = document.getElementById('dataDisplay');
394
  const dataTable = document.getElementById('dataTable');
395
- const dataPlaceholder = document.getElementById('dataPlaceholder'); // Added
396
  const tableDataHeader = document.getElementById('tableDataHeader');
397
  const sqlInput = document.getElementById('sqlInput');
398
  const runSqlButton = document.getElementById('runSqlButton');
@@ -400,34 +199,35 @@
400
  const queryResultDisplay = document.getElementById('queryResultDisplay');
401
  const queryResultTable = document.getElementById('queryResultTable');
402
  const statusMessage = document.getElementById('statusMessage');
403
- const loadingIndicator = document.getElementById('loadingIndicator');
404
 
405
- // --- API URL is relative ---
406
  const API_BASE_URL = '';
407
  let currentTables = [];
408
  let selectedTable = null;
409
  let pollingIntervalId = null;
410
- const POLLING_INTERVAL_MS = 1000; // Refresh table list every 10 seconds
411
-
412
- // --- Utility Functions (keep existing) ---
413
- function showLoader(show) {
414
- loadingIndicator.style.display = show ? 'inline-block' : 'none';
415
- }
416
-
417
- function showStatus(message, isError = false) {
418
- statusMessage.textContent = message;
419
- statusMessage.className = isError ? 'error' : 'success';
420
- statusMessage.style.display = 'block';
421
- setTimeout(() => { statusMessage.style.display = 'none'; }, 5000);
422
- }
423
-
424
- function clearStatus() {
425
- statusMessage.textContent = '';
426
- statusMessage.style.display = 'none';
427
- }
 
428
 
429
  async function fetchAPI(endpoint, options = {}) {
430
- showLoader(true);
431
  clearStatus();
432
  const url = `${API_BASE_URL}${endpoint}`;
433
  try {
@@ -440,25 +240,24 @@
440
  } catch (e) { /* Ignore */ }
441
  throw new Error(errorDetail);
442
  }
443
- // Check if the response is likely JSON before trying to parse
444
  const contentType = response.headers.get("content-type");
445
  if (contentType && contentType.includes("application/json")) {
446
  return await response.json();
447
  }
448
- // Assume text for non-JSON or if content-type is missing
449
  return await response.text();
450
  } catch (error) {
451
  console.error('API Fetch Error:', error);
452
  showStatus(`Error: ${error.message}`, true);
453
  throw error;
454
  } finally {
455
- showLoader(false);
456
  }
457
  }
458
 
 
459
  function renderTable(data, tableElement) {
460
  tableElement.innerHTML = '';
461
- if (!data || !Array.isArray(data)) { // Check if data is an array
462
  tableElement.innerHTML = '<tbody><tr><td>Invalid data format received.</td></tr></tbody>';
463
  console.error("Invalid data format for renderTable:", data);
464
  return;
@@ -485,12 +284,9 @@
485
  });
486
  });
487
  }
488
-
489
- // --- renderSchema FIX ---
490
- function renderSchema(schemaData) {
491
- // Target the table directly
492
- schemaTable.innerHTML = ''; // Clear previous table content
493
- schemaPlaceholder.style.display = 'none'; // Hide placeholder
494
 
495
  if (!schemaData || !schemaData.columns || schemaData.columns.length === 0) {
496
  schemaTable.innerHTML = '<tbody><tr><td colspan="2">No schema information available.</td></tr></tbody>';
@@ -514,21 +310,21 @@
514
  }
515
 
516
 
517
- // --- Event Handlers (Modified) ---
518
 
519
  async function loadTables(isPolling = false) {
 
520
  if (!isPolling) {
521
  tableList.innerHTML = '<li>Loading tables...</li>';
522
  schemaTable.innerHTML = '';
523
  dataTable.innerHTML = '';
524
  tableDataHeader.textContent = '';
525
  queryResultContainer.style.display = 'none';
526
- schemaPlaceholder.style.display = 'block'; // Show placeholders initially
527
  dataPlaceholder.style.display = 'block';
528
  }
529
  try {
530
  const newTables = await fetchAPI('/tables');
531
- // Check if the list actually changed before re-rendering
532
  if (!isPolling || JSON.stringify(newTables) !== JSON.stringify(currentTables)) {
533
  console.log("Table list changed, updating UI.");
534
  currentTables = newTables;
@@ -536,27 +332,19 @@
536
  if (!isPolling) {
537
  showStatus("Tables loaded.", false);
538
  }
539
- // If the currently selected table disappeared, clear the displays
540
  if (selectedTable && !currentTables.includes(selectedTable)) {
541
  console.log(`Selected table "${selectedTable}" removed.`);
542
  selectedTable = null;
543
  tableDataHeader.textContent = '';
544
  schemaDisplay.innerHTML = '<h4>Schema</h4><p id="schemaPlaceholder">Select a table from the list.</p><table id="schemaTable"></table>';
545
  dataDisplayContainer.innerHTML = '<h4>Data <span id="tableDataHeader"></span></h4><p id="dataPlaceholder">Select a table from the list.</p><div id="dataDisplay"><table id="dataTable"></table></div>';
546
- // Re-assign potentially overwritten elements
547
  schemaTable = document.getElementById('schemaTable');
548
  dataTable = document.getElementById('dataTable');
549
  schemaPlaceholder = document.getElementById('schemaPlaceholder');
550
  dataPlaceholder = document.getElementById('dataPlaceholder');
551
  tableDataHeader = document.getElementById('tableDataHeader');
552
- } else if (selectedTable && !isPolling) {
553
- // Optionally refresh current table view if needed,
554
- // but might be disruptive. Let's skip for now.
555
- // handleTableSelection(tableList.querySelector(`li[data-table-name="${selectedTable}"]`));
556
  }
557
  }
558
-
559
- // Clear placeholders if tables loaded successfully
560
  if (currentTables.length > 0 && !isPolling) {
561
  schemaPlaceholder.style.display = 'none';
562
  dataPlaceholder.style.display = 'none';
@@ -565,20 +353,20 @@
565
  schemaPlaceholder.textContent = 'No tables found in the database.';
566
  dataPlaceholder.textContent = 'No tables found in the database.';
567
  }
568
-
569
  } catch (error) {
570
- if (!isPolling) { // Only show initial load error prominently
571
  tableList.innerHTML = '<li>Error loading tables.</li>';
572
  schemaPlaceholder.textContent = 'Error loading tables.';
573
  dataPlaceholder.textContent = 'Error loading tables.';
574
  } else {
575
- console.error("Polling error:", error); // Log polling errors quietly
576
  }
577
  }
578
  }
579
 
580
  function displayTables(tables) {
581
- tableList.innerHTML = '';
 
582
  if (tables.length === 0) {
583
  tableList.innerHTML = '<li>No tables found.</li>';
584
  return;
@@ -588,7 +376,6 @@
588
  li.textContent = tableName;
589
  li.dataset.tableName = tableName;
590
  li.onclick = () => handleTableSelection(li);
591
- // Re-apply active class if this table was selected
592
  if (tableName === selectedTable) {
593
  li.classList.add('active');
594
  }
@@ -597,6 +384,7 @@
597
  }
598
 
599
  async function handleTableSelection(listItem) {
 
600
  const currentActive = tableList.querySelector('.active');
601
  if (currentActive) {
602
  currentActive.classList.remove('active');
@@ -608,10 +396,10 @@
608
 
609
  queryResultContainer.style.display = 'none';
610
  dataDisplayContainer.style.display = 'flex';
611
- dataPlaceholder.style.display = 'none'; // Hide placeholder
612
 
613
  tableDataHeader.textContent = `for table "${selectedTable}"`;
614
- schemaPlaceholder.style.display = 'none'; // Hide placeholder
615
  schemaTable.innerHTML = '<tbody><tr><td colspan="2">Loading schema...</td></tr></tbody>';
616
  dataTable.innerHTML = '<tbody><tr><td>Loading data...</td></tr></tbody>';
617
 
@@ -630,35 +418,31 @@
630
 
631
 
632
  async function runCustomQuery() {
 
633
  const sql = sqlInput.value.trim();
634
  if (!sql) {
635
  showStatus("SQL query cannot be empty.", true);
636
  return;
637
  }
638
-
639
- // Clear active selection in sidebar if a custom query is run
640
  const currentActive = tableList.querySelector('.active');
641
  if (currentActive) {
642
  currentActive.classList.remove('active');
643
  }
644
- selectedTable = null; // Deselect table
645
 
646
  dataDisplayContainer.style.display = 'none';
647
  dataPlaceholder.style.display = 'none';
648
- schemaPlaceholder.style.display = 'block'; // Show placeholders again
649
  schemaTable.innerHTML = '';
650
  tableDataHeader.textContent = '';
651
 
652
  queryResultContainer.style.display = 'block';
653
  queryResultTable.innerHTML = '<tbody><tr><td>Running query...</td></tr></tbody>';
654
 
655
-
656
  try {
657
  const resultData = await fetchAPI('/query', {
658
  method: 'POST',
659
- headers: {
660
- 'Content-Type': 'application/json',
661
- },
662
  body: JSON.stringify({ sql: sql }),
663
  });
664
  renderTable(resultData, queryResultTable);
@@ -668,13 +452,12 @@
668
  }
669
  }
670
 
671
- // --- Polling Function ---
672
  function startPolling() {
673
- stopPolling(); // Clear existing interval if any
674
  console.log(`Starting polling every ${POLLING_INTERVAL_MS / 1000} seconds.`);
675
  pollingIntervalId = setInterval(() => loadTables(true), POLLING_INTERVAL_MS);
676
  }
677
-
678
  function stopPolling() {
679
  if (pollingIntervalId) {
680
  console.log("Stopping polling.");
@@ -685,28 +468,14 @@
685
 
686
  // --- Initial Setup ---
687
  runSqlButton.onclick = runCustomQuery;
688
-
689
- // Load tables automatically when the page loads and start polling
690
  document.addEventListener('DOMContentLoaded', () => {
691
  loadTables().then(() => {
692
- // Start polling only after the initial load succeeds
693
  if (currentTables.length >= 0) { // Start even if no tables initially
694
  startPolling();
695
  }
696
  });
697
  });
698
 
699
- // Optional: Stop polling when the page is hidden (e.g., tab switched)
700
- // document.addEventListener("visibilitychange", () => {
701
- // if (document.visibilityState === 'hidden') {
702
- // stopPolling();
703
- // } else {
704
- // // Optionally restart polling immediately or wait for next interval
705
- // loadTables(); // Refresh immediately when tab becomes visible
706
- // startPolling();
707
- // }
708
- // });
709
-
710
  </script>
711
 
712
  </body>
 
30
  --status-error-bg: #f8d7da;
31
  --status-error-text: #842029;
32
  --status-error-border: #f5c2c7;
33
+ /* --loader-border: #f3f3f3; */ /* No longer needed */
34
+ /* --loader-spinner: #007bff; */ /* No longer needed */
35
  --input-border: #ced4da;
36
  --input-bg: #fff;
37
  --link-color: #007bff;
 
39
 
40
  @media (prefers-color-scheme: dark) {
41
  :root {
42
+ --bg-color: #1a1a1a;
43
+ --text-color: #e8e8e8;
44
+ --header-bg: #2a622d;
45
  --header-text: #e8e8e8;
46
+ --sidebar-bg: #2c2c2c;
47
  --sidebar-text: #adb5bd;
48
  --sidebar-hover-bg: #444;
49
+ --sidebar-active-bg: #0056b3;
50
  --sidebar-active-text: white;
51
+ --content-bg: #212121;
52
  --border-color: #444;
53
  --border-color-light: #333;
54
  --table-header-bg: #2c2c2c;
55
  --table-row-even-bg: #252525;
56
+ --button-primary-bg: #218838;
57
  --button-primary-hover-bg: #1e7e34;
58
  --button-primary-text: white;
59
  --status-success-bg: #143620;
 
62
  --status-error-bg: #58151c;
63
  --status-error-text: #f5c6cb;
64
  --status-error-border: #842029;
65
+ /* --loader-border: #444; */ /* No longer needed */
66
+ /* --loader-spinner: #4dabf7; */ /* No longer needed */
67
  --input-border: #555;
68
  --input-bg: #333;
69
  --link-color: #4dabf7;
70
  }
 
71
  input[type="text"], textarea {
72
  color: var(--text-color);
73
  background-color: var(--input-bg);
74
  }
 
75
  th, td {
76
  color: var(--text-color);
77
  }
78
+ ::placeholder { color: #888; opacity: 1; }
79
+ :-ms-input-placeholder { color: #888; }
80
+ ::-ms-input-placeholder { color: #888; }
 
 
 
 
 
 
 
 
81
  }
82
 
83
  /* --- General Styles --- */
84
  body {
85
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
86
+ margin: 0; padding: 0; display: flex; flex-direction: column; height: 100vh;
87
+ background-color: var(--bg-color); color: var(--text-color);
88
+ transition: background-color 0.3s, color 0.3s;
 
 
 
 
 
 
 
 
89
  }
90
+ a { color: var(--link-color); }
91
 
92
  /* --- Header --- */
93
  header {
94
+ background-color: var(--header-bg); color: var(--header-text); padding: 15px 20px;
95
+ display: flex; align-items: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1);
96
+ font-size: 1.2em; font-weight: bold; transition: background-color 0.3s, color 0.3s;
97
+ }
98
+ /* Removed header .loader styling */
99
+
100
+ /* --- Container, Sidebar, Main Content (Keep as before) --- */
101
+ .container { display: flex; flex: 1; overflow: hidden; }
102
+ #sidebar { width: 220px; background-color: var(--sidebar-bg); padding: 15px; overflow-y: auto; border-right: 1px solid var(--border-color); transition: background-color 0.3s; }
103
+ #sidebar h3 { margin-top: 0; margin-bottom: 15px; color: var(--sidebar-text); border-bottom: 1px solid var(--border-color); padding-bottom: 10px; }
104
+ #tableList { list-style: none; padding: 0; margin: 0; }
105
+ #tableList li { padding: 8px 10px; cursor: pointer; border-radius: 4px; margin-bottom: 5px; transition: background-color 0.2s, color 0.2s; font-size: 0.95em; color: var(--sidebar-text); }
106
+ #tableList li:hover { background-color: var(--sidebar-hover-bg); }
107
+ #tableList li.active { background-color: var(--sidebar-active-bg); color: var(--sidebar-active-text); font-weight: bold; }
108
+ #mainContent { flex: 1; display: flex; flex-direction: column; padding: 20px; overflow: hidden; }
109
+ .content-area { flex: 1; display: flex; flex-direction: column; overflow: hidden; gap: 15px; }
110
+ #schemaDisplay, #dataDisplayContainer, #queryResultContainer { background-color: var(--content-bg); border: 1px solid var(--border-color); border-radius: 5px; padding: 15px; margin-bottom: 0; overflow: auto; box-shadow: 0 1px 3px rgba(0,0,0,0.05); transition: background-color 0.3s, border-color 0.3s; }
111
+ #schemaDisplay h4, #dataDisplayContainer h4, #queryResultContainer h4 { margin-top: 0; color: var(--sidebar-text); border-bottom: 1px solid var(--border-color-light); padding-bottom: 10px; margin-bottom: 15px; }
112
+ #dataDisplayContainer { flex: 1; display: flex; flex-direction: column; }
113
+ #dataDisplay { flex: 1; overflow: auto; min-height: 100px; }
114
+ #schemaDisplay p, #dataDisplayContainer p { color: #888; }
115
+
116
+ /* --- Query Area (Keep as before) --- */
117
+ #queryArea { padding-top: 20px; border-top: 1px solid var(--border-color); background-color: var(--content-bg); padding: 15px; border-radius: 5px; box-shadow: 0 -1px 3px rgba(0,0,0,0.05); margin-top: 15px; transition: background-color 0.3s, border-color 0.3s; }
118
+ #queryArea h4 { margin-top: 0; margin-bottom: 10px; color: var(--sidebar-text); }
119
+ #queryArea textarea { width: 100%; min-height: 80px; padding: 10px; border: 1px solid var(--input-border); border-radius: 4px; box-sizing: border-box; font-family: monospace; margin-bottom: 10px; resize: vertical; background-color: var(--input-bg); color: var(--text-color); transition: background-color 0.3s, border-color 0.3s, color 0.3s; }
120
+ #queryArea button { padding: 10px 20px; background-color: var(--button-primary-bg); color: var(--button-primary-text); border: none; border-radius: 4px; cursor: pointer; transition: background-color 0.2s; font-weight: bold; }
121
+ #queryArea button:hover { background-color: var(--button-primary-hover-bg); }
122
+
123
+ /* --- Tables (Keep as before) --- */
124
+ table { width: 100%; border-collapse: collapse; margin-top: 0; font-size: 0.9em; }
125
+ th, td { border: 1px solid var(--border-color-light); padding: 10px 12px; text-align: left; white-space: nowrap; max-width: 250px; overflow: hidden; text-overflow: ellipsis; color: var(--text-color); transition: border-color 0.3s; }
126
+ th { background-color: var(--table-header-bg); font-weight: 600; position: sticky; top: 0; z-index: 1; transition: background-color 0.3s; }
127
+ tr:nth-child(even) { background-color: var(--table-row-even-bg); transition: background-color 0.3s; }
128
+ tr:nth-child(odd) { background-color: var(--content-bg); transition: background-color 0.3s; }
129
+
130
+ /* --- Status Message (Keep as before) --- */
131
+ #statusMessage { padding: 10px 15px; margin-top: 15px; border-radius: 4px; display: none; font-size: 0.9em; transition: background-color 0.3s, color 0.3s, border-color 0.3s; }
132
+ #statusMessage.success { background-color: var(--status-success-bg); color: var(--status-success-text); border: 1px solid var(--status-success-border); }
133
+ #statusMessage.error { background-color: var(--status-error-bg); color: var(--status-error-text); border: 1px solid var(--status-error-border); }
134
+
135
+ /* Removed .loader and @keyframes spin styling */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  </style>
137
  </head>
138
  <body>
139
 
140
  <header>
141
  <span>🦆 DuckDB Explorer</span>
142
+ <!-- Removed loader div -->
143
  </header>
144
 
145
  <div class="container">
 
183
  </div>
184
 
185
  <script>
186
+ // --- Keep the existing element variables EXCEPT loadingIndicator ---
187
  const tableList = document.getElementById('tableList');
188
  const schemaDisplay = document.getElementById('schemaDisplay');
189
  const schemaTable = document.getElementById('schemaTable');
190
+ const schemaPlaceholder = document.getElementById('schemaPlaceholder');
191
  const dataDisplayContainer = document.getElementById('dataDisplayContainer');
192
  const dataDisplay = document.getElementById('dataDisplay');
193
  const dataTable = document.getElementById('dataTable');
194
+ const dataPlaceholder = document.getElementById('dataPlaceholder');
195
  const tableDataHeader = document.getElementById('tableDataHeader');
196
  const sqlInput = document.getElementById('sqlInput');
197
  const runSqlButton = document.getElementById('runSqlButton');
 
199
  const queryResultDisplay = document.getElementById('queryResultDisplay');
200
  const queryResultTable = document.getElementById('queryResultTable');
201
  const statusMessage = document.getElementById('statusMessage');
202
+ // const loadingIndicator = document.getElementById('loadingIndicator'); // Removed
203
 
204
+ // --- API URL, currentTables, selectedTable, polling (Keep as before) ---
205
  const API_BASE_URL = '';
206
  let currentTables = [];
207
  let selectedTable = null;
208
  let pollingIntervalId = null;
209
+ const POLLING_INTERVAL_MS = 10000;
210
+
211
+ // --- Utility Functions ---
212
+ // --- REMOVE showLoader function ---
213
+ // function showLoader(show) {
214
+ // loadingIndicator.style.display = show ? 'inline-block' : 'none';
215
+ // }
216
+
217
+ // --- Keep showStatus, clearStatus ---
218
+ function showStatus(message, isError = false) {
219
+ statusMessage.textContent = message;
220
+ statusMessage.className = isError ? 'error' : 'success';
221
+ statusMessage.style.display = 'block';
222
+ setTimeout(() => { statusMessage.style.display = 'none'; }, 5000);
223
+ }
224
+ function clearStatus() {
225
+ statusMessage.textContent = '';
226
+ statusMessage.style.display = 'none';
227
+ }
228
 
229
  async function fetchAPI(endpoint, options = {}) {
230
+ // REMOVE showLoader(true);
231
  clearStatus();
232
  const url = `${API_BASE_URL}${endpoint}`;
233
  try {
 
240
  } catch (e) { /* Ignore */ }
241
  throw new Error(errorDetail);
242
  }
 
243
  const contentType = response.headers.get("content-type");
244
  if (contentType && contentType.includes("application/json")) {
245
  return await response.json();
246
  }
 
247
  return await response.text();
248
  } catch (error) {
249
  console.error('API Fetch Error:', error);
250
  showStatus(`Error: ${error.message}`, true);
251
  throw error;
252
  } finally {
253
+ // REMOVE showLoader(false);
254
  }
255
  }
256
 
257
+ // --- Keep renderTable, renderSchema ---
258
  function renderTable(data, tableElement) {
259
  tableElement.innerHTML = '';
260
+ if (!data || !Array.isArray(data)) {
261
  tableElement.innerHTML = '<tbody><tr><td>Invalid data format received.</td></tr></tbody>';
262
  console.error("Invalid data format for renderTable:", data);
263
  return;
 
284
  });
285
  });
286
  }
287
+ function renderSchema(schemaData) {
288
+ schemaTable.innerHTML = '';
289
+ schemaPlaceholder.style.display = 'none';
 
 
 
290
 
291
  if (!schemaData || !schemaData.columns || schemaData.columns.length === 0) {
292
  schemaTable.innerHTML = '<tbody><tr><td colspan="2">No schema information available.</td></tr></tbody>';
 
310
  }
311
 
312
 
313
+ // --- Event Handlers (Keep and ensure showLoader calls are removed) ---
314
 
315
  async function loadTables(isPolling = false) {
316
+ // ... (keep existing logic, just remove showLoader calls) ...
317
  if (!isPolling) {
318
  tableList.innerHTML = '<li>Loading tables...</li>';
319
  schemaTable.innerHTML = '';
320
  dataTable.innerHTML = '';
321
  tableDataHeader.textContent = '';
322
  queryResultContainer.style.display = 'none';
323
+ schemaPlaceholder.style.display = 'block';
324
  dataPlaceholder.style.display = 'block';
325
  }
326
  try {
327
  const newTables = await fetchAPI('/tables');
 
328
  if (!isPolling || JSON.stringify(newTables) !== JSON.stringify(currentTables)) {
329
  console.log("Table list changed, updating UI.");
330
  currentTables = newTables;
 
332
  if (!isPolling) {
333
  showStatus("Tables loaded.", false);
334
  }
 
335
  if (selectedTable && !currentTables.includes(selectedTable)) {
336
  console.log(`Selected table "${selectedTable}" removed.`);
337
  selectedTable = null;
338
  tableDataHeader.textContent = '';
339
  schemaDisplay.innerHTML = '<h4>Schema</h4><p id="schemaPlaceholder">Select a table from the list.</p><table id="schemaTable"></table>';
340
  dataDisplayContainer.innerHTML = '<h4>Data <span id="tableDataHeader"></span></h4><p id="dataPlaceholder">Select a table from the list.</p><div id="dataDisplay"><table id="dataTable"></table></div>';
 
341
  schemaTable = document.getElementById('schemaTable');
342
  dataTable = document.getElementById('dataTable');
343
  schemaPlaceholder = document.getElementById('schemaPlaceholder');
344
  dataPlaceholder = document.getElementById('dataPlaceholder');
345
  tableDataHeader = document.getElementById('tableDataHeader');
 
 
 
 
346
  }
347
  }
 
 
348
  if (currentTables.length > 0 && !isPolling) {
349
  schemaPlaceholder.style.display = 'none';
350
  dataPlaceholder.style.display = 'none';
 
353
  schemaPlaceholder.textContent = 'No tables found in the database.';
354
  dataPlaceholder.textContent = 'No tables found in the database.';
355
  }
 
356
  } catch (error) {
357
+ if (!isPolling) {
358
  tableList.innerHTML = '<li>Error loading tables.</li>';
359
  schemaPlaceholder.textContent = 'Error loading tables.';
360
  dataPlaceholder.textContent = 'Error loading tables.';
361
  } else {
362
+ console.error("Polling error:", error);
363
  }
364
  }
365
  }
366
 
367
  function displayTables(tables) {
368
+ // ... (keep existing logic) ...
369
+ tableList.innerHTML = '';
370
  if (tables.length === 0) {
371
  tableList.innerHTML = '<li>No tables found.</li>';
372
  return;
 
376
  li.textContent = tableName;
377
  li.dataset.tableName = tableName;
378
  li.onclick = () => handleTableSelection(li);
 
379
  if (tableName === selectedTable) {
380
  li.classList.add('active');
381
  }
 
384
  }
385
 
386
  async function handleTableSelection(listItem) {
387
+ // ... (keep existing logic, just remove showLoader calls) ...
388
  const currentActive = tableList.querySelector('.active');
389
  if (currentActive) {
390
  currentActive.classList.remove('active');
 
396
 
397
  queryResultContainer.style.display = 'none';
398
  dataDisplayContainer.style.display = 'flex';
399
+ dataPlaceholder.style.display = 'none';
400
 
401
  tableDataHeader.textContent = `for table "${selectedTable}"`;
402
+ schemaPlaceholder.style.display = 'none';
403
  schemaTable.innerHTML = '<tbody><tr><td colspan="2">Loading schema...</td></tr></tbody>';
404
  dataTable.innerHTML = '<tbody><tr><td>Loading data...</td></tr></tbody>';
405
 
 
418
 
419
 
420
  async function runCustomQuery() {
421
+ // ... (keep existing logic, just remove showLoader calls) ...
422
  const sql = sqlInput.value.trim();
423
  if (!sql) {
424
  showStatus("SQL query cannot be empty.", true);
425
  return;
426
  }
 
 
427
  const currentActive = tableList.querySelector('.active');
428
  if (currentActive) {
429
  currentActive.classList.remove('active');
430
  }
431
+ selectedTable = null;
432
 
433
  dataDisplayContainer.style.display = 'none';
434
  dataPlaceholder.style.display = 'none';
435
+ schemaPlaceholder.style.display = 'block';
436
  schemaTable.innerHTML = '';
437
  tableDataHeader.textContent = '';
438
 
439
  queryResultContainer.style.display = 'block';
440
  queryResultTable.innerHTML = '<tbody><tr><td>Running query...</td></tr></tbody>';
441
 
 
442
  try {
443
  const resultData = await fetchAPI('/query', {
444
  method: 'POST',
445
+ headers: { 'Content-Type': 'application/json', },
 
 
446
  body: JSON.stringify({ sql: sql }),
447
  });
448
  renderTable(resultData, queryResultTable);
 
452
  }
453
  }
454
 
455
+ // --- Keep Polling Functions ---
456
  function startPolling() {
457
+ stopPolling();
458
  console.log(`Starting polling every ${POLLING_INTERVAL_MS / 1000} seconds.`);
459
  pollingIntervalId = setInterval(() => loadTables(true), POLLING_INTERVAL_MS);
460
  }
 
461
  function stopPolling() {
462
  if (pollingIntervalId) {
463
  console.log("Stopping polling.");
 
468
 
469
  // --- Initial Setup ---
470
  runSqlButton.onclick = runCustomQuery;
 
 
471
  document.addEventListener('DOMContentLoaded', () => {
472
  loadTables().then(() => {
 
473
  if (currentTables.length >= 0) { // Start even if no tables initially
474
  startPolling();
475
  }
476
  });
477
  });
478
 
 
 
 
 
 
 
 
 
 
 
 
479
  </script>
480
 
481
  </body>