dbviewer 0.6.5 → 0.6.7
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 +12 -36
- data/app/helpers/dbviewer/filter_helper.rb +6 -6
- data/app/helpers/dbviewer/table_rendering_helper.rb +3 -3
- data/app/views/dbviewer/logs/index.html.erb +17 -1
- data/app/views/dbviewer/tables/show.html.erb +62 -25
- data/app/views/layouts/dbviewer/application.html.erb +143 -16
- data/lib/dbviewer/engine.rb +0 -5
- data/lib/dbviewer/version.rb +1 -1
- data/lib/dbviewer.rb +0 -2
- metadata +2 -4
- data/lib/dbviewer/query/collection.rb +0 -39
- data/lib/dbviewer/validator.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b12cc2cd164b8f28545ecdcc490ce5a9351a469c4d702ed87a64d5f617713f82
|
4
|
+
data.tar.gz: 919d3a36d71de8320f2d7001c0a2b31e8b9c9b9ce0ee29f1ed31e40aa3ec4189
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b101e0c8b7e4f64ec7b8b0d8b446a555e860195a9ffa2b99bc34532c35e1c7b8eea87c7072d316265115194796f02ebfea487f9a3d4859036eebd7b2f1d89221
|
7
|
+
data.tar.gz: '096938554cc411491970f725d117df540112779247a210c71ff8b14bd7f418580721c6c91830c2b6083394528f3ebaab08dce8ff111bd421fd0002a5e04b416d'
|
data/README.md
CHANGED
@@ -9,42 +9,18 @@ It's designed for development, debugging, and database analysis, offering a clea
|
|
9
9
|
|
10
10
|
## ✨ Features
|
11
11
|
|
12
|
-
- **Dashboard
|
13
|
-
- **Table Overview
|
14
|
-
- **Detailed Schema Information
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
- **
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
-
|
24
|
-
- Browse table records with customizable pagination (10, 20, 50, or 100 records per page)
|
25
|
-
- Sort data by any column in ascending or descending order
|
26
|
-
- Navigate through large datasets with an intuitive pagination interface
|
27
|
-
- Scrollable table with fixed headers for improved navigation
|
28
|
-
- Single-line cell display with ellipsis for wide content (tooltips on hover)
|
29
|
-
- Export table data to CSV format (configurable via `enable_data_export` option)
|
30
|
-
- **SQL Queries**:
|
31
|
-
- Run custom SELECT queries against your database in a secure, read-only environment
|
32
|
-
- View table structure reference while writing queries
|
33
|
-
- Protection against potentially harmful SQL operations
|
34
|
-
- Query execution statistics and timing
|
35
|
-
- **Multiple Database Connections**:
|
36
|
-
- Connect to multiple databases within your application
|
37
|
-
- Switch between connections on-the-fly to view different database schemas
|
38
|
-
- Add new database connections from the UI without code changes
|
39
|
-
- Test connections to verify they're working properly
|
40
|
-
- **Enhanced UI Features**:
|
41
|
-
- Responsive, Bootstrap-based interface that works on desktop and mobile
|
42
|
-
- Fixed header navigation with quick access to all features
|
43
|
-
- Modern sidebar layout with improved filtering and scrollable table list
|
44
|
-
- Clean tabbed interface for exploring different aspects of table structure
|
45
|
-
- Advanced table filtering with keyboard navigation support
|
46
|
-
- Proper formatting for various data types (dates, JSON, arrays, etc.)
|
47
|
-
- Enhanced data presentation with appropriate styling
|
12
|
+
- **Dashboard**
|
13
|
+
- **Table Overview**
|
14
|
+
- **Detailed Schema Information**
|
15
|
+
- **Entity Relationship Diagram (ERD)**
|
16
|
+
- **Data Browsing**
|
17
|
+
- **SQL Queries**
|
18
|
+
- **Multiple Database Connections**
|
19
|
+
- **Enhanced UI Features**
|
20
|
+
|
21
|
+
## 🧪 Demo Application
|
22
|
+
|
23
|
+
You can explore a live demo of DBViewer at [https://dbviewer-demo.wailantirajoh.tech/](https://dbviewer-demo.wailantirajoh.tech/). This demo showcases all the features of DBViewer on a sample database, allowing you to try out the tool before installing it in your own application.
|
48
24
|
|
49
25
|
## 📸 Screenshots
|
50
26
|
|
@@ -78,30 +78,30 @@ module Dbviewer
|
|
78
78
|
visible_field = form.text_field("column_filters[#{column_name}_display]",
|
79
79
|
disabled: true,
|
80
80
|
value: "",
|
81
|
-
class: "form-control form-control-sm column-filter rounded-0 disabled-filter",
|
81
|
+
class: "form-control form-control-sm column-filter border-end-1 border-start-0 rounded-0 disabled-filter",
|
82
82
|
data: { column: "#{column_name}_display" })
|
83
83
|
|
84
84
|
hidden_field + visible_field
|
85
85
|
elsif column_type && column_type =~ /datetime/
|
86
86
|
form.datetime_local_field("column_filters[#{column_name}]",
|
87
87
|
value: value,
|
88
|
-
class: "form-control form-control-sm column-filter rounded-0",
|
88
|
+
class: "form-control form-control-sm column-filter border-end-1 border-start-0 rounded-0",
|
89
89
|
data: { column: column_name })
|
90
90
|
elsif column_type && column_type =~ /^date$/
|
91
91
|
form.date_field("column_filters[#{column_name}]",
|
92
92
|
value: value,
|
93
|
-
class: "form-control form-control-sm column-filter rounded-0",
|
93
|
+
class: "form-control form-control-sm column-filter border-end-1 border-start-0 rounded-0",
|
94
94
|
data: { column: column_name })
|
95
95
|
elsif column_type && column_type =~ /^time$/
|
96
96
|
form.time_field("column_filters[#{column_name}]",
|
97
97
|
value: value,
|
98
|
-
class: "form-control form-control-sm column-filter rounded-0",
|
98
|
+
class: "form-control form-control-sm column-filter border-end-1 border-start-0 rounded-0",
|
99
99
|
data: { column: column_name })
|
100
100
|
else
|
101
101
|
form.text_field("column_filters[#{column_name}]",
|
102
102
|
value: value,
|
103
103
|
placeholder: "",
|
104
|
-
class: "form-control form-control-sm column-filter rounded-0",
|
104
|
+
class: "form-control form-control-sm column-filter border-end-1 border-start-0 rounded-0",
|
105
105
|
data: { column: column_name })
|
106
106
|
end
|
107
107
|
end
|
@@ -119,7 +119,7 @@ module Dbviewer
|
|
119
119
|
form.select("column_filters[#{column_name}_operator]",
|
120
120
|
options_for_select(operator_options, selected_operator),
|
121
121
|
{ include_blank: false },
|
122
|
-
{ class: "form-select form-select-sm operator-select" })
|
122
|
+
{ class: "form-select form-select-sm operator-select border-end-1 border-start-0 border-bottom-0" })
|
123
123
|
end
|
124
124
|
|
125
125
|
# Render complete filter input group for a column
|
@@ -7,7 +7,7 @@ module Dbviewer
|
|
7
7
|
content_tag(:tr) do
|
8
8
|
# Start with action column header (sticky first column)
|
9
9
|
headers = [
|
10
|
-
content_tag(:th, class: "px-3 py-2 text-center action-column action-column-header", width: "60px", rowspan: 2) do
|
10
|
+
content_tag(:th, class: "px-3 py-2 text-center action-column action-column-header border-bottom-0", width: "60px", rowspan: 2) do
|
11
11
|
content_tag(:span, "Actions")
|
12
12
|
end
|
13
13
|
]
|
@@ -15,7 +15,7 @@ module Dbviewer
|
|
15
15
|
# Add all data columns
|
16
16
|
headers += records.columns.map do |column_name|
|
17
17
|
is_sorted = order_by == column_name
|
18
|
-
content_tag(:th, class: "px-3 py-2 sortable-column #{is_sorted ? 'sorted' : ''}") do
|
18
|
+
content_tag(:th, class: "px-3 py-2 sortable-column border-bottom-0 #{is_sorted ? 'sorted' : ''}") do
|
19
19
|
sortable_column_header(column_name, order_by, order_direction, table_name, current_page, per_page, column_filters)
|
20
20
|
end
|
21
21
|
end
|
@@ -30,7 +30,7 @@ module Dbviewer
|
|
30
30
|
|
31
31
|
content_tag(:tr, class: "column-filters") do
|
32
32
|
filters = records.columns.map do |column_name|
|
33
|
-
content_tag(:th, class: "p-0") do
|
33
|
+
content_tag(:th, class: "p-0 border-bottom-0") do
|
34
34
|
render_column_filter(form, column_name, columns, column_filters)
|
35
35
|
end
|
36
36
|
end
|
@@ -1,6 +1,17 @@
|
|
1
1
|
<% content_for :title, "SQL Query Logs" %>
|
2
2
|
|
3
3
|
<div class="container-fluid">
|
4
|
+
<% unless Rails.env.development? %>
|
5
|
+
<div class="alert alert-warning mb-3">
|
6
|
+
<div class="d-flex align-items-center">
|
7
|
+
<i class="bi bi-exclamation-triangle-fill me-2 fs-5"></i>
|
8
|
+
<div>
|
9
|
+
<strong>Warning:</strong> SQL Query logs are disabled in non-development environments.
|
10
|
+
</div>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
<% end %>
|
14
|
+
|
4
15
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
5
16
|
<h1>
|
6
17
|
<i class="bi bi-journal-code me-2"></i>SQL Query Logs
|
@@ -378,7 +389,12 @@
|
|
378
389
|
<td colspan="6" class="text-center py-5 empty-data-message">
|
379
390
|
<i class="bi bi-database-x fs-2 d-block mb-2"></i>
|
380
391
|
No SQL queries logged yet.
|
381
|
-
|
392
|
+
<% if Rails.env.development? %>
|
393
|
+
<p class="mt-2">Navigate through the application to see queries being logged here.</p>
|
394
|
+
<% else %>
|
395
|
+
<p class="mt-2">SQL Query logs are disabled in non-development environments.</p>
|
396
|
+
<small class="text-muted d-block">To enable logs in this environment, set <code>enable_query_logging = true</code> in your configuration.</small>
|
397
|
+
<% end %>
|
382
398
|
</td>
|
383
399
|
</tr>
|
384
400
|
<% end %>
|
@@ -181,7 +181,7 @@
|
|
181
181
|
left: 0;
|
182
182
|
z-index: 40 !important; /* Even higher z-index to stay on top of everything */
|
183
183
|
background-color: var(--bs-tertiary-bg, #f8f9fa) !important;
|
184
|
-
|
184
|
+
box-shadow: 2px 0 6px rgba(0, 0, 0, 0.04) !important;
|
185
185
|
}
|
186
186
|
|
187
187
|
[data-bs-theme="dark"] .action-column-header {
|
@@ -206,7 +206,7 @@
|
|
206
206
|
|
207
207
|
/* Fix action column for entire table */
|
208
208
|
.action-column {
|
209
|
-
|
209
|
+
box-shadow: 2px 0 6px rgba(0, 0, 0, 0.04);
|
210
210
|
}
|
211
211
|
|
212
212
|
/* Ensure equal padding for all cells */
|
@@ -332,25 +332,27 @@
|
|
332
332
|
|
333
333
|
<% content_for :sidebar_active do %>active<% end %>
|
334
334
|
|
335
|
-
<div class="
|
336
|
-
<div class="d-flex justify-content-between align-items-center">
|
337
|
-
<
|
338
|
-
|
339
|
-
|
340
|
-
<
|
341
|
-
<
|
342
|
-
|
343
|
-
|
344
|
-
<
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
<
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
335
|
+
<div class="mb-4">
|
336
|
+
<div class="d-block d-md-flex justify-content-between align-items-center">
|
337
|
+
<div class="mb-3 mb-md-0">
|
338
|
+
<h1>Table: <%= @table_name %></h1>
|
339
|
+
</div>
|
340
|
+
<div class="d-flex flex-wrap gap-2">
|
341
|
+
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#tableStructureModal">
|
342
|
+
<i class="bi bi-table me-1"></i> Table Structure
|
343
|
+
</button>
|
344
|
+
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#miniErdModal">
|
345
|
+
<i class="bi bi-diagram-3 me-1"></i> View Relationships
|
346
|
+
</button>
|
347
|
+
<% if Dbviewer.configuration.enable_data_export %>
|
348
|
+
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#csvExportModal">
|
349
|
+
<i class="bi bi-file-earmark-spreadsheet me-1"></i> Export CSV
|
350
|
+
</button>
|
351
|
+
<% end %>
|
352
|
+
<%= link_to query_table_path(@table_name), class: "btn btn-primary" do %>
|
353
|
+
<i class="bi bi-code-square me-1"></i> Run SQL Query
|
354
|
+
<% end %>
|
355
|
+
</div>
|
354
356
|
</div>
|
355
357
|
</div>
|
356
358
|
|
@@ -450,7 +452,7 @@
|
|
450
452
|
<%= form.hidden_field :order_direction, value: @order_direction %>
|
451
453
|
<%= form.hidden_field :page, value: 1 %> <!-- Reset to first page on filter -->
|
452
454
|
|
453
|
-
<table class="table table-
|
455
|
+
<table class="table table-striped rounded-none">
|
454
456
|
<thead class="dbviewer-table-header">
|
455
457
|
<%= render_sortable_header_row(@records, @order_by, @order_direction, @table_name, @current_page, @per_page, @column_filters) %>
|
456
458
|
<%= render_column_filters_row(form, @records, @columns, @column_filters) %>
|
@@ -475,7 +477,7 @@
|
|
475
477
|
<div class="modal-body">
|
476
478
|
<!-- Record Data Section -->
|
477
479
|
<div class="table-responsive">
|
478
|
-
<table class="table
|
480
|
+
<table class="table record-detail-table">
|
479
481
|
<thead>
|
480
482
|
<tr>
|
481
483
|
<th width="30%">Column</th>
|
@@ -524,6 +526,42 @@
|
|
524
526
|
</div>
|
525
527
|
|
526
528
|
<style>
|
529
|
+
/* Borderless table styling */
|
530
|
+
.table {
|
531
|
+
border-collapse: separate;
|
532
|
+
border-spacing: 0;
|
533
|
+
}
|
534
|
+
|
535
|
+
.table th,
|
536
|
+
.table td {
|
537
|
+
border: none;
|
538
|
+
border-bottom: 1px solid var(--bs-border-subtle, rgba(0, 0, 0, 0.05));
|
539
|
+
}
|
540
|
+
|
541
|
+
.table thead th {
|
542
|
+
border-bottom: 2px solid var(--bs-border-subtle, rgba(0, 0, 0, 0.08));
|
543
|
+
font-weight: 500;
|
544
|
+
}
|
545
|
+
|
546
|
+
/* Add a subtle hover effect on table rows */
|
547
|
+
.table tbody tr:hover {
|
548
|
+
background-color: var(--bs-tertiary-bg, rgba(0, 0, 0, 0.02));
|
549
|
+
}
|
550
|
+
|
551
|
+
/* Dark mode compatibility */
|
552
|
+
[data-bs-theme="dark"] .table th,
|
553
|
+
[data-bs-theme="dark"] .table td {
|
554
|
+
border-bottom: 1px solid var(--bs-border-subtle, rgba(255, 255, 255, 0.05));
|
555
|
+
}
|
556
|
+
|
557
|
+
[data-bs-theme="dark"] .table thead th {
|
558
|
+
border-bottom: 2px solid var(--bs-border-subtle, rgba(255, 255, 255, 0.08));
|
559
|
+
}
|
560
|
+
|
561
|
+
[data-bs-theme="dark"] .table tbody tr:hover {
|
562
|
+
background-color: var(--bs-tertiary-bg, rgba(255, 255, 255, 0.03));
|
563
|
+
}
|
564
|
+
|
527
565
|
/* Column filter styling */
|
528
566
|
.column-filters td {
|
529
567
|
padding: 0.5rem;
|
@@ -539,8 +577,7 @@
|
|
539
577
|
left: 0;
|
540
578
|
z-index: 30; /* Increased z-index to ensure it stays on top */
|
541
579
|
background-color: var(--bs-body-bg, #fff); /* Use body background color */
|
542
|
-
box-shadow: 2px 0
|
543
|
-
border-right: 1px solid var(--bs-border-color);
|
580
|
+
box-shadow: 2px 0 6px rgba(0, 0, 0, 0.04);
|
544
581
|
}
|
545
582
|
|
546
583
|
.copy-factory-btn {
|
@@ -2,6 +2,7 @@
|
|
2
2
|
<html data-bs-theme="light">
|
3
3
|
<head>
|
4
4
|
<title><%= content_for?(:title) ? yield(:title) + " - DB Viewer" : "DB Viewer" %></title>
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
5
6
|
<%= csrf_meta_tags %>
|
6
7
|
<%= csp_meta_tag %>
|
7
8
|
|
@@ -607,7 +608,7 @@
|
|
607
608
|
}
|
608
609
|
|
609
610
|
[data-bs-theme="dark"] .navbar.bg-primary {
|
610
|
-
background: linear-gradient(to right, #
|
611
|
+
background: linear-gradient(to right, #161819, #1a1d20) !important;
|
611
612
|
border-bottom: 1px solid rgba(73, 80, 87, 0.5);
|
612
613
|
}
|
613
614
|
|
@@ -657,12 +658,13 @@
|
|
657
658
|
}
|
658
659
|
|
659
660
|
[data-bs-theme="dark"] .list-group-item {
|
660
|
-
background-color:
|
661
|
+
background-color: #1a1d20;
|
661
662
|
border-color: rgba(73, 80, 87, 0.3);
|
662
663
|
color: #e9ecef;
|
663
664
|
}
|
664
665
|
|
665
|
-
[data-bs-theme="dark"] .list-group-item.active
|
666
|
+
[data-bs-theme="dark"] .list-group-item.active,
|
667
|
+
[data-bs-theme="dark"] .list-group-item.active:hover {
|
666
668
|
background-color: rgba(13, 110, 253, 0.15);
|
667
669
|
border-color: rgba(73, 80, 87, 0.3);
|
668
670
|
color: #6ea8fe;
|
@@ -670,7 +672,7 @@
|
|
670
672
|
|
671
673
|
[data-bs-theme="dark"] .list-group-item-action:hover,
|
672
674
|
[data-bs-theme="dark"] .list-group-item-action:focus {
|
673
|
-
background-color: rgba(255, 255, 255, 0.
|
675
|
+
background-color: rgba(255, 255, 255, 0.05);
|
674
676
|
color: #f8f9fa;
|
675
677
|
}
|
676
678
|
|
@@ -881,6 +883,14 @@
|
|
881
883
|
background: linear-gradient(135deg, #0d6efd, #0a58ca);
|
882
884
|
color: #ffffff;
|
883
885
|
}
|
886
|
+
|
887
|
+
[data-bs-theme="light"] .offcanvas .nav-link {
|
888
|
+
color:rgb(78, 86, 95);
|
889
|
+
}
|
890
|
+
|
891
|
+
[data-bs-theme="dark"] .offcanvas .nav-link {
|
892
|
+
color:rgb(191, 191, 191);
|
893
|
+
}
|
884
894
|
|
885
895
|
/* Enhanced table styling - Grafana-like compact design */
|
886
896
|
.table {
|
@@ -1057,12 +1067,6 @@
|
|
1057
1067
|
color: #ff6b6b !important;
|
1058
1068
|
}
|
1059
1069
|
|
1060
|
-
/* Styling for form controls in SQL logs page */
|
1061
|
-
[data-bs-theme="dark"] .list-group-item {
|
1062
|
-
background-color: #212529;
|
1063
|
-
border-color: #495057;
|
1064
|
-
}
|
1065
|
-
|
1066
1070
|
/* Code block styling for SQL logs */
|
1067
1071
|
[data-bs-theme="light"] .code-block {
|
1068
1072
|
background-color: #f8f9fa;
|
@@ -1393,11 +1397,15 @@
|
|
1393
1397
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary dbviewer-navbar fixed-top">
|
1394
1398
|
<div class="container-fluid">
|
1395
1399
|
<a class="navbar-brand" href="<%= dbviewer.root_path %>"><i class="bi bi-database-fill me-1"></i>DB Viewer</a>
|
1396
|
-
<
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1400
|
-
|
1400
|
+
<div class="d-flex align-items-center">
|
1401
|
+
<button class="navbar-toggler border-0 px-2 d-lg-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarOffcanvas"
|
1402
|
+
aria-controls="navbarOffcanvas" aria-expanded="false" aria-label="Toggle navigation">
|
1403
|
+
<span class="navbar-toggler-icon"></span>
|
1404
|
+
</button>
|
1405
|
+
</div>
|
1406
|
+
|
1407
|
+
<!-- Visible navigation items on larger screens -->
|
1408
|
+
<div class="collapse navbar-collapse d-none d-lg-flex" id="navbarNav">
|
1401
1409
|
<ul class="navbar-nav">
|
1402
1410
|
<li class="nav-item">
|
1403
1411
|
<%= link_to raw('<i class="bi bi-table"></i> Tables'), dbviewer.tables_path, class: "nav-link #{tables_nav_class}" %>
|
@@ -1446,6 +1454,56 @@
|
|
1446
1454
|
</ul>
|
1447
1455
|
</div>
|
1448
1456
|
</div>
|
1457
|
+
|
1458
|
+
<!-- Offcanvas sidebar for mobile/tablet view that slides from right -->
|
1459
|
+
<div class="offcanvas offcanvas-end d-lg-none" tabindex="-1" id="navbarOffcanvas" aria-labelledby="offcanvasNavbarLabel">
|
1460
|
+
<div class="offcanvas-header bg-light-subtle">
|
1461
|
+
<h5 class="offcanvas-title" id="offcanvasNavbarLabel"><i class="bi bi-database-fill me-2 text-primary"></i>DB Viewer</h5>
|
1462
|
+
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
1463
|
+
</div>
|
1464
|
+
<div class="offcanvas-body bg-body-tertiary">
|
1465
|
+
<ul class="navbar-nav mb-2 mb-lg-0 fw-medium">
|
1466
|
+
<li class="nav-item py-1">
|
1467
|
+
<%= link_to raw('<i class="bi bi-table me-2 text-primary"></i> Tables'), dbviewer.tables_path, class: "nav-link rounded #{tables_nav_class}" %>
|
1468
|
+
</li>
|
1469
|
+
<li class="nav-item py-1">
|
1470
|
+
<%= link_to raw('<i class="bi bi-diagram-3 me-2 text-primary"></i> ERD'), dbviewer.entity_relationship_diagrams_path, class: "nav-link rounded #{erd_nav_class}" %>
|
1471
|
+
</li>
|
1472
|
+
<% if Dbviewer.configuration.enable_query_logging %>
|
1473
|
+
<li class="nav-item py-1">
|
1474
|
+
<%= link_to raw('<i class="bi bi-journal-code me-2 text-primary"></i> SQL Logs'), dbviewer.logs_path, class: "nav-link rounded #{logs_nav_class}" %>
|
1475
|
+
</li>
|
1476
|
+
<% end %>
|
1477
|
+
<li class="nav-item dropdown py-1">
|
1478
|
+
<a class="nav-link dropdown-toggle d-flex align-items-center rounded" href="#" id="offcanvasDbDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
1479
|
+
<i class="bi bi-database me-2 text-primary"></i> <%= (current_conn = available_connections.find { |c| c[:current] }) ? current_conn[:name] : "Database" %>
|
1480
|
+
</a>
|
1481
|
+
<ul class="dropdown-menu shadow-sm mt-2" aria-labelledby="offcanvasDbDropdown">
|
1482
|
+
<% available_connections.each do |connection| %>
|
1483
|
+
<li>
|
1484
|
+
<%= button_to connection_path(connection[:key]), method: :post, class: "dropdown-item border-0 w-100 text-start #{'active' if connection[:current]}" do %>
|
1485
|
+
<% if connection[:current] %>
|
1486
|
+
<i class="bi bi-check2-circle me-2 text-primary"></i>
|
1487
|
+
<% else %>
|
1488
|
+
<i class="bi bi-circle me-2"></i>
|
1489
|
+
<% end %>
|
1490
|
+
<%= connection[:name] %>
|
1491
|
+
<% end %>
|
1492
|
+
</li>
|
1493
|
+
<% end %>
|
1494
|
+
<li><hr class="dropdown-divider"></li>
|
1495
|
+
<li><%= link_to "<i class='bi bi-gear me-2'></i> Manage Connections".html_safe, connections_path, class: "dropdown-item" %></li>
|
1496
|
+
</ul>
|
1497
|
+
</li>
|
1498
|
+
<li class="mt-4 pt-2 border-top">
|
1499
|
+
<div class="d-flex align-items-center py-2">
|
1500
|
+
<i class="bi bi-tools me-2 text-secondary"></i>
|
1501
|
+
<span class="text-secondary"><%= Rails.env %> environment</span>
|
1502
|
+
</div>
|
1503
|
+
</li>
|
1504
|
+
</ul>
|
1505
|
+
</div>
|
1506
|
+
</div>
|
1449
1507
|
</nav>
|
1450
1508
|
<!-- Spacer to prevent content from hiding under fixed navbar -->
|
1451
1509
|
<div class="dbviewer-navbar-spacer"></div>
|
@@ -1472,7 +1530,7 @@
|
|
1472
1530
|
<% end %>
|
1473
1531
|
|
1474
1532
|
<div class="d-flex d-lg-none align-items-center mb-3">
|
1475
|
-
<button class="btn btn-sm
|
1533
|
+
<button class="btn btn-sm dbviewer-sidebar-toggle" type="button">
|
1476
1534
|
<i class="bi bi-list me-1"></i> Tables
|
1477
1535
|
</button>
|
1478
1536
|
</div>
|
@@ -1593,6 +1651,75 @@
|
|
1593
1651
|
}
|
1594
1652
|
}, 250);
|
1595
1653
|
});
|
1654
|
+
|
1655
|
+
// Offcanvas enhancement for theme synchronization
|
1656
|
+
const offcanvasElement = document.getElementById('navbarOffcanvas');
|
1657
|
+
if (offcanvasElement) {
|
1658
|
+
// Get all theme toggles
|
1659
|
+
const allThemeToggles = document.querySelectorAll('.theme-toggle');
|
1660
|
+
|
1661
|
+
// Handle theme change from any toggle button
|
1662
|
+
allThemeToggles.forEach(toggleBtn => {
|
1663
|
+
toggleBtn.addEventListener('click', function() {
|
1664
|
+
const currentTheme = document.documentElement.getAttribute('data-bs-theme') || 'light';
|
1665
|
+
|
1666
|
+
// Update all theme toggle buttons to maintain consistency
|
1667
|
+
allThemeToggles.forEach(btn => {
|
1668
|
+
// Update icon in all theme toggle buttons
|
1669
|
+
if (currentTheme === 'dark') {
|
1670
|
+
btn.querySelector('span').innerHTML = '<i class="bi bi-sun-fill"></i>';
|
1671
|
+
btn.setAttribute('aria-label', 'Switch to light mode');
|
1672
|
+
} else {
|
1673
|
+
btn.querySelector('span').innerHTML = '<i class="bi bi-moon-fill"></i>';
|
1674
|
+
btn.setAttribute('aria-label', 'Switch to dark mode');
|
1675
|
+
}
|
1676
|
+
});
|
1677
|
+
});
|
1678
|
+
});
|
1679
|
+
|
1680
|
+
// Function to sync offcanvas colors with current theme
|
1681
|
+
function syncOffcanvasWithTheme() {
|
1682
|
+
const currentTheme = document.documentElement.getAttribute('data-bs-theme') || 'light';
|
1683
|
+
if (currentTheme === 'dark') {
|
1684
|
+
offcanvasElement.querySelector('.offcanvas-header').classList.remove('bg-light-subtle');
|
1685
|
+
offcanvasElement.querySelector('.offcanvas-header').classList.add('bg-dark-subtle');
|
1686
|
+
} else {
|
1687
|
+
offcanvasElement.querySelector('.offcanvas-header').classList.remove('bg-dark-subtle');
|
1688
|
+
offcanvasElement.querySelector('.offcanvas-header').classList.add('bg-light-subtle');
|
1689
|
+
}
|
1690
|
+
}
|
1691
|
+
|
1692
|
+
// Sync on page load
|
1693
|
+
document.addEventListener('DOMContentLoaded', syncOffcanvasWithTheme);
|
1694
|
+
|
1695
|
+
// Listen for theme changes
|
1696
|
+
document.addEventListener('dbviewerThemeChanged', syncOffcanvasWithTheme);
|
1697
|
+
|
1698
|
+
// Handle link click in offcanvas (auto-close on mobile)
|
1699
|
+
const offcanvasLinks = offcanvasElement.querySelectorAll('.nav-link:not(.dropdown-toggle)');
|
1700
|
+
offcanvasLinks.forEach(link => {
|
1701
|
+
link.addEventListener('click', function() {
|
1702
|
+
if (window.innerWidth < 992) {
|
1703
|
+
bootstrap.Offcanvas.getInstance(offcanvasElement).hide();
|
1704
|
+
}
|
1705
|
+
});
|
1706
|
+
});
|
1707
|
+
|
1708
|
+
// Fix offcanvas backdrop on desktop
|
1709
|
+
window.addEventListener('resize', function() {
|
1710
|
+
if (window.innerWidth >= 992) {
|
1711
|
+
const offcanvasInstance = bootstrap.Offcanvas.getInstance(offcanvasElement);
|
1712
|
+
if (offcanvasInstance) {
|
1713
|
+
offcanvasInstance.hide();
|
1714
|
+
}
|
1715
|
+
// Also remove any backdrop
|
1716
|
+
const backdrop = document.querySelector('.offcanvas-backdrop');
|
1717
|
+
if (backdrop) {
|
1718
|
+
backdrop.remove();
|
1719
|
+
}
|
1720
|
+
}
|
1721
|
+
});
|
1722
|
+
}
|
1596
1723
|
});
|
1597
1724
|
</script>
|
1598
1725
|
</body>
|
data/lib/dbviewer/engine.rb
CHANGED
@@ -2,11 +2,6 @@ module Dbviewer
|
|
2
2
|
class Engine < ::Rails::Engine
|
3
3
|
isolate_namespace Dbviewer
|
4
4
|
|
5
|
-
# Autoload lib directory
|
6
|
-
lib_path = File.expand_path("..", __dir__)
|
7
|
-
config.autoload_paths << lib_path
|
8
|
-
config.eager_load_paths << lib_path
|
9
|
-
|
10
5
|
# Register generators
|
11
6
|
config.app_generators do |g|
|
12
7
|
g.templates.unshift File.expand_path("../../generators/dbviewer/templates", __dir__)
|
data/lib/dbviewer/version.rb
CHANGED
data/lib/dbviewer.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require "dbviewer/version"
|
2
2
|
require "dbviewer/configuration"
|
3
3
|
require "dbviewer/engine"
|
4
|
-
require "dbviewer/validator"
|
5
4
|
require "dbviewer/validator/sql"
|
6
5
|
|
7
6
|
require "dbviewer/storage/base"
|
@@ -10,7 +9,6 @@ require "dbviewer/storage/file_storage"
|
|
10
9
|
|
11
10
|
require "dbviewer/query/executor"
|
12
11
|
require "dbviewer/query/analyzer"
|
13
|
-
require "dbviewer/query/collection"
|
14
12
|
require "dbviewer/query/logger"
|
15
13
|
require "dbviewer/query/notification_subscriber"
|
16
14
|
require "dbviewer/query/parser"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dbviewer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wailan Tirajoh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-06-
|
11
|
+
date: 2025-06-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -109,7 +109,6 @@ files:
|
|
109
109
|
- lib/dbviewer/datatable/query_params.rb
|
110
110
|
- lib/dbviewer/engine.rb
|
111
111
|
- lib/dbviewer/query/analyzer.rb
|
112
|
-
- lib/dbviewer/query/collection.rb
|
113
112
|
- lib/dbviewer/query/executor.rb
|
114
113
|
- lib/dbviewer/query/logger.rb
|
115
114
|
- lib/dbviewer/query/notification_subscriber.rb
|
@@ -117,7 +116,6 @@ files:
|
|
117
116
|
- lib/dbviewer/storage/base.rb
|
118
117
|
- lib/dbviewer/storage/file_storage.rb
|
119
118
|
- lib/dbviewer/storage/in_memory_storage.rb
|
120
|
-
- lib/dbviewer/validator.rb
|
121
119
|
- lib/dbviewer/validator/sql.rb
|
122
120
|
- lib/dbviewer/version.rb
|
123
121
|
- lib/generators/dbviewer/install_generator.rb
|
@@ -1,39 +0,0 @@
|
|
1
|
-
module Dbviewer
|
2
|
-
module Query
|
3
|
-
# Collection handles the storage and retrieval of SQL queries
|
4
|
-
# This class is maintained for backward compatibility
|
5
|
-
# New code should use InMemoryStorage or FileStorage directly
|
6
|
-
class Collection < ::Dbviewer::Storage::InMemoryStorage
|
7
|
-
# Maximum number of queries to keep in memory
|
8
|
-
MAX_QUERIES = 1000
|
9
|
-
|
10
|
-
# Get recent queries, optionally filtered
|
11
|
-
def filter(limit: 100, table_filter: nil, request_id: nil, min_duration: nil)
|
12
|
-
result = all
|
13
|
-
|
14
|
-
# Apply filters if provided
|
15
|
-
result = filter_by_table(result, table_filter) if table_filter.present?
|
16
|
-
result = filter_by_request_id(result, request_id) if request_id.present?
|
17
|
-
result = filter_by_duration(result, min_duration) if min_duration.present?
|
18
|
-
|
19
|
-
# Return most recent queries first, limited to requested amount
|
20
|
-
result.reverse.first(limit)
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def filter_by_table(queries, table_filter)
|
26
|
-
queries.select { |q| q[:sql].downcase.include?(table_filter.downcase) }
|
27
|
-
end
|
28
|
-
|
29
|
-
def filter_by_request_id(queries, request_id)
|
30
|
-
queries.select { |q| q[:request_id].to_s.include?(request_id) }
|
31
|
-
end
|
32
|
-
|
33
|
-
def filter_by_duration(queries, min_duration)
|
34
|
-
min_ms = min_duration.to_f
|
35
|
-
queries.select { |q| q[:duration_ms] >= min_ms }
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
data/lib/dbviewer/validator.rb
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Dbviewer
|
4
|
-
# Validator namespace contains validation classes for different types of content
|
5
|
-
# such as SQL queries, configuration parameters, etc.
|
6
|
-
module Validator
|
7
|
-
# This module serves as a namespace for various validator classes
|
8
|
-
end
|
9
|
-
end
|