dbviewer 0.6.7 → 0.7.0

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 (27) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/dbviewer/entity_relationship_diagram.js +553 -0
  3. data/app/assets/javascripts/dbviewer/home.js +287 -0
  4. data/app/assets/javascripts/dbviewer/layout.js +194 -0
  5. data/app/assets/javascripts/dbviewer/query.js +277 -0
  6. data/app/assets/javascripts/dbviewer/table.js +1563 -0
  7. data/app/assets/stylesheets/dbviewer/application.css +1460 -21
  8. data/app/assets/stylesheets/dbviewer/entity_relationship_diagram.css +181 -0
  9. data/app/assets/stylesheets/dbviewer/home.css +229 -0
  10. data/app/assets/stylesheets/dbviewer/logs.css +64 -0
  11. data/app/assets/stylesheets/dbviewer/query.css +171 -0
  12. data/app/assets/stylesheets/dbviewer/table.css +1144 -0
  13. data/app/views/dbviewer/connections/index.html.erb +0 -30
  14. data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +14 -713
  15. data/app/views/dbviewer/home/index.html.erb +9 -499
  16. data/app/views/dbviewer/logs/index.html.erb +5 -220
  17. data/app/views/dbviewer/tables/index.html.erb +0 -65
  18. data/app/views/dbviewer/tables/query.html.erb +129 -565
  19. data/app/views/dbviewer/tables/show.html.erb +4 -2429
  20. data/app/views/layouts/dbviewer/application.html.erb +13 -1544
  21. data/lib/dbviewer/version.rb +1 -1
  22. metadata +12 -7
  23. data/app/assets/javascripts/dbviewer/connections.js +0 -70
  24. data/app/assets/stylesheets/dbviewer/dbviewer.css +0 -0
  25. data/app/assets/stylesheets/dbviewer/enhanced.css +0 -0
  26. data/app/views/dbviewer/connections/new.html.erb +0 -79
  27. data/app/views/dbviewer/tables/mini_erd.html.erb +0 -517
@@ -1,3 +1,8 @@
1
+ <% content_for :head do %>
2
+ <%= stylesheet_link_tag "dbviewer/home", "data-turbo-track": "reload" %>
3
+ <%= javascript_include_tag "dbviewer/home", "data-turbo-track": "reload" %>
4
+ <% end %>
5
+
1
6
  <div class="container-fluid px-0">
2
7
  <div class="row mb-3">
3
8
  <div class="col">
@@ -131,502 +136,7 @@
131
136
  </div>
132
137
  </div>
133
138
 
134
- <script>
135
- document.addEventListener('DOMContentLoaded', function() {
136
- // Helper function to format numbers with commas
137
- function numberWithDelimiter(number) {
138
- return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
139
- }
140
-
141
- // Helper function to format file sizes
142
- function numberToHumanSize(bytes) {
143
- if (bytes === null || bytes === undefined) return 'N/A';
144
- if (bytes === 0) return '0 Bytes';
145
-
146
- const k = 1024;
147
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
148
- const i = Math.floor(Math.log(bytes) / Math.log(k));
149
-
150
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
151
- }
152
-
153
- // Function to update analytics cards
154
- function updateTablesCount(data) {
155
- document.getElementById('tables-loading').classList.add('d-none');
156
- document.getElementById('tables-count').classList.remove('d-none');
157
- document.getElementById('tables-count').textContent = data.total_tables || 0;
158
- }
159
-
160
- function updateRelationshipsCount(data) {
161
- document.getElementById('relationships-loading').classList.add('d-none');
162
- document.getElementById('relationships-count').classList.remove('d-none');
163
- document.getElementById('relationships-count').textContent = data.total_relationships || 0;
164
- }
165
-
166
- function updateDatabaseSize(data) {
167
- document.getElementById('size-loading').classList.add('d-none');
168
- document.getElementById('size-count').classList.remove('d-none');
169
- document.getElementById('size-count').textContent = numberToHumanSize(data.schema_size);
170
- }
171
-
172
- function updateRecordsData(recordsData) {
173
- // Update records count
174
- document.getElementById('records-loading').classList.add('d-none');
175
- document.getElementById('records-count').classList.remove('d-none');
176
- document.getElementById('records-count').textContent = numberWithDelimiter(recordsData.total_records || 0);
177
-
178
- // Update largest tables
179
- updateLargestTables(recordsData);
180
- }
181
-
182
- // Function to update largest tables
183
- function updateLargestTables(data) {
184
- const container = document.getElementById('largest-tables-container');
185
-
186
- if (data.largest_tables && data.largest_tables.length > 0) {
187
- const tableHtml = `
188
- <div class="table-responsive">
189
- <table class="table table-sm table-hover">
190
- <thead>
191
- <tr>
192
- <th>Table Name</th>
193
- <th class="text-end">Records</th>
194
- </tr>
195
- </thead>
196
- <tbody>
197
- ${data.largest_tables.map(table => `
198
- <tr>
199
- <td>
200
- <a href="${window.location.origin}${window.location.pathname.replace(/\/$/, '')}/tables/${table.name}">
201
- ${table.name}
202
- </a>
203
- </td>
204
- <td class="text-end">${numberWithDelimiter(table.record_count)}</td>
205
- </tr>
206
- `).join('')}
207
- </tbody>
208
- </table>
209
- </div>
210
- `;
211
- container.innerHTML = tableHtml;
212
- } else {
213
- container.innerHTML = `
214
- <div class="text-center my-4 empty-data-message">
215
- <p>No table data available</p>
216
- </div>
217
- `;
218
- }
219
- }
220
-
221
- // Function to update recent queries
222
- function updateRecentQueries(data) {
223
- const container = document.getElementById('recent-queries-container');
224
- const linkContainer = document.getElementById('queries-view-all-link');
225
-
226
- if (data.enabled) {
227
- // Show "View All Logs" link if query logging is enabled
228
- linkContainer.innerHTML = `
229
- <a href="${window.location.origin}${window.location.pathname.replace(/\/$/, '')}/logs" class="btn btn-sm btn-primary">View All Logs</a>
230
- `;
231
- linkContainer.classList.remove('d-none');
232
-
233
- if (data.queries && data.queries.length > 0) {
234
- const tableHtml = `
235
- <div class="table-responsive">
236
- <table class="table table-sm table-hover mb-0">
237
- <thead>
238
- <tr>
239
- <th>Query</th>
240
- <th class="text-end" style="width: 120px">Duration</th>
241
- <th class="text-end" style="width: 180px">Time</th>
242
- </tr>
243
- </thead>
244
- <tbody>
245
- ${data.queries.map(query => {
246
- const duration = query.duration_ms;
247
- const durationClass = duration > 100 ? 'query-duration-slow' : 'query-duration';
248
- const timestamp = new Date(query.timestamp);
249
- const timeString = timestamp.toLocaleTimeString();
250
-
251
- return `
252
- <tr>
253
- <td class="text-truncate" style="max-width: 500px;">
254
- <code class="sql-query-code">${query.sql}</code>
255
- </td>
256
- <td class="text-end">
257
- <span class="${durationClass}">
258
- ${duration} ms
259
- </span>
260
- </td>
261
- <td class="text-end query-timestamp">
262
- <small>${timeString}</small>
263
- </td>
264
- </tr>
265
- `;
266
- }).join('')}
267
- </tbody>
268
- </table>
269
- </div>
270
- `;
271
- container.innerHTML = tableHtml;
272
- } else {
273
- container.innerHTML = `
274
- <div class="text-center my-4 empty-data-message">
275
- <p>No queries recorded yet</p>
276
- </div>
277
- `;
278
- }
279
- } else {
280
- container.innerHTML = `
281
- <div class="text-center my-4 empty-data-message">
282
- <p>Query logging is disabled</p>
283
- <small class="text-muted">Enable it in the configuration to see SQL queries here</small>
284
- </div>
285
- `;
286
- }
287
- }
288
-
289
- // Function to show error state
290
- function showError(containerId, message) {
291
- const container = document.getElementById(containerId);
292
- container.innerHTML = `
293
- <div class="text-center my-4 text-danger">
294
- <i class="bi bi-exclamation-triangle fs-2 d-block mb-2"></i>
295
- <p>Error loading data</p>
296
- <small>${message}</small>
297
- </div>
298
- `;
299
- }
300
-
301
- // Load tables count data
302
- fetch('<%= dbviewer.api_tables_path %>', {
303
- headers: {
304
- 'Accept': 'application/json',
305
- 'X-Requested-With': 'XMLHttpRequest'
306
- }
307
- })
308
- .then(response => {
309
- if (!response.ok) {
310
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
311
- }
312
- return response.json();
313
- })
314
- .then(data => {
315
- updateTablesCount(data);
316
- })
317
- .catch(error => {
318
- console.error('Error loading tables count:', error);
319
- const loading = document.getElementById('tables-loading');
320
- const count = document.getElementById('tables-count');
321
- loading.classList.add('d-none');
322
- count.classList.remove('d-none');
323
- count.innerHTML = '<span class="text-danger">Error</span>';
324
- });
325
-
326
- // Load database size data
327
- fetch('<%= dbviewer.size_api_database_path %>', {
328
- headers: {
329
- 'Accept': 'application/json',
330
- 'X-Requested-With': 'XMLHttpRequest'
331
- }
332
- })
333
- .then(response => {
334
- if (!response.ok) {
335
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
336
- }
337
- return response.json();
338
- })
339
- .then(data => {
340
- updateDatabaseSize(data);
341
- })
342
- .catch(error => {
343
- console.error('Error loading database size:', error);
344
- const loading = document.getElementById('size-loading');
345
- const count = document.getElementById('size-count');
346
- loading.classList.add('d-none');
347
- count.classList.remove('d-none');
348
- count.innerHTML = '<span class="text-danger">Error</span>';
349
- });
350
-
351
- // Load records data separately
352
- fetch('<%= dbviewer.records_api_tables_path %>', {
353
- headers: {
354
- 'Accept': 'application/json',
355
- 'X-Requested-With': 'XMLHttpRequest'
356
- }
357
- })
358
- .then(response => {
359
- if (!response.ok) {
360
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
361
- }
362
- return response.json();
363
- })
364
- .then(recordsData => {
365
- updateRecordsData(recordsData);
366
- })
367
- .catch(error => {
368
- console.error('Error loading records data:', error);
369
- // Update records-related UI with error state
370
- const recordsLoading = document.getElementById('records-loading');
371
- const recordsCount = document.getElementById('records-count');
372
- recordsLoading.classList.add('d-none');
373
- recordsCount.classList.remove('d-none');
374
- recordsCount.innerHTML = '<span class="text-danger">Error</span>';
375
-
376
- showError('largest-tables-container', error.message);
377
- });
378
-
379
- // Load recent queries data
380
- fetch('<%= dbviewer.recent_api_queries_path %>', {
381
- headers: {
382
- 'Accept': 'application/json',
383
- 'X-Requested-With': 'XMLHttpRequest'
384
- }
385
- })
386
- .then(response => {
387
- if (!response.ok) {
388
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
389
- }
390
- return response.json();
391
- })
392
- .then(data => {
393
- updateRecentQueries(data);
394
- })
395
- .catch(error => {
396
- console.error('Error loading recent queries:', error);
397
- showError('recent-queries-container', error.message);
398
- });
399
- });
400
- </script>
401
-
402
- <style>
403
- /* ================================================
404
- CSS Custom Properties (CSS Variables)
405
- ================================================ */
406
- :root {
407
- /* Colors */
408
- --dbviewer-code-bg: rgba(0, 0, 0, 0.05);
409
- --dbviewer-code-border: rgba(0, 0, 0, 0.1);
410
- --dbviewer-muted-color: #6c757d;
411
- --dbviewer-success-color: #28a745;
412
- --dbviewer-danger-color: #dc3545;
413
- --dbviewer-warning-color: #ffc107;
414
-
415
- /* Skeleton loader colors */
416
- --skeleton-base-color: #f0f0f0;
417
- --skeleton-highlight-color: #e0e0e0;
418
-
419
- /* Typography */
420
- --dbviewer-monospace-font: 'Courier New', Courier, monospace;
421
- --dbviewer-code-font-size: 0.85rem;
422
-
423
- /* Spacing and sizing */
424
- --dbviewer-border-radius: 4px;
425
- --dbviewer-border-radius-sm: 3px;
426
- --dbviewer-padding-sm: 2px 4px;
427
- }
428
-
429
- /* ================================================
430
- Dark Mode Support
431
- ================================================ */
432
- @media (prefers-color-scheme: dark) {
433
- :root {
434
- --dbviewer-code-bg: rgba(255, 255, 255, 0.1);
435
- --dbviewer-code-border: rgba(255, 255, 255, 0.15);
436
- --dbviewer-muted-color: #adb5bd;
437
- --skeleton-base-color: #2a2a2a;
438
- --skeleton-highlight-color: #404040;
439
- }
440
- }
441
-
442
- /* Bootstrap dark mode support */
443
- [data-bs-theme="dark"] {
444
- --dbviewer-code-bg: rgba(255, 255, 255, 0.1);
445
- --dbviewer-code-border: rgba(255, 255, 255, 0.15);
446
- --dbviewer-muted-color: #adb5bd;
447
- --skeleton-base-color: #2a2a2a;
448
- --skeleton-highlight-color: #404040;
449
- }
450
-
451
- /* ================================================
452
- SQL Query Styling
453
- ================================================ */
454
- .sql-query-code {
455
- font-family: var(--dbviewer-monospace-font);
456
- font-size: var(--dbviewer-code-font-size);
457
- background-color: var(--dbviewer-code-bg);
458
- padding: var(--dbviewer-padding-sm);
459
- border-radius: var(--dbviewer-border-radius-sm);
460
- border: 1px solid var(--dbviewer-code-border);
461
- transition: background-color 0.2s ease, border-color 0.2s ease;
462
- }
463
-
464
- .sql-query-code:hover {
465
- background-color: var(--dbviewer-code-bg);
466
- filter: brightness(0.95);
467
- }
468
-
469
- /* ================================================
470
- Query Performance Indicators
471
- ================================================ */
472
- .query-duration {
473
- color: var(--dbviewer-success-color);
474
- font-weight: 500;
475
- font-variant-numeric: tabular-nums;
476
- transition: color 0.2s ease;
477
- }
478
-
479
- .query-duration-slow {
480
- color: var(--dbviewer-danger-color);
481
- font-weight: 600;
482
- font-variant-numeric: tabular-nums;
483
- transition: color 0.2s ease;
484
- }
485
-
486
- .query-timestamp {
487
- color: var(--dbviewer-muted-color);
488
- font-variant-numeric: tabular-nums;
489
- transition: color 0.2s ease;
490
- }
491
-
492
- /* ================================================
493
- Empty States and Messages
494
- ================================================ */
495
- .empty-data-message {
496
- color: var(--dbviewer-muted-color);
497
- transition: color 0.2s ease;
498
- }
499
-
500
- .empty-data-message p {
501
- margin-bottom: 0.5rem;
502
- font-weight: 500;
503
- }
504
-
505
- .empty-data-message small {
506
- opacity: 0.8;
507
- }
508
-
509
- /* ================================================
510
- Loading States
511
- ================================================ */
512
- .spinner-border-sm {
513
- width: 1rem;
514
- height: 1rem;
515
- }
516
-
517
- /* ================================================
518
- Skeleton Loader System
519
- ================================================ */
520
- .skeleton-loader {
521
- display: inline-block;
522
- height: 1.2em;
523
- width: 100%;
524
- background: linear-gradient(
525
- 90deg,
526
- var(--skeleton-base-color) 25%,
527
- var(--skeleton-highlight-color) 37%,
528
- var(--skeleton-base-color) 63%
529
- );
530
- background-size: 400% 100%;
531
- animation: skeleton-loading 1.2s ease-in-out infinite;
532
- border-radius: var(--dbviewer-border-radius);
533
- }
534
-
535
- /* Skeleton loader variants */
536
- .skeleton-loader.number-loader {
537
- width: 2.5em;
538
- height: 1.5em;
539
- margin-bottom: 0.2em;
540
- }
541
-
542
- .skeleton-loader.table-cell-loader {
543
- width: 6em;
544
- height: 1.2em;
545
- }
546
-
547
- .skeleton-loader.records-loader {
548
- width: 3em;
549
- height: 1.2em;
550
- }
551
-
552
- .skeleton-loader.query-cell-loader {
553
- width: 12em;
554
- height: 1.2em;
555
- }
556
-
557
- .skeleton-loader.duration-cell-loader {
558
- width: 4em;
559
- height: 1.2em;
560
- }
561
-
562
- .skeleton-loader.time-cell-loader {
563
- width: 7em;
564
- height: 1.2em;
565
- }
566
-
567
- /* ================================================
568
- Animations
569
- ================================================ */
570
- @keyframes skeleton-loading {
571
- 0% {
572
- background-position: 100% 50%;
573
- }
574
- 100% {
575
- background-position: 0 50%;
576
- }
577
- }
578
-
579
- /* ================================================
580
- Table Enhancements
581
- ================================================ */
582
- .table-hover tbody tr:hover .sql-query-code {
583
- background-color: var(--dbviewer-code-bg);
584
- filter: brightness(0.9);
585
- }
586
-
587
- /* ================================================
588
- Responsive Design
589
- ================================================ */
590
- @media (max-width: 768px) {
591
- .sql-query-code {
592
- font-size: 0.75rem;
593
- padding: 1px 3px;
594
- }
595
-
596
- .query-cell-loader {
597
- width: 8em;
598
- }
599
-
600
- .duration-cell-loader {
601
- width: 3em;
602
- }
603
-
604
- .time-cell-loader {
605
- width: 5em;
606
- }
607
- }
608
-
609
- /* ================================================
610
- Accessibility Improvements
611
- ================================================ */
612
- @media (prefers-reduced-motion: reduce) {
613
- .skeleton-loader {
614
- animation: none;
615
- background: var(--skeleton-base-color);
616
- }
617
-
618
- .sql-query-code,
619
- .query-duration,
620
- .query-duration-slow,
621
- .query-timestamp,
622
- .empty-data-message {
623
- transition: none;
624
- }
625
- }
626
-
627
- /* Focus states for better keyboard navigation */
628
- .sql-query-code:focus-visible {
629
- outline: 2px solid var(--dbviewer-success-color);
630
- outline-offset: 2px;
631
- }
632
- </style>
139
+ <input type="text" id="recent_api_queries_path" class="d-none" value="<%= dbviewer.recent_api_queries_path %>">
140
+ <input type="text" id="api_tables_path" class="d-none" value="<%= dbviewer.api_tables_path %>">
141
+ <input type="text" id="size_api_database_path" class="d-none" value="<%= dbviewer.size_api_database_path %>">
142
+ <input type="text" id="records_api_tables_path" class="d-none" value="<%= dbviewer.records_api_tables_path %>">