dbviewer 0.5.2 → 0.5.4
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.
- checksums.yaml +4 -4
- data/README.md +92 -0
- data/app/controllers/concerns/dbviewer/database_operations.rb +11 -19
- data/app/controllers/dbviewer/api/entity_relationship_diagrams_controller.rb +84 -0
- data/app/controllers/dbviewer/api/queries_controller.rb +1 -1
- data/app/controllers/dbviewer/entity_relationship_diagrams_controller.rb +5 -6
- data/app/controllers/dbviewer/logs_controller.rb +1 -1
- data/app/controllers/dbviewer/tables_controller.rb +2 -8
- data/app/helpers/dbviewer/application_helper.rb +1 -1
- data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +232 -141
- data/app/views/dbviewer/tables/show.html.erb +278 -404
- data/config/routes.rb +7 -0
- data/lib/dbviewer/database/cache_manager.rb +78 -0
- data/lib/dbviewer/database/dynamic_model_factory.rb +62 -0
- data/lib/dbviewer/database/manager.rb +204 -0
- data/lib/dbviewer/database/metadata_manager.rb +129 -0
- data/lib/dbviewer/datatable/query_operations.rb +330 -0
- data/lib/dbviewer/datatable/query_params.rb +41 -0
- data/lib/dbviewer/engine.rb +1 -1
- data/lib/dbviewer/query/analyzer.rb +250 -0
- data/lib/dbviewer/query/collection.rb +39 -0
- data/lib/dbviewer/query/executor.rb +93 -0
- data/lib/dbviewer/query/logger.rb +108 -0
- data/lib/dbviewer/query/parser.rb +56 -0
- data/lib/dbviewer/storage/file_storage.rb +0 -3
- data/lib/dbviewer/version.rb +1 -1
- data/lib/dbviewer.rb +24 -7
- metadata +14 -14
- data/lib/dbviewer/cache_manager.rb +0 -78
- data/lib/dbviewer/database_manager.rb +0 -249
- data/lib/dbviewer/dynamic_model_factory.rb +0 -60
- data/lib/dbviewer/error_handler.rb +0 -18
- data/lib/dbviewer/logger.rb +0 -77
- data/lib/dbviewer/query_analyzer.rb +0 -239
- data/lib/dbviewer/query_collection.rb +0 -37
- data/lib/dbviewer/query_executor.rb +0 -91
- data/lib/dbviewer/query_parser.rb +0 -53
- data/lib/dbviewer/table_metadata_manager.rb +0 -136
- data/lib/dbviewer/table_query_operations.rb +0 -621
- data/lib/dbviewer/table_query_params.rb +0 -39
@@ -318,6 +318,9 @@
|
|
318
318
|
<h1>Table: <%= @table_name %></h1>
|
319
319
|
</div>
|
320
320
|
<div class="d-flex gap-2">
|
321
|
+
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#tableStructureModal">
|
322
|
+
<i class="bi bi-table me-1"></i> Table Structure
|
323
|
+
</button>
|
321
324
|
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#miniErdModal">
|
322
325
|
<i class="bi bi-diagram-3 me-1"></i> View Relationships
|
323
326
|
</button>
|
@@ -442,59 +445,6 @@
|
|
442
445
|
</div>
|
443
446
|
</div>
|
444
447
|
|
445
|
-
<!-- Two-column layout for Timeline and Structure -->
|
446
|
-
<div class="row two-column-layout">
|
447
|
-
<!-- Timeline Column -->
|
448
|
-
<div class="col-md-6 mb-4">
|
449
|
-
<% if @timestamp_data.present? %>
|
450
|
-
<div class="dbviewer-card card h-100">
|
451
|
-
<div class="card-header d-flex justify-content-between align-items-center">
|
452
|
-
<h5 class="mb-0"><i class="bi bi-graph-up me-2"></i>Record Creation Timeline</h5>
|
453
|
-
<div>
|
454
|
-
<%= time_grouping_links(@table_name, @time_grouping) %>
|
455
|
-
</div>
|
456
|
-
</div>
|
457
|
-
<div class="card-body">
|
458
|
-
<div class="chart-container">
|
459
|
-
<canvas id="timestampChart"></canvas>
|
460
|
-
</div>
|
461
|
-
<div class="mt-3 text-center">
|
462
|
-
<small class="text-muted">
|
463
|
-
<i class="bi bi-info-circle"></i>
|
464
|
-
Timeline shows <%= @time_grouping %> record creation patterns based on <code>created_at</code> column.
|
465
|
-
</small>
|
466
|
-
</div>
|
467
|
-
</div>
|
468
|
-
</div>
|
469
|
-
<% else %>
|
470
|
-
<div class="dbviewer-card card h-100">
|
471
|
-
<div class="card-header">
|
472
|
-
<h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>Creation Timeline</h5>
|
473
|
-
</div>
|
474
|
-
<div class="card-body d-flex justify-content-center align-items-center text-center text-muted">
|
475
|
-
<div>
|
476
|
-
<i class="bi bi-calendar-x display-4 mb-3"></i>
|
477
|
-
<p>No creation timestamp data available for this table.</p>
|
478
|
-
<small>Timeline visualization is only available for tables with a <code>created_at</code> column.</small>
|
479
|
-
</div>
|
480
|
-
</div>
|
481
|
-
</div>
|
482
|
-
<% end %>
|
483
|
-
</div>
|
484
|
-
|
485
|
-
<!-- Structure Column -->
|
486
|
-
<div class="col-md-6 mb-4">
|
487
|
-
<div class="dbviewer-card card h-100">
|
488
|
-
<div class="card-header">
|
489
|
-
<h5 class="mb-0"><i class="bi bi-diagram-3 me-2"></i>Table Structure</h5>
|
490
|
-
</div>
|
491
|
-
<div class="card-body structure-container">
|
492
|
-
<%= render 'table_structure' %>
|
493
|
-
</div>
|
494
|
-
</div>
|
495
|
-
</div>
|
496
|
-
</div>
|
497
|
-
|
498
448
|
<!-- Record Detail Modal -->
|
499
449
|
<div class="modal fade" id="recordDetailModal" tabindex="-1" aria-labelledby="recordDetailModalLabel" aria-hidden="true">
|
500
450
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
@@ -536,6 +486,194 @@
|
|
536
486
|
</div>
|
537
487
|
</div>
|
538
488
|
|
489
|
+
<!-- Table Structure Modal -->
|
490
|
+
<div class="modal fade" id="tableStructureModal" tabindex="-1" aria-labelledby="tableStructureModalLabel" aria-hidden="true">
|
491
|
+
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
492
|
+
<div class="modal-content">
|
493
|
+
<div class="modal-header">
|
494
|
+
<h5 class="modal-title" id="tableStructureModalLabel"><%= @table_name %> Structure</h5>
|
495
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
496
|
+
</div>
|
497
|
+
<div class="modal-body">
|
498
|
+
<%= render 'table_structure' %>
|
499
|
+
</div>
|
500
|
+
<div class="modal-footer">
|
501
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
502
|
+
</div>
|
503
|
+
</div>
|
504
|
+
</div>
|
505
|
+
</div>
|
506
|
+
|
507
|
+
<style>
|
508
|
+
/* Column filter styling */
|
509
|
+
.column-filters td {
|
510
|
+
padding: 0.5rem;
|
511
|
+
background-color: var(--bs-tertiary-bg, #f8f9fa);
|
512
|
+
}
|
513
|
+
|
514
|
+
/* Action column styling */
|
515
|
+
.action-column {
|
516
|
+
width: 60px;
|
517
|
+
min-width: 60px; /* Ensure minimum width */
|
518
|
+
white-space: nowrap;
|
519
|
+
position: sticky;
|
520
|
+
left: 0;
|
521
|
+
z-index: 30; /* Increased z-index to ensure it stays on top */
|
522
|
+
background-color: var(--bs-body-bg, #fff); /* Use body background color */
|
523
|
+
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
|
524
|
+
border-right: 1px solid var(--bs-border-color);
|
525
|
+
}
|
526
|
+
|
527
|
+
/* Ensure proper background color for actions column in dark mode */
|
528
|
+
[data-bs-theme="dark"] .action-column {
|
529
|
+
background-color: var(--bs-body-bg, #212529); /* Use body background in dark mode */
|
530
|
+
}
|
531
|
+
|
532
|
+
/* Maintain zebra striping with sticky action column */
|
533
|
+
.table-striped > tbody > tr:nth-of-type(odd) > .action-column {
|
534
|
+
background-color: var(--bs-tertiary-bg, #f8f9fa);
|
535
|
+
}
|
536
|
+
|
537
|
+
.table-striped > tbody > tr:nth-of-type(even) > .action-column {
|
538
|
+
background-color: var(--bs-body-bg, #fff);
|
539
|
+
}
|
540
|
+
|
541
|
+
[data-bs-theme="dark"] .table-striped > tbody > tr:nth-of-type(odd) > .action-column {
|
542
|
+
background-color: var(--bs-tertiary-bg, #2b3035);
|
543
|
+
}
|
544
|
+
|
545
|
+
[data-bs-theme="dark"] .table-striped > tbody > tr:nth-of-type(even) > .action-column {
|
546
|
+
background-color: var(--bs-body-bg, #212529);
|
547
|
+
}
|
548
|
+
|
549
|
+
.view-record-btn {
|
550
|
+
padding: 0.1rem 0.4rem;
|
551
|
+
width: 32px;
|
552
|
+
}
|
553
|
+
|
554
|
+
.view-record-btn:hover {
|
555
|
+
opacity: 0.85;
|
556
|
+
transform: translateY(-1px);
|
557
|
+
}
|
558
|
+
|
559
|
+
/* Record detail modal styling */
|
560
|
+
.record-detail-table tr:first-child th,
|
561
|
+
.record-detail-table tr:first-child td {
|
562
|
+
border-top: none;
|
563
|
+
}
|
564
|
+
|
565
|
+
.record-detail-table .code-block {
|
566
|
+
background-color: var(--bs-light);
|
567
|
+
padding: 0.5rem;
|
568
|
+
border-radius: 0.25rem;
|
569
|
+
overflow-x: auto;
|
570
|
+
max-height: 200px;
|
571
|
+
}
|
572
|
+
|
573
|
+
/* Relationships section styling */
|
574
|
+
#relationshipsSection {
|
575
|
+
border-top: 1px solid var(--bs-border-color);
|
576
|
+
padding-top: 1rem;
|
577
|
+
}
|
578
|
+
|
579
|
+
#relationshipsSection h6 {
|
580
|
+
color: var(--bs-emphasis-color);
|
581
|
+
margin-bottom: 1rem;
|
582
|
+
}
|
583
|
+
|
584
|
+
[data-bs-theme="dark"] #relationshipsSection {
|
585
|
+
border-top-color: #495057;
|
586
|
+
}
|
587
|
+
|
588
|
+
.relationships-table .btn-outline-primary {
|
589
|
+
font-size: 0.75rem;
|
590
|
+
padding: 0.25rem 0.5rem;
|
591
|
+
}
|
592
|
+
|
593
|
+
.relationships-table code {
|
594
|
+
background-color: var(--bs-gray-100);
|
595
|
+
padding: 0.125rem 0.25rem;
|
596
|
+
border-radius: 0.125rem;
|
597
|
+
font-size: 0.875rem;
|
598
|
+
}
|
599
|
+
|
600
|
+
[data-bs-theme="dark"] .relationships-table code {
|
601
|
+
background-color: var(--bs-gray-800);
|
602
|
+
color: var(--bs-gray-100);
|
603
|
+
}
|
604
|
+
margin-bottom: 0;
|
605
|
+
}
|
606
|
+
|
607
|
+
[data-bs-theme="dark"] .record-detail-table .code-block {
|
608
|
+
background-color: var(--bs-dark);
|
609
|
+
}
|
610
|
+
|
611
|
+
/* Fullscreen table styles */
|
612
|
+
.table-fullscreen {
|
613
|
+
position: fixed !important;
|
614
|
+
top: 0 !important;
|
615
|
+
left: 0 !important;
|
616
|
+
width: 100vw !important;
|
617
|
+
height: 100vh !important;
|
618
|
+
z-index: 9999 !important;
|
619
|
+
background: var(--bs-body-bg) !important;
|
620
|
+
margin: 0 !important;
|
621
|
+
border-radius: 0 !important;
|
622
|
+
overflow: hidden !important;
|
623
|
+
display: flex !important;
|
624
|
+
flex-direction: column !important;
|
625
|
+
}
|
626
|
+
|
627
|
+
.table-fullscreen .card-body {
|
628
|
+
flex: 1 !important;
|
629
|
+
overflow: hidden !important;
|
630
|
+
display: flex !important;
|
631
|
+
flex-direction: column !important;
|
632
|
+
}
|
633
|
+
|
634
|
+
.table-fullscreen .table-responsive {
|
635
|
+
flex: 1 !important;
|
636
|
+
overflow: auto !important;
|
637
|
+
}
|
638
|
+
|
639
|
+
.table-fullscreen .card-header {
|
640
|
+
flex-shrink: 0 !important;
|
641
|
+
position: sticky !important;
|
642
|
+
top: 0 !important;
|
643
|
+
z-index: 10000 !important;
|
644
|
+
background: var(--bs-body-bg) !important;
|
645
|
+
border-bottom: 1px solid var(--bs-border-color) !important;
|
646
|
+
}
|
647
|
+
|
648
|
+
/* Hide pagination in fullscreen mode */
|
649
|
+
.table-fullscreen .pagination-container {
|
650
|
+
display: none !important;
|
651
|
+
}
|
652
|
+
|
653
|
+
/* Adjust table header in fullscreen */
|
654
|
+
.table-fullscreen .dbviewer-table-header {
|
655
|
+
position: sticky !important;
|
656
|
+
top: 0 !important;
|
657
|
+
z-index: 100 !important;
|
658
|
+
}
|
659
|
+
|
660
|
+
/* Ensure body doesn't scroll when table is fullscreen */
|
661
|
+
body.table-fullscreen-active {
|
662
|
+
overflow: hidden !important;
|
663
|
+
}
|
664
|
+
|
665
|
+
/* Fullscreen button hover effect */
|
666
|
+
#fullscreen-toggle:hover {
|
667
|
+
background-color: var(--bs-secondary-bg) !important;
|
668
|
+
border-color: var(--bs-secondary-border-subtle) !important;
|
669
|
+
}
|
670
|
+
|
671
|
+
/* Smooth transitions */
|
672
|
+
#table-section {
|
673
|
+
transition: all 0.3s ease-in-out;
|
674
|
+
}
|
675
|
+
</style>
|
676
|
+
|
539
677
|
<script>
|
540
678
|
document.addEventListener('DOMContentLoaded', function() {
|
541
679
|
// Record Detail Modal functionality
|
@@ -768,19 +906,7 @@
|
|
768
906
|
const cacheBuster = new Date().getTime();
|
769
907
|
const fetchUrl = `<%= dbviewer.mini_erd_table_path(@table_name, format: :json) %>?_=${cacheBuster}`;
|
770
908
|
|
771
|
-
|
772
|
-
console.log('Loading fresh Mini ERD data from:', fetchUrl);
|
773
|
-
|
774
|
-
// Set a timeout to handle long-running requests
|
775
|
-
const timeoutPromise = new Promise((_, reject) =>
|
776
|
-
setTimeout(() => reject(new Error('Request timeout after 10 seconds')), 10000)
|
777
|
-
);
|
778
|
-
|
779
|
-
// Race the fetch against a timeout
|
780
|
-
Promise.race([
|
781
|
-
fetch(fetchUrl),
|
782
|
-
timeoutPromise
|
783
|
-
])
|
909
|
+
fetch(fetchUrl)
|
784
910
|
.then(response => {
|
785
911
|
if (!response.ok) {
|
786
912
|
throw new Error(`Server returned ${response.status} ${response.statusText}`);
|
@@ -1208,288 +1334,102 @@
|
|
1208
1334
|
// Retry fetching data
|
1209
1335
|
fetchErdData();
|
1210
1336
|
}
|
1211
|
-
|
1212
|
-
|
1337
|
+
|
1338
|
+
// Column sorting enhancement
|
1339
|
+
const sortableColumns = document.querySelectorAll('.sortable-column');
|
1340
|
+
sortableColumns.forEach(column => {
|
1341
|
+
const link = column.querySelector('.column-sort-link');
|
1342
|
+
|
1343
|
+
// Mouse over effects
|
1344
|
+
column.addEventListener('mouseenter', () => {
|
1345
|
+
const sortIcon = column.querySelector('.sort-icon');
|
1346
|
+
if (sortIcon && sortIcon.classList.contains('invisible')) {
|
1347
|
+
sortIcon.style.visibility = 'visible';
|
1348
|
+
sortIcon.style.opacity = '0.3';
|
1349
|
+
}
|
1350
|
+
});
|
1351
|
+
|
1352
|
+
column.addEventListener('mouseleave', () => {
|
1353
|
+
const sortIcon = column.querySelector('.sort-icon');
|
1354
|
+
if (sortIcon && sortIcon.classList.contains('invisible')) {
|
1355
|
+
sortIcon.style.visibility = 'hidden';
|
1356
|
+
sortIcon.style.opacity = '0';
|
1357
|
+
}
|
1358
|
+
});
|
1359
|
+
|
1360
|
+
// Keyboard accessibility
|
1361
|
+
if (link) {
|
1362
|
+
link.addEventListener('keydown', (e) => {
|
1363
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
1364
|
+
e.preventDefault();
|
1365
|
+
link.click();
|
1366
|
+
}
|
1367
|
+
});
|
1368
|
+
}
|
1369
|
+
});
|
1213
1370
|
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
background-color: var(--bs-tertiary-bg, #f8f9fa);
|
1219
|
-
}
|
1220
|
-
|
1221
|
-
/* Action column styling */
|
1222
|
-
.action-column {
|
1223
|
-
width: 60px;
|
1224
|
-
min-width: 60px; /* Ensure minimum width */
|
1225
|
-
white-space: nowrap;
|
1226
|
-
position: sticky;
|
1227
|
-
left: 0;
|
1228
|
-
z-index: 30; /* Increased z-index to ensure it stays on top */
|
1229
|
-
background-color: var(--bs-body-bg, #fff); /* Use body background color */
|
1230
|
-
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
|
1231
|
-
border-right: 1px solid var(--bs-border-color);
|
1232
|
-
}
|
1233
|
-
|
1234
|
-
/* Ensure proper background color for actions column in dark mode */
|
1235
|
-
[data-bs-theme="dark"] .action-column {
|
1236
|
-
background-color: var(--bs-body-bg, #212529); /* Use body background in dark mode */
|
1237
|
-
}
|
1238
|
-
|
1239
|
-
/* Maintain zebra striping with sticky action column */
|
1240
|
-
.table-striped > tbody > tr:nth-of-type(odd) > .action-column {
|
1241
|
-
background-color: var(--bs-tertiary-bg, #f8f9fa);
|
1242
|
-
}
|
1243
|
-
|
1244
|
-
.table-striped > tbody > tr:nth-of-type(even) > .action-column {
|
1245
|
-
background-color: var(--bs-body-bg, #fff);
|
1246
|
-
}
|
1247
|
-
|
1248
|
-
[data-bs-theme="dark"] .table-striped > tbody > tr:nth-of-type(odd) > .action-column {
|
1249
|
-
background-color: var(--bs-tertiary-bg, #2b3035);
|
1250
|
-
}
|
1251
|
-
|
1252
|
-
[data-bs-theme="dark"] .table-striped > tbody > tr:nth-of-type(even) > .action-column {
|
1253
|
-
background-color: var(--bs-body-bg, #212529);
|
1254
|
-
}
|
1255
|
-
|
1256
|
-
.view-record-btn {
|
1257
|
-
padding: 0.1rem 0.4rem;
|
1258
|
-
width: 32px;
|
1259
|
-
}
|
1260
|
-
|
1261
|
-
.view-record-btn:hover {
|
1262
|
-
opacity: 0.85;
|
1263
|
-
transform: translateY(-1px);
|
1264
|
-
}
|
1265
|
-
|
1266
|
-
/* Record detail modal styling */
|
1267
|
-
.record-detail-table tr:first-child th,
|
1268
|
-
.record-detail-table tr:first-child td {
|
1269
|
-
border-top: none;
|
1270
|
-
}
|
1271
|
-
|
1272
|
-
.record-detail-table .code-block {
|
1273
|
-
background-color: var(--bs-light);
|
1274
|
-
padding: 0.5rem;
|
1275
|
-
border-radius: 0.25rem;
|
1276
|
-
overflow-x: auto;
|
1277
|
-
max-height: 200px;
|
1278
|
-
}
|
1279
|
-
|
1280
|
-
/* Relationships section styling */
|
1281
|
-
#relationshipsSection {
|
1282
|
-
border-top: 1px solid var(--bs-border-color);
|
1283
|
-
padding-top: 1rem;
|
1284
|
-
}
|
1285
|
-
|
1286
|
-
#relationshipsSection h6 {
|
1287
|
-
color: var(--bs-emphasis-color);
|
1288
|
-
margin-bottom: 1rem;
|
1289
|
-
}
|
1290
|
-
|
1291
|
-
[data-bs-theme="dark"] #relationshipsSection {
|
1292
|
-
border-top-color: #495057;
|
1293
|
-
}
|
1294
|
-
|
1295
|
-
.relationships-table .btn-outline-primary {
|
1296
|
-
font-size: 0.75rem;
|
1297
|
-
padding: 0.25rem 0.5rem;
|
1298
|
-
}
|
1299
|
-
|
1300
|
-
.relationships-table code {
|
1301
|
-
background-color: var(--bs-gray-100);
|
1302
|
-
padding: 0.125rem 0.25rem;
|
1303
|
-
border-radius: 0.125rem;
|
1304
|
-
font-size: 0.875rem;
|
1305
|
-
}
|
1306
|
-
|
1307
|
-
[data-bs-theme="dark"] .relationships-table code {
|
1308
|
-
background-color: var(--bs-gray-800);
|
1309
|
-
color: var(--bs-gray-100);
|
1310
|
-
}
|
1311
|
-
margin-bottom: 0;
|
1312
|
-
}
|
1313
|
-
|
1314
|
-
[data-bs-theme="dark"] .record-detail-table .code-block {
|
1315
|
-
background-color: var(--bs-dark);
|
1316
|
-
}
|
1317
|
-
|
1318
|
-
/* Fullscreen table styles */
|
1319
|
-
.table-fullscreen {
|
1320
|
-
position: fixed !important;
|
1321
|
-
top: 0 !important;
|
1322
|
-
left: 0 !important;
|
1323
|
-
width: 100vw !important;
|
1324
|
-
height: 100vh !important;
|
1325
|
-
z-index: 9999 !important;
|
1326
|
-
background: var(--bs-body-bg) !important;
|
1327
|
-
margin: 0 !important;
|
1328
|
-
border-radius: 0 !important;
|
1329
|
-
overflow: hidden !important;
|
1330
|
-
display: flex !important;
|
1331
|
-
flex-direction: column !important;
|
1332
|
-
}
|
1333
|
-
|
1334
|
-
.table-fullscreen .card-body {
|
1335
|
-
flex: 1 !important;
|
1336
|
-
overflow: hidden !important;
|
1337
|
-
display: flex !important;
|
1338
|
-
flex-direction: column !important;
|
1339
|
-
}
|
1340
|
-
|
1341
|
-
.table-fullscreen .table-responsive {
|
1342
|
-
flex: 1 !important;
|
1343
|
-
overflow: auto !important;
|
1344
|
-
}
|
1345
|
-
|
1346
|
-
.table-fullscreen .card-header {
|
1347
|
-
flex-shrink: 0 !important;
|
1348
|
-
position: sticky !important;
|
1349
|
-
top: 0 !important;
|
1350
|
-
z-index: 10000 !important;
|
1351
|
-
background: var(--bs-body-bg) !important;
|
1352
|
-
border-bottom: 1px solid var(--bs-border-color) !important;
|
1353
|
-
}
|
1354
|
-
|
1355
|
-
/* Hide pagination in fullscreen mode */
|
1356
|
-
.table-fullscreen .pagination-container {
|
1357
|
-
display: none !important;
|
1358
|
-
}
|
1359
|
-
|
1360
|
-
/* Adjust table header in fullscreen */
|
1361
|
-
.table-fullscreen .dbviewer-table-header {
|
1362
|
-
position: sticky !important;
|
1363
|
-
top: 0 !important;
|
1364
|
-
z-index: 100 !important;
|
1365
|
-
}
|
1366
|
-
|
1367
|
-
/* Ensure body doesn't scroll when table is fullscreen */
|
1368
|
-
body.table-fullscreen-active {
|
1369
|
-
overflow: hidden !important;
|
1370
|
-
}
|
1371
|
-
|
1372
|
-
/* Fullscreen button hover effect */
|
1373
|
-
#fullscreen-toggle:hover {
|
1374
|
-
background-color: var(--bs-secondary-bg) !important;
|
1375
|
-
border-color: var(--bs-secondary-border-subtle) !important;
|
1376
|
-
}
|
1377
|
-
|
1378
|
-
/* Smooth transitions */
|
1379
|
-
#table-section {
|
1380
|
-
transition: all 0.3s ease-in-out;
|
1381
|
-
}
|
1382
|
-
</style>
|
1383
|
-
|
1384
|
-
<% if @timestamp_data.present? %>
|
1385
|
-
<script>
|
1386
|
-
document.addEventListener('DOMContentLoaded', function() {
|
1387
|
-
const timeGrouping = '<%= @time_grouping %>';
|
1388
|
-
const chartData = <%= raw @timestamp_data.to_json %>;
|
1389
|
-
|
1390
|
-
// Reverse the data so it's chronological
|
1391
|
-
const labels = chartData.map(item => item.label).reverse();
|
1392
|
-
const values = chartData.map(item => item.value).reverse();
|
1393
|
-
|
1394
|
-
// Chart colors based on time grouping
|
1395
|
-
let chartColor;
|
1396
|
-
let chartTitle;
|
1397
|
-
|
1398
|
-
switch(timeGrouping) {
|
1399
|
-
case 'hourly':
|
1400
|
-
chartColor = 'rgba(75, 192, 192, 0.7)';
|
1401
|
-
chartTitle = 'Hourly Record Creation';
|
1402
|
-
break;
|
1403
|
-
case 'weekly':
|
1404
|
-
chartColor = 'rgba(153, 102, 255, 0.7)';
|
1405
|
-
chartTitle = 'Weekly Record Creation';
|
1406
|
-
break;
|
1407
|
-
default:
|
1408
|
-
chartColor = 'rgba(54, 162, 235, 0.7)';
|
1409
|
-
chartTitle = 'Daily Record Creation';
|
1410
|
-
}
|
1371
|
+
// Table fullscreen functionality
|
1372
|
+
const fullscreenToggle = document.getElementById('fullscreen-toggle');
|
1373
|
+
const fullscreenIcon = document.getElementById('fullscreen-icon');
|
1374
|
+
const tableSection = document.getElementById('table-section');
|
1411
1375
|
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
1415
|
-
|
1416
|
-
|
1417
|
-
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1427
|
-
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1432
|
-
title: {
|
1433
|
-
display: true,
|
1434
|
-
text: chartTitle,
|
1435
|
-
font: {
|
1436
|
-
size: 16
|
1437
|
-
}
|
1438
|
-
}
|
1439
|
-
},
|
1440
|
-
scales: {
|
1441
|
-
y: {
|
1442
|
-
beginAtZero: true,
|
1443
|
-
title: {
|
1444
|
-
display: true,
|
1445
|
-
text: 'Number of Records'
|
1446
|
-
},
|
1447
|
-
ticks: {
|
1448
|
-
precision: 0
|
1449
|
-
}
|
1450
|
-
},
|
1451
|
-
x: {
|
1452
|
-
title: {
|
1453
|
-
display: true,
|
1454
|
-
text: timeGrouping.charAt(0).toUpperCase() + timeGrouping.slice(1)
|
1455
|
-
}
|
1456
|
-
}
|
1376
|
+
if (fullscreenToggle && tableSection) {
|
1377
|
+
// Key for storing fullscreen state in localStorage
|
1378
|
+
const fullscreenStateKey = 'dbviewer-table-fullscreen-<%= @table_name %>';
|
1379
|
+
|
1380
|
+
// Function to apply fullscreen state
|
1381
|
+
function applyFullscreenState(isFullscreen) {
|
1382
|
+
if (isFullscreen) {
|
1383
|
+
// Enter fullscreen
|
1384
|
+
tableSection.classList.add('table-fullscreen');
|
1385
|
+
document.body.classList.add('table-fullscreen-active');
|
1386
|
+
fullscreenIcon.classList.remove('bi-fullscreen');
|
1387
|
+
fullscreenIcon.classList.add('bi-fullscreen-exit');
|
1388
|
+
fullscreenToggle.setAttribute('title', 'Exit fullscreen');
|
1389
|
+
} else {
|
1390
|
+
// Exit fullscreen
|
1391
|
+
tableSection.classList.remove('table-fullscreen');
|
1392
|
+
document.body.classList.remove('table-fullscreen-active');
|
1393
|
+
fullscreenIcon.classList.remove('bi-fullscreen-exit');
|
1394
|
+
fullscreenIcon.classList.add('bi-fullscreen');
|
1395
|
+
fullscreenToggle.setAttribute('title', 'Toggle fullscreen');
|
1457
1396
|
}
|
1458
1397
|
}
|
1459
|
-
});
|
1460
|
-
|
1461
|
-
// Column sorting enhancement
|
1462
|
-
const sortableColumns = document.querySelectorAll('.sortable-column');
|
1463
|
-
sortableColumns.forEach(column => {
|
1464
|
-
const link = column.querySelector('.column-sort-link');
|
1465
1398
|
|
1466
|
-
//
|
1467
|
-
|
1468
|
-
const
|
1469
|
-
if (
|
1470
|
-
|
1471
|
-
sortIcon.style.opacity = '0.3';
|
1399
|
+
// Restore fullscreen state from localStorage on page load
|
1400
|
+
try {
|
1401
|
+
const savedState = localStorage.getItem(fullscreenStateKey);
|
1402
|
+
if (savedState === 'true') {
|
1403
|
+
applyFullscreenState(true);
|
1472
1404
|
}
|
1473
|
-
})
|
1405
|
+
} catch (e) {
|
1406
|
+
// Handle localStorage not available (private browsing, etc.)
|
1407
|
+
console.warn('Could not restore fullscreen state:', e);
|
1408
|
+
}
|
1474
1409
|
|
1475
|
-
|
1476
|
-
const
|
1477
|
-
|
1478
|
-
|
1479
|
-
|
1410
|
+
fullscreenToggle.addEventListener('click', function() {
|
1411
|
+
const isFullscreen = tableSection.classList.contains('table-fullscreen');
|
1412
|
+
const newState = !isFullscreen;
|
1413
|
+
|
1414
|
+
// Apply the new state
|
1415
|
+
applyFullscreenState(newState);
|
1416
|
+
|
1417
|
+
// Save state to localStorage
|
1418
|
+
try {
|
1419
|
+
localStorage.setItem(fullscreenStateKey, newState.toString());
|
1420
|
+
} catch (e) {
|
1421
|
+
// Handle localStorage not available (private browsing, etc.)
|
1422
|
+
console.warn('Could not save fullscreen state:', e);
|
1480
1423
|
}
|
1481
1424
|
});
|
1482
1425
|
|
1483
|
-
//
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
});
|
1491
|
-
}
|
1492
|
-
});
|
1426
|
+
// Exit fullscreen with Escape key
|
1427
|
+
document.addEventListener('keydown', function(e) {
|
1428
|
+
if (e.key === 'Escape' && tableSection.classList.contains('table-fullscreen')) {
|
1429
|
+
fullscreenToggle.click();
|
1430
|
+
}
|
1431
|
+
});
|
1432
|
+
}
|
1493
1433
|
});
|
1494
1434
|
|
1495
1435
|
// Helper function to create relationship sections
|
@@ -1584,70 +1524,4 @@
|
|
1584
1524
|
|
1585
1525
|
return section;
|
1586
1526
|
}
|
1587
|
-
|
1588
|
-
// Table fullscreen functionality
|
1589
|
-
document.addEventListener('DOMContentLoaded', function() {
|
1590
|
-
const fullscreenToggle = document.getElementById('fullscreen-toggle');
|
1591
|
-
const fullscreenIcon = document.getElementById('fullscreen-icon');
|
1592
|
-
const tableSection = document.getElementById('table-section');
|
1593
|
-
|
1594
|
-
if (fullscreenToggle && tableSection) {
|
1595
|
-
// Key for storing fullscreen state in localStorage
|
1596
|
-
const fullscreenStateKey = 'dbviewer-table-fullscreen-<%= @table_name %>';
|
1597
|
-
|
1598
|
-
// Function to apply fullscreen state
|
1599
|
-
function applyFullscreenState(isFullscreen) {
|
1600
|
-
if (isFullscreen) {
|
1601
|
-
// Enter fullscreen
|
1602
|
-
tableSection.classList.add('table-fullscreen');
|
1603
|
-
document.body.classList.add('table-fullscreen-active');
|
1604
|
-
fullscreenIcon.classList.remove('bi-fullscreen');
|
1605
|
-
fullscreenIcon.classList.add('bi-fullscreen-exit');
|
1606
|
-
fullscreenToggle.setAttribute('title', 'Exit fullscreen');
|
1607
|
-
} else {
|
1608
|
-
// Exit fullscreen
|
1609
|
-
tableSection.classList.remove('table-fullscreen');
|
1610
|
-
document.body.classList.remove('table-fullscreen-active');
|
1611
|
-
fullscreenIcon.classList.remove('bi-fullscreen-exit');
|
1612
|
-
fullscreenIcon.classList.add('bi-fullscreen');
|
1613
|
-
fullscreenToggle.setAttribute('title', 'Toggle fullscreen');
|
1614
|
-
}
|
1615
|
-
}
|
1616
|
-
|
1617
|
-
// Restore fullscreen state from localStorage on page load
|
1618
|
-
try {
|
1619
|
-
const savedState = localStorage.getItem(fullscreenStateKey);
|
1620
|
-
if (savedState === 'true') {
|
1621
|
-
applyFullscreenState(true);
|
1622
|
-
}
|
1623
|
-
} catch (e) {
|
1624
|
-
// Handle localStorage not available (private browsing, etc.)
|
1625
|
-
console.warn('Could not restore fullscreen state:', e);
|
1626
|
-
}
|
1627
|
-
|
1628
|
-
fullscreenToggle.addEventListener('click', function() {
|
1629
|
-
const isFullscreen = tableSection.classList.contains('table-fullscreen');
|
1630
|
-
const newState = !isFullscreen;
|
1631
|
-
|
1632
|
-
// Apply the new state
|
1633
|
-
applyFullscreenState(newState);
|
1634
|
-
|
1635
|
-
// Save state to localStorage
|
1636
|
-
try {
|
1637
|
-
localStorage.setItem(fullscreenStateKey, newState.toString());
|
1638
|
-
} catch (e) {
|
1639
|
-
// Handle localStorage not available (private browsing, etc.)
|
1640
|
-
console.warn('Could not save fullscreen state:', e);
|
1641
|
-
}
|
1642
|
-
});
|
1643
|
-
|
1644
|
-
// Exit fullscreen with Escape key
|
1645
|
-
document.addEventListener('keydown', function(e) {
|
1646
|
-
if (e.key === 'Escape' && tableSection.classList.contains('table-fullscreen')) {
|
1647
|
-
fullscreenToggle.click();
|
1648
|
-
}
|
1649
|
-
});
|
1650
|
-
}
|
1651
|
-
});
|
1652
1527
|
</script>
|
1653
|
-
<% end %>
|