dbviewer 0.5.3 → 0.5.5
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 +4 -3
- data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +95 -121
- data/lib/dbviewer/version.rb +1 -1
- data/lib/generators/dbviewer/{initializer_generator.rb → install_generator.rb} +1 -1
- data/lib/generators/dbviewer/templates/initializer.rb +16 -16
- 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: 5f0218efe5ea6bc79f61ca87472b6b413916b82e3aedf28e6030c37f302b2ca3
|
4
|
+
data.tar.gz: 65c58c041d85f80f0ad609e54225ec9c5bf3707bc36954ea07c36b95eb69a23c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15b2f9f0cf3543c59bc0230ef23070b93d64c7038387e8d384aa2613ef0f28badec97957c2b3d48ab991da86f22f48e9fd71a41844d9247d4cf3d8c015f46331
|
7
|
+
data.tar.gz: 05a6f0cfdd61f901a6dd17edbcd2a93c5244f461a4bfe61b350c2ed864af7e83ed1afa6489a8d2d0e81a66b307d0aa65ea8b81c080a4dc3cca896cec4ee20598
|
data/README.md
CHANGED
@@ -111,7 +111,7 @@ This is necessary because API-only Rails applications don't include the Flash mi
|
|
111
111
|
You can configure DBViewer by using our generator to create an initializer in your application:
|
112
112
|
|
113
113
|
```bash
|
114
|
-
rails generate dbviewer:
|
114
|
+
rails generate dbviewer:install
|
115
115
|
```
|
116
116
|
|
117
117
|
This will create a file at `config/initializers/dbviewer.rb` with the default configuration:
|
@@ -172,7 +172,7 @@ config.query_logging_mode = :file # Store queries in a log file
|
|
172
172
|
config.query_log_path = "log/dbviewer.log" # Path where query log file will be stored
|
173
173
|
```
|
174
174
|
|
175
|
-
The file format uses one JSON entry per line, making it easy to analyze with standard tools.
|
175
|
+
The file format uses one JSON entry per line, making it easy to analyze with standard tools. Query Log collector are disabled by default on non development environtment.
|
176
176
|
|
177
177
|
## 🔒 Security Features
|
178
178
|
|
@@ -226,7 +226,7 @@ With the addition of Basic Authentication, DBViewer can now be used in any envir
|
|
226
226
|
3. Access the tool through your regular application URL:
|
227
227
|
|
228
228
|
```
|
229
|
-
https://yourdomain.com/dbviewer
|
229
|
+
https://yourdomain.com/dbviewer
|
230
230
|
```
|
231
231
|
|
232
232
|
## 📝 Security Note
|
@@ -334,6 +334,7 @@ gem build dbviewer.gemspec
|
|
334
334
|
3. The dummy app includes sample data across multiple tables to test various DBViewer features
|
335
335
|
|
336
336
|
### Architecture Diagram
|
337
|
+
|
337
338
|
```mermaid
|
338
339
|
graph TB
|
339
340
|
subgraph "DBViewer Engine"
|
@@ -33,12 +33,35 @@
|
|
33
33
|
<div class="card-body p-0">
|
34
34
|
<div id="erd-container" class="w-100 h-100" style="min-height: 450px;">
|
35
35
|
<div id="erd-loading" class="d-flex justify-content-center align-items-center h-100" style="min-height: 450px;">
|
36
|
-
<div class="text-center">
|
37
|
-
<div class="
|
38
|
-
<
|
36
|
+
<div class="text-center" style="width: 100%; max-width: 500px;">
|
37
|
+
<div class="mb-4">
|
38
|
+
<i class="bi bi-diagram-3 text-primary" style="font-size: 3rem;"></i>
|
39
39
|
</div>
|
40
|
-
<
|
41
|
-
<
|
40
|
+
<h5 class="mb-3">Generating Entity Relationship Diagram</h5>
|
41
|
+
<p id="loading-phase" class="mb-3">Initializing...</p>
|
42
|
+
|
43
|
+
<!-- Progress bar for table loading -->
|
44
|
+
<div class="progress mb-3" style="height: 8px;">
|
45
|
+
<div id="table-progress-bar" class="progress-bar bg-primary" role="progressbar"
|
46
|
+
style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
|
50
|
+
<!-- Progress text -->
|
51
|
+
<div class="d-flex justify-content-between align-items-center mb-2">
|
52
|
+
<small class="text-muted">Table Details</small>
|
53
|
+
<small id="table-progress-text" class="text-muted">0 / 0</small>
|
54
|
+
</div>
|
55
|
+
|
56
|
+
<!-- Relationships loading indicator -->
|
57
|
+
<div id="relationships-status" class="d-flex align-items-center justify-content-center mt-3">
|
58
|
+
<div class="spinner-border spinner-border-sm text-secondary me-2" role="status">
|
59
|
+
<span class="visually-hidden">Loading...</span>
|
60
|
+
</div>
|
61
|
+
<small class="text-muted">Loading relationships...</small>
|
62
|
+
</div>
|
63
|
+
|
64
|
+
<small class="text-muted d-block mt-3">This may take a moment for databases with many tables</small>
|
42
65
|
</div>
|
43
66
|
</div>
|
44
67
|
<!-- The ERD will be rendered here -->
|
@@ -175,11 +198,13 @@
|
|
175
198
|
console.log('Relationships loaded:', data);
|
176
199
|
relationships = data.relationships || [];
|
177
200
|
relationshipsLoaded = true;
|
201
|
+
updateRelationshipsStatus(true);
|
178
202
|
return relationships;
|
179
203
|
})
|
180
204
|
.catch(error => {
|
181
205
|
console.error('Error fetching relationships:', error);
|
182
206
|
relationshipsLoaded = true; // Mark as loaded even on error to prevent infinite loading
|
207
|
+
updateRelationshipsStatus(true);
|
183
208
|
return [];
|
184
209
|
});
|
185
210
|
}
|
@@ -187,9 +212,48 @@
|
|
187
212
|
// Function to update loading status
|
188
213
|
function updateLoadingStatus(message) {
|
189
214
|
const loadingElement = document.getElementById('erd-loading');
|
190
|
-
const
|
191
|
-
if (
|
192
|
-
|
215
|
+
const loadingPhase = document.getElementById('loading-phase');
|
216
|
+
if (loadingPhase) {
|
217
|
+
loadingPhase.textContent = message;
|
218
|
+
}
|
219
|
+
}
|
220
|
+
|
221
|
+
// Function to update table loading progress
|
222
|
+
function updateTableProgress(loaded, total) {
|
223
|
+
const progressBar = document.getElementById('table-progress-bar');
|
224
|
+
const progressText = document.getElementById('table-progress-text');
|
225
|
+
|
226
|
+
if (progressBar && progressText) {
|
227
|
+
const percentage = total > 0 ? Math.round((loaded / total) * 100) : 0;
|
228
|
+
progressBar.style.width = percentage + '%';
|
229
|
+
progressBar.setAttribute('aria-valuenow', percentage);
|
230
|
+
progressText.textContent = `${loaded} / ${total}`;
|
231
|
+
|
232
|
+
// Update progress bar color based on completion
|
233
|
+
if (percentage === 100) {
|
234
|
+
progressBar.classList.remove('bg-primary');
|
235
|
+
progressBar.classList.add('bg-success');
|
236
|
+
}
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
240
|
+
// Function to update relationships status
|
241
|
+
function updateRelationshipsStatus(loaded) {
|
242
|
+
const relationshipsStatus = document.getElementById('relationships-status');
|
243
|
+
if (relationshipsStatus) {
|
244
|
+
if (loaded) {
|
245
|
+
relationshipsStatus.innerHTML = `
|
246
|
+
<i class="bi bi-check-circle text-success me-2"></i>
|
247
|
+
<small class="text-success">Relationships loaded</small>
|
248
|
+
`;
|
249
|
+
} else {
|
250
|
+
relationshipsStatus.innerHTML = `
|
251
|
+
<div class="spinner-border spinner-border-sm text-secondary me-2" role="status">
|
252
|
+
<span class="visually-hidden">Loading...</span>
|
253
|
+
</div>
|
254
|
+
<small class="text-muted">Loading relationships...</small>
|
255
|
+
`;
|
256
|
+
}
|
193
257
|
}
|
194
258
|
}
|
195
259
|
|
@@ -203,8 +267,12 @@
|
|
203
267
|
let columnsLoadedCount = 0;
|
204
268
|
const totalTables = tables.length;
|
205
269
|
|
270
|
+
// Initialize progress bar
|
271
|
+
updateTableProgress(0, totalTables);
|
272
|
+
updateLoadingStatus('Loading table details...');
|
273
|
+
|
206
274
|
// Start fetching relationships immediately
|
207
|
-
|
275
|
+
updateRelationshipsStatus(false);
|
208
276
|
const relationshipsPromise = fetchRelationships();
|
209
277
|
|
210
278
|
// First pass: add all tables with minimal info and start loading columns
|
@@ -227,8 +295,8 @@
|
|
227
295
|
tableColumns[tableName] = data.columns;
|
228
296
|
columnsLoadedCount++;
|
229
297
|
|
230
|
-
// Update
|
231
|
-
|
298
|
+
// Update progress bar
|
299
|
+
updateTableProgress(columnsLoadedCount, totalTables);
|
232
300
|
|
233
301
|
checkIfReadyToUpdate();
|
234
302
|
}
|
@@ -236,6 +304,7 @@
|
|
236
304
|
.catch(error => {
|
237
305
|
console.error(`Error fetching columns for table ${tableName}:`, error);
|
238
306
|
columnsLoadedCount++;
|
307
|
+
updateTableProgress(columnsLoadedCount, totalTables);
|
239
308
|
checkIfReadyToUpdate();
|
240
309
|
});
|
241
310
|
});
|
@@ -261,9 +330,9 @@
|
|
261
330
|
if (isUpdatingDiagram) return;
|
262
331
|
|
263
332
|
isUpdatingDiagram = true;
|
264
|
-
console.log('
|
333
|
+
console.log('Rendering diagram with full column and relationship data');
|
265
334
|
|
266
|
-
updateLoadingStatus('Generating diagram...');
|
335
|
+
updateLoadingStatus('Generating final diagram...');
|
267
336
|
|
268
337
|
// Regenerate the diagram with complete data
|
269
338
|
let updatedDefinition = 'erDiagram\n';
|
@@ -289,43 +358,33 @@
|
|
289
358
|
updatedDefinition += ' %% No relationships found in the database schema\n';
|
290
359
|
}
|
291
360
|
|
292
|
-
// Create
|
293
|
-
const
|
294
|
-
|
295
|
-
|
361
|
+
// Create the diagram element
|
362
|
+
const erdDiv = document.createElement('div');
|
363
|
+
erdDiv.className = 'mermaid';
|
364
|
+
erdDiv.innerHTML = updatedDefinition;
|
296
365
|
|
297
|
-
//
|
298
|
-
const container = document.getElementById('erd-container');
|
299
|
-
|
300
|
-
// First, clean up any previous zoom instance
|
301
|
-
if (panZoomInstance) {
|
302
|
-
panZoomInstance.destroy();
|
303
|
-
panZoomInstance = null;
|
304
|
-
}
|
305
|
-
|
306
|
-
// Create a temporary container
|
366
|
+
// Create a temporary container for rendering
|
307
367
|
const tempContainer = document.createElement('div');
|
308
368
|
tempContainer.style.visibility = 'hidden';
|
309
369
|
tempContainer.style.position = 'absolute';
|
310
370
|
tempContainer.style.width = '100%';
|
311
|
-
tempContainer.appendChild(
|
371
|
+
tempContainer.appendChild(erdDiv);
|
312
372
|
document.body.appendChild(tempContainer);
|
313
373
|
|
314
|
-
// Render in the temporary container
|
315
|
-
mermaid.init(undefined,
|
316
|
-
console.log('Diagram fully
|
374
|
+
// Render the diagram in the temporary container
|
375
|
+
mermaid.init(undefined, erdDiv).then(function() {
|
376
|
+
console.log('Diagram fully rendered with all data');
|
317
377
|
|
318
|
-
// Clear original container and move the rendered content
|
319
378
|
try {
|
320
379
|
// Remove from temp container without destroying
|
321
|
-
tempContainer.removeChild(
|
380
|
+
tempContainer.removeChild(erdDiv);
|
322
381
|
|
323
382
|
// Hide loading indicator
|
324
383
|
document.getElementById('erd-loading').style.display = 'none';
|
325
384
|
|
326
385
|
// Clear main container and add the diagram
|
327
386
|
container.innerHTML = '';
|
328
|
-
container.appendChild(
|
387
|
+
container.appendChild(erdDiv);
|
329
388
|
|
330
389
|
// Remove temp container
|
331
390
|
document.body.removeChild(tempContainer);
|
@@ -342,101 +401,16 @@
|
|
342
401
|
isUpdatingDiagram = false;
|
343
402
|
}
|
344
403
|
}).catch(function(error) {
|
345
|
-
console.error('Error rendering
|
404
|
+
console.error('Error rendering diagram:', error);
|
346
405
|
document.body.removeChild(tempContainer);
|
347
406
|
isUpdatingDiagram = false;
|
348
|
-
showError('Error rendering diagram', 'There was an error
|
407
|
+
showError('Error rendering diagram', 'There was an error rendering the entity relationship diagram.', error.message);
|
349
408
|
});
|
350
409
|
}
|
351
410
|
|
352
|
-
// Add initial relationships placeholder (empty)
|
353
|
-
mermaidDefinition += ' %% Relationships loading...\n';
|
354
|
-
|
355
|
-
// Create a div for the initial diagram (shows immediately with table names only)
|
356
|
-
const erdDiv = document.createElement('div');
|
357
|
-
erdDiv.className = 'mermaid';
|
358
|
-
erdDiv.innerHTML = mermaidDefinition;
|
359
|
-
|
360
411
|
// Get the container reference for later use
|
361
412
|
const container = document.getElementById('erd-container');
|
362
413
|
|
363
|
-
// Create a temporary container for initial rendering
|
364
|
-
const tempInitContainer = document.createElement('div');
|
365
|
-
tempInitContainer.style.visibility = 'hidden';
|
366
|
-
tempInitContainer.style.position = 'absolute';
|
367
|
-
tempInitContainer.style.width = '100%';
|
368
|
-
tempInitContainer.appendChild(erdDiv);
|
369
|
-
document.body.appendChild(tempInitContainer);
|
370
|
-
|
371
|
-
// Update loading status for initial render
|
372
|
-
updateLoadingStatus('Rendering initial diagram...');
|
373
|
-
|
374
|
-
// Render the initial diagram in the temporary container
|
375
|
-
mermaid.init(undefined, erdDiv).then(function() {
|
376
|
-
try {
|
377
|
-
// Remove from temp container without destroying
|
378
|
-
tempInitContainer.removeChild(erdDiv);
|
379
|
-
|
380
|
-
// Hide the loading indicator temporarily (will show updated loading for data fetching)
|
381
|
-
document.getElementById('erd-loading').style.display = 'none';
|
382
|
-
|
383
|
-
// Add the rendered diagram to the main container
|
384
|
-
container.appendChild(erdDiv);
|
385
|
-
|
386
|
-
// Remove temp container
|
387
|
-
document.body.removeChild(tempInitContainer);
|
388
|
-
|
389
|
-
// Setup initial zoom controls
|
390
|
-
setTimeout(() => {
|
391
|
-
setupZoomControls();
|
392
|
-
// Show a subtle loading indicator that data is still loading
|
393
|
-
showDataLoadingIndicator();
|
394
|
-
}, 100);
|
395
|
-
} catch(err) {
|
396
|
-
console.error('Error moving initial diagram to container:', err);
|
397
|
-
}
|
398
|
-
}).catch(function(error) {
|
399
|
-
console.error('Error rendering initial diagram:', error);
|
400
|
-
document.body.removeChild(tempInitContainer);
|
401
|
-
showError('Error generating diagram', 'There was an error generating the initial entity relationship diagram.', error.message);
|
402
|
-
});
|
403
|
-
|
404
|
-
// Function to show a subtle loading indicator for data loading
|
405
|
-
function showDataLoadingIndicator() {
|
406
|
-
// Create a small loading badge in the top-right corner
|
407
|
-
const loadingBadge = document.createElement('div');
|
408
|
-
loadingBadge.id = 'data-loading-badge';
|
409
|
-
loadingBadge.className = 'position-absolute top-0 end-0 m-3';
|
410
|
-
loadingBadge.style.zIndex = '1000';
|
411
|
-
loadingBadge.innerHTML = `
|
412
|
-
<div class="badge bg-info d-flex align-items-center">
|
413
|
-
<div class="spinner-border spinner-border-sm me-2" role="status" style="width: 0.8rem; height: 0.8rem;">
|
414
|
-
<span class="visually-hidden">Loading...</span>
|
415
|
-
</div>
|
416
|
-
Loading details...
|
417
|
-
</div>
|
418
|
-
`;
|
419
|
-
|
420
|
-
container.style.position = 'relative';
|
421
|
-
container.appendChild(loadingBadge);
|
422
|
-
|
423
|
-
// Remove the badge when data loading is complete
|
424
|
-
const checkComplete = () => {
|
425
|
-
if (columnsLoadedCount === totalTables && relationshipsLoaded) {
|
426
|
-
setTimeout(() => {
|
427
|
-
const badge = document.getElementById('data-loading-badge');
|
428
|
-
if (badge) {
|
429
|
-
badge.remove();
|
430
|
-
}
|
431
|
-
}, 500); // Small delay to show completion
|
432
|
-
} else {
|
433
|
-
setTimeout(checkComplete, 500);
|
434
|
-
}
|
435
|
-
};
|
436
|
-
|
437
|
-
setTimeout(checkComplete, 1000);
|
438
|
-
}
|
439
|
-
|
440
414
|
// SVG Pan Zoom instance
|
441
415
|
let panZoomInstance = null;
|
442
416
|
|
data/lib/dbviewer/version.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Dbviewer
|
2
2
|
module Generators
|
3
|
-
class
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
4
|
source_root File.expand_path("../templates", __FILE__)
|
5
5
|
desc "Creates a DBViewer initializer file at config/initializers/dbviewer.rb"
|
6
6
|
|
@@ -1,21 +1,21 @@
|
|
1
1
|
Dbviewer.configure do |config|
|
2
|
-
config.per_page_options = [ 10, 20, 50, 100, 250 ]
|
3
|
-
config.default_per_page =
|
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
|
2
|
+
config.per_page_options = [ 10, 20, 50, 100, 250 ] # Default pagination options
|
3
|
+
config.default_per_page = 50 # Default records per page
|
4
|
+
config.max_query_length = 10000 # Maximum SQL query length
|
5
|
+
config.cache_expiry = 300 # Cache expiration in seconds
|
6
|
+
config.max_records = 10000 # Maximum records to return in any query
|
7
|
+
config.enable_data_export = false # Whether to allow data exporting
|
8
|
+
config.query_timeout = 30 # SQL query timeout in seconds
|
9
9
|
|
10
10
|
# Query logging options
|
11
|
-
config.enable_query_logging =
|
12
|
-
config.query_logging_mode = :memory
|
13
|
-
config.query_log_path = "log/dbviewer.log"
|
14
|
-
config.max_memory_queries = 1000
|
11
|
+
config.enable_query_logging = true # Enable or disable query logging completely (default: true)
|
12
|
+
config.query_logging_mode = :file # Storage mode for SQL queries (:memory or :file)
|
13
|
+
config.query_log_path = "log/dbviewer.log" # Path for query log file when in :file mode
|
14
|
+
config.max_memory_queries = 1000 # Maximum number of queries to store in memory
|
15
15
|
|
16
|
-
# Authentication options
|
17
|
-
# config.admin_credentials = {
|
18
|
-
|
19
|
-
#
|
20
|
-
|
16
|
+
# Authentication options (Basic Auth)
|
17
|
+
# config.admin_credentials = {
|
18
|
+
# username: "admin",
|
19
|
+
# password: "your_secure_password"
|
20
|
+
# }
|
21
21
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dbviewer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wailan Tirajoh
|
@@ -99,7 +99,7 @@ files:
|
|
99
99
|
- lib/dbviewer/storage/file_storage.rb
|
100
100
|
- lib/dbviewer/storage/in_memory_storage.rb
|
101
101
|
- lib/dbviewer/version.rb
|
102
|
-
- lib/generators/dbviewer/
|
102
|
+
- lib/generators/dbviewer/install_generator.rb
|
103
103
|
- lib/generators/dbviewer/templates/initializer.rb
|
104
104
|
- lib/tasks/dbviewer_tasks.rake
|
105
105
|
homepage: https://github.com/wailantirajoh/dbviewer
|