dbviewer 0.5.2 → 0.5.3
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 +92 -0
- data/app/controllers/concerns/dbviewer/database_operations.rb +11 -19
- data/app/controllers/dbviewer/api/entity_relationship_diagrams_controller.rb +84 -0
- data/app/controllers/dbviewer/api/queries_controller.rb +1 -1
- data/app/controllers/dbviewer/entity_relationship_diagrams_controller.rb +5 -6
- data/app/controllers/dbviewer/logs_controller.rb +1 -1
- data/app/controllers/dbviewer/tables_controller.rb +2 -8
- data/app/helpers/dbviewer/application_helper.rb +1 -1
- data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +217 -100
- data/app/views/dbviewer/tables/show.html.erb +278 -404
- data/config/routes.rb +7 -0
- data/lib/dbviewer/database/cache_manager.rb +78 -0
- data/lib/dbviewer/database/dynamic_model_factory.rb +62 -0
- data/lib/dbviewer/database/manager.rb +204 -0
- data/lib/dbviewer/database/metadata_manager.rb +129 -0
- data/lib/dbviewer/datatable/query_operations.rb +330 -0
- data/lib/dbviewer/datatable/query_params.rb +41 -0
- data/lib/dbviewer/engine.rb +1 -1
- data/lib/dbviewer/query/analyzer.rb +250 -0
- data/lib/dbviewer/query/collection.rb +39 -0
- data/lib/dbviewer/query/executor.rb +93 -0
- data/lib/dbviewer/query/logger.rb +108 -0
- data/lib/dbviewer/query/parser.rb +56 -0
- data/lib/dbviewer/storage/file_storage.rb +0 -3
- data/lib/dbviewer/version.rb +1 -1
- data/lib/dbviewer.rb +24 -7
- metadata +14 -14
- data/lib/dbviewer/cache_manager.rb +0 -78
- data/lib/dbviewer/database_manager.rb +0 -249
- data/lib/dbviewer/dynamic_model_factory.rb +0 -60
- data/lib/dbviewer/error_handler.rb +0 -18
- data/lib/dbviewer/logger.rb +0 -77
- data/lib/dbviewer/query_analyzer.rb +0 -239
- data/lib/dbviewer/query_collection.rb +0 -37
- data/lib/dbviewer/query_executor.rb +0 -91
- data/lib/dbviewer/query_parser.rb +0 -53
- data/lib/dbviewer/table_metadata_manager.rb +0 -136
- data/lib/dbviewer/table_query_operations.rb +0 -621
- data/lib/dbviewer/table_query_params.rb +0 -39
@@ -150,9 +150,48 @@
|
|
150
150
|
|
151
151
|
// Generate the ERD diagram
|
152
152
|
const tables = <%= raw @tables.to_json %>;
|
153
|
-
|
154
|
-
|
155
|
-
|
153
|
+
|
154
|
+
console.log('Tables:', tables);
|
155
|
+
|
156
|
+
// Initialize empty relationships - will be loaded asynchronously
|
157
|
+
let relationships = [];
|
158
|
+
let relationshipsLoaded = false;
|
159
|
+
|
160
|
+
// Function to fetch relationships asynchronously
|
161
|
+
function fetchRelationships() {
|
162
|
+
return fetch('<%= dbviewer.relationships_api_entity_relationship_diagrams_path %>', {
|
163
|
+
headers: {
|
164
|
+
'Accept': 'application/json',
|
165
|
+
'X-Requested-With': 'XMLHttpRequest'
|
166
|
+
}
|
167
|
+
})
|
168
|
+
.then(response => {
|
169
|
+
if (!response.ok) {
|
170
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
171
|
+
}
|
172
|
+
return response.json();
|
173
|
+
})
|
174
|
+
.then(data => {
|
175
|
+
console.log('Relationships loaded:', data);
|
176
|
+
relationships = data.relationships || [];
|
177
|
+
relationshipsLoaded = true;
|
178
|
+
return relationships;
|
179
|
+
})
|
180
|
+
.catch(error => {
|
181
|
+
console.error('Error fetching relationships:', error);
|
182
|
+
relationshipsLoaded = true; // Mark as loaded even on error to prevent infinite loading
|
183
|
+
return [];
|
184
|
+
});
|
185
|
+
}
|
186
|
+
|
187
|
+
// Function to update loading status
|
188
|
+
function updateLoadingStatus(message) {
|
189
|
+
const loadingElement = document.getElementById('erd-loading');
|
190
|
+
const loadingText = loadingElement.querySelector('p');
|
191
|
+
if (loadingText) {
|
192
|
+
loadingText.textContent = message;
|
193
|
+
}
|
194
|
+
}
|
156
195
|
|
157
196
|
// Create the ER diagram definition in Mermaid syntax
|
158
197
|
let mermaidDefinition = 'erDiagram\n';
|
@@ -160,7 +199,15 @@
|
|
160
199
|
// We'll store table column data here as we fetch it
|
161
200
|
const tableColumns = {};
|
162
201
|
|
163
|
-
//
|
202
|
+
// Track loading progress
|
203
|
+
let columnsLoadedCount = 0;
|
204
|
+
const totalTables = tables.length;
|
205
|
+
|
206
|
+
// Start fetching relationships immediately
|
207
|
+
updateLoadingStatus('Loading database relationships...');
|
208
|
+
const relationshipsPromise = fetchRelationships();
|
209
|
+
|
210
|
+
// First pass: add all tables with minimal info and start loading columns
|
164
211
|
tables.forEach(function(table) {
|
165
212
|
const tableName = table.name;
|
166
213
|
mermaidDefinition += ` ${tableName} {\n`;
|
@@ -178,121 +225,134 @@
|
|
178
225
|
.then(data => {
|
179
226
|
if (data && data.columns) {
|
180
227
|
tableColumns[tableName] = data.columns;
|
181
|
-
|
228
|
+
columnsLoadedCount++;
|
229
|
+
|
230
|
+
// Update loading status
|
231
|
+
updateLoadingStatus(`Loading table details... (${columnsLoadedCount}/${totalTables} tables)`);
|
232
|
+
|
233
|
+
checkIfReadyToUpdate();
|
182
234
|
}
|
183
235
|
})
|
184
236
|
.catch(error => {
|
185
237
|
console.error(`Error fetching columns for table ${tableName}:`, error);
|
238
|
+
columnsLoadedCount++;
|
239
|
+
checkIfReadyToUpdate();
|
186
240
|
});
|
187
241
|
});
|
188
242
|
|
243
|
+
// Function to check if we're ready to update the diagram with full data
|
244
|
+
function checkIfReadyToUpdate() {
|
245
|
+
if (columnsLoadedCount === totalTables && relationshipsLoaded) {
|
246
|
+
updateDiagramWithFullData();
|
247
|
+
}
|
248
|
+
}
|
249
|
+
|
250
|
+
// Wait for relationships to load and check if ready
|
251
|
+
relationshipsPromise.finally(() => {
|
252
|
+
checkIfReadyToUpdate();
|
253
|
+
});
|
254
|
+
|
189
255
|
// Track if we're currently updating the diagram
|
190
256
|
let isUpdatingDiagram = false;
|
191
257
|
|
192
|
-
// Function to update the diagram once we have
|
193
|
-
function
|
258
|
+
// Function to update the diagram once we have all data
|
259
|
+
function updateDiagramWithFullData() {
|
194
260
|
// Prevent multiple simultaneous updates
|
195
261
|
if (isUpdatingDiagram) return;
|
196
262
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
263
|
+
isUpdatingDiagram = true;
|
264
|
+
console.log('Updating diagram with full column and relationship data');
|
265
|
+
|
266
|
+
updateLoadingStatus('Generating diagram...');
|
267
|
+
|
268
|
+
// Regenerate the diagram with complete data
|
269
|
+
let updatedDefinition = 'erDiagram\n';
|
270
|
+
|
271
|
+
tables.forEach(function(table) {
|
272
|
+
const tableName = table.name;
|
273
|
+
updatedDefinition += ` ${tableName} {\n`;
|
204
274
|
|
205
|
-
|
206
|
-
|
207
|
-
updatedDefinition += `
|
208
|
-
|
209
|
-
const columns = tableColumns[tableName] || [];
|
210
|
-
columns.forEach(column => {
|
211
|
-
updatedDefinition += ` ${column.type || 'string'} ${column.name}\n`;
|
212
|
-
});
|
213
|
-
|
214
|
-
updatedDefinition += ' }\n';
|
275
|
+
const columns = tableColumns[tableName] || [];
|
276
|
+
columns.forEach(column => {
|
277
|
+
updatedDefinition += ` ${column.type || 'string'} ${column.name}\n`;
|
215
278
|
});
|
216
279
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
updatedDefinition +=
|
224
|
-
}
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
280
|
+
updatedDefinition += ' }\n';
|
281
|
+
});
|
282
|
+
|
283
|
+
// Add relationships
|
284
|
+
if (relationships && relationships.length > 0) {
|
285
|
+
relationships.forEach(function(rel) {
|
286
|
+
updatedDefinition += ` ${rel.from_table} }|--|| ${rel.to_table} : "${rel.from_column} → ${rel.to_column}"\n`;
|
287
|
+
});
|
288
|
+
} else {
|
289
|
+
updatedDefinition += ' %% No relationships found in the database schema\n';
|
290
|
+
}
|
291
|
+
|
292
|
+
// Create a new diagram element
|
293
|
+
const updatedErdDiv = document.createElement('div');
|
294
|
+
updatedErdDiv.className = 'mermaid';
|
295
|
+
updatedErdDiv.innerHTML = updatedDefinition;
|
296
|
+
|
297
|
+
// Get the container but don't clear it yet
|
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
|
307
|
+
const tempContainer = document.createElement('div');
|
308
|
+
tempContainer.style.visibility = 'hidden';
|
309
|
+
tempContainer.style.position = 'absolute';
|
310
|
+
tempContainer.style.width = '100%';
|
311
|
+
tempContainer.appendChild(updatedErdDiv);
|
312
|
+
document.body.appendChild(tempContainer);
|
313
|
+
|
314
|
+
// Render in the temporary container first
|
315
|
+
mermaid.init(undefined, updatedErdDiv).then(function() {
|
316
|
+
console.log('Diagram fully updated with all data');
|
247
317
|
|
248
|
-
//
|
249
|
-
|
250
|
-
|
318
|
+
// Clear original container and move the rendered content
|
319
|
+
try {
|
320
|
+
// Remove from temp container without destroying
|
321
|
+
tempContainer.removeChild(updatedErdDiv);
|
251
322
|
|
252
|
-
//
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
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);
|
323
|
+
// Hide loading indicator
|
324
|
+
document.getElementById('erd-loading').style.display = 'none';
|
325
|
+
|
326
|
+
// Clear main container and add the diagram
|
327
|
+
container.innerHTML = '';
|
328
|
+
container.appendChild(updatedErdDiv);
|
329
|
+
|
330
|
+
// Remove temp container
|
277
331
|
document.body.removeChild(tempContainer);
|
332
|
+
|
333
|
+
// Wait a bit for the DOM to stabilize before initializing pan-zoom
|
334
|
+
setTimeout(() => {
|
335
|
+
setupZoomControls();
|
336
|
+
// Mark diagram as ready for download
|
337
|
+
diagramReady = true;
|
338
|
+
isUpdatingDiagram = false;
|
339
|
+
}, 100);
|
340
|
+
} catch(err) {
|
341
|
+
console.error('Error moving diagram to container:', err);
|
278
342
|
isUpdatingDiagram = false;
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
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`;
|
343
|
+
}
|
344
|
+
}).catch(function(error) {
|
345
|
+
console.error('Error rendering updated diagram:', error);
|
346
|
+
document.body.removeChild(tempContainer);
|
347
|
+
isUpdatingDiagram = false;
|
348
|
+
showError('Error rendering diagram', 'There was an error updating the diagram with complete data.', error.message);
|
289
349
|
});
|
290
|
-
} else {
|
291
|
-
// Add a note if no relationships are found
|
292
|
-
mermaidDefinition += ' %% No relationships found in the database schema\n';
|
293
350
|
}
|
294
351
|
|
295
|
-
//
|
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)
|
296
356
|
const erdDiv = document.createElement('div');
|
297
357
|
erdDiv.className = 'mermaid';
|
298
358
|
erdDiv.innerHTML = mermaidDefinition;
|
@@ -308,13 +368,16 @@
|
|
308
368
|
tempInitContainer.appendChild(erdDiv);
|
309
369
|
document.body.appendChild(tempInitContainer);
|
310
370
|
|
371
|
+
// Update loading status for initial render
|
372
|
+
updateLoadingStatus('Rendering initial diagram...');
|
373
|
+
|
311
374
|
// Render the initial diagram in the temporary container
|
312
375
|
mermaid.init(undefined, erdDiv).then(function() {
|
313
376
|
try {
|
314
377
|
// Remove from temp container without destroying
|
315
378
|
tempInitContainer.removeChild(erdDiv);
|
316
379
|
|
317
|
-
// Hide the loading indicator
|
380
|
+
// Hide the loading indicator temporarily (will show updated loading for data fetching)
|
318
381
|
document.getElementById('erd-loading').style.display = 'none';
|
319
382
|
|
320
383
|
// Add the rendered diagram to the main container
|
@@ -323,19 +386,57 @@
|
|
323
386
|
// Remove temp container
|
324
387
|
document.body.removeChild(tempInitContainer);
|
325
388
|
|
326
|
-
// Setup zoom controls
|
389
|
+
// Setup initial zoom controls
|
327
390
|
setTimeout(() => {
|
328
391
|
setupZoomControls();
|
392
|
+
// Show a subtle loading indicator that data is still loading
|
393
|
+
showDataLoadingIndicator();
|
329
394
|
}, 100);
|
330
395
|
} catch(err) {
|
331
396
|
console.error('Error moving initial diagram to container:', err);
|
332
397
|
}
|
333
398
|
}).catch(function(error) {
|
334
|
-
console.error('Error rendering diagram:', error);
|
399
|
+
console.error('Error rendering initial diagram:', error);
|
335
400
|
document.body.removeChild(tempInitContainer);
|
336
|
-
showError('Error generating diagram', 'There was an error generating the entity relationship diagram.', error.message);
|
401
|
+
showError('Error generating diagram', 'There was an error generating the initial entity relationship diagram.', error.message);
|
337
402
|
});
|
338
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
|
+
|
339
440
|
// SVG Pan Zoom instance
|
340
441
|
let panZoomInstance = null;
|
341
442
|
|
@@ -677,6 +778,22 @@
|
|
677
778
|
text-align: center;
|
678
779
|
}
|
679
780
|
|
781
|
+
/* Data loading badge styling */
|
782
|
+
#data-loading-badge {
|
783
|
+
animation: pulse 2s infinite;
|
784
|
+
}
|
785
|
+
|
786
|
+
@keyframes pulse {
|
787
|
+
0% { opacity: 1; }
|
788
|
+
50% { opacity: 0.7; }
|
789
|
+
100% { opacity: 1; }
|
790
|
+
}
|
791
|
+
|
792
|
+
#data-loading-badge .badge {
|
793
|
+
font-size: 0.75rem;
|
794
|
+
padding: 0.4rem 0.6rem;
|
795
|
+
}
|
796
|
+
|
680
797
|
/* Mermaid override for text size */
|
681
798
|
.mermaid .entityLabel div {
|
682
799
|
font-size: 20px !important;
|