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 +4 -4
- data/README.md +18 -10
- data/app/controllers/dbviewer/application_controller.rb +1 -1
- data/app/controllers/dbviewer/home_controller.rb +67 -2
- data/app/views/dbviewer/home/index.html.erb +87 -54
- data/config/routes.rb +7 -1
- data/lib/dbviewer/engine.rb +5 -0
- data/lib/dbviewer/version.rb +1 -1
- data/lib/generators/dbviewer/initializer_generator.rb +12 -0
- data/lib/generators/dbviewer/templates/initializer.rb +18 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9caec4627e8912e40d85d63c9105cdc80b2bae4227e145372693855a40fbc29
|
4
|
+
data.tar.gz: ca759885a1ec9522860d9926e1814f68457fa453832f7b3ea3298aa5c86222eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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 =
|
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
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
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
|
|
@@ -6,10 +6,75 @@ module Dbviewer
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def analytics
|
9
|
-
|
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:
|
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-
|
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-
|
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-
|
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
|
-
|
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
|
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 =
|
179
|
-
|
180
|
-
|
181
|
-
|
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 =
|
189
|
-
|
190
|
-
|
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(
|
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(
|
183
|
+
function updateLargestTables(data) {
|
198
184
|
const container = document.getElementById('largest-tables-container');
|
199
185
|
|
200
|
-
if (
|
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
|
-
${
|
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
|
316
|
-
fetch('<%=
|
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(
|
329
|
-
|
330
|
-
updateLargestTables(analytics);
|
364
|
+
.then(recordsData => {
|
365
|
+
updateRecordsData(recordsData);
|
331
366
|
})
|
332
367
|
.catch(error => {
|
333
|
-
console.error('Error loading
|
334
|
-
// Update
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
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
|
-
|
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"
|
data/lib/dbviewer/engine.rb
CHANGED
@@ -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
|
data/lib/dbviewer/version.rb
CHANGED
@@ -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.
|
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-
|
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:
|