dbviewer 0.3.1
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +250 -0
- data/Rakefile +8 -0
- data/app/assets/stylesheets/dbviewer/application.css +21 -0
- data/app/assets/stylesheets/dbviewer/dbviewer.css +0 -0
- data/app/assets/stylesheets/dbviewer/enhanced.css +0 -0
- data/app/controllers/concerns/dbviewer/database_operations.rb +354 -0
- data/app/controllers/concerns/dbviewer/error_handling.rb +42 -0
- data/app/controllers/concerns/dbviewer/pagination_concern.rb +43 -0
- data/app/controllers/dbviewer/application_controller.rb +21 -0
- data/app/controllers/dbviewer/databases_controller.rb +0 -0
- data/app/controllers/dbviewer/entity_relationship_diagrams_controller.rb +24 -0
- data/app/controllers/dbviewer/home_controller.rb +10 -0
- data/app/controllers/dbviewer/logs_controller.rb +39 -0
- data/app/controllers/dbviewer/tables_controller.rb +73 -0
- data/app/helpers/dbviewer/application_helper.rb +118 -0
- data/app/jobs/dbviewer/application_job.rb +4 -0
- data/app/mailers/dbviewer/application_mailer.rb +6 -0
- data/app/models/dbviewer/application_record.rb +5 -0
- data/app/services/dbviewer/file_storage.rb +0 -0
- data/app/services/dbviewer/in_memory_storage.rb +0 -0
- data/app/services/dbviewer/query_analyzer.rb +0 -0
- data/app/services/dbviewer/query_collection.rb +0 -0
- data/app/services/dbviewer/query_logger.rb +0 -0
- data/app/services/dbviewer/query_parser.rb +82 -0
- data/app/services/dbviewer/query_storage.rb +0 -0
- data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +564 -0
- data/app/views/dbviewer/home/index.html.erb +237 -0
- data/app/views/dbviewer/logs/index.html.erb +614 -0
- data/app/views/dbviewer/shared/_sidebar.html.erb +177 -0
- data/app/views/dbviewer/tables/_table_structure.html.erb +102 -0
- data/app/views/dbviewer/tables/index.html.erb +128 -0
- data/app/views/dbviewer/tables/query.html.erb +600 -0
- data/app/views/dbviewer/tables/show.html.erb +271 -0
- data/app/views/layouts/dbviewer/application.html.erb +728 -0
- data/config/routes.rb +22 -0
- data/lib/dbviewer/configuration.rb +79 -0
- data/lib/dbviewer/database_manager.rb +450 -0
- data/lib/dbviewer/engine.rb +20 -0
- data/lib/dbviewer/initializer.rb +23 -0
- data/lib/dbviewer/logger.rb +102 -0
- data/lib/dbviewer/query_analyzer.rb +109 -0
- data/lib/dbviewer/query_collection.rb +41 -0
- data/lib/dbviewer/query_parser.rb +82 -0
- data/lib/dbviewer/sql_validator.rb +194 -0
- data/lib/dbviewer/storage/base.rb +31 -0
- data/lib/dbviewer/storage/file_storage.rb +96 -0
- data/lib/dbviewer/storage/in_memory_storage.rb +59 -0
- data/lib/dbviewer/version.rb +3 -0
- data/lib/dbviewer.rb +65 -0
- data/lib/tasks/dbviewer_tasks.rake +4 -0
- metadata +126 -0
@@ -0,0 +1,728 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html data-bs-theme="light">
|
3
|
+
<head>
|
4
|
+
<title><%= content_for?(:title) ? yield(:title) + " - DB Viewer" : "DB Viewer" %></title>
|
5
|
+
<%= csrf_meta_tags %>
|
6
|
+
<%= csp_meta_tag %>
|
7
|
+
|
8
|
+
<!-- Prevent theme flash during page load -->
|
9
|
+
<script>
|
10
|
+
(function() {
|
11
|
+
const savedTheme = localStorage.getItem('dbviewerTheme');
|
12
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
13
|
+
|
14
|
+
if (savedTheme === 'dark' || (!savedTheme && prefersDark)) {
|
15
|
+
document.documentElement.setAttribute('data-bs-theme', 'dark');
|
16
|
+
}
|
17
|
+
})();
|
18
|
+
</script>
|
19
|
+
|
20
|
+
<%= yield :head %>
|
21
|
+
|
22
|
+
<!-- Bootstrap CSS -->
|
23
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
24
|
+
<!-- Bootstrap Icons -->
|
25
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
26
|
+
<!-- Chart.js for Data Visualization -->
|
27
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
|
28
|
+
|
29
|
+
<style>
|
30
|
+
/* Core layout styles */
|
31
|
+
.dbviewer-wrapper { display: flex; flex-direction: column; min-height: 100vh; }
|
32
|
+
.dbviewer-navbar { height: 56px; }
|
33
|
+
.dbviewer-navbar-spacer { height: 56px; } /* Creates space for the fixed navbar */
|
34
|
+
.dbviewer-content { display: flex; flex: 1; min-height: calc(100vh - 56px); padding-top: 0; }
|
35
|
+
|
36
|
+
/* Smooth theme transitions */
|
37
|
+
html {
|
38
|
+
transition: background-color 0.2s ease;
|
39
|
+
}
|
40
|
+
|
41
|
+
body {
|
42
|
+
transition: color 0.2s ease, background-color 0.2s ease;
|
43
|
+
}
|
44
|
+
|
45
|
+
/* Smooth theme transitions */
|
46
|
+
html {
|
47
|
+
transition: background-color 0.2s ease;
|
48
|
+
}
|
49
|
+
|
50
|
+
body {
|
51
|
+
transition: color 0.2s ease, background-color 0.2s ease;
|
52
|
+
}
|
53
|
+
|
54
|
+
/* Sidebar styles */
|
55
|
+
.dbviewer-sidebar {
|
56
|
+
width: 260px;
|
57
|
+
height: calc(100vh - 56px);
|
58
|
+
position: fixed;
|
59
|
+
top: 56px; /* Positioned right below the fixed navbar */
|
60
|
+
left: 0;
|
61
|
+
z-index: 1000;
|
62
|
+
display: flex;
|
63
|
+
flex-direction: column;
|
64
|
+
transition: transform 0.3s ease, background-color 0.2s ease, border-color 0.2s ease;
|
65
|
+
overflow: hidden; /* Changed from overflow-y: auto */
|
66
|
+
}
|
67
|
+
|
68
|
+
/* Dark mode overrides */
|
69
|
+
[data-bs-theme="light"] .dbviewer-sidebar {
|
70
|
+
background: #f8f9fa;
|
71
|
+
border-right: 1px solid #dee2e6;
|
72
|
+
}
|
73
|
+
|
74
|
+
[data-bs-theme="dark"] .dbviewer-sidebar {
|
75
|
+
background: #212529;
|
76
|
+
border-right: 1px solid #495057;
|
77
|
+
}
|
78
|
+
|
79
|
+
.dbviewer-sidebar-header {
|
80
|
+
padding: 1rem 1.25rem;
|
81
|
+
font-weight: 500;
|
82
|
+
align-items: center;
|
83
|
+
justify-content: space-between;
|
84
|
+
flex-shrink: 0;
|
85
|
+
}
|
86
|
+
|
87
|
+
[data-bs-theme="light"] .dbviewer-sidebar-header {
|
88
|
+
border-bottom: 1px solid #dee2e6;
|
89
|
+
background: #f1f3f5;
|
90
|
+
}
|
91
|
+
|
92
|
+
[data-bs-theme="dark"] .dbviewer-sidebar-header {
|
93
|
+
border-bottom: 1px solid #495057;
|
94
|
+
background: #343a40;
|
95
|
+
}
|
96
|
+
|
97
|
+
.dbviewer-sidebar-header h5 {
|
98
|
+
overflow: hidden;
|
99
|
+
text-overflow: ellipsis;
|
100
|
+
white-space: nowrap;
|
101
|
+
max-width: 80%;
|
102
|
+
cursor: default;
|
103
|
+
position: relative;
|
104
|
+
}
|
105
|
+
|
106
|
+
/* Tooltip for database name */
|
107
|
+
.dbviewer-sidebar-header h5[title]:hover::after {
|
108
|
+
content: attr(title);
|
109
|
+
position: absolute;
|
110
|
+
left: 0;
|
111
|
+
top: 100%;
|
112
|
+
z-index: 1000;
|
113
|
+
background-color: #343a40;
|
114
|
+
color: white;
|
115
|
+
padding: 0.375rem 0.75rem;
|
116
|
+
border-radius: 0.25rem;
|
117
|
+
font-size: 0.875rem;
|
118
|
+
white-space: normal;
|
119
|
+
max-width: 250px;
|
120
|
+
box-shadow: 0 2px 10px rgba(0,0,0,.2);
|
121
|
+
margin-top: 6px;
|
122
|
+
}
|
123
|
+
|
124
|
+
.dbviewer-sidebar-top {
|
125
|
+
flex-shrink: 0;
|
126
|
+
padding-bottom: 0.25rem;
|
127
|
+
}
|
128
|
+
|
129
|
+
[data-bs-theme="light"] .dbviewer-sidebar-top {
|
130
|
+
background: #f8f9fa;
|
131
|
+
border-bottom: 1px solid #eaeaea;
|
132
|
+
}
|
133
|
+
|
134
|
+
[data-bs-theme="dark"] .dbviewer-sidebar-top {
|
135
|
+
background: #212529;
|
136
|
+
border-bottom: 1px solid #495057;
|
137
|
+
}
|
138
|
+
|
139
|
+
.dbviewer-sidebar-content {
|
140
|
+
flex: 1;
|
141
|
+
overflow-y: auto;
|
142
|
+
padding: 0;
|
143
|
+
height: 100%;
|
144
|
+
}
|
145
|
+
|
146
|
+
.dbviewer-main {
|
147
|
+
flex: 1;
|
148
|
+
margin-left: 260px;
|
149
|
+
padding: 2rem 2.5rem;
|
150
|
+
padding-top: 1.5rem; /* Adjusted for fixed header */
|
151
|
+
min-width: 0;
|
152
|
+
}
|
153
|
+
|
154
|
+
/* Mobile responsiveness */
|
155
|
+
@media (max-width: 991.98px) {
|
156
|
+
.dbviewer-sidebar { transform: translateX(-100%); }
|
157
|
+
.dbviewer-sidebar.active { transform: translateX(0); box-shadow: 5px 0 15px rgba(0,0,0,0.1); }
|
158
|
+
.dbviewer-main { margin-left: 0; padding: 1rem; }
|
159
|
+
|
160
|
+
/* Overlay for mobile sidebar */
|
161
|
+
.dbviewer-sidebar-overlay {
|
162
|
+
position: fixed;
|
163
|
+
top: 56px; /* Start below the fixed navbar */
|
164
|
+
left: 0;
|
165
|
+
right: 0;
|
166
|
+
bottom: 0;
|
167
|
+
background: rgba(0,0,0,0.5);
|
168
|
+
z-index: 999;
|
169
|
+
opacity: 0;
|
170
|
+
visibility: hidden;
|
171
|
+
transition: opacity 0.3s, visibility 0.3s;
|
172
|
+
}
|
173
|
+
|
174
|
+
[data-bs-theme="light"] .dbviewer-sidebar-overlay {
|
175
|
+
background: rgba(0,0,0,0.5);
|
176
|
+
}
|
177
|
+
|
178
|
+
[data-bs-theme="dark"] .dbviewer-sidebar-overlay {
|
179
|
+
background: rgba(0,0,0,0.7);
|
180
|
+
}
|
181
|
+
|
182
|
+
.dbviewer-sidebar-overlay.active {
|
183
|
+
opacity: 1;
|
184
|
+
visibility: visible;
|
185
|
+
}
|
186
|
+
|
187
|
+
body.dbviewer-sidebar-open {
|
188
|
+
overflow: hidden;
|
189
|
+
}
|
190
|
+
}
|
191
|
+
|
192
|
+
@media (max-width: 767.98px) {
|
193
|
+
.dbviewer-sidebar { width: 210px; }
|
194
|
+
.dbviewer-main { padding: 0.75rem; }
|
195
|
+
.dbviewer-sidebar-header h5 { max-width: 70%; font-size: 1rem; }
|
196
|
+
}
|
197
|
+
|
198
|
+
/* Table filter input */
|
199
|
+
.dbviewer-table-filter-container { position: relative; padding: 0.5rem 1rem; margin-bottom: 0.25rem; }
|
200
|
+
.dbviewer-table-filter-icon { position: absolute; left: 1.25rem; top: 50%; transform: translateY(-50%); color: #6c757d; font-size: 0.85rem; padding: 0.25rem; width: 1.5rem; text-align: center; }
|
201
|
+
.dbviewer-table-filter { border-radius: 20px; padding-left: 2.5rem; margin-bottom: 1rem; }
|
202
|
+
|
203
|
+
/* Table structure and visualization components */
|
204
|
+
.dbviewer-card { box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,0.075); }
|
205
|
+
|
206
|
+
[data-bs-theme="light"] .dbviewer-card {
|
207
|
+
border: 1px solid #eaeaea;
|
208
|
+
}
|
209
|
+
|
210
|
+
[data-bs-theme="dark"] .dbviewer-card {
|
211
|
+
border: 1px solid #495057;
|
212
|
+
}
|
213
|
+
|
214
|
+
.dbviewer-scrollable { max-height: 500px; overflow-y: auto; }
|
215
|
+
|
216
|
+
/* Badge styling for dark mode */
|
217
|
+
[data-bs-theme="dark"] .bg-secondary-subtle {
|
218
|
+
background-color: rgba(255, 255, 255, 0.15) !important;
|
219
|
+
color: #e9ecef !important;
|
220
|
+
}
|
221
|
+
|
222
|
+
[data-bs-theme="light"] .bg-secondary-subtle {
|
223
|
+
background-color: #e9ecef !important;
|
224
|
+
color: #212529 !important;
|
225
|
+
}
|
226
|
+
|
227
|
+
/* Table header styling */
|
228
|
+
.dbviewer-table-header {
|
229
|
+
background-color: rgba(0, 0, 0, 0.05);
|
230
|
+
}
|
231
|
+
|
232
|
+
[data-bs-theme="dark"] .dbviewer-table-header {
|
233
|
+
background-color: rgba(255, 255, 255, 0.05);
|
234
|
+
}
|
235
|
+
|
236
|
+
/* List group styling for dark mode */
|
237
|
+
[data-bs-theme="dark"] .list-group-item {
|
238
|
+
background-color: var(--bs-dark);
|
239
|
+
border-color: rgba(255, 255, 255, 0.15);
|
240
|
+
color: var(--bs-light);
|
241
|
+
}
|
242
|
+
|
243
|
+
[data-bs-theme="dark"] .dbviewer-table-header {
|
244
|
+
position: sticky;
|
245
|
+
top: 0;
|
246
|
+
background: #212529;
|
247
|
+
z-index: 1;
|
248
|
+
}
|
249
|
+
|
250
|
+
/* Equal height for timeline and structure cards */
|
251
|
+
.two-column-layout .card { height: 100%; display: flex; flex-direction: column; }
|
252
|
+
.two-column-layout .card-body { flex: 1; display: flex; flex-direction: column; }
|
253
|
+
.two-column-layout .chart-container { flex: 1; min-height: 250px; }
|
254
|
+
.two-column-layout .structure-container { padding: 0; }
|
255
|
+
.two-column-layout .tab-content { flex: 1; overflow: hidden; display: flex; flex-direction: column; }
|
256
|
+
.two-column-layout .tab-pane { flex: 1; overflow: hidden; }
|
257
|
+
.two-column-layout .table-responsive { overflow-x: auto; }
|
258
|
+
|
259
|
+
[data-bs-theme="light"] .two-column-layout .sticky-top {
|
260
|
+
top: 0;
|
261
|
+
z-index: 999;
|
262
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
263
|
+
}
|
264
|
+
|
265
|
+
[data-bs-theme="dark"] .two-column-layout .sticky-top {
|
266
|
+
top: 0;
|
267
|
+
z-index: 999;
|
268
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
|
269
|
+
}
|
270
|
+
|
271
|
+
/* Dark mode toggle */
|
272
|
+
.theme-toggle {
|
273
|
+
background: transparent;
|
274
|
+
border: none;
|
275
|
+
padding: 0.25rem 0.5rem;
|
276
|
+
cursor: pointer;
|
277
|
+
display: flex;
|
278
|
+
align-items: center;
|
279
|
+
color: white;
|
280
|
+
opacity: 0.8;
|
281
|
+
transition: opacity 0.2s ease, transform 0.3s ease;
|
282
|
+
}
|
283
|
+
|
284
|
+
.theme-toggle:hover {
|
285
|
+
opacity: 1;
|
286
|
+
transform: rotate(12deg);
|
287
|
+
}
|
288
|
+
|
289
|
+
.theme-toggle:active {
|
290
|
+
transform: rotate(24deg);
|
291
|
+
}
|
292
|
+
|
293
|
+
.theme-toggle .bi-sun,
|
294
|
+
[data-bs-theme="dark"] .theme-toggle .bi-moon {
|
295
|
+
display: none;
|
296
|
+
}
|
297
|
+
|
298
|
+
[data-bs-theme="dark"] .theme-toggle .bi-sun {
|
299
|
+
display: inline-block;
|
300
|
+
}
|
301
|
+
|
302
|
+
.theme-toggle .bi-moon {
|
303
|
+
display: inline-block;
|
304
|
+
}
|
305
|
+
|
306
|
+
.theme-toggle i {
|
307
|
+
font-size: 1.2rem;
|
308
|
+
}
|
309
|
+
|
310
|
+
/* Header styles for dark mode */
|
311
|
+
.navbar {
|
312
|
+
transition: background-color 0.3s ease, border-color 0.3s ease;
|
313
|
+
}
|
314
|
+
|
315
|
+
[data-bs-theme="light"] .navbar.bg-primary {
|
316
|
+
background-color: #0d6efd !important;
|
317
|
+
}
|
318
|
+
|
319
|
+
[data-bs-theme="dark"] .navbar.bg-primary {
|
320
|
+
background-color: #212529 !important;
|
321
|
+
border-bottom: 1px solid #495057;
|
322
|
+
}
|
323
|
+
|
324
|
+
/* Sidebar list group styling for dark mode */
|
325
|
+
[data-bs-theme="dark"] .list-group-item {
|
326
|
+
background-color: #212529;
|
327
|
+
border-color: #495057;
|
328
|
+
color: #e9ecef;
|
329
|
+
}
|
330
|
+
|
331
|
+
[data-bs-theme="dark"] .list-group-item.active {
|
332
|
+
background-color: #0d6efd;
|
333
|
+
border-color: #0d6efd;
|
334
|
+
}
|
335
|
+
|
336
|
+
[data-bs-theme="dark"] .list-group-item-action:hover,
|
337
|
+
[data-bs-theme="dark"] .list-group-item-action:focus {
|
338
|
+
background-color: #343a40;
|
339
|
+
color: #f8f9fa;
|
340
|
+
}
|
341
|
+
|
342
|
+
[data-bs-theme="dark"] .list-group-item.text-muted {
|
343
|
+
color: #adb5bd !important;
|
344
|
+
}
|
345
|
+
|
346
|
+
/* Make card headers in dark mode look better */
|
347
|
+
[data-bs-theme="dark"] .card-header {
|
348
|
+
background-color: #2c3034;
|
349
|
+
border-bottom: 1px solid #495057;
|
350
|
+
}
|
351
|
+
|
352
|
+
/* Empty data messages */
|
353
|
+
[data-bs-theme="light"] .empty-data-message {
|
354
|
+
color: #6c757d;
|
355
|
+
}
|
356
|
+
|
357
|
+
[data-bs-theme="dark"] .empty-data-message {
|
358
|
+
color: #adb5bd;
|
359
|
+
}
|
360
|
+
|
361
|
+
/* Query timestamp */
|
362
|
+
[data-bs-theme="light"] .query-timestamp {
|
363
|
+
color: #6c757d;
|
364
|
+
}
|
365
|
+
|
366
|
+
[data-bs-theme="dark"] .query-timestamp {
|
367
|
+
color: #adb5bd;
|
368
|
+
}
|
369
|
+
|
370
|
+
/* Code blocks in tables */
|
371
|
+
[data-bs-theme="dark"] table code {
|
372
|
+
color: #e685b5;
|
373
|
+
background-color: rgba(230, 133, 181, 0.1);
|
374
|
+
padding: 2px 4px;
|
375
|
+
border-radius: 3px;
|
376
|
+
}
|
377
|
+
|
378
|
+
/* Table links */
|
379
|
+
[data-bs-theme="dark"] .table a {
|
380
|
+
color: #6ea8fe;
|
381
|
+
text-decoration: none;
|
382
|
+
}
|
383
|
+
|
384
|
+
[data-bs-theme="dark"] .table a:hover {
|
385
|
+
color: #8bb9fe;
|
386
|
+
text-decoration: underline;
|
387
|
+
}
|
388
|
+
|
389
|
+
/* SQL query code in tables */
|
390
|
+
code.sql-query-code {
|
391
|
+
display: inline-block;
|
392
|
+
white-space: nowrap;
|
393
|
+
max-width: 100%;
|
394
|
+
overflow: hidden;
|
395
|
+
text-overflow: ellipsis;
|
396
|
+
padding: 2px 4px;
|
397
|
+
border-radius: 3px;
|
398
|
+
}
|
399
|
+
|
400
|
+
[data-bs-theme="light"] code.sql-query-code {
|
401
|
+
background-color: rgba(0, 0, 0, 0.05);
|
402
|
+
}
|
403
|
+
|
404
|
+
[data-bs-theme="dark"] code.sql-query-code {
|
405
|
+
background-color: rgba(255, 255, 255, 0.1);
|
406
|
+
color: #65cdff;
|
407
|
+
}
|
408
|
+
|
409
|
+
/* Query duration styling */
|
410
|
+
.query-duration {
|
411
|
+
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
412
|
+
font-size: 0.875rem;
|
413
|
+
}
|
414
|
+
|
415
|
+
[data-bs-theme="light"] .query-duration-slow {
|
416
|
+
color: #dc3545;
|
417
|
+
font-weight: 500;
|
418
|
+
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
419
|
+
font-size: 0.875rem;
|
420
|
+
}
|
421
|
+
|
422
|
+
[data-bs-theme="dark"] .query-duration-slow {
|
423
|
+
color: #ff6b6b;
|
424
|
+
font-weight: 500;
|
425
|
+
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
426
|
+
font-size: 0.875rem;
|
427
|
+
}
|
428
|
+
|
429
|
+
/* Database name badge */
|
430
|
+
.database-name-badge {
|
431
|
+
font-size: 0.95rem;
|
432
|
+
font-weight: 500;
|
433
|
+
text-transform: lowercase;
|
434
|
+
letter-spacing: 0.02em;
|
435
|
+
}
|
436
|
+
|
437
|
+
[data-bs-theme="light"] .database-name-badge {
|
438
|
+
background-color: #0d6efd;
|
439
|
+
color: #ffffff;
|
440
|
+
}
|
441
|
+
|
442
|
+
[data-bs-theme="dark"] .database-name-badge {
|
443
|
+
background-color: #0d6efd;
|
444
|
+
color: #ffffff;
|
445
|
+
}
|
446
|
+
|
447
|
+
/* Table headings */
|
448
|
+
[data-bs-theme="dark"] .table thead th {
|
449
|
+
color: #e9ecef;
|
450
|
+
border-color: #495057;
|
451
|
+
border-bottom: 2px solid #495057;
|
452
|
+
}
|
453
|
+
|
454
|
+
/* Card title colors */
|
455
|
+
[data-bs-theme="dark"] .card-title {
|
456
|
+
color: #f8f9fa;
|
457
|
+
}
|
458
|
+
|
459
|
+
/* SQL logs specific styling */
|
460
|
+
[data-bs-theme="dark"] pre.sql-query {
|
461
|
+
background-color: #2c3034;
|
462
|
+
border-color: #495057;
|
463
|
+
}
|
464
|
+
|
465
|
+
[data-bs-theme="dark"] pre.sql-query code {
|
466
|
+
color: #ff8bd0;
|
467
|
+
}
|
468
|
+
|
469
|
+
/* Badge text colors for better contrast in dark mode */
|
470
|
+
[data-bs-theme="dark"] .badge.bg-info {
|
471
|
+
color: #000 !important;
|
472
|
+
}
|
473
|
+
|
474
|
+
/* Alert styling in dark mode */
|
475
|
+
[data-bs-theme="dark"] .alert-info {
|
476
|
+
background-color: rgba(13, 202, 240, 0.2);
|
477
|
+
border-color: rgba(13, 202, 240, 0.3);
|
478
|
+
color: #9eeaf9;
|
479
|
+
}
|
480
|
+
|
481
|
+
/* Background colors for card headers in dark mode */
|
482
|
+
[data-bs-theme="dark"] .bg-danger-subtle {
|
483
|
+
background-color: rgba(220, 53, 69, 0.2) !important;
|
484
|
+
}
|
485
|
+
|
486
|
+
[data-bs-theme="dark"] .bg-info-subtle {
|
487
|
+
background-color: rgba(13, 202, 240, 0.2) !important;
|
488
|
+
}
|
489
|
+
|
490
|
+
/* Text colors for card headers in dark mode */
|
491
|
+
[data-bs-theme="dark"] .text-info {
|
492
|
+
color: #0dcaf0 !important;
|
493
|
+
}
|
494
|
+
|
495
|
+
[data-bs-theme="dark"] .text-danger {
|
496
|
+
color: #ff6b6b !important;
|
497
|
+
}
|
498
|
+
|
499
|
+
/* Styling for form controls in SQL logs page */
|
500
|
+
[data-bs-theme="dark"] .list-group-item {
|
501
|
+
background-color: #212529;
|
502
|
+
border-color: #495057;
|
503
|
+
}
|
504
|
+
|
505
|
+
/* Code block styling for SQL logs */
|
506
|
+
[data-bs-theme="light"] .code-block {
|
507
|
+
background-color: #f8f9fa;
|
508
|
+
}
|
509
|
+
|
510
|
+
[data-bs-theme="dark"] .code-block {
|
511
|
+
background-color: #2c3034;
|
512
|
+
}
|
513
|
+
|
514
|
+
/* Pattern code styling */
|
515
|
+
[data-bs-theme="dark"] .pattern-code {
|
516
|
+
color: #f783ac;
|
517
|
+
}
|
518
|
+
|
519
|
+
/* Query binds styling */
|
520
|
+
[data-bs-theme="light"] .query-binds-summary {
|
521
|
+
color: #6c757d;
|
522
|
+
}
|
523
|
+
|
524
|
+
[data-bs-theme="dark"] .query-binds-summary {
|
525
|
+
color: #adb5bd;
|
526
|
+
}
|
527
|
+
|
528
|
+
[data-bs-theme="dark"] .query-binds {
|
529
|
+
color: #20c997;
|
530
|
+
}
|
531
|
+
|
532
|
+
/* Enhanced code syntax highlighting */
|
533
|
+
[data-bs-theme="dark"] code.syntax-highlighted {
|
534
|
+
color: #ff8bd0;
|
535
|
+
}
|
536
|
+
|
537
|
+
/* Request ID styling */
|
538
|
+
[data-bs-theme="light"] .request-id {
|
539
|
+
color: #6c757d;
|
540
|
+
}
|
541
|
+
|
542
|
+
[data-bs-theme="dark"] .request-id {
|
543
|
+
color: #adb5bd;
|
544
|
+
}
|
545
|
+
|
546
|
+
[data-bs-theme="dark"] .stat-card-bg {
|
547
|
+
background-color: #2c3034;
|
548
|
+
}
|
549
|
+
|
550
|
+
.stat-card-bg {
|
551
|
+
background-color: var(--bs-light);
|
552
|
+
}
|
553
|
+
</style>
|
554
|
+
</head>
|
555
|
+
<body>
|
556
|
+
<div class="dbviewer-wrapper">
|
557
|
+
<!-- Top Navigation Bar (Fixed) -->
|
558
|
+
<nav class="navbar navbar-expand-lg navbar-dark bg-primary dbviewer-navbar fixed-top">
|
559
|
+
<div class="container-fluid">
|
560
|
+
<a class="navbar-brand" href="<%= dbviewer.root_path %>"><i class="bi bi-database-fill me-1"></i>DB Viewer</a>
|
561
|
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
562
|
+
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
563
|
+
<span class="navbar-toggler-icon"></span>
|
564
|
+
</button>
|
565
|
+
<div class="collapse navbar-collapse" id="navbarNav">
|
566
|
+
<ul class="navbar-nav">
|
567
|
+
<li class="nav-item">
|
568
|
+
<%= link_to raw('<i class="bi bi-table"></i> Tables'), dbviewer.tables_path, class: "nav-link #{tables_nav_class}" %>
|
569
|
+
</li>
|
570
|
+
<li class="nav-item">
|
571
|
+
<%= link_to raw('<i class="bi bi-diagram-3"></i> ERD'), dbviewer.entity_relationship_diagrams_path, class: "nav-link #{erd_nav_class}" %>
|
572
|
+
</li>
|
573
|
+
<li class="nav-item">
|
574
|
+
<%= link_to raw('<i class="bi bi-journal-code"></i> SQL Logs'), dbviewer.logs_path, class: "nav-link #{logs_nav_class}" %>
|
575
|
+
</li>
|
576
|
+
</ul>
|
577
|
+
<ul class="navbar-nav ms-auto">
|
578
|
+
<li class="nav-item">
|
579
|
+
<button type="button" class="theme-toggle nav-link" aria-label="<%= theme_toggle_label %>">
|
580
|
+
<%= theme_toggle_icon %>
|
581
|
+
</button>
|
582
|
+
</li>
|
583
|
+
<li class="nav-item">
|
584
|
+
<span class="navbar-text ms-2 text-light d-flex align-items-center">
|
585
|
+
<small><i class="bi bi-database"></i> <%= Rails.env %> environment</small>
|
586
|
+
</span>
|
587
|
+
</li>
|
588
|
+
</ul>
|
589
|
+
</div>
|
590
|
+
</div>
|
591
|
+
</nav>
|
592
|
+
<!-- Spacer to prevent content from hiding under fixed navbar -->
|
593
|
+
<div class="dbviewer-navbar-spacer"></div>
|
594
|
+
|
595
|
+
<!-- Main Content with Sidebar -->
|
596
|
+
<div class="dbviewer-content">
|
597
|
+
<!-- Sidebar (always present) -->
|
598
|
+
<div class="dbviewer-sidebar">
|
599
|
+
<%= yield :sidebar %>
|
600
|
+
</div>
|
601
|
+
|
602
|
+
<!-- Main Content Area -->
|
603
|
+
<div class="dbviewer-main">
|
604
|
+
<div class="dbviewer-main-content">
|
605
|
+
<div class="d-flex d-lg-none align-items-center mb-3">
|
606
|
+
<button class="btn btn-sm btn-outline-primary dbviewer-sidebar-toggle" type="button">
|
607
|
+
<i class="bi bi-list me-1"></i> Tables
|
608
|
+
</button>
|
609
|
+
</div>
|
610
|
+
|
611
|
+
<%= yield %>
|
612
|
+
</div>
|
613
|
+
</div>
|
614
|
+
</div>
|
615
|
+
</div>
|
616
|
+
|
617
|
+
<!-- Bootstrap JS -->
|
618
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
619
|
+
|
620
|
+
<!-- Dark Mode Toggle Script -->
|
621
|
+
<script>
|
622
|
+
document.addEventListener('DOMContentLoaded', function() {
|
623
|
+
// Theme toggle functionality
|
624
|
+
const themeToggleBtn = document.querySelector('.theme-toggle');
|
625
|
+
const htmlElement = document.documentElement;
|
626
|
+
|
627
|
+
// Check for saved theme preference or respect OS preference
|
628
|
+
const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
629
|
+
const savedTheme = localStorage.getItem('dbviewerTheme');
|
630
|
+
|
631
|
+
// Set initial theme
|
632
|
+
if (savedTheme) {
|
633
|
+
htmlElement.setAttribute('data-bs-theme', savedTheme);
|
634
|
+
} else if (prefersDarkMode) {
|
635
|
+
htmlElement.setAttribute('data-bs-theme', 'dark');
|
636
|
+
localStorage.setItem('dbviewerTheme', 'dark');
|
637
|
+
}
|
638
|
+
|
639
|
+
// Toggle theme when button is clicked
|
640
|
+
if (themeToggleBtn) {
|
641
|
+
themeToggleBtn.addEventListener('click', function() {
|
642
|
+
const currentTheme = htmlElement.getAttribute('data-bs-theme');
|
643
|
+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
644
|
+
|
645
|
+
// Update theme
|
646
|
+
htmlElement.setAttribute('data-bs-theme', newTheme);
|
647
|
+
localStorage.setItem('dbviewerTheme', newTheme);
|
648
|
+
|
649
|
+
// Dispatch event for other components to respond to theme change (Monaco editor)
|
650
|
+
const themeChangeEvent = new CustomEvent('dbviewerThemeChanged', {
|
651
|
+
detail: { theme: newTheme }
|
652
|
+
});
|
653
|
+
document.dispatchEvent(themeChangeEvent);
|
654
|
+
});
|
655
|
+
}
|
656
|
+
|
657
|
+
// Check if styles are loaded properly
|
658
|
+
const styleCheck = getComputedStyle(document.documentElement).getPropertyValue('--dbviewer-styles-loaded');
|
659
|
+
if (!styleCheck) {
|
660
|
+
console.log('DBViewer: Using fallback inline styles (asset pipeline may not be available)');
|
661
|
+
} else {
|
662
|
+
console.log('DBViewer: External CSS loaded successfully');
|
663
|
+
}
|
664
|
+
|
665
|
+
const toggleBtn = document.querySelector('.dbviewer-sidebar-toggle');
|
666
|
+
const closeBtn = document.querySelector('.dbviewer-sidebar-close');
|
667
|
+
const sidebar = document.querySelector('.dbviewer-sidebar');
|
668
|
+
const overlay = document.createElement('div');
|
669
|
+
|
670
|
+
// Create and configure overlay for mobile
|
671
|
+
overlay.className = 'dbviewer-sidebar-overlay';
|
672
|
+
document.body.appendChild(overlay);
|
673
|
+
|
674
|
+
function showSidebar() {
|
675
|
+
sidebar.classList.add('active');
|
676
|
+
document.body.classList.add('dbviewer-sidebar-open');
|
677
|
+
setTimeout(() => {
|
678
|
+
overlay.classList.add('active');
|
679
|
+
}, 50);
|
680
|
+
}
|
681
|
+
|
682
|
+
function hideSidebar() {
|
683
|
+
sidebar.classList.remove('active');
|
684
|
+
overlay.classList.remove('active');
|
685
|
+
setTimeout(() => {
|
686
|
+
document.body.classList.remove('dbviewer-sidebar-open');
|
687
|
+
}, 300);
|
688
|
+
}
|
689
|
+
|
690
|
+
if (toggleBtn) {
|
691
|
+
toggleBtn.addEventListener('click', function() {
|
692
|
+
if (sidebar.classList.contains('active')) {
|
693
|
+
hideSidebar();
|
694
|
+
} else {
|
695
|
+
showSidebar();
|
696
|
+
// Focus the search input when sidebar becomes visible
|
697
|
+
setTimeout(() => {
|
698
|
+
const searchInput = document.getElementById('tableSearch');
|
699
|
+
if (searchInput) searchInput.focus();
|
700
|
+
}, 300); // Small delay to allow for animation
|
701
|
+
}
|
702
|
+
});
|
703
|
+
}
|
704
|
+
|
705
|
+
if (closeBtn) {
|
706
|
+
closeBtn.addEventListener('click', function() {
|
707
|
+
hideSidebar();
|
708
|
+
});
|
709
|
+
}
|
710
|
+
|
711
|
+
overlay.addEventListener('click', function() {
|
712
|
+
hideSidebar();
|
713
|
+
});
|
714
|
+
|
715
|
+
// Close sidebar on window resize (from mobile to desktop)
|
716
|
+
let resizeTimer;
|
717
|
+
window.addEventListener('resize', function() {
|
718
|
+
clearTimeout(resizeTimer);
|
719
|
+
resizeTimer = setTimeout(function() {
|
720
|
+
if (window.innerWidth >= 992 && sidebar.classList.contains('active')) {
|
721
|
+
overlay.classList.remove('active');
|
722
|
+
}
|
723
|
+
}, 250);
|
724
|
+
});
|
725
|
+
});
|
726
|
+
</script>
|
727
|
+
</body>
|
728
|
+
</html>
|