dbviewer 0.5.2 → 0.5.4

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +92 -0
  3. data/app/controllers/concerns/dbviewer/database_operations.rb +11 -19
  4. data/app/controllers/dbviewer/api/entity_relationship_diagrams_controller.rb +84 -0
  5. data/app/controllers/dbviewer/api/queries_controller.rb +1 -1
  6. data/app/controllers/dbviewer/entity_relationship_diagrams_controller.rb +5 -6
  7. data/app/controllers/dbviewer/logs_controller.rb +1 -1
  8. data/app/controllers/dbviewer/tables_controller.rb +2 -8
  9. data/app/helpers/dbviewer/application_helper.rb +1 -1
  10. data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +232 -141
  11. data/app/views/dbviewer/tables/show.html.erb +278 -404
  12. data/config/routes.rb +7 -0
  13. data/lib/dbviewer/database/cache_manager.rb +78 -0
  14. data/lib/dbviewer/database/dynamic_model_factory.rb +62 -0
  15. data/lib/dbviewer/database/manager.rb +204 -0
  16. data/lib/dbviewer/database/metadata_manager.rb +129 -0
  17. data/lib/dbviewer/datatable/query_operations.rb +330 -0
  18. data/lib/dbviewer/datatable/query_params.rb +41 -0
  19. data/lib/dbviewer/engine.rb +1 -1
  20. data/lib/dbviewer/query/analyzer.rb +250 -0
  21. data/lib/dbviewer/query/collection.rb +39 -0
  22. data/lib/dbviewer/query/executor.rb +93 -0
  23. data/lib/dbviewer/query/logger.rb +108 -0
  24. data/lib/dbviewer/query/parser.rb +56 -0
  25. data/lib/dbviewer/storage/file_storage.rb +0 -3
  26. data/lib/dbviewer/version.rb +1 -1
  27. data/lib/dbviewer.rb +24 -7
  28. metadata +14 -14
  29. data/lib/dbviewer/cache_manager.rb +0 -78
  30. data/lib/dbviewer/database_manager.rb +0 -249
  31. data/lib/dbviewer/dynamic_model_factory.rb +0 -60
  32. data/lib/dbviewer/error_handler.rb +0 -18
  33. data/lib/dbviewer/logger.rb +0 -77
  34. data/lib/dbviewer/query_analyzer.rb +0 -239
  35. data/lib/dbviewer/query_collection.rb +0 -37
  36. data/lib/dbviewer/query_executor.rb +0 -91
  37. data/lib/dbviewer/query_parser.rb +0 -53
  38. data/lib/dbviewer/table_metadata_manager.rb +0 -136
  39. data/lib/dbviewer/table_query_operations.rb +0 -621
  40. data/lib/dbviewer/table_query_params.rb +0 -39
@@ -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="spinner-border text-primary mb-3" role="status">
38
- <span class="visually-hidden">Loading...</span>
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
- <p>Generating Entity Relationship Diagram...</p>
41
- <small class="text-muted">This may take a moment for databases with many tables</small>
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 -->
@@ -150,9 +173,89 @@
150
173
 
151
174
  // Generate the ERD diagram
152
175
  const tables = <%= raw @tables.to_json %>;
153
- const relationships = <%= raw @table_relationships.to_json %>;
154
-
155
- console.log(tables, relationships)
176
+
177
+ console.log('Tables:', tables);
178
+
179
+ // Initialize empty relationships - will be loaded asynchronously
180
+ let relationships = [];
181
+ let relationshipsLoaded = false;
182
+
183
+ // Function to fetch relationships asynchronously
184
+ function fetchRelationships() {
185
+ return fetch('<%= dbviewer.relationships_api_entity_relationship_diagrams_path %>', {
186
+ headers: {
187
+ 'Accept': 'application/json',
188
+ 'X-Requested-With': 'XMLHttpRequest'
189
+ }
190
+ })
191
+ .then(response => {
192
+ if (!response.ok) {
193
+ throw new Error(`HTTP error! status: ${response.status}`);
194
+ }
195
+ return response.json();
196
+ })
197
+ .then(data => {
198
+ console.log('Relationships loaded:', data);
199
+ relationships = data.relationships || [];
200
+ relationshipsLoaded = true;
201
+ updateRelationshipsStatus(true);
202
+ return relationships;
203
+ })
204
+ .catch(error => {
205
+ console.error('Error fetching relationships:', error);
206
+ relationshipsLoaded = true; // Mark as loaded even on error to prevent infinite loading
207
+ updateRelationshipsStatus(true);
208
+ return [];
209
+ });
210
+ }
211
+
212
+ // Function to update loading status
213
+ function updateLoadingStatus(message) {
214
+ const loadingElement = document.getElementById('erd-loading');
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
+ }
257
+ }
258
+ }
156
259
 
157
260
  // Create the ER diagram definition in Mermaid syntax
158
261
  let mermaidDefinition = 'erDiagram\n';
@@ -160,7 +263,19 @@
160
263
  // We'll store table column data here as we fetch it
161
264
  const tableColumns = {};
162
265
 
163
- // First pass: add all tables with minimal info
266
+ // Track loading progress
267
+ let columnsLoadedCount = 0;
268
+ const totalTables = tables.length;
269
+
270
+ // Initialize progress bar
271
+ updateTableProgress(0, totalTables);
272
+ updateLoadingStatus('Loading table details...');
273
+
274
+ // Start fetching relationships immediately
275
+ updateRelationshipsStatus(false);
276
+ const relationshipsPromise = fetchRelationships();
277
+
278
+ // First pass: add all tables with minimal info and start loading columns
164
279
  tables.forEach(function(table) {
165
280
  const tableName = table.name;
166
281
  mermaidDefinition += ` ${tableName} {\n`;
@@ -178,164 +293,124 @@
178
293
  .then(data => {
179
294
  if (data && data.columns) {
180
295
  tableColumns[tableName] = data.columns;
181
- updateDiagramWithColumns();
296
+ columnsLoadedCount++;
297
+
298
+ // Update progress bar
299
+ updateTableProgress(columnsLoadedCount, totalTables);
300
+
301
+ checkIfReadyToUpdate();
182
302
  }
183
303
  })
184
304
  .catch(error => {
185
305
  console.error(`Error fetching columns for table ${tableName}:`, error);
306
+ columnsLoadedCount++;
307
+ updateTableProgress(columnsLoadedCount, totalTables);
308
+ checkIfReadyToUpdate();
186
309
  });
187
310
  });
188
311
 
312
+ // Function to check if we're ready to update the diagram with full data
313
+ function checkIfReadyToUpdate() {
314
+ if (columnsLoadedCount === totalTables && relationshipsLoaded) {
315
+ updateDiagramWithFullData();
316
+ }
317
+ }
318
+
319
+ // Wait for relationships to load and check if ready
320
+ relationshipsPromise.finally(() => {
321
+ checkIfReadyToUpdate();
322
+ });
323
+
189
324
  // Track if we're currently updating the diagram
190
325
  let isUpdatingDiagram = false;
191
326
 
192
- // Function to update the diagram once we have columns
193
- function updateDiagramWithColumns() {
327
+ // Function to update the diagram once we have all data
328
+ function updateDiagramWithFullData() {
194
329
  // Prevent multiple simultaneous updates
195
330
  if (isUpdatingDiagram) return;
196
331
 
197
- // Check if we have all the tables loaded
198
- if (Object.keys(tableColumns).length === tables.length) {
199
- isUpdatingDiagram = true;
200
- console.log('Updating diagram with full column data');
201
-
202
- // Regenerate the diagram with complete column data
203
- let updatedDefinition = 'erDiagram\n';
332
+ isUpdatingDiagram = true;
333
+ console.log('Rendering diagram with full column and relationship data');
334
+
335
+ updateLoadingStatus('Generating final diagram...');
336
+
337
+ // Regenerate the diagram with complete data
338
+ let updatedDefinition = 'erDiagram\n';
339
+
340
+ tables.forEach(function(table) {
341
+ const tableName = table.name;
342
+ updatedDefinition += ` ${tableName} {\n`;
204
343
 
205
- tables.forEach(function(table) {
206
- const tableName = table.name;
207
- updatedDefinition += ` ${tableName} {\n`;
208
-
209
- const columns = tableColumns[tableName] || [];
210
- columns.forEach(column => {
211
- updatedDefinition += ` ${column.type || 'string'} ${column.name}\n`;
212
- });
213
-
214
- updatedDefinition += ' }\n';
344
+ const columns = tableColumns[tableName] || [];
345
+ columns.forEach(column => {
346
+ updatedDefinition += ` ${column.type || 'string'} ${column.name}\n`;
215
347
  });
216
348
 
217
- // Add relationships
218
- if (relationships && relationships.length > 0) {
219
- relationships.forEach(function(rel) {
220
- updatedDefinition += ` ${rel.from_table} }|--|| ${rel.to_table} : "${rel.from_column} → ${rel.to_column}"\n`;
221
- });
222
- } else {
223
- updatedDefinition += ' %% No relationships found in the database schema\n';
224
- }
225
-
226
- // Create a new diagram element
227
- const updatedErdDiv = document.createElement('div');
228
- updatedErdDiv.className = 'mermaid';
229
- updatedErdDiv.innerHTML = updatedDefinition;
230
-
231
- // Get the container but don't clear it yet
232
- const container = document.getElementById('erd-container');
233
-
234
- // First, clean up any previous zoom instance
235
- if (panZoomInstance) {
236
- panZoomInstance.destroy();
237
- panZoomInstance = null;
238
- }
239
-
240
- // Create a temporary container
241
- const tempContainer = document.createElement('div');
242
- tempContainer.style.visibility = 'hidden';
243
- tempContainer.style.position = 'absolute';
244
- tempContainer.style.width = '100%';
245
- tempContainer.appendChild(updatedErdDiv);
246
- document.body.appendChild(tempContainer);
349
+ updatedDefinition += ' }\n';
350
+ });
351
+
352
+ // Add relationships
353
+ if (relationships && relationships.length > 0) {
354
+ relationships.forEach(function(rel) {
355
+ updatedDefinition += ` ${rel.from_table} }|--|| ${rel.to_table} : "${rel.from_column} ${rel.to_column}"\n`;
356
+ });
357
+ } else {
358
+ updatedDefinition += ' %% No relationships found in the database schema\n';
359
+ }
360
+
361
+ // Create the diagram element
362
+ const erdDiv = document.createElement('div');
363
+ erdDiv.className = 'mermaid';
364
+ erdDiv.innerHTML = updatedDefinition;
365
+
366
+ // Create a temporary container for rendering
367
+ const tempContainer = document.createElement('div');
368
+ tempContainer.style.visibility = 'hidden';
369
+ tempContainer.style.position = 'absolute';
370
+ tempContainer.style.width = '100%';
371
+ tempContainer.appendChild(erdDiv);
372
+ document.body.appendChild(tempContainer);
373
+
374
+ // Render the diagram in the temporary container
375
+ mermaid.init(undefined, erdDiv).then(function() {
376
+ console.log('Diagram fully rendered with all data');
247
377
 
248
- // Render in the temporary container first
249
- mermaid.init(undefined, updatedErdDiv).then(function() {
250
- console.log('Diagram fully updated with column data');
378
+ try {
379
+ // Remove from temp container without destroying
380
+ tempContainer.removeChild(erdDiv);
251
381
 
252
- // Clear original container and move the rendered content
253
- try {
254
- // Remove from temp container without destroying
255
- tempContainer.removeChild(updatedErdDiv);
256
-
257
- // Clear main container and add the diagram
258
- container.innerHTML = '';
259
- container.appendChild(updatedErdDiv);
260
-
261
- // Remove temp container
262
- document.body.removeChild(tempContainer);
263
-
264
- // Wait a bit for the DOM to stabilize before initializing pan-zoom
265
- setTimeout(() => {
266
- setupZoomControls();
267
- // Mark diagram as ready for download
268
- diagramReady = true;
269
- isUpdatingDiagram = false;
270
- }, 100);
271
- } catch(err) {
272
- console.error('Error moving diagram to container:', err);
273
- isUpdatingDiagram = false;
274
- }
275
- }).catch(function(error) {
276
- console.error('Error rendering updated diagram:', error);
382
+ // Hide loading indicator
383
+ document.getElementById('erd-loading').style.display = 'none';
384
+
385
+ // Clear main container and add the diagram
386
+ container.innerHTML = '';
387
+ container.appendChild(erdDiv);
388
+
389
+ // Remove temp container
277
390
  document.body.removeChild(tempContainer);
391
+
392
+ // Wait a bit for the DOM to stabilize before initializing pan-zoom
393
+ setTimeout(() => {
394
+ setupZoomControls();
395
+ // Mark diagram as ready for download
396
+ diagramReady = true;
397
+ isUpdatingDiagram = false;
398
+ }, 100);
399
+ } catch(err) {
400
+ console.error('Error moving diagram to container:', err);
278
401
  isUpdatingDiagram = false;
279
- showError('Error rendering diagram', 'There was an error updating the diagram with complete data.', error.message);
280
- });
281
- }
282
- }
283
-
284
- // Add relationships
285
- if (relationships && relationships.length > 0) {
286
- relationships.forEach(function(rel) {
287
- // Format: "Customer ||--o{ Order : places"
288
- mermaidDefinition += ` ${rel.from_table} }|--|| ${rel.to_table} : "${rel.from_column} → ${rel.to_column}"\n`;
402
+ }
403
+ }).catch(function(error) {
404
+ console.error('Error rendering diagram:', error);
405
+ document.body.removeChild(tempContainer);
406
+ isUpdatingDiagram = false;
407
+ showError('Error rendering diagram', 'There was an error rendering the entity relationship diagram.', error.message);
289
408
  });
290
- } else {
291
- // Add a note if no relationships are found
292
- mermaidDefinition += ' %% No relationships found in the database schema\n';
293
409
  }
294
410
 
295
- // Create a div for the initial diagram
296
- const erdDiv = document.createElement('div');
297
- erdDiv.className = 'mermaid';
298
- erdDiv.innerHTML = mermaidDefinition;
299
-
300
411
  // Get the container reference for later use
301
412
  const container = document.getElementById('erd-container');
302
413
 
303
- // Create a temporary container for initial rendering
304
- const tempInitContainer = document.createElement('div');
305
- tempInitContainer.style.visibility = 'hidden';
306
- tempInitContainer.style.position = 'absolute';
307
- tempInitContainer.style.width = '100%';
308
- tempInitContainer.appendChild(erdDiv);
309
- document.body.appendChild(tempInitContainer);
310
-
311
- // Render the initial diagram in the temporary container
312
- mermaid.init(undefined, erdDiv).then(function() {
313
- try {
314
- // Remove from temp container without destroying
315
- tempInitContainer.removeChild(erdDiv);
316
-
317
- // Hide the loading indicator
318
- document.getElementById('erd-loading').style.display = 'none';
319
-
320
- // Add the rendered diagram to the main container
321
- container.appendChild(erdDiv);
322
-
323
- // Remove temp container
324
- document.body.removeChild(tempInitContainer);
325
-
326
- // Setup zoom controls after diagram is rendered
327
- setTimeout(() => {
328
- setupZoomControls();
329
- }, 100);
330
- } catch(err) {
331
- console.error('Error moving initial diagram to container:', err);
332
- }
333
- }).catch(function(error) {
334
- console.error('Error rendering diagram:', error);
335
- document.body.removeChild(tempInitContainer);
336
- showError('Error generating diagram', 'There was an error generating the entity relationship diagram.', error.message);
337
- });
338
-
339
414
  // SVG Pan Zoom instance
340
415
  let panZoomInstance = null;
341
416
 
@@ -677,6 +752,22 @@
677
752
  text-align: center;
678
753
  }
679
754
 
755
+ /* Data loading badge styling */
756
+ #data-loading-badge {
757
+ animation: pulse 2s infinite;
758
+ }
759
+
760
+ @keyframes pulse {
761
+ 0% { opacity: 1; }
762
+ 50% { opacity: 0.7; }
763
+ 100% { opacity: 1; }
764
+ }
765
+
766
+ #data-loading-badge .badge {
767
+ font-size: 0.75rem;
768
+ padding: 0.4rem 0.6rem;
769
+ }
770
+
680
771
  /* Mermaid override for text size */
681
772
  .mermaid .entityLabel div {
682
773
  font-size: 20px !important;