dbviewer 0.4.3 → 0.4.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: 7cc641a4845b5267ef95a4fd7b5c368a9f4145dd0f60322eb753e8b4a270414a
4
- data.tar.gz: 37ee52e24a3578c61f939b14d74cea8d125e4f737ed98abafa109c9a3e71e627
3
+ metadata.gz: a9caec4627e8912e40d85d63c9105cdc80b2bae4227e145372693855a40fbc29
4
+ data.tar.gz: ca759885a1ec9522860d9926e1814f68457fa453832f7b3ea3298aa5c86222eb
5
5
  SHA512:
6
- metadata.gz: 4253de79918ecd100eeb6f8e17b4e64346060295defd167cb709227db1eec0cd6716e230c1c56ae251dac5361643eaf8abd529fc566a31cad27edfdcc72ad424
7
- data.tar.gz: af7b679d9ec6116487322ce9309ca4390e02e22fdaf8d873dff8c71f83ab8142a8bd7d08f34e9fd127d2a233c963b9903bf82bfd75972f8f6452f67c5994ab1e
6
+ metadata.gz: d71176e418170a1d82439a1266687d6f125c27dd96dcf008fb05ccecc169d11dafb52f1c5940549e030f3487fe8c2408f73dda51e7ddae8e05b598cde621943f
7
+ data.tar.gz: cebdde38e2e4500a265b20b86d1e6daee3ed7ba95447b348635efb00e963f7d895b8f147ab94b2b8c50656940c74279ebfbd17a90ff3921341cec2a0121b84cd
data/README.md CHANGED
@@ -169,7 +169,13 @@ end
169
169
 
170
170
  ## ⚙️ Configuration Options
171
171
 
172
- You can configure DBViewer by creating an initializer in your application:
172
+ You can configure DBViewer by using our generator to create an initializer in your application:
173
+
174
+ ```bash
175
+ rails generate dbviewer:initializer
176
+ ```
177
+
178
+ This will create a file at `config/initializers/dbviewer.rb` with the default configuration:
173
179
 
174
180
  ```ruby
175
181
  # config/initializers/dbviewer.rb
@@ -183,16 +189,18 @@ Dbviewer.configure do |config|
183
189
  config.query_timeout = 30 # SQL query timeout in seconds
184
190
 
185
191
  # Query logging options
186
- config.enable_query_logging = true # Enable or disable query logging completely (default: true)
192
+ config.enable_query_logging = false # Enable or disable query logging completely (default: true)
187
193
  config.query_logging_mode = :memory # Storage mode for SQL queries (:memory or :file)
188
194
  config.query_log_path = "log/dbviewer.log" # Path for query log file when in :file mode
189
195
  config.max_memory_queries = 1000 # Maximum number of queries to store in memory
190
196
 
191
197
  # Authentication options
192
- config.admin_credentials = { username: "admin", password: "your_secure_password" } # Basic HTTP auth credentials
198
+ # config.admin_credentials = { username: "admin", password: "your_secure_password" } # Basic HTTP auth credentials
193
199
  end
194
200
  ```
195
201
 
202
+ You can also create this file manually if you prefer.
203
+
196
204
  The configuration is accessed through `Dbviewer.configuration` throughout the codebase. You can also access it via `Dbviewer.config` which is an alias for backward compatibility.
197
205
 
198
206
  ## 🪵 Query Logging
@@ -302,13 +310,13 @@ The simplest way to update is using Bundler:
302
310
 
303
311
  - Update your Gemfile with the desired version:
304
312
 
305
- ```ruby
306
- # For the latest version
307
- gem "dbviewer", group: :development
308
-
309
- # Or specify a version
310
- gem "dbviewer", "~> 0.3.2", group: :development
311
- ```
313
+ ```ruby
314
+ # For the latest version
315
+ gem "dbviewer", group: :development
316
+
317
+ # Or specify a version
318
+ gem "dbviewer", "~> 0.3.2", group: :development
319
+ ```
312
320
 
313
321
  - Run bundle update:
314
322
 
@@ -3,7 +3,7 @@ module Dbviewer
3
3
  include Dbviewer::DatabaseOperations
4
4
  include Dbviewer::ErrorHandling
5
5
 
6
- # before_action :authenticate_with_basic_auth
6
+ before_action :authenticate_with_basic_auth
7
7
  before_action :set_tables
8
8
 
9
9
  private
@@ -6,10 +6,75 @@ module Dbviewer
6
6
  end
7
7
 
8
8
  def analytics
9
- @analytics = fetch_database_analytics
9
+ # This method is deprecated but kept for backward compatibility
10
+ analytics_data = fetch_database_analytics
11
+ # Remove record data which will be served by the records endpoint
12
+ analytics_data.delete(:total_records)
13
+ analytics_data.delete(:largest_tables)
14
+ analytics_data.delete(:empty_tables)
15
+ analytics_data.delete(:avg_records_per_table)
10
16
 
11
17
  respond_to do |format|
12
- format.json { render json: @analytics }
18
+ format.json { render json: analytics_data }
19
+ end
20
+ end
21
+
22
+ def tables_count
23
+ tables = fetch_tables_with_stats(include_record_counts: false)
24
+
25
+ respond_to do |format|
26
+ format.json { render json: { total_tables: tables.size } }
27
+ end
28
+ end
29
+
30
+ def relationships_count
31
+ begin
32
+ tables = fetch_tables_with_stats(include_record_counts: false)
33
+ total_relationships = 0
34
+
35
+ tables.each do |table|
36
+ metadata = fetch_table_metadata(table[:name])
37
+ total_relationships += metadata[:foreign_keys].size if metadata && metadata[:foreign_keys]
38
+ end
39
+
40
+ respond_to do |format|
41
+ format.json { render json: { total_relationships: total_relationships } }
42
+ end
43
+ rescue => e
44
+ Rails.logger.error("Error calculating relationship count: #{e.message}")
45
+ respond_to do |format|
46
+ format.json { render json: { total_relationships: 0, error: e.message }, status: :internal_server_error }
47
+ end
48
+ end
49
+ end
50
+
51
+ def database_size
52
+ begin
53
+ size = calculate_schema_size
54
+
55
+ respond_to do |format|
56
+ format.json { render json: { schema_size: size } }
57
+ end
58
+ rescue => e
59
+ Rails.logger.error("Error calculating schema size: #{e.message}")
60
+ respond_to do |format|
61
+ format.json { render json: { schema_size: nil, error: e.message }, status: :internal_server_error }
62
+ end
63
+ end
64
+ end
65
+
66
+ def records
67
+ tables = fetch_tables_with_stats(include_record_counts: true)
68
+
69
+ records_data = {
70
+ total_records: tables.sum { |t| t[:record_count] },
71
+ largest_tables: tables.sort_by { |t| -t[:record_count] }.first(10),
72
+ empty_tables: tables.select { |t| t[:record_count] == 0 },
73
+ avg_records_per_table: tables.any? ? (tables.sum { |t| t[:record_count] }.to_f / tables.size).round(1) : 0
74
+ }
75
+
76
+ respond_to do |format|
77
+ format.json { render json: records_data }
13
78
  end
14
79
  end
15
80
 
@@ -9,7 +9,7 @@
9
9
  </div>
10
10
 
11
11
  <div class="row g-3 mb-4" id="analytics-cards">
12
- <div class="col-md-3">
12
+ <div class="col-md-4">
13
13
  <div class="card h-100 border-0 shadow-sm <%= stat_card_bg_class %>">
14
14
  <div class="card-body d-flex align-items-center">
15
15
  <div class="metric-icon me-3">
@@ -28,7 +28,7 @@
28
28
  </div>
29
29
  </div>
30
30
 
31
- <div class="col-md-3">
31
+ <div class="col-md-4">
32
32
  <div class="card h-100 border-0 shadow-sm <%= stat_card_bg_class %>">
33
33
  <div class="card-body d-flex align-items-center">
34
34
  <div class="metric-icon me-3">
@@ -47,27 +47,7 @@
47
47
  </div>
48
48
  </div>
49
49
 
50
- <div class="col-md-3">
51
- <div class="card h-100 border-0 shadow-sm <%= stat_card_bg_class %>">
52
- <div class="card-body d-flex align-items-center">
53
- <div class="metric-icon me-3">
54
- <i class="bi bi-link-45deg fs-4"></i>
55
- </div>
56
- <div class="text-start">
57
- <h5 class="mb-1">Relationships</h5>
58
- <h2 class="mb-0">
59
- <span class="skeleton-loader number-loader" id="relationships-loading">
60
- &nbsp;&nbsp;
61
- </span>
62
- <span id="relationships-count" class="d-none">0</span>
63
- </h2>
64
- <small class="text-muted d-block">Foreign Key Connections</small>
65
- </div>
66
- </div>
67
- </div>
68
- </div>
69
-
70
- <div class="col-md-3">
50
+ <div class="col-md-4">
71
51
  <div class="card h-100 border-0 shadow-sm <%= stat_card_bg_class %>">
72
52
  <div class="card-body d-flex align-items-center">
73
53
  <div class="metric-icon me-3">
@@ -171,33 +151,39 @@ document.addEventListener('DOMContentLoaded', function() {
171
151
  }
172
152
 
173
153
  // Function to update analytics cards
174
- function updateAnalyticsCards(analytics) {
175
- // Update tables count
154
+ function updateTablesCount(data) {
176
155
  document.getElementById('tables-loading').classList.add('d-none');
177
156
  document.getElementById('tables-count').classList.remove('d-none');
178
- document.getElementById('tables-count').textContent = analytics.total_tables || 0;
179
-
180
- // Update records count
181
- document.getElementById('records-loading').classList.add('d-none');
182
- document.getElementById('records-count').classList.remove('d-none');
183
- document.getElementById('records-count').textContent = numberWithDelimiter(analytics.total_records || 0);
184
-
185
- // Update relationships count
157
+ document.getElementById('tables-count').textContent = data.total_tables || 0;
158
+ }
159
+
160
+ function updateRelationshipsCount(data) {
186
161
  document.getElementById('relationships-loading').classList.add('d-none');
187
162
  document.getElementById('relationships-count').classList.remove('d-none');
188
- document.getElementById('relationships-count').textContent = analytics.total_relationships || 0;
189
-
190
- // Update database size
163
+ document.getElementById('relationships-count').textContent = data.total_relationships || 0;
164
+ }
165
+
166
+ function updateDatabaseSize(data) {
191
167
  document.getElementById('size-loading').classList.add('d-none');
192
168
  document.getElementById('size-count').classList.remove('d-none');
193
- document.getElementById('size-count').textContent = numberToHumanSize(analytics.schema_size);
169
+ document.getElementById('size-count').textContent = numberToHumanSize(data.schema_size);
170
+ }
171
+
172
+ function updateRecordsData(recordsData) {
173
+ // Update records count
174
+ document.getElementById('records-loading').classList.add('d-none');
175
+ document.getElementById('records-count').classList.remove('d-none');
176
+ document.getElementById('records-count').textContent = numberWithDelimiter(recordsData.total_records || 0);
177
+
178
+ // Update largest tables
179
+ updateLargestTables(recordsData);
194
180
  }
195
181
 
196
182
  // Function to update largest tables
197
- function updateLargestTables(analytics) {
183
+ function updateLargestTables(data) {
198
184
  const container = document.getElementById('largest-tables-container');
199
185
 
200
- if (analytics.largest_tables && analytics.largest_tables.length > 0) {
186
+ if (data.largest_tables && data.largest_tables.length > 0) {
201
187
  const tableHtml = `
202
188
  <div class="table-responsive">
203
189
  <table class="table table-sm table-hover">
@@ -208,7 +194,7 @@ document.addEventListener('DOMContentLoaded', function() {
208
194
  </tr>
209
195
  </thead>
210
196
  <tbody>
211
- ${analytics.largest_tables.map(table => `
197
+ ${data.largest_tables.map(table => `
212
198
  <tr>
213
199
  <td>
214
200
  <a href="${window.location.origin}${window.location.pathname.replace(/\/$/, '')}/tables/${table.name}">
@@ -312,8 +298,58 @@ document.addEventListener('DOMContentLoaded', function() {
312
298
  `;
313
299
  }
314
300
 
315
- // Load analytics data
316
- fetch('<%= api_analytics_path %>', {
301
+ // Load tables count data
302
+ fetch('<%= api_tables_path %>', {
303
+ headers: {
304
+ 'Accept': 'application/json',
305
+ 'X-Requested-With': 'XMLHttpRequest'
306
+ }
307
+ })
308
+ .then(response => {
309
+ if (!response.ok) {
310
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
311
+ }
312
+ return response.json();
313
+ })
314
+ .then(data => {
315
+ updateTablesCount(data);
316
+ })
317
+ .catch(error => {
318
+ console.error('Error loading tables count:', error);
319
+ const loading = document.getElementById('tables-loading');
320
+ const count = document.getElementById('tables-count');
321
+ loading.classList.add('d-none');
322
+ count.classList.remove('d-none');
323
+ count.innerHTML = '<span class="text-danger">Error</span>';
324
+ });
325
+
326
+ // Load database size data
327
+ fetch('<%= api_database_size_path %>', {
328
+ headers: {
329
+ 'Accept': 'application/json',
330
+ 'X-Requested-With': 'XMLHttpRequest'
331
+ }
332
+ })
333
+ .then(response => {
334
+ if (!response.ok) {
335
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
336
+ }
337
+ return response.json();
338
+ })
339
+ .then(data => {
340
+ updateDatabaseSize(data);
341
+ })
342
+ .catch(error => {
343
+ console.error('Error loading database size:', error);
344
+ const loading = document.getElementById('size-loading');
345
+ const count = document.getElementById('size-count');
346
+ loading.classList.add('d-none');
347
+ count.classList.remove('d-none');
348
+ count.innerHTML = '<span class="text-danger">Error</span>';
349
+ });
350
+
351
+ // Load records data separately
352
+ fetch('<%= api_records_path %>', {
317
353
  headers: {
318
354
  'Accept': 'application/json',
319
355
  'X-Requested-With': 'XMLHttpRequest'
@@ -325,20 +361,17 @@ document.addEventListener('DOMContentLoaded', function() {
325
361
  }
326
362
  return response.json();
327
363
  })
328
- .then(analytics => {
329
- updateAnalyticsCards(analytics);
330
- updateLargestTables(analytics);
364
+ .then(recordsData => {
365
+ updateRecordsData(recordsData);
331
366
  })
332
367
  .catch(error => {
333
- console.error('Error loading analytics:', error);
334
- // Update cards with error state
335
- ['tables-loading', 'records-loading', 'relationships-loading', 'size-loading'].forEach(id => {
336
- const loading = document.getElementById(id);
337
- const count = document.getElementById(id.replace('-loading', '-count'));
338
- loading.classList.add('d-none');
339
- count.classList.remove('d-none');
340
- count.innerHTML = '<span class="text-danger">Error</span>';
341
- });
368
+ console.error('Error loading records data:', error);
369
+ // Update records-related UI with error state
370
+ const recordsLoading = document.getElementById('records-loading');
371
+ const recordsCount = document.getElementById('records-count');
372
+ recordsLoading.classList.add('d-none');
373
+ recordsCount.classList.remove('d-none');
374
+ recordsCount.innerHTML = '<span class="text-danger">Error</span>';
342
375
 
343
376
  showError('largest-tables-container', error.message);
344
377
  });
data/config/routes.rb CHANGED
@@ -18,7 +18,13 @@ Dbviewer::Engine.routes.draw do
18
18
 
19
19
  # Homepage and API endpoints
20
20
  get "dashboard", to: "home#index", as: :dashboard
21
- get "api/analytics", to: "home#analytics"
21
+
22
+ # Analytics API endpoints
23
+ get "api/analytics", to: "home#analytics" # Legacy/combined endpoint
24
+ get "api/records", to: "home#records"
25
+ get "api/tables", to: "home#tables_count"
26
+ get "api/relationships", to: "home#relationships_count"
27
+ get "api/database_size", to: "home#database_size"
22
28
  get "api/recent_queries", to: "home#recent_queries"
23
29
 
24
30
  root to: "home#index"
@@ -6,6 +6,11 @@ module Dbviewer
6
6
  config.autoload_paths << File.expand_path("../../", __FILE__)
7
7
  config.eager_load_paths << File.expand_path("../../", __FILE__)
8
8
 
9
+ # Register generators
10
+ config.app_generators do |g|
11
+ g.templates.unshift File.expand_path("../../generators/dbviewer/templates", __dir__)
12
+ end
13
+
9
14
  # Initialize the engine safely
10
15
  initializer "dbviewer.setup", after: :load_config_initializers do |app|
11
16
  Dbviewer.init
@@ -1,3 +1,3 @@
1
1
  module Dbviewer
2
- VERSION = "0.4.3"
2
+ VERSION = "0.4.6"
3
3
  end
@@ -0,0 +1,12 @@
1
+ module Dbviewer
2
+ module Generators
3
+ class InitializerGenerator < Rails::Generators::Base
4
+ source_root File.expand_path("../templates", __FILE__)
5
+ desc "Creates a DBViewer initializer file at config/initializers/dbviewer.rb"
6
+
7
+ def copy_initializer_file
8
+ copy_file "initializer.rb", "config/initializers/dbviewer.rb"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ Dbviewer.configure do |config|
2
+ config.per_page_options = [ 10, 20, 50, 100, 250 ]
3
+ config.default_per_page = 20
4
+ config.max_query_length = 10000
5
+ config.cache_expiry = 300
6
+ config.max_records = 10000
7
+ config.enable_data_export = false
8
+ config.query_timeout = 30
9
+
10
+ # Query logging options
11
+ config.enable_query_logging = false
12
+ config.query_logging_mode = :memory
13
+ config.query_log_path = "log/dbviewer.log"
14
+ config.max_memory_queries = 1000
15
+
16
+ # Authentication options
17
+ # config.admin_credentials = { username: "admin", password: "your_secure_password" } # Basic HTTP auth credentials
18
+ 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.4.3
4
+ version: 0.4.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-24 00:00:00.000000000 Z
11
+ date: 2025-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -95,6 +95,8 @@ files:
95
95
  - lib/dbviewer/table_query_operations.rb
96
96
  - lib/dbviewer/table_query_params.rb
97
97
  - lib/dbviewer/version.rb
98
+ - lib/generators/dbviewer/initializer_generator.rb
99
+ - lib/generators/dbviewer/templates/initializer.rb
98
100
  - lib/tasks/dbviewer_tasks.rake
99
101
  homepage: https://github.com/wailantirajoh/dbviewer
100
102
  licenses: