dbviewer 0.3.5 → 0.3.15

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.
@@ -0,0 +1,517 @@
1
+ <div class="modal-header">
2
+ <h5 class="modal-title">Relationships for <%= @table_name %></h5>
3
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
4
+ </div>
5
+ <div class="modal-body p-0">
6
+ <div id="mini-erd-container" class="w-100" style="min-height: 450px; height: 100%;">
7
+ <div id="mini-erd-loading" class="d-flex justify-content-center align-items-center" style="height: 100%; min-height: 450px;">
8
+ <div class="text-center">
9
+ <div class="spinner-border text-primary mb-3" role="status">
10
+ <span class="visually-hidden">Loading...</span>
11
+ </div>
12
+ <p>Generating Relationships Diagram...</p>
13
+ </div>
14
+ </div>
15
+ <!-- The mini ERD will be rendered here -->
16
+ <div id="mini-erd-error" class="alert alert-danger m-3 d-none">
17
+ <h5>Error generating diagram</h5>
18
+ <p id="mini-erd-error-message">There was an error rendering the relationships diagram.</p>
19
+ <pre id="mini-erd-error-details" class="bg-light p-2 small mt-2"></pre>
20
+ </div>
21
+ </div>
22
+
23
+ <!-- Debug section - will be visible if there are any issues -->
24
+ <div id="debug-data" class="d-none m-3 border-top pt-3">
25
+ <details>
26
+ <summary>Debug Information</summary>
27
+ <div class="alert alert-info small">
28
+ <pre id="erd-data-debug" style="max-height: 100px; overflow: auto;"><%= @erd_data.to_json %></pre>
29
+ </div>
30
+ </details>
31
+ </div>
32
+ </div>
33
+ <div class="modal-footer">
34
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
35
+ <a href="<%= dbviewer.entity_relationship_diagrams_path %>" class="btn btn-primary">View Full ERD</a>
36
+ </div>
37
+
38
+ <script>
39
+ // Immediately invoke this function to initialize everything
40
+ (function() {
41
+ // Check if mermaid is loaded first
42
+ if (typeof mermaid === 'undefined') {
43
+ console.error('Mermaid library not loaded!');
44
+ showError('Mermaid library not loaded', 'The diagram library could not be loaded. Please check your internet connection and try again.');
45
+ return;
46
+ }
47
+
48
+ console.log('Initializing Mermaid for mini ERD');
49
+
50
+ // Configure Mermaid
51
+ mermaid.initialize({
52
+ startOnLoad: false,
53
+ theme: document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'dark' : 'default',
54
+ securityLevel: 'loose',
55
+ er: {
56
+ diagramPadding: 20,
57
+ layoutDirection: 'TB',
58
+ minEntityWidth: 100,
59
+ minEntityHeight: 75,
60
+ entityPadding: 15,
61
+ stroke: 'gray',
62
+ fill: 'honeydew',
63
+ fontSize: 12
64
+ }
65
+ });
66
+
67
+ // Immediately hide debug data - only reveal if there's an error
68
+ const debugElement = document.getElementById('debug-data');
69
+ if (debugElement) {
70
+ debugElement.classList.add('d-none');
71
+ }
72
+
73
+ // Directly parse the ERD data instead of waiting for DOM loaded event
74
+ // This is important because the modal might already be loaded
75
+ try {
76
+ // Try to parse the data - it's critical to wrap this in JSON.parse when needed
77
+ <% if @erd_data.is_a?(String) %>
78
+ // If somehow we got a string instead of a hash
79
+ const erdData = JSON.parse(<%= raw @erd_data.to_json %>);
80
+ <% else %>
81
+ // Normal case - Ruby hash will be converted to JS object
82
+ const erdData = <%= raw @erd_data.to_json %>;
83
+ <% end %>
84
+
85
+ if (erdData && typeof erdData === 'object') {
86
+ // Store data in debug element for troubleshooting
87
+ const debugDataEl = document.getElementById('erd-data-debug');
88
+ if (debugDataEl) {
89
+ debugDataEl.textContent = JSON.stringify(erdData, null, 2);
90
+ }
91
+
92
+ renderMiniERD(erdData);
93
+ } else {
94
+ throw new Error('ERD data is not an object');
95
+ }
96
+ } catch (e) {
97
+ console.error('Error parsing ERD data:', e);
98
+ showError('Data parsing error', 'Failed to parse relationship data: ' + e.message);
99
+
100
+ // Show debug data when there's an error
101
+ if (debugElement) {
102
+ debugElement.classList.remove('d-none');
103
+ }
104
+ }
105
+ })();
106
+
107
+ function showError(title, message, details = '') {
108
+ const errorContainer = document.getElementById('mini-erd-error');
109
+ const errorMessage = document.getElementById('mini-erd-error-message');
110
+ const errorDetails = document.getElementById('mini-erd-error-details');
111
+
112
+ // Hide the loading indicator
113
+ document.getElementById('mini-erd-loading').style.display = 'none';
114
+
115
+ // Set error message
116
+ errorMessage.textContent = message;
117
+
118
+ // Set error details if provided
119
+ if (details) {
120
+ errorDetails.textContent = details;
121
+ errorDetails.classList.remove('d-none');
122
+ } else {
123
+ errorDetails.classList.add('d-none');
124
+ }
125
+
126
+ // Show the error container
127
+ errorContainer.classList.remove('d-none');
128
+ }
129
+
130
+ function renderMiniERD(tableData) {
131
+ try {
132
+ const tables = tableData.tables || [];
133
+ const relationships = tableData.relationships || [];
134
+
135
+ // Validate data before proceeding
136
+ if (!Array.isArray(tables) || !Array.isArray(relationships)) {
137
+ showError('Invalid data format', 'The relationship data is not in the expected format.');
138
+ console.error('Invalid data format received:', tableData);
139
+ return;
140
+ }
141
+
142
+ console.log(`Found ${tables.length} tables and ${relationships.length} relationships`);
143
+
144
+ // Create the ER diagram definition in Mermaid syntax
145
+ let mermaidDefinition = 'erDiagram\n';
146
+
147
+ // Add tables to the diagram - ensure we have at least one table
148
+ if (tables.length === 0) {
149
+ mermaidDefinition += ` <%= @table_name.gsub(/[^\w]/, '_') %> {\n`;
150
+ mermaidDefinition += ` string id PK\n`;
151
+ mermaidDefinition += ` }\n`;
152
+ } else {
153
+ tables.forEach(function(table) {
154
+ const tableName = table.name;
155
+
156
+ if (!tableName) {
157
+ console.warn('Table with no name found:', table);
158
+ return; // Skip this table
159
+ }
160
+
161
+ // Clean table name for mermaid (remove special characters)
162
+ const cleanTableName = tableName.replace(/[^\w]/g, '_');
163
+
164
+ // Make the current table stand out with a different visualization
165
+ if (tableName === '<%= @table_name %>') {
166
+ mermaidDefinition += ` ${cleanTableName} {\n`;
167
+ mermaidDefinition += ` string id PK\n`;
168
+ mermaidDefinition += ` }\n`;
169
+ } else {
170
+ mermaidDefinition += ` ${cleanTableName} {\n`;
171
+ mermaidDefinition += ` string id\n`;
172
+ mermaidDefinition += ` }\n`;
173
+ }
174
+ });
175
+ }
176
+
177
+ // Add relationships
178
+ if (relationships && relationships.length > 0) {
179
+ relationships.forEach(function(rel) {
180
+ try {
181
+ // Ensure all required properties exist
182
+ if (!rel.from_table || !rel.to_table) {
183
+ console.error('Missing table in relationship:', rel);
184
+ return; // Skip this relationship
185
+ }
186
+
187
+ // Clean up table names for mermaid (remove special characters)
188
+ const fromTable = rel.from_table.replace(/[^\w]/g, '_');
189
+ const toTable = rel.to_table.replace(/[^\w]/g, '_');
190
+ const relationLabel = rel.from_column || '';
191
+
192
+ // Customize the display based on direction
193
+ mermaidDefinition += ` ${fromTable} }|--|| ${toTable} : "${relationLabel}"\n`;
194
+ } catch (err) {
195
+ console.error('Error processing relationship:', err, rel);
196
+ }
197
+ });
198
+ } else {
199
+ // Add a note if no relationships are found
200
+ mermaidDefinition += ' %% No relationships found for this table\n';
201
+ }
202
+
203
+ // Log the generated mermaid definition for debugging
204
+ console.log('Mermaid Definition:', mermaidDefinition);
205
+
206
+ // Hide the loading indicator first since render might take time
207
+ document.getElementById('mini-erd-loading').style.display = 'none';
208
+
209
+ // Render the diagram
210
+ try {
211
+ mermaid.render('mini-erd-graph', mermaidDefinition)
212
+ .then(function(result) {
213
+ console.log('Mermaid rendering successful');
214
+
215
+ // Get the container
216
+ const container = document.getElementById('mini-erd-container');
217
+
218
+ // Insert the rendered SVG
219
+ container.innerHTML = result.svg;
220
+
221
+ // Apply SVG-Pan-Zoom to make the diagram interactive
222
+ try {
223
+ const svgElement = container.querySelector('svg');
224
+ if (svgElement && typeof svgPanZoom !== 'undefined') {
225
+ svgPanZoom(svgElement, {
226
+ zoomEnabled: true,
227
+ controlIconsEnabled: true,
228
+ fit: true,
229
+ center: true
230
+ });
231
+ }
232
+ } catch (e) {
233
+ console.warn('Failed to initialize svg-pan-zoom:', e);
234
+ // Not critical, continue without pan-zoom
235
+ }
236
+
237
+ // Add highlighting for the current table
238
+ setTimeout(function() {
239
+ try {
240
+ const cleanTableName = '<%= @table_name %>'.replace(/[^\w]/g, '_');
241
+ const currentTableElement = container.querySelector(`[id*="${cleanTableName}"]`);
242
+ if (currentTableElement) {
243
+ const rect = currentTableElement.querySelector('rect');
244
+ if (rect) {
245
+ // Highlight the current table
246
+ rect.setAttribute('fill', document.documentElement.getAttribute('data-bs-theme') === 'dark' ? '#2c3034' : '#e2f0ff');
247
+ rect.setAttribute('stroke', document.documentElement.getAttribute('data-bs-theme') === 'dark' ? '#6ea8fe' : '#0d6efd');
248
+ rect.setAttribute('stroke-width', '2');
249
+ }
250
+ }
251
+ } catch (e) {
252
+ console.error('Error highlighting current table:', e);
253
+ }
254
+ }, 100);
255
+ })
256
+ .catch(function(error) {
257
+ console.error('Error rendering mini ERD:', error);
258
+ showError(
259
+ 'Error rendering diagram',
260
+ 'There was an error rendering the relationships diagram.',
261
+ error.message || 'Unknown error'
262
+ );
263
+
264
+ // Show debug data when there's an error
265
+ document.getElementById('debug-data').classList.remove('d-none');
266
+ });
267
+ } catch (renderError) {
268
+ console.error('Exception in mermaid.render call:', renderError);
269
+ showError(
270
+ 'Rendering exception',
271
+ 'Failed to render the diagram.',
272
+ renderError.message || 'Unknown error'
273
+ );
274
+
275
+ // Show debug data when there's an error
276
+ document.getElementById('debug-data').classList.remove('d-none');
277
+ }
278
+ } catch (error) {
279
+ console.error('Exception in renderMiniERD function:', error);
280
+ showError(
281
+ 'Exception generating diagram',
282
+ 'There was an exception processing the relationships diagram.',
283
+ error.message || 'Unknown error'
284
+ );
285
+
286
+ // Show debug data when there's an error
287
+ document.getElementById('debug-data').classList.remove('d-none');
288
+ }
289
+ }
290
+ </script>
291
+
292
+ <script>
293
+ // Immediately invoke this function to initialize everything
294
+ (function() {
295
+ // Check if mermaid is loaded first
296
+ if (typeof mermaid === 'undefined') {
297
+ console.error('Mermaid library not loaded!');
298
+ showError('Mermaid library not loaded', 'The diagram library could not be loaded. Please check your internet connection and try again.');
299
+ return;
300
+ }
301
+
302
+ console.log('Initializing Mermaid for mini ERD');
303
+
304
+ // Configure Mermaid
305
+ mermaid.initialize({
306
+ startOnLoad: false,
307
+ theme: document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'dark' : 'default',
308
+ securityLevel: 'loose',
309
+ er: {
310
+ diagramPadding: 20,
311
+ layoutDirection: 'TB',
312
+ minEntityWidth: 100,
313
+ minEntityHeight: 75,
314
+ entityPadding: 15,
315
+ stroke: 'gray',
316
+ fill: 'honeydew',
317
+ fontSize: 12
318
+ }
319
+ });
320
+
321
+ // Directly parse the ERD data instead of waiting for DOM loaded event
322
+ // This is important because the modal might already be loaded
323
+ try {
324
+ // Parse ERD data directly from Rails
325
+ const erdData = <%= raw @erd_data.to_json %>;
326
+
327
+ // Display debug info
328
+ document.getElementById('debug-data').classList.remove('d-none');
329
+ document.getElementById('erd-data-debug').textContent = JSON.stringify(erdData, null, 2);
330
+
331
+ renderMiniERD(erdData);
332
+ } catch (e) {
333
+ console.error('Error parsing ERD data:', e);
334
+ showError('Data parsing error', 'Failed to parse relationship data: ' + e.message);
335
+ }
336
+ })();
337
+
338
+ function showError(title, message, details = '') {
339
+ const errorContainer = document.getElementById('mini-erd-error');
340
+ const errorMessage = document.getElementById('mini-erd-error-message');
341
+ const errorDetails = document.getElementById('mini-erd-error-details');
342
+
343
+ // Hide the loading indicator
344
+ document.getElementById('mini-erd-loading').style.display = 'none';
345
+
346
+ // Set error message
347
+ errorMessage.textContent = message;
348
+
349
+ // Set error details if provided
350
+ if (details) {
351
+ errorDetails.textContent = details;
352
+ errorDetails.classList.remove('d-none');
353
+ } else {
354
+ errorDetails.classList.add('d-none');
355
+ }
356
+
357
+ // Show the error container
358
+ errorContainer.classList.remove('d-none');
359
+ }
360
+
361
+ function renderMiniERD(tableData) {
362
+ try {
363
+ console.log('ERD Data received:', tableData); // Debug log
364
+
365
+ const tables = tableData.tables || [];
366
+ const relationships = tableData.relationships || [];
367
+
368
+ // Validate data before proceeding
369
+ if (!Array.isArray(tables) || !Array.isArray(relationships)) {
370
+ showError('Invalid data format', 'The relationship data is not in the expected format.');
371
+ console.error('Invalid data format received:', tableData);
372
+ return;
373
+ }
374
+
375
+ console.log(`Found ${tables.length} tables and ${relationships.length} relationships`);
376
+
377
+ // Create the ER diagram definition in Mermaid syntax
378
+ let mermaidDefinition = 'erDiagram\n';
379
+
380
+ // Add tables to the diagram - ensure we have at least one table
381
+ if (tables.length === 0) {
382
+ mermaidDefinition += ` <%= @table_name %> {\n`;
383
+ mermaidDefinition += ` string id PK\n`;
384
+ mermaidDefinition += ` }\n`;
385
+ } else {
386
+ tables.forEach(function(table) {
387
+ const tableName = table.name;
388
+
389
+ if (!tableName) {
390
+ console.warn('Table with no name found:', table);
391
+ return; // Skip this table
392
+ }
393
+
394
+ // Clean table name for mermaid (remove special characters)
395
+ const cleanTableName = tableName.replace(/[^\w]/g, '_');
396
+
397
+ // Make the current table stand out with a different visualization
398
+ if (tableName === '<%= @table_name %>') {
399
+ mermaidDefinition += ` ${cleanTableName} {\n`;
400
+ mermaidDefinition += ` string id PK\n`;
401
+ mermaidDefinition += ` }\n`;
402
+ } else {
403
+ mermaidDefinition += ` ${cleanTableName} {\n`;
404
+ mermaidDefinition += ` string id\n`;
405
+ mermaidDefinition += ` }\n`;
406
+ }
407
+ });
408
+ }
409
+
410
+ // Add relationships
411
+ if (relationships && relationships.length > 0) {
412
+ relationships.forEach(function(rel) {
413
+ try {
414
+ // Ensure all required properties exist
415
+ if (!rel.from_table || !rel.to_table) {
416
+ console.error('Missing table in relationship:', rel);
417
+ return; // Skip this relationship
418
+ }
419
+
420
+ // Clean up table names for mermaid (remove special characters)
421
+ const fromTable = rel.from_table.replace(/[^\w]/g, '_');
422
+ const toTable = rel.to_table.replace(/[^\w]/g, '_');
423
+ const relationLabel = rel.from_column || '';
424
+
425
+ // Customize the display based on direction
426
+ mermaidDefinition += ` ${fromTable} }|--|| ${toTable} : "${relationLabel}"\n`;
427
+ } catch (err) {
428
+ console.error('Error processing relationship:', err, rel);
429
+ }
430
+ });
431
+ } else {
432
+ // Add a note if no relationships are found
433
+ mermaidDefinition += ' %% No relationships found for this table\n';
434
+ }
435
+
436
+ // Create a div for the diagram
437
+ const miniErdDiv = document.createElement('div');
438
+ miniErdDiv.className = 'mermaid';
439
+ miniErdDiv.innerHTML = mermaidDefinition;
440
+
441
+ // Get the container reference
442
+ const container = document.getElementById('mini-erd-container');
443
+
444
+ // Log the generated mermaid definition for debugging
445
+ console.log('Mermaid Definition:', mermaidDefinition);
446
+
447
+ // Hide the loading indicator first since render might take time
448
+ document.getElementById('mini-erd-loading').style.display = 'none';
449
+
450
+ // Render the diagram
451
+ try {
452
+ mermaid.render('mini-erd-graph', mermaidDefinition)
453
+ .then(function(result) {
454
+ console.log('Mermaid rendering successful');
455
+ // Insert the rendered SVG
456
+ container.innerHTML = result.svg;
457
+
458
+ // Apply SVG-Pan-Zoom to make the diagram interactive
459
+ try {
460
+ const svgElement = container.querySelector('svg');
461
+ if (svgElement && typeof svgPanZoom !== 'undefined') {
462
+ svgPanZoom(svgElement, {
463
+ zoomEnabled: true,
464
+ controlIconsEnabled: true,
465
+ fit: true,
466
+ center: true
467
+ });
468
+ }
469
+ } catch (e) {
470
+ console.warn('Failed to initialize svg-pan-zoom:', e);
471
+ // Not critical, continue without pan-zoom
472
+ }
473
+
474
+ // Add highlighting for the current table
475
+ setTimeout(function() {
476
+ try {
477
+ const currentTableElement = container.querySelector(`[id*="${'<%= @table_name %>'.replace(/[^\w]/g, '_')}"]`);
478
+ if (currentTableElement) {
479
+ const rect = currentTableElement.querySelector('rect');
480
+ if (rect) {
481
+ // Highlight the current table
482
+ rect.setAttribute('fill', document.documentElement.getAttribute('data-bs-theme') === 'dark' ? '#2c3034' : '#e2f0ff');
483
+ rect.setAttribute('stroke', document.documentElement.getAttribute('data-bs-theme') === 'dark' ? '#6ea8fe' : '#0d6efd');
484
+ rect.setAttribute('stroke-width', '2');
485
+ }
486
+ }
487
+ } catch (e) {
488
+ console.error('Error highlighting current table:', e);
489
+ }
490
+ }, 100);
491
+ })
492
+ .catch(function(error) {
493
+ console.error('Error rendering mini ERD:', error);
494
+ showError(
495
+ 'Error rendering diagram',
496
+ 'There was an error rendering the relationships diagram.',
497
+ error.message || 'Unknown error'
498
+ );
499
+ });
500
+ } catch (renderError) {
501
+ console.error('Exception in mermaid.render call:', renderError);
502
+ showError(
503
+ 'Rendering exception',
504
+ 'Failed to render the diagram.',
505
+ renderError.message || 'Unknown error'
506
+ );
507
+ }
508
+ } catch (error) {
509
+ console.error('Exception in renderMiniERD function:', error);
510
+ showError(
511
+ 'Exception generating diagram',
512
+ 'There was an exception processing the relationships diagram.',
513
+ error.message || 'Unknown error'
514
+ );
515
+ }
516
+ }
517
+ </script>