dbviewer 0.6.6 → 0.6.8

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 (28) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -36
  3. data/app/assets/javascripts/dbviewer/entity_relationship_diagram.js +553 -0
  4. data/app/assets/javascripts/dbviewer/home.js +287 -0
  5. data/app/assets/javascripts/dbviewer/layout.js +194 -0
  6. data/app/assets/javascripts/dbviewer/query.js +277 -0
  7. data/app/assets/javascripts/dbviewer/table.js +1563 -0
  8. data/app/assets/stylesheets/dbviewer/application.css +1460 -21
  9. data/app/assets/stylesheets/dbviewer/entity_relationship_diagram.css +181 -0
  10. data/app/assets/stylesheets/dbviewer/home.css +229 -0
  11. data/app/assets/stylesheets/dbviewer/logs.css +64 -0
  12. data/app/assets/stylesheets/dbviewer/query.css +171 -0
  13. data/app/assets/stylesheets/dbviewer/table.css +1144 -0
  14. data/app/views/dbviewer/connections/index.html.erb +0 -30
  15. data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +14 -713
  16. data/app/views/dbviewer/home/index.html.erb +9 -499
  17. data/app/views/dbviewer/logs/index.html.erb +22 -221
  18. data/app/views/dbviewer/tables/index.html.erb +0 -65
  19. data/app/views/dbviewer/tables/query.html.erb +129 -565
  20. data/app/views/dbviewer/tables/show.html.erb +4 -2429
  21. data/app/views/layouts/dbviewer/application.html.erb +13 -1544
  22. data/lib/dbviewer/version.rb +1 -1
  23. metadata +12 -7
  24. data/app/assets/javascripts/dbviewer/connections.js +0 -70
  25. data/app/assets/stylesheets/dbviewer/dbviewer.css +0 -0
  26. data/app/assets/stylesheets/dbviewer/enhanced.css +0 -0
  27. data/app/views/dbviewer/connections/new.html.erb +0 -79
  28. data/app/views/dbviewer/tables/mini_erd.html.erb +0 -517
@@ -1,5 +1,16 @@
1
1
  <% content_for :title, "Entity Relationship Diagram" %>
2
2
 
3
+
4
+ <% content_for :head do %>
5
+ <%# Include mermaid.js for diagram rendering %>
6
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
7
+ <%# Include svg-pan-zoom for better diagram interaction %>
8
+ <script src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.1/dist/svg-pan-zoom.min.js"></script>
9
+
10
+ <%= stylesheet_link_tag "dbviewer/entity_relationship_diagram", "data-turbo-track": "reload" %>
11
+ <%= javascript_include_tag "dbviewer/entity_relationship_diagram", "data-turbo-track": "reload" %>
12
+ <% end %>
13
+
3
14
  <div class="container-fluid h-100">
4
15
  <div class="row h-100">
5
16
  <div class="col-md-12 p-0">
@@ -77,716 +88,6 @@
77
88
  </div>
78
89
  </div>
79
90
 
80
- <%# Include mermaid.js for diagram rendering %>
81
- <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
82
- <%# Include svg-pan-zoom for better diagram interaction %>
83
- <script src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.1/dist/svg-pan-zoom.min.js"></script>
84
-
85
- <script>
86
- document.addEventListener('DOMContentLoaded', function() {
87
- // Check if mermaid is loaded first
88
- if (typeof mermaid === 'undefined') {
89
- console.error('Mermaid library not loaded!');
90
- showError('Mermaid library not loaded', 'The diagram library could not be loaded. Please check your internet connection and try again.');
91
- return;
92
- }
93
-
94
- // Initialize mermaid with theme detection like mini ERD
95
- mermaid.initialize({
96
- startOnLoad: true,
97
- theme: document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'dark' : 'default',
98
- securityLevel: 'loose',
99
- er: {
100
- diagramPadding: 20,
101
- layoutDirection: 'TB',
102
- minEntityWidth: 100,
103
- minEntityHeight: 75,
104
- entityPadding: 15,
105
- stroke: 'gray',
106
- fill: 'honeydew',
107
- fontSize: 20
108
- }
109
- });
110
-
111
- // Function to show error messages
112
- function showError(title, message, details = '') {
113
- const errorContainer = document.getElementById('erd-error');
114
- const errorMessage = document.getElementById('erd-error-message');
115
- const errorDetails = document.getElementById('erd-error-details');
116
- const loadingIndicator = document.getElementById('erd-loading');
117
-
118
- if (loadingIndicator) {
119
- loadingIndicator.style.display = 'none';
120
- }
121
-
122
- if (errorContainer && errorMessage) {
123
- // Set error message
124
- errorMessage.textContent = message;
125
-
126
- // Set error details if provided
127
- if (details && errorDetails) {
128
- errorDetails.textContent = details;
129
- errorDetails.classList.remove('d-none');
130
- } else if (errorDetails) {
131
- errorDetails.classList.add('d-none');
132
- }
133
-
134
- // Show the error container
135
- errorContainer.classList.remove('d-none');
136
- }
137
- }
138
-
139
- // ER Diagram download functionality
140
- let diagramReady = false;
141
-
142
- // Function to show a temporary downloading indicator
143
- function showDownloadingIndicator(format) {
144
- // Create toast element
145
- const toastEl = document.createElement('div');
146
- toastEl.className = 'position-fixed bottom-0 end-0 p-3';
147
- toastEl.style.zIndex = '5000';
148
- toastEl.innerHTML = `
149
- <div class="toast show" role="alert" aria-live="assertive" aria-atomic="true">
150
- <div class="toast-header">
151
- <strong class="me-auto"><i class="bi bi-download"></i> Downloading ERD</strong>
152
- <small>just now</small>
153
- <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
154
- </div>
155
- <div class="toast-body">
156
- <div class="d-flex align-items-center">
157
- <div class="spinner-border spinner-border-sm me-2" role="status">
158
- <span class="visually-hidden">Loading...</span>
159
- </div>
160
- Preparing ${format} file for download...
161
- </div>
162
- </div>
163
- </div>
164
- `;
165
-
166
- document.body.appendChild(toastEl);
167
-
168
- // Automatically remove after a delay
169
- setTimeout(() => {
170
- toastEl.remove();
171
- }, 3000);
172
- }
173
-
174
- // Generate the ERD diagram
175
- const tables = <%= raw @tables.to_json %>;
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
- }
259
-
260
- // Create the ER diagram definition in Mermaid syntax
261
- let mermaidDefinition = 'erDiagram\n';
262
-
263
- // We'll store table column data here as we fetch it
264
- const tableColumns = {};
265
-
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
279
- tables.forEach(function(table) {
280
- const tableName = table.name;
281
- mermaidDefinition += ` ${tableName} {\n`;
282
- mermaidDefinition += ` string id\n`;
283
- mermaidDefinition += ' }\n';
284
-
285
- // Start loading column data asynchronously
286
- fetch(`<%= dbviewer.tables_path %>/${tableName}?format=json`, {
287
- headers: {
288
- 'Accept': 'application/json',
289
- 'X-Requested-With': 'XMLHttpRequest'
290
- }
291
- })
292
- .then(response => response.json())
293
- .then(data => {
294
- if (data && data.columns) {
295
- tableColumns[tableName] = data.columns;
296
- columnsLoadedCount++;
297
-
298
- // Update progress bar
299
- updateTableProgress(columnsLoadedCount, totalTables);
300
-
301
- checkIfReadyToUpdate();
302
- }
303
- })
304
- .catch(error => {
305
- console.error(`Error fetching columns for table ${tableName}:`, error);
306
- columnsLoadedCount++;
307
- updateTableProgress(columnsLoadedCount, totalTables);
308
- checkIfReadyToUpdate();
309
- });
310
- });
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
-
324
- // Track if we're currently updating the diagram
325
- let isUpdatingDiagram = false;
326
-
327
- // Function to update the diagram once we have all data
328
- function updateDiagramWithFullData() {
329
- // Prevent multiple simultaneous updates
330
- if (isUpdatingDiagram) return;
331
-
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`;
343
-
344
- const columns = tableColumns[tableName] || [];
345
- columns.forEach(column => {
346
- updatedDefinition += ` ${column.type || 'string'} ${column.name}\n`;
347
- });
348
-
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');
377
-
378
- try {
379
- // Remove from temp container without destroying
380
- tempContainer.removeChild(erdDiv);
381
-
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
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);
401
- isUpdatingDiagram = false;
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);
408
- });
409
- }
410
-
411
- // Get the container reference for later use
412
- const container = document.getElementById('erd-container');
413
-
414
- // SVG Pan Zoom instance
415
- let panZoomInstance = null;
416
-
417
- // Setup zoom controls using svg-pan-zoom library
418
- function setupZoomControls() {
419
- const diagramContainer = document.getElementById('erd-container');
420
- const svgElement = diagramContainer.querySelector('svg');
421
-
422
- if (!svgElement) {
423
- console.warn('SVG element not found for zoom controls');
424
- return;
425
- }
426
-
427
- // Make sure SVG has proper attributes for zooming
428
- svgElement.setAttribute('width', '100%');
429
- svgElement.setAttribute('height', '100%');
430
-
431
- // Initialize svg-pan-zoom
432
- panZoomInstance = svgPanZoom(svgElement, {
433
- zoomEnabled: true,
434
- controlIconsEnabled: false,
435
- fit: true,
436
- center: true,
437
- minZoom: 0.1,
438
- maxZoom: 20,
439
- zoomScaleSensitivity: 0.3,
440
- onZoom: function(newZoom) {
441
- // Update zoom percentage display
442
- const zoomDisplay = document.getElementById('zoomPercentage');
443
- if (zoomDisplay) {
444
- zoomDisplay.textContent = `${Math.round(newZoom * 100)}%`;
445
- }
446
- }
447
- });
448
-
449
- // Set initial zoom to 100%
450
- panZoomInstance.zoom(1);
451
-
452
- // Add event listeners for zoom controls
453
- document.getElementById('zoomIn').addEventListener('click', function() {
454
- panZoomInstance.zoomIn();
455
- });
456
-
457
- document.getElementById('zoomOut').addEventListener('click', function() {
458
- panZoomInstance.zoomOut();
459
- });
460
-
461
- document.getElementById('resetView').addEventListener('click', function() {
462
- panZoomInstance.reset();
463
- });
464
-
465
- // Update initial percentage display
466
- const zoomDisplay = document.getElementById('zoomPercentage');
467
- if (zoomDisplay) {
468
- zoomDisplay.textContent = '100%';
469
- }
470
-
471
- // Mark diagram as ready for download
472
- diagramReady = true;
473
- }
474
-
475
- // Function to download the ERD as SVG
476
- function downloadAsSVG() {
477
- if (!diagramReady) {
478
- alert('Please wait for the diagram to finish loading.');
479
- return;
480
- }
481
-
482
- // Show loading indicator
483
- showDownloadingIndicator('SVG');
484
-
485
- try {
486
- // Get the SVG element
487
- const svgElement = document.querySelector('#erd-container svg');
488
- if (!svgElement) {
489
- alert('SVG diagram not found.');
490
- return;
491
- }
492
-
493
- // Create a clone of the SVG to modify for download
494
- const clonedSvg = svgElement.cloneNode(true);
495
-
496
- // Set explicit dimensions to ensure proper rendering
497
- clonedSvg.setAttribute('width', svgElement.getBoundingClientRect().width);
498
- clonedSvg.setAttribute('height', svgElement.getBoundingClientRect().height);
499
-
500
- // Convert SVG to a string
501
- const serializer = new XMLSerializer();
502
- let svgString = serializer.serializeToString(clonedSvg);
503
-
504
- // Add XML declaration and doctype
505
- svgString = '<?xml version="1.0" standalone="no"?>\n' + svgString;
506
-
507
- // Create a Blob with the SVG data
508
- const blob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
509
-
510
- // Create a timestamp for filename
511
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
512
-
513
- // Create download link and trigger download
514
- const downloadLink = document.createElement('a');
515
- downloadLink.href = URL.createObjectURL(blob);
516
- downloadLink.download = `database_erd_${timestamp}.svg`;
517
- document.body.appendChild(downloadLink);
518
- downloadLink.click();
519
- document.body.removeChild(downloadLink);
520
- } catch (error) {
521
- console.error('Error downloading SVG:', error);
522
- alert('Error downloading SVG. Please check console for details.');
523
- }
524
- }
525
-
526
- // Function to download the ERD as PNG
527
- function downloadAsPNG() {
528
- if (!diagramReady) {
529
- alert('Please wait for the diagram to finish loading.');
530
- return;
531
- }
532
-
533
- // Show loading indicator
534
- showDownloadingIndicator('PNG');
535
-
536
- try {
537
- // Get the SVG element
538
- const svgElement = document.querySelector('#erd-container svg');
539
- if (!svgElement) {
540
- alert('SVG diagram not found.');
541
- return;
542
- }
543
-
544
- // Create a clone of the SVG to modify for download
545
- const clonedSvg = svgElement.cloneNode(true);
546
-
547
- // Set explicit dimensions to ensure proper rendering
548
- const width = svgElement.getBoundingClientRect().width;
549
- const height = svgElement.getBoundingClientRect().height;
550
- clonedSvg.setAttribute('width', width);
551
- clonedSvg.setAttribute('height', height);
552
-
553
- // Convert SVG to a string
554
- const serializer = new XMLSerializer();
555
- const svgString = serializer.serializeToString(clonedSvg);
556
-
557
- // Create a Blob with the SVG data
558
- const svgBlob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
559
- const svgUrl = URL.createObjectURL(svgBlob);
560
-
561
- // Create an Image object to draw to canvas
562
- const img = new Image();
563
- img.onload = function() {
564
- // Create canvas with appropriate dimensions
565
- const canvas = document.createElement('canvas');
566
- canvas.width = width * 2; // Scale up for better quality
567
- canvas.height = height * 2;
568
-
569
- // Get drawing context and scale it
570
- const ctx = canvas.getContext('2d');
571
- ctx.scale(2, 2); // Scale up for better quality
572
-
573
- // Draw white background (SVG may have transparency)
574
- ctx.fillStyle = 'white';
575
- ctx.fillRect(0, 0, width, height);
576
-
577
- // Draw the image onto the canvas
578
- ctx.drawImage(img, 0, 0, width, height);
579
-
580
- // Create timestamp for filename
581
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
582
-
583
- // Convert canvas to PNG and trigger download
584
- canvas.toBlob(function(blob) {
585
- const downloadLink = document.createElement('a');
586
- downloadLink.href = URL.createObjectURL(blob);
587
- downloadLink.download = `database_erd_${timestamp}.png`;
588
- document.body.appendChild(downloadLink);
589
- downloadLink.click();
590
- document.body.removeChild(downloadLink);
591
- }, 'image/png');
592
-
593
- // Clean up
594
- URL.revokeObjectURL(svgUrl);
595
- };
596
-
597
- // Set the image source to the SVG URL
598
- img.src = svgUrl;
599
- } catch (error) {
600
- console.error('Error downloading PNG:', error);
601
- alert('Error downloading PNG. Please check console for details.');
602
- }
603
- }
604
-
605
- // Set up event listeners for download buttons
606
- document.getElementById('downloadSvg').addEventListener('click', function(e) {
607
- e.preventDefault();
608
- downloadAsSVG();
609
- });
610
-
611
- document.getElementById('downloadPng').addEventListener('click', function(e) {
612
- e.preventDefault();
613
- downloadAsPNG();
614
- });
615
- });
616
- </script>
617
-
618
- <style>
619
- #erd-container {
620
- overflow: auto;
621
- height: calc(100vh - 125px);
622
- padding: 20px;
623
- position: relative;
624
- }
625
-
626
- .mermaid {
627
- display: flex;
628
- justify-content: center;
629
- min-width: 100%;
630
- }
631
-
632
- /* Loading state styling */
633
- #erd-loading {
634
- background-color: var(--bs-body-bg);
635
- }
636
-
637
- #erd-loading .text-center p {
638
- margin-bottom: 0.5rem;
639
- font-weight: 500;
640
- }
641
-
642
- #erd-loading .text-center small {
643
- font-size: 0.875rem;
644
- }
645
-
646
- /* Error state styling */
647
- #erd-error {
648
- max-width: 600px;
649
- margin: 2rem auto;
650
- }
651
-
652
- #erd-error h5 {
653
- color: var(--bs-danger);
654
- margin-bottom: 0.75rem;
655
- }
656
-
657
- #erd-error-details {
658
- font-size: 0.8rem;
659
- max-height: 150px;
660
- overflow-y: auto;
661
- }
662
-
663
- /* SVG Pan Zoom styles */
664
- .svg-pan-zoom_viewport {
665
- transition: 0.2s;
666
- }
667
-
668
- /* Make sure SVG maintains its size */
669
- #erd-container svg {
670
- width: 100%;
671
- height: auto;
672
- display: block;
673
- min-width: 800px;
674
- min-height: 600px;
675
- }
676
-
677
- /* Override mermaid defaults for a better look */
678
- .entityBox {
679
- fill: #f8f9fa;
680
- stroke: #6c757d;
681
- }
682
-
683
- .entityLabel, .mermaid .label {
684
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
685
- font-size: 20px !important;
686
- }
687
-
688
- /* Dark mode overrides - comprehensive styling like mini ERD */
689
- [data-bs-theme="dark"] .entityBox {
690
- fill: #2D3748;
691
- stroke: #6ea8fe;
692
- }
693
-
694
- [data-bs-theme="dark"] .entityLabel,
695
- [data-bs-theme="dark"] .mermaid .label {
696
- color: #f8f9fa;
697
- }
698
-
699
- [data-bs-theme="dark"] #erd-error-details {
700
- background-color: var(--bs-dark) !important;
701
- color: var(--bs-light);
702
- border-color: var(--bs-border-color);
703
- }
704
-
705
- /* Dark mode: Update mermaid diagram elements */
706
- [data-bs-theme="dark"] .mermaid .er .entityBox {
707
- fill: #2D3748 !important;
708
- stroke: #6ea8fe !important;
709
- stroke-width: 1.5px !important;
710
- }
711
-
712
- [data-bs-theme="dark"] .mermaid .er .entityLabel {
713
- fill: #f8f9fa !important;
714
- color: #f8f9fa !important;
715
- }
716
-
717
- [data-bs-theme="dark"] .mermaid .er .relationshipLine {
718
- stroke: #6ea8fe !important;
719
- stroke-width: 2px !important;
720
- }
721
-
722
- [data-bs-theme="dark"] .mermaid .er .relationshipLabel {
723
- fill: #f8f9fa !important;
724
- color: #f8f9fa !important;
725
- }
726
-
727
- [data-bs-theme="dark"] .mermaid .er .attributeBoxEven,
728
- [data-bs-theme="dark"] .mermaid .er .attributeBoxOdd {
729
- fill: #374151 !important;
730
- }
731
-
732
- [data-bs-theme="dark"] .mermaid text {
733
- fill: #f8f9fa !important;
734
- }
735
-
736
- /* Loading indicator dark mode */
737
- [data-bs-theme="dark"] #erd-loading {
738
- background-color: var(--bs-dark);
739
- color: var(--bs-light);
740
- }
741
-
742
- [data-bs-theme="dark"] #erd-loading .spinner-border {
743
- color: #6ea8fe;
744
- }
745
-
746
- /* Zoom percentage display styling */
747
- #zoomPercentage {
748
- font-size: 0.9rem;
749
- font-weight: 500;
750
- width: 45px;
751
- display: inline-block;
752
- text-align: center;
753
- }
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
-
771
- /* Mermaid override for text size */
772
- .mermaid .entityLabel div {
773
- font-size: 20px !important;
774
- }
775
-
776
- .mermaid .er.relationshipLabel {
777
- font-size: 20px !important;
778
- }
779
-
780
- /* Enhanced table highlighting for current table */
781
- .current-table-highlight rect {
782
- fill: var(--bs-primary-bg-subtle) !important;
783
- stroke: var(--bs-primary) !important;
784
- stroke-width: 2px !important;
785
- }
786
-
787
- [data-bs-theme="dark"] .current-table-highlight rect {
788
- fill: #2c3034 !important;
789
- stroke: #6ea8fe !important;
790
- stroke-width: 2px !important;
791
- }
792
- </style>
91
+ <input type="text" id="tables" class="d-none" value='<%= raw @tables.to_json %>'>
92
+ <input type="text" id="tables_path" class="d-none" value='<%= dbviewer.tables_path %>'>
93
+ <input type="text" id="relationships_api_path" class="d-none" value='<%= dbviewer.relationships_api_entity_relationship_diagrams_path %>'>