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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c368abfe3b3131b1df3b3234e1e560c2cb5f715c035680d3848ff5ff07d99cc3
4
- data.tar.gz: 7f9b27e43d86b0181f14d9deceebcb1d6624b22cb51885ab5c3b929814cb97f9
3
+ metadata.gz: 151a189372c94a737108969cd768ab9dbc4e16a2abc608f07026d1a6b7ee65a5
4
+ data.tar.gz: a5fa3a4f4aa8e1acd606f60c2264412a261beec6b6889675ef941d05d1e593cb
5
5
  SHA512:
6
- metadata.gz: 05f35000d4c57ec2e17e0c0fe56d49520add9f0c061debc25f5f6407ec1e52ece7a1d4361c0b92d283135492bc78d86890083e9781a30705ebcddc0fce2e58df
7
- data.tar.gz: a76035a207c87105f9331dc4246a133da9941a5a9e435133b80254fc0037578bc69b5dbb17cb40113049ba50625d3a81816df118015446af8e9c368127e481c9
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
- if Rails.env.development?
91
- mount Dbviewer::Engine, at: "/dbviewer"
92
- end
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
- ## 🌱 Production Access (Not Recommended)
238
+ ### Basic Authentication
214
239
 
215
- By default, DBViewer only runs in development or test environments for security reasons. If you need to access it in production (not recommended):
240
+ You can enable HTTP Basic Authentication to secure access to DBViewer:
216
241
 
217
- 1. Set an environment variable with a secure random key:
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
- DBVIEWER_PRODUCTION_ACCESS_KEY=your_secure_random_key
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
- 2. Add an additional constraint in your routes:
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
- if Rails.env.production?
227
- constraints ->(req) { req.params[:access_key] == ENV["DBVIEWER_PRODUCTION_ACCESS_KEY"] } do
228
- mount Dbviewer::Engine, at: "/dbviewer"
229
- end
230
- else
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
- 3. Access the tool with the override parameter:
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 is designed for development purposes. It's not recommended to use it in production as it provides direct access to your database contents. If you must use it in production, ensure it's protected behind authentication and use the production access key mechanism with a strong random key.
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 :ensure_development_environment
6
+ before_action :authenticate_with_basic_auth
7
7
  before_action :set_tables
8
8
 
9
9
  private
10
10
 
11
- def ensure_development_environment
12
- unless Rails.env.development? || Rails.env.test? || params[:override_env_check] == ENV["DBVIEWER_PRODUCTION_ACCESS_KEY"]
13
- render plain: "DBViewer is only available in development and test environments for security reasons.", status: :forbidden
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
- <a href="<%= dbviewer.logs_path %>" class="btn btn-sm btn-primary">View All Logs</a>
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
- <% queries = Dbviewer::Logger.instance.recent_queries(limit: 10) %>
103
-
104
- <% if queries.any? %>
105
- <div class="table-responsive">
106
- <table class="table table-sm table-hover mb-0">
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
- <thead>
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
- <td class="text-truncate" style="max-width: 500px;">
119
- <code class="sql-query-code"><%= query[:sql] %></code>
120
- </td>
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
- <% end %>
131
- </tbody>
132
- </table>
133
- </div>
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>No queries recorded yet</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
- <button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#csvExportModal">
15
- <i class="bi bi-file-earmark-spreadsheet me-1"></i> Export CSV
16
- </button>
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
- <!-- CSV Export Modal -->
24
- <div class="modal fade" id="csvExportModal" tabindex="-1" aria-labelledby="csvExportModalLabel" aria-hidden="true">
25
- <div class="modal-dialog">
26
- <div class="modal-content">
27
- <div class="modal-header">
28
- <h5 class="modal-title" id="csvExportModalLabel">Export <strong><%= @table_name %></strong> to CSV</h5>
29
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
30
- </div>
31
- <div class="modal-body">
32
- <%= form_with url: export_csv_table_path(@table_name), method: :get, id: "csvExportForm" do |form| %>
33
- <div class="mb-3">
34
- <label for="limit" class="form-label">Maximum number of records</label>
35
- <input type="number" class="form-control" id="limit" name="limit" value="10000" min="1" max="100000">
36
- <div class="form-text">Limit the number of records to export. Large exports may take some time.</div>
37
- </div>
38
-
39
- <% if @total_count > 10000 %>
40
- <div class="alert alert-warning">
41
- <i class="bi bi-exclamation-triangle-fill me-2"></i>
42
- This table has <%= number_with_delimiter(@total_count) %> records. Exporting all records may be slow.
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
- <div class="mb-3 form-check">
47
- <input type="checkbox" class="form-check-input" id="includeHeaders" name="include_headers" checked>
48
- <label class="form-check-label" for="includeHeaders">Include column headers</label>
49
- </div>
50
- <% end %>
51
- </div>
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
- </div>
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
- <li class="nav-item">
575
- <%= link_to raw('<i class="bi bi-journal-code"></i> SQL Logs'), dbviewer.logs_path, class: "nav-link #{logs_nav_class}" %>
576
- </li>
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
- # Admin access credentials (username, password)
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Dbviewer
2
- VERSION = "0.3.4"
2
+ VERSION = "0.3.6"
3
3
  end
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
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-19 00:00:00.000000000 Z
11
+ date: 2025-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails