dbviewer 0.3.4 → 0.3.6
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 +66 -19
- data/app/controllers/dbviewer/application_controller.rb +8 -4
- data/app/controllers/dbviewer/logs_controller.rb +8 -0
- data/app/controllers/dbviewer/tables_controller.rb +6 -0
- data/app/views/dbviewer/home/index.html.erb +40 -31
- data/app/views/dbviewer/tables/show.html.erb +40 -36
- data/app/views/layouts/dbviewer/application.html.erb +16 -3
- data/lib/dbviewer/configuration.rb +6 -1
- data/lib/dbviewer/logger.rb +2 -0
- data/lib/dbviewer/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 151a189372c94a737108969cd768ab9dbc4e16a2abc608f07026d1a6b7ee65a5
|
4
|
+
data.tar.gz: a5fa3a4f4aa8e1acd606f60c2264412a261beec6b6889675ef941d05d1e593cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8345197920cf8c6a006d3faecd9a0b9f2a6ef4c972226707203ba43b121e05c3a02195eec4d38a699231ae488741802a9dff5c7317ddc576f4e27381ad897e7
|
7
|
+
data.tar.gz: b9a8ff1804963de25e85f9a288c198ee4aaa23cf25253ff99f0ce84f3e470e2e35a47c304ff24b2cbd962d9c913fb69b08f11d6bbd2d213af52113ce4d048e18
|
data/README.md
CHANGED
@@ -24,6 +24,7 @@ It's designed for development, debugging, and database analysis, offering a clea
|
|
24
24
|
- Navigate through large datasets with an intuitive pagination interface
|
25
25
|
- Scrollable table with fixed headers for improved navigation
|
26
26
|
- Single-line cell display with ellipsis for wide content (tooltips on hover)
|
27
|
+
- Export table data to CSV format (configurable via `enable_data_export` option)
|
27
28
|
- **SQL Queries**:
|
28
29
|
- Run custom SELECT queries against your database in a secure, read-only environment
|
29
30
|
- View table structure reference while writing queries
|
@@ -87,13 +88,24 @@ Rails.application.routes.draw do
|
|
87
88
|
# Your application routes...
|
88
89
|
|
89
90
|
# Mount the DBViewer engine
|
90
|
-
|
91
|
-
|
92
|
-
|
91
|
+
mount Dbviewer::Engine, at: "/dbviewer"
|
92
|
+
# The engine can be mounted in any environment when using Basic Authentication
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
Configure Basic Authentication in an initializer to secure access (strongly recommended):
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
# config/initializers/dbviewer.rb
|
100
|
+
Dbviewer.configure do |config|
|
101
|
+
config.admin_credentials = {
|
102
|
+
username: "your_username",
|
103
|
+
password: "your_secure_password"
|
104
|
+
}
|
93
105
|
end
|
94
106
|
```
|
95
107
|
|
96
|
-
Then, visit `/dbviewer` in your browser to access the database viewer.
|
108
|
+
Then, visit `/dbviewer` in your browser to access the database viewer. You'll be prompted for your username and password.
|
97
109
|
|
98
110
|
### Rails API-only Applications
|
99
111
|
|
@@ -168,9 +180,13 @@ Dbviewer.configure do |config|
|
|
168
180
|
config.query_timeout = 30 # SQL query timeout in seconds
|
169
181
|
|
170
182
|
# Query logging options
|
183
|
+
config.enable_query_logging = true # Enable or disable query logging completely (default: true)
|
171
184
|
config.query_logging_mode = :memory # Storage mode for SQL queries (:memory or :file)
|
172
185
|
config.query_log_path = "log/dbviewer.log" # Path for query log file when in :file mode
|
173
186
|
config.max_memory_queries = 1000 # Maximum number of queries to store in memory
|
187
|
+
|
188
|
+
# Authentication options
|
189
|
+
config.admin_credentials = { username: "admin", password: "your_secure_password" } # Basic HTTP auth credentials
|
174
190
|
end
|
175
191
|
```
|
176
192
|
|
@@ -180,6 +196,14 @@ The configuration is accessed through `Dbviewer.configuration` throughout the co
|
|
180
196
|
|
181
197
|
DBViewer includes a powerful SQL query logging system that captures and analyzes database queries. You can access this log through the `/dbviewer/logs` endpoint. The logging system offers two storage backends:
|
182
198
|
|
199
|
+
### Disabling Query Logging
|
200
|
+
|
201
|
+
You can completely disable query logging if you don't need this feature:
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
config.enable_query_logging = false # Disable query logging completely
|
205
|
+
```
|
206
|
+
|
183
207
|
### In-Memory Storage (Default)
|
184
208
|
|
185
209
|
By default, queries are stored in memory. This provides fast access but queries are lost when the application restarts:
|
@@ -209,37 +233,60 @@ DBViewer includes several security features to protect your database:
|
|
209
233
|
- **Query Limits**: Automatic LIMIT clause added to prevent excessive data retrieval
|
210
234
|
- **Pattern Detection**: Detection of SQL injection patterns and suspicious constructs
|
211
235
|
- **Error Handling**: Informative error messages without exposing sensitive information
|
236
|
+
- **HTTP Basic Authentication**: Protect access with username and password authentication
|
212
237
|
|
213
|
-
|
238
|
+
### Basic Authentication
|
214
239
|
|
215
|
-
|
240
|
+
You can enable HTTP Basic Authentication to secure access to DBViewer:
|
216
241
|
|
217
|
-
|
242
|
+
```ruby
|
243
|
+
Dbviewer.configure do |config|
|
244
|
+
config.admin_credentials = {
|
245
|
+
username: "your_username",
|
246
|
+
password: "your_secure_password"
|
247
|
+
}
|
248
|
+
end
|
249
|
+
```
|
218
250
|
|
219
|
-
|
220
|
-
|
221
|
-
|
251
|
+
When credentials are provided, all DBViewer routes will be protected by HTTP Basic Authentication.
|
252
|
+
Without valid credentials, users will be prompted for a username and password before they can access any DBViewer page.
|
253
|
+
|
254
|
+
## 🌱 Production Access
|
222
255
|
|
223
|
-
|
256
|
+
With the addition of Basic Authentication, DBViewer can now be used in any environment including production. We recommend the following for production deployments:
|
257
|
+
|
258
|
+
1. **Always** enable HTTP Basic Authentication with strong credentials:
|
224
259
|
|
225
260
|
```ruby
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
mount Dbviewer::Engine, at: "/dbviewer"
|
261
|
+
Dbviewer.configure do |config|
|
262
|
+
config.admin_credentials = {
|
263
|
+
username: "unique_username",
|
264
|
+
password: SecureRandom.hex(16) # Generate a strong random password
|
265
|
+
}
|
232
266
|
end
|
233
267
|
```
|
234
268
|
|
235
|
-
|
269
|
+
2. Mount the engine in your routes file:
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
# In any environment, with Basic Auth protection
|
273
|
+
mount Dbviewer::Engine, at: "/dbviewer"
|
274
|
+
```
|
275
|
+
|
276
|
+
3. Access the tool through your regular application URL:
|
236
277
|
```
|
237
278
|
https://yourdomain.com/dbviewer?override_env_check=your_secure_random_key
|
238
279
|
```
|
239
280
|
|
240
281
|
## 📝 Security Note
|
241
282
|
|
242
|
-
⚠️ **Warning**: This engine
|
283
|
+
⚠️ **Warning**: This engine provides direct access to your database contents, which contains sensitive information. Always protect it with HTTP Basic Authentication by configuring strong credentials as shown above.
|
284
|
+
|
285
|
+
When used in production, ensure:
|
286
|
+
|
287
|
+
- You use long, randomly generated passwords (e.g., with `SecureRandom.hex(16)`)
|
288
|
+
- You access DBViewer over HTTPS connections only
|
289
|
+
- Access is limited to trusted administrators only
|
243
290
|
|
244
291
|
## 🤌🏻 Contributing
|
245
292
|
|
@@ -3,14 +3,18 @@ module Dbviewer
|
|
3
3
|
include Dbviewer::DatabaseOperations
|
4
4
|
include Dbviewer::ErrorHandling
|
5
5
|
|
6
|
-
before_action :
|
6
|
+
before_action :authenticate_with_basic_auth
|
7
7
|
before_action :set_tables
|
8
8
|
|
9
9
|
private
|
10
10
|
|
11
|
-
def
|
12
|
-
unless
|
13
|
-
|
11
|
+
def authenticate_with_basic_auth
|
12
|
+
return unless Dbviewer.configuration.admin_credentials.present?
|
13
|
+
|
14
|
+
credentials = Dbviewer.configuration.admin_credentials
|
15
|
+
authenticate_or_request_with_http_basic("DBViewer Authentication") do |username, password|
|
16
|
+
ActiveSupport::SecurityUtils.secure_compare(username, credentials[:username]) &
|
17
|
+
ActiveSupport::SecurityUtils.secure_compare(password, credentials[:password])
|
14
18
|
end
|
15
19
|
end
|
16
20
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Dbviewer
|
2
2
|
class LogsController < ApplicationController
|
3
3
|
before_action :set_filters, only: [ :index ]
|
4
|
+
before_action :check_logging_enabled
|
4
5
|
|
5
6
|
def index
|
6
7
|
@queries = dbviewer_logger.recent_queries(
|
@@ -28,6 +29,13 @@ module Dbviewer
|
|
28
29
|
|
29
30
|
private
|
30
31
|
|
32
|
+
def check_logging_enabled
|
33
|
+
unless Dbviewer.configuration.enable_query_logging
|
34
|
+
flash[:warning] = "Query logging is disabled. Enable it in the configuration to use this feature."
|
35
|
+
redirect_to root_path
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
31
39
|
def set_filters
|
32
40
|
@table_filter = params[:table_filter]
|
33
41
|
@request_id = params[:request_id]
|
@@ -62,6 +62,12 @@ module Dbviewer
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def export_csv
|
65
|
+
unless Dbviewer.configuration.enable_data_export
|
66
|
+
flash[:alert] = "Data export is disabled in the configuration"
|
67
|
+
redirect_to table_path(params[:id])
|
68
|
+
return
|
69
|
+
end
|
70
|
+
|
65
71
|
table_name = params[:id]
|
66
72
|
limit = (params[:limit] || 10000).to_i
|
67
73
|
include_headers = params[:include_headers] != "0"
|
@@ -94,46 +94,55 @@
|
|
94
94
|
<div class="card shadow-sm">
|
95
95
|
<div class="card-header d-flex justify-content-between align-items-center">
|
96
96
|
<h5 class="card-title mb-0">Recent SQL Queries</h5>
|
97
|
-
|
97
|
+
<% if Dbviewer.configuration.enable_query_logging %>
|
98
|
+
<a href="<%= dbviewer.logs_path %>" class="btn btn-sm btn-primary">View All Logs</a>
|
99
|
+
<% end %>
|
98
100
|
</div>
|
99
101
|
<div class="card-body p-0">
|
100
102
|
<% begin %>
|
101
103
|
<% require_dependency "dbviewer/logger" %>
|
102
|
-
<%
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
<
|
104
|
+
<% if Dbviewer.configuration.enable_query_logging %>
|
105
|
+
<% queries = Dbviewer::Logger.instance.recent_queries(limit: 10) %>
|
106
|
+
|
107
|
+
<% if queries.any? %>
|
108
|
+
<div class="table-responsive">
|
109
|
+
<table class="table table-sm table-hover mb-0">
|
107
110
|
|
108
|
-
|
109
|
-
<tr>
|
110
|
-
<th>Query</th>
|
111
|
-
<th class="text-end" style="width: 120px">Duration</th>
|
112
|
-
<th class="text-end" style="width: 180px">Time</th>
|
113
|
-
</tr>
|
114
|
-
</thead>
|
115
|
-
<tbody>
|
116
|
-
<% queries.each do |query| %>
|
111
|
+
<thead>
|
117
112
|
<tr>
|
118
|
-
<
|
119
|
-
|
120
|
-
</
|
121
|
-
<td class="text-end">
|
122
|
-
<span class="<%= query[:duration_ms] > 100 ? 'query-duration-slow' : 'query-duration' %>">
|
123
|
-
<%= query[:duration_ms] %> ms
|
124
|
-
</span>
|
125
|
-
</td>
|
126
|
-
<td class="text-end query-timestamp">
|
127
|
-
<small><%= query[:timestamp].strftime("%H:%M:%S") %></small>
|
128
|
-
</td>
|
113
|
+
<th>Query</th>
|
114
|
+
<th class="text-end" style="width: 120px">Duration</th>
|
115
|
+
<th class="text-end" style="width: 180px">Time</th>
|
129
116
|
</tr>
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
117
|
+
</thead>
|
118
|
+
<tbody>
|
119
|
+
<% queries.each do |query| %>
|
120
|
+
<tr>
|
121
|
+
<td class="text-truncate" style="max-width: 500px;">
|
122
|
+
<code class="sql-query-code"><%= query[:sql] %></code>
|
123
|
+
</td>
|
124
|
+
<td class="text-end">
|
125
|
+
<span class="<%= query[:duration_ms] > 100 ? 'query-duration-slow' : 'query-duration' %>">
|
126
|
+
<%= query[:duration_ms] %> ms
|
127
|
+
</span>
|
128
|
+
</td>
|
129
|
+
<td class="text-end query-timestamp">
|
130
|
+
<small><%= query[:timestamp].strftime("%H:%M:%S") %></small>
|
131
|
+
</td>
|
132
|
+
</tr>
|
133
|
+
<% end %>
|
134
|
+
</tbody>
|
135
|
+
</table>
|
136
|
+
</div>
|
137
|
+
<% else %>
|
138
|
+
<div class="text-center my-4 empty-data-message">
|
139
|
+
<p>No queries recorded yet</p>
|
140
|
+
</div>
|
141
|
+
<% end %>
|
134
142
|
<% else %>
|
135
143
|
<div class="text-center my-4 empty-data-message">
|
136
|
-
<p>
|
144
|
+
<p>Query logging is disabled</p>
|
145
|
+
<small class="text-muted">Enable it in the configuration to see SQL queries here</small>
|
137
146
|
</div>
|
138
147
|
<% end %>
|
139
148
|
<% rescue => e %>
|
@@ -11,53 +11,57 @@
|
|
11
11
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
12
12
|
<h1>Table: <%= @table_name %></h1>
|
13
13
|
<div class="d-flex gap-2">
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
<% if Dbviewer.configuration.enable_data_export %>
|
15
|
+
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#csvExportModal">
|
16
|
+
<i class="bi bi-file-earmark-spreadsheet me-1"></i> Export CSV
|
17
|
+
</button>
|
18
|
+
<% end %>
|
17
19
|
<%= link_to query_table_path(@table_name), class: "btn btn-primary" do %>
|
18
20
|
<i class="bi bi-code-square me-1"></i> Run SQL Query
|
19
21
|
<% end %>
|
20
22
|
</div>
|
21
23
|
</div>
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
-
<div class="modal-
|
26
|
-
<div class="modal-
|
27
|
-
<div class="modal-
|
28
|
-
<
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
<
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
<
|
42
|
-
|
25
|
+
<% if Dbviewer.configuration.enable_data_export %>
|
26
|
+
<!-- CSV Export Modal -->
|
27
|
+
<div class="modal fade" id="csvExportModal" tabindex="-1" aria-labelledby="csvExportModalLabel" aria-hidden="true">
|
28
|
+
<div class="modal-dialog">
|
29
|
+
<div class="modal-content">
|
30
|
+
<div class="modal-header">
|
31
|
+
<h5 class="modal-title" id="csvExportModalLabel">Export <strong><%= @table_name %></strong> to CSV</h5>
|
32
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
33
|
+
</div>
|
34
|
+
<div class="modal-body">
|
35
|
+
<%= form_with url: export_csv_table_path(@table_name), method: :get, id: "csvExportForm" do |form| %>
|
36
|
+
<div class="mb-3">
|
37
|
+
<label for="limit" class="form-label">Maximum number of records</label>
|
38
|
+
<input type="number" class="form-control" id="limit" name="limit" value="10000" min="1" max="100000">
|
39
|
+
<div class="form-text">Limit the number of records to export. Large exports may take some time.</div>
|
40
|
+
</div>
|
41
|
+
|
42
|
+
<% if @total_count > 10000 %>
|
43
|
+
<div class="alert alert-warning">
|
44
|
+
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
45
|
+
This table has <%= number_with_delimiter(@total_count) %> records. Exporting all records may be slow.
|
46
|
+
</div>
|
47
|
+
<% end %>
|
48
|
+
|
49
|
+
<div class="mb-3 form-check">
|
50
|
+
<input type="checkbox" class="form-check-input" id="includeHeaders" name="include_headers" checked>
|
51
|
+
<label class="form-check-label" for="includeHeaders">Include column headers</label>
|
43
52
|
</div>
|
44
53
|
<% end %>
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
<div class="modal-footer">
|
53
|
-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
54
|
-
<button type="submit" form="csvExportForm" class="btn btn-success">
|
55
|
-
<i class="bi bi-download me-1"></i> Export CSV
|
56
|
-
</button>
|
54
|
+
</div>
|
55
|
+
<div class="modal-footer">
|
56
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
57
|
+
<button type="submit" form="csvExportForm" class="btn btn-success">
|
58
|
+
<i class="bi bi-download me-1"></i> Export CSV
|
59
|
+
</button>
|
60
|
+
</div>
|
57
61
|
</div>
|
58
62
|
</div>
|
59
63
|
</div>
|
60
|
-
|
64
|
+
<% end %>
|
61
65
|
|
62
66
|
<!-- Records Section -->
|
63
67
|
<div class="dbviewer-card card mb-4">
|
@@ -571,9 +571,11 @@
|
|
571
571
|
<li class="nav-item">
|
572
572
|
<%= link_to raw('<i class="bi bi-diagram-3"></i> ERD'), dbviewer.entity_relationship_diagrams_path, class: "nav-link #{erd_nav_class}" %>
|
573
573
|
</li>
|
574
|
-
|
575
|
-
|
576
|
-
|
574
|
+
<% if Dbviewer.configuration.enable_query_logging %>
|
575
|
+
<li class="nav-item">
|
576
|
+
<%= link_to raw('<i class="bi bi-journal-code"></i> SQL Logs'), dbviewer.logs_path, class: "nav-link #{logs_nav_class}" %>
|
577
|
+
</li>
|
578
|
+
<% end %>
|
577
579
|
</ul>
|
578
580
|
<ul class="navbar-nav ms-auto">
|
579
581
|
<li class="nav-item">
|
@@ -603,6 +605,17 @@
|
|
603
605
|
<!-- Main Content Area -->
|
604
606
|
<div class="dbviewer-main">
|
605
607
|
<div class="dbviewer-main-content">
|
608
|
+
<!-- Flash Messages -->
|
609
|
+
<% if flash.any? %>
|
610
|
+
<% flash.each do |type, message| %>
|
611
|
+
<% alert_class = type.to_s == 'notice' ? 'alert-info' : 'alert-danger' %>
|
612
|
+
<div class="alert <%= alert_class %> alert-dismissible fade show mb-3" role="alert">
|
613
|
+
<%= message %>
|
614
|
+
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
615
|
+
</div>
|
616
|
+
<% end %>
|
617
|
+
<% end %>
|
618
|
+
|
606
619
|
<div class="d-flex d-lg-none align-items-center mb-3">
|
607
620
|
<button class="btn btn-sm btn-outline-primary dbviewer-sidebar-toggle" type="button">
|
608
621
|
<i class="bi bi-list me-1"></i> Tables
|
@@ -31,7 +31,11 @@ module Dbviewer
|
|
31
31
|
# Maximum number of queries to keep in memory
|
32
32
|
attr_accessor :max_memory_queries
|
33
33
|
|
34
|
-
#
|
34
|
+
# Enable or disable query logging completely
|
35
|
+
attr_accessor :enable_query_logging
|
36
|
+
|
37
|
+
# Admin access credentials hash with :username and :password keys
|
38
|
+
# @example { username: 'admin', password: 'secret' }
|
35
39
|
attr_accessor :admin_credentials
|
36
40
|
|
37
41
|
def initialize
|
@@ -45,6 +49,7 @@ module Dbviewer
|
|
45
49
|
@query_logging_mode = :memory
|
46
50
|
@query_log_path = "log/dbviewer.log"
|
47
51
|
@max_memory_queries = 1000
|
52
|
+
@enable_query_logging = true
|
48
53
|
@admin_credentials = nil
|
49
54
|
end
|
50
55
|
end
|
data/lib/dbviewer/logger.rb
CHANGED
@@ -10,6 +10,8 @@ module Dbviewer
|
|
10
10
|
|
11
11
|
# Add a new SQL event query to the logger
|
12
12
|
def add(event)
|
13
|
+
# Return early if query logging is disabled
|
14
|
+
return unless Dbviewer.configuration.enable_query_logging
|
13
15
|
return if QueryParser.should_skip_query?(event)
|
14
16
|
|
15
17
|
current_time = Time.now
|
data/lib/dbviewer/version.rb
CHANGED
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.3.
|
4
|
+
version: 0.3.6
|
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-05-
|
11
|
+
date: 2025-05-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|