dbviewer 0.6.7 → 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 (27) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/dbviewer/entity_relationship_diagram.js +553 -0
  3. data/app/assets/javascripts/dbviewer/home.js +287 -0
  4. data/app/assets/javascripts/dbviewer/layout.js +194 -0
  5. data/app/assets/javascripts/dbviewer/query.js +277 -0
  6. data/app/assets/javascripts/dbviewer/table.js +1563 -0
  7. data/app/assets/stylesheets/dbviewer/application.css +1460 -21
  8. data/app/assets/stylesheets/dbviewer/entity_relationship_diagram.css +181 -0
  9. data/app/assets/stylesheets/dbviewer/home.css +229 -0
  10. data/app/assets/stylesheets/dbviewer/logs.css +64 -0
  11. data/app/assets/stylesheets/dbviewer/query.css +171 -0
  12. data/app/assets/stylesheets/dbviewer/table.css +1144 -0
  13. data/app/views/dbviewer/connections/index.html.erb +0 -30
  14. data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +14 -713
  15. data/app/views/dbviewer/home/index.html.erb +9 -499
  16. data/app/views/dbviewer/logs/index.html.erb +5 -220
  17. data/app/views/dbviewer/tables/index.html.erb +0 -65
  18. data/app/views/dbviewer/tables/query.html.erb +129 -565
  19. data/app/views/dbviewer/tables/show.html.erb +4 -2429
  20. data/app/views/layouts/dbviewer/application.html.erb +13 -1544
  21. data/lib/dbviewer/version.rb +1 -1
  22. metadata +12 -7
  23. data/app/assets/javascripts/dbviewer/connections.js +0 -70
  24. data/app/assets/stylesheets/dbviewer/dbviewer.css +0 -0
  25. data/app/assets/stylesheets/dbviewer/enhanced.css +0 -0
  26. data/app/views/dbviewer/connections/new.html.erb +0 -79
  27. data/app/views/dbviewer/tables/mini_erd.html.erb +0 -517
@@ -4,183 +4,10 @@
4
4
 
5
5
  <% content_for :head do %>
6
6
  <link href="https://cdn.jsdelivr.net/npm/vscode-codicons@0.0.17/dist/codicon.min.css" rel="stylesheet">
7
- <style>
8
- /* Monaco Editor styling */
9
- #monaco-editor {
10
- margin-bottom: 1rem;
11
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
12
- }
13
-
14
- .monaco-editor-container {
15
- border: 1px solid #ced4da;
16
- transition: border-color 0.3s ease, box-shadow 0.3s ease;
17
- }
18
-
19
- [data-bs-theme="dark"] .monaco-editor-container {
20
- border: 1px solid #495057;
21
- }
22
-
23
- [data-bs-theme="dark"] #monaco-editor {
24
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
25
- }
26
-
27
- .example-queries {
28
- display: flex;
29
- flex-wrap: wrap;
30
- gap: 5px;
31
- margin-top: 8px;
32
- }
33
-
34
- .example-query {
35
- display: inline-block;
36
- transition: all 0.2s ease;
37
- cursor: pointer;
38
- font-size: 0.85rem;
39
- white-space: nowrap;
40
- overflow: hidden;
41
- text-overflow: ellipsis;
42
- max-width: 100%;
43
- }
44
-
45
- [data-bs-theme="light"] .example-query {
46
- border-color: #ced4da;
47
- }
48
-
49
- [data-bs-theme="dark"] .example-query {
50
- border-color: #495057;
51
- color: #f8f9fa;
52
- }
53
-
54
- /* Result table styling */
55
- .results-table {
56
- border-collapse: collapse;
57
- }
58
-
59
- [data-bs-theme="dark"] .results-table {
60
- border-color: #495057;
61
- }
62
-
63
- .example-query:hover {
64
- background-color: #0d6efd;
65
- color: white;
66
- border-color: #0d6efd;
67
- }
68
-
69
- /* Keyboard shortcut helper */
70
- .keyboard-hint {
71
- font-size: 0.8rem;
72
- margin-left: 8px;
73
- opacity: 0.7;
74
- }
75
-
76
- [data-bs-theme="light"] .shortcut-hints {
77
- color: #6c757d;
78
- }
79
-
80
- [data-bs-theme="dark"] .shortcut-hints {
81
- color: #adb5bd;
82
- }
83
-
84
- /* Monaco status bar */
85
- .monaco-status-bar {
86
- display: flex;
87
- justify-content: space-between;
88
- padding: 3px 8px;
89
- font-size: 0.75rem;
90
- border-top: none;
91
- border-bottom-left-radius: 4px;
92
- border-bottom-right-radius: 4px;
93
- }
94
-
95
- [data-bs-theme="light"] .monaco-status-bar {
96
- background-color: #f8f9fa;
97
- border: 1px solid #ced4da;
98
- color: #6c757d;
99
- }
100
-
101
- [data-bs-theme="dark"] .monaco-status-bar {
102
- background-color: #343a40;
103
- border: 1px solid #495057;
104
- color: #adb5bd;
105
- }
106
-
107
- .monaco-status-bar .column-info {
108
- font-weight: 500;
109
- }
110
-
111
- /* Table structure styles */
112
- #tableStructureHeader .btn-link {
113
- font-weight: 500;
114
- display: flex;
115
- align-items: center;
116
- width: 100%;
117
- text-align: left;
118
- }
119
-
120
- [data-bs-theme="light"] #tableStructureHeader .btn-link {
121
- color: #212529;
122
- }
123
-
124
- [data-bs-theme="dark"] #tableStructureHeader .btn-link {
125
- color: #f8f9fa;
126
- }
127
-
128
- [data-bs-theme="light"] .table-columns-count {
129
- color: #6c757d;
130
- }
131
-
132
- [data-bs-theme="dark"] .table-columns-count {
133
- color: #adb5bd;
134
- }
135
-
136
- #tableStructureHeader .btn-link:hover,
137
- #tableStructureHeader .btn-link:focus {
138
- text-decoration: none;
139
- color: #0d6efd;
140
- }
141
-
142
- #tableStructureHeader .btn-link i {
143
- transition: transform 0.2s ease-in-out;
144
- }
145
-
146
- /* Table style overrides for query page */
147
- [data-bs-theme="dark"] .table-sm th,
148
- [data-bs-theme="dark"] .table-sm td {
149
- border-color: #495057;
150
- }
151
-
152
- /* Results card styling */
153
- [data-bs-theme="dark"] .card-header h5 {
154
- color: #f8f9fa;
155
- }
156
-
157
- /* Alert styling for dark mode */
158
- [data-bs-theme="dark"] .alert-warning {
159
- background-color: rgba(255, 193, 7, 0.15);
160
- border-color: rgba(255, 193, 7, 0.4);
161
- color: #ffc107;
162
- }
163
-
164
- [data-bs-theme="dark"] .alert-danger {
165
- background-color: rgba(220, 53, 69, 0.15);
166
- border-color: rgba(220, 53, 69, 0.4);
167
- color: #f8d7da;
168
- }
169
-
170
- /* Make headings stand out in dark mode */
171
- [data-bs-theme="dark"] h1,
172
- [data-bs-theme="dark"] h2,
173
- [data-bs-theme="dark"] h3,
174
- [data-bs-theme="dark"] h4,
175
- [data-bs-theme="dark"] h5,
176
- [data-bs-theme="dark"] h6 {
177
- color: #f8f9fa;
178
- }
179
- </style>
7
+ <%= stylesheet_link_tag "dbviewer/query", "data-turbo-track": "reload" %>
8
+ <%= javascript_include_tag "dbviewer/query", "data-turbo-track": "reload", type: :module %>
180
9
  <% end %>
181
10
 
182
- <% content_for :sidebar_active do %>active<% end %>
183
-
184
11
  <div class="d-flex justify-content-between align-items-center mb-4">
185
12
  <h1>Query: <%= @table_name %></h1>
186
13
  <div>
@@ -189,408 +16,145 @@
189
16
  <% end %>
190
17
  </div>
191
18
  </div>
192
-
193
- <% if flash[:warning].present? %>
194
- <div class="alert alert-warning alert-dismissible fade show" role="alert">
195
- <%= flash[:warning] %>
196
- <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
197
- </div>
198
- <% end %>
199
-
200
- <div class="card mb-4">
201
- <div class="card-header">
202
- <h5>SQL Query (Read-Only)</h5>
203
- </div>
204
- <div class="card-body">
205
- <%= form_with url: query_table_path(@table_name), method: :post, local: true, id: "sql-query-form" do |form| %>
206
- <div class="mb-3">
207
- <div id="monaco-editor" class="monaco-editor-container" style="min-height: 200px; border-radius: 4px; margin-bottom: 0rem;"
208
- data-initial-query="<%= CGI.escapeHTML(@query.to_s) %>"></div>
209
- <%= form.hidden_field :query, id: "query-input", value: @query.to_s %>
210
- </div>
211
-
212
- <div class="d-flex justify-content-between align-items-start">
213
- <div class="form-text">
214
- <strong>Examples:</strong><br>
215
- <div class="example-queries">
216
- <code class="example-query btn btn-sm btn-outline-secondary mb-1">SELECT * FROM <%= @table_name %> LIMIT 100</code>
217
- <code class="example-query btn btn-sm btn-outline-secondary mb-1">SELECT
218
- <%
219
- # Display first 3 columns or all if less than 3
220
- display_cols = @columns.present? ? @columns.first(3).map { |c| c[:name] }.join(", ") : "column1, column2"
221
- # Get first non-ID column for WHERE example if available
222
- where_col = @columns.present? ? (@columns.find { |c| !c[:name].to_s.downcase.include?("id") } || @columns.first)[:name] : "column_name"
223
- # Get a numeric column for aggregation if available
224
- num_col = @columns.present? ? (@columns.find { |c| c[:type].to_s.downcase.include?("int") || c[:type].to_s.downcase.include?("num") } || @columns.first)[:name] : "id"
225
- %>
226
- <%= display_cols %> FROM <%= @table_name %> WHERE <%= where_col %> = 'value'</code>
227
- <code class="example-query btn btn-sm btn-outline-secondary mb-1">SELECT COUNT(*) FROM <%= @table_name %> GROUP BY <%= num_col %></code>
228
- </div>
229
- </div>
230
- <div>
231
- <%= form.submit "Run Query", class: "btn btn-primary" %>
232
- <span class="keyboard-hint d-none d-md-inline">(or press Cmd+Enter / Ctrl+Enter)</span>
233
- <div class="small mt-2 d-none d-md-block shortcut-hints">
234
- <strong>Shortcuts:</strong>
235
- <span class="me-2">Cmd+Alt+T: Toggle table structure</span>
236
- <span class="me-2">Cmd+Alt+S: Insert SELECT</span>
237
- <span>Cmd+Alt+W: Insert WHERE</span>
238
- </div>
19
+
20
+ <% if flash[:warning].present? %>
21
+ <div class="alert alert-warning alert-dismissible fade show" role="alert">
22
+ <%= flash[:warning] %>
23
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
24
+ </div>
25
+ <% end %>
26
+
27
+ <div class="card mb-4">
28
+ <div class="card-header">
29
+ <h5>SQL Query (Read-Only)</h5>
30
+ </div>
31
+ <div class="card-body">
32
+ <%= form_with url: query_table_path(@table_name), method: :post, local: true, id: "sql-query-form" do |form| %>
33
+ <div class="mb-3">
34
+ <div id="monaco-editor" class="monaco-editor-container" style="min-height: 200px; border-radius: 4px; margin-bottom: 0rem;"
35
+ data-initial-query="<%= CGI.escapeHTML(@query.to_s) %>"></div>
36
+ <%= form.hidden_field :query, id: "query-input", value: @query.to_s %>
37
+ </div>
38
+
39
+ <div class="d-flex justify-content-between align-items-start">
40
+ <div class="form-text">
41
+ <strong>Examples:</strong><br>
42
+ <div class="example-queries">
43
+ <code class="example-query btn btn-sm btn-outline-secondary mb-1">SELECT * FROM <%= @table_name %> LIMIT 100</code>
44
+ <code class="example-query btn btn-sm btn-outline-secondary mb-1">SELECT
45
+ <%
46
+ # Display first 3 columns or all if less than 3
47
+ display_cols = @columns.present? ? @columns.first(3).map { |c| c[:name] }.join(", ") : "column1, column2"
48
+ # Get first non-ID column for WHERE example if available
49
+ where_col = @columns.present? ? (@columns.find { |c| !c[:name].to_s.downcase.include?("id") } || @columns.first)[:name] : "column_name"
50
+ # Get a numeric column for aggregation if available
51
+ num_col = @columns.present? ? (@columns.find { |c| c[:type].to_s.downcase.include?("int") || c[:type].to_s.downcase.include?("num") } || @columns.first)[:name] : "id"
52
+ %>
53
+ <%= display_cols %> FROM <%= @table_name %> WHERE <%= where_col %> = 'value'</code>
54
+ <code class="example-query btn btn-sm btn-outline-secondary mb-1">SELECT COUNT(*) FROM <%= @table_name %> GROUP BY <%= num_col %></code>
239
55
  </div>
240
56
  </div>
241
- <% end %>
242
- </div>
243
- </div>
244
- <div class="card mb-3">
245
- <div class="card-header" id="tableStructureHeader">
246
- <h6 class="mb-0">
247
- <button class="btn btn-link btn-sm text-decoration-none p-0 collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#tableStructureContent" aria-expanded="false" aria-controls="tableStructureContent">
248
- <i class="bi bi-chevron-down me-1"></i>
249
- Table Structure Reference
250
- <small class="table-columns-count ms-2">(<%= @columns.present? ? @columns.size : 0 %> columns)</small>
251
- </button>
252
- </h6>
253
- </div>
254
- <div id="tableStructureContent" class="collapse" aria-labelledby="tableStructureHeader">
255
- <div class="card-body p-2">
256
- <% if @columns.present? %>
257
- <div class="table-responsive">
258
- <table class="table table-sm table-bordered mb-0">
259
- <thead>
260
- <tr>
261
- <th>Column</th>
262
- <th>Type</th>
263
- </tr>
264
- </thead>
265
- <tbody>
266
- <% @columns.each do |column| %>
267
- <tr>
268
- <td><code><%= column[:name] %><%= " (PK)" if column[:primary] %></code></td>
269
- <td><span class="badge bg-secondary"><%= column[:type] %></span></td>
270
- </tr>
271
- <% end %>
272
- </tbody>
273
- </table>
57
+ <div>
58
+ <%= form.submit "Run Query", class: "btn btn-primary" %>
59
+ <span class="keyboard-hint d-none d-md-inline">(or press Cmd+Enter / Ctrl+Enter)</span>
60
+ <div class="small mt-2 d-none d-md-block shortcut-hints">
61
+ <strong>Shortcuts:</strong>
62
+ <span class="me-2">Cmd+Alt+T: Toggle table structure</span>
63
+ <span class="me-2">Cmd+Alt+S: Insert SELECT</span>
64
+ <span>Cmd+Alt+W: Insert WHERE</span>
274
65
  </div>
275
- <% else %>
276
- <p class="mb-0">No column information available.</p>
277
- <% end %>
66
+ </div>
278
67
  </div>
279
- </div>
68
+ <% end %>
280
69
  </div>
281
-
282
-
283
- <% if @error.present? %>
284
- <div class="alert alert-danger" role="alert">
285
- <strong>Error:</strong> <%= @error %>
286
- </div>
287
- <% end %>
288
-
289
- <% if @records.present? %>
290
- <div class="card">
291
- <div class="card-header d-flex justify-content-between align-items-center">
292
- <h5>Results</h5>
293
- <span class="badge bg-info">Rows: <%= @records.rows.count %></span>
294
- </div>
295
- <div class="card-body">
70
+ </div>
71
+
72
+ <div class="card mb-3">
73
+ <div class="card-header" id="tableStructureHeader">
74
+ <h6 class="mb-0">
75
+ <button class="btn btn-link btn-sm text-decoration-none p-0 collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#tableStructureContent" aria-expanded="false" aria-controls="tableStructureContent">
76
+ <i class="bi bi-chevron-down me-1"></i>
77
+ Table Structure Reference
78
+ <small class="table-columns-count ms-2">(<%= @columns.present? ? @columns.size : 0 %> columns)</small>
79
+ </button>
80
+ </h6>
81
+ </div>
82
+ <div id="tableStructureContent" class="collapse" aria-labelledby="tableStructureHeader">
83
+ <div class="card-body p-2">
84
+ <% if @columns.present? %>
296
85
  <div class="table-responsive">
297
- <table class="table table-bordered table-striped">
298
- <% if @records.columns.any? %>
299
- <thead>
300
- <tr>
301
- <% @records.columns.each do |column_name| %>
302
- <th><%= column_name %></th>
303
- <% end %>
304
- </tr>
305
- </thead>
306
- <tbody>
307
- <% if @records.rows.any? %>
308
- <% @records.rows.each do |row| %>
309
- <tr>
310
- <% row.each do |cell| %>
311
- <td><%= format_cell_value(cell) %></td>
312
- <% end %>
313
- </tr>
314
- <% end %>
315
- <% else %>
316
- <tr>
317
- <td colspan="<%= @records.columns.count %>">Query executed successfully, but returned no rows.</td>
318
- </tr>
319
- <% end %>
320
- </tbody>
321
- <% else %>
86
+ <table class="table table-sm table-bordered mb-0">
87
+ <thead>
322
88
  <tr>
323
- <td>Query executed successfully, but returned no columns.</td>
89
+ <th>Column</th>
90
+ <th>Type</th>
324
91
  </tr>
325
- <% end %>
92
+ </thead>
93
+ <tbody>
94
+ <% @columns.each do |column| %>
95
+ <tr>
96
+ <td><code><%= column[:name] %><%= " (PK)" if column[:primary] %></code></td>
97
+ <td><span class="badge bg-secondary"><%= column[:type] %></span></td>
98
+ </tr>
99
+ <% end %>
100
+ </tbody>
326
101
  </table>
327
102
  </div>
328
- </div>
103
+ <% else %>
104
+ <p class="mb-0">No column information available.</p>
105
+ <% end %>
329
106
  </div>
330
- <% end %>
107
+ </div>
331
108
  </div>
332
109
 
333
- <script type="module">
334
- import * as monaco from 'https://cdn.jsdelivr.net/npm/monaco-editor@0.39.0/+esm';
335
-
336
- // Helper function to decode HTML entities
337
- function decodeHTMLEntities(text) {
338
- const textarea = document.createElement('textarea');
339
- textarea.innerHTML = text;
340
- return textarea.value;
341
- }
342
-
343
- // Get initial query value from a data attribute to avoid string escaping issues
344
- const initialQueryEncoded = document.getElementById('monaco-editor').getAttribute('data-initial-query');
345
- const initialQuery = decodeHTMLEntities(initialQueryEncoded);
346
-
347
- // Determine initial theme based on document theme
348
- const initialTheme = document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'vs-dark' : 'vs';
349
-
350
- // Initialize Monaco Editor with SQL syntax highlighting
351
- const editor = monaco.editor.create(document.getElementById('monaco-editor'), {
352
- value: initialQuery || '',
353
- language: 'sql',
354
- theme: initialTheme,
355
- automaticLayout: true, // Resize automatically
356
- minimap: { enabled: true },
357
- scrollBeyondLastLine: false,
358
- lineNumbers: 'on',
359
- renderLineHighlight: 'all',
360
- tabSize: 2,
361
- wordWrap: 'on',
362
- formatOnPaste: true,
363
- formatOnType: true,
364
- autoIndent: 'full',
365
- folding: true,
366
- glyphMargin: false,
367
- suggestOnTriggerCharacters: true,
368
- fixedOverflowWidgets: true,
369
- quickSuggestions: {
370
- other: true,
371
- comments: true,
372
- strings: true
373
- },
374
- suggest: {
375
- showKeywords: true,
376
- showSnippets: true,
377
- preview: true,
378
- showIcons: true,
379
- maxVisibleSuggestions: 12
380
- }
381
- });
382
-
383
- // Theme change listener
384
- document.addEventListener('dbviewerThemeChanged', (event) => {
385
- const newTheme = event.detail.theme === 'dark' ? 'vs-dark' : 'vs';
386
- monaco.editor.setTheme(newTheme);
387
-
388
- // Update editor container border color
389
- const editorContainer = document.querySelector('.monaco-editor-container');
390
- if (editorContainer) {
391
- editorContainer.style.borderColor = event.detail.theme === 'dark' ? '#495057' : '#ced4da';
392
- }
393
-
394
- // Update status bar styling based on theme
395
- updateStatusBarTheme(event.detail.theme);
396
-
397
- // Update example query buttons
398
- const exampleQueries = document.querySelectorAll('.example-query');
399
- exampleQueries.forEach(query => {
400
- if (event.detail.theme === 'dark') {
401
- query.style.borderColor = '#495057';
402
- if (!query.classList.contains('btn-primary')) {
403
- query.style.color = '#f8f9fa';
404
- }
405
- } else {
406
- query.style.borderColor = '#ced4da';
407
- if (!query.classList.contains('btn-primary')) {
408
- query.style.color = '';
409
- }
410
- }
411
- });
412
- });
413
-
414
- function updateStatusBarTheme(theme) {
415
- const statusBar = document.querySelector('.monaco-status-bar');
416
- if (!statusBar) return;
417
-
418
- if (theme === 'dark') {
419
- statusBar.style.backgroundColor = '#343a40';
420
- statusBar.style.borderColor = '#495057';
421
- statusBar.style.color = '#adb5bd';
422
- } else {
423
- statusBar.style.backgroundColor = '#f8f9fa';
424
- statusBar.style.borderColor = '#ced4da';
425
- statusBar.style.color = '#6c757d';
426
- }
427
- }
428
-
429
- // Set up SQL intellisense with table/column completions
430
- const tableName = "<%= @table_name %>";
431
- const columns = [
432
- <% if @columns.present? %>
433
- <% @columns.each do |column| %>
434
- { name: "<%= column[:name] %>", type: "<%= column[:type] %>" },
435
- <% end %>
436
- <% end %>
437
- ];
438
-
439
- // Register SQL completion providers
440
- monaco.languages.registerCompletionItemProvider('sql', {
441
- provideCompletionItems: function(model, position) {
442
- const textUntilPosition = model.getValueInRange({
443
- startLineNumber: position.lineNumber,
444
- startColumn: 1,
445
- endLineNumber: position.lineNumber,
446
- endColumn: position.column
447
- });
448
-
449
- const suggestions = [];
450
-
451
- // Add table name suggestion
452
- suggestions.push({
453
- label: tableName,
454
- kind: monaco.languages.CompletionItemKind.Class,
455
- insertText: tableName,
456
- detail: 'Table name'
457
- });
458
-
459
- // Add column name suggestions
460
- columns.forEach(col => {
461
- suggestions.push({
462
- label: col.name,
463
- kind: monaco.languages.CompletionItemKind.Field,
464
- insertText: col.name,
465
- detail: `Column (${col.type})`
466
- });
467
- });
468
-
469
- // Add common SQL keywords
470
- const keywords = [
471
- { label: 'SELECT', insertText: 'SELECT ' },
472
- { label: 'FROM', insertText: 'FROM ' },
473
- { label: 'WHERE', insertText: 'WHERE ' },
474
- { label: 'ORDER BY', insertText: 'ORDER BY ' },
475
- { label: 'GROUP BY', insertText: 'GROUP BY ' },
476
- { label: 'HAVING', insertText: 'HAVING ' },
477
- { label: 'LIMIT', insertText: 'LIMIT ' },
478
- { label: 'JOIN', insertText: 'JOIN ' },
479
- { label: 'LEFT JOIN', insertText: 'LEFT JOIN ' },
480
- { label: 'INNER JOIN', insertText: 'INNER JOIN ' }
481
- ];
482
-
483
- keywords.forEach(kw => {
484
- suggestions.push({
485
- label: kw.label,
486
- kind: monaco.languages.CompletionItemKind.Keyword,
487
- insertText: kw.insertText
488
- });
489
- });
490
-
491
- return { suggestions };
492
- }
493
- });
494
-
495
- // Handle form submission - transfer content to hidden input before submitting
496
- document.getElementById('sql-query-form').addEventListener('submit', function(event) {
497
- // Stop the form from submitting immediately
498
- event.preventDefault();
499
-
500
- // Get the query value from the editor and set it to the hidden input
501
- const queryValue = editor.getValue();
502
- document.getElementById('query-input').value = queryValue;
503
-
504
- // Log for debugging
505
- console.log('Submitting query:', queryValue);
506
-
507
- // Now manually submit the form
508
- this.submit();
509
- });
110
+ <% if @error.present? %>
111
+ <div class="alert alert-danger" role="alert">
112
+ <strong>Error:</strong> <%= @error %>
113
+ </div>
114
+ <% end %>
510
115
 
511
- // Make example queries clickable
512
- document.querySelectorAll('.example-query').forEach(example => {
513
- example.style.cursor = 'pointer';
514
- example.addEventListener('click', () => {
515
- const query = decodeHTMLEntities(example.textContent);
516
- editor.setValue(query);
517
- editor.focus();
518
- });
519
- });
116
+ <% if @records.present? %>
117
+ <div class="card">
118
+ <div class="card-header d-flex justify-content-between align-items-center">
119
+ <h5>Results</h5>
120
+ <span class="badge bg-info">Rows: <%= @records.rows.count %></span>
121
+ </div>
122
+ <div class="card-body">
123
+ <div class="table-responsive">
124
+ <table class="table table-bordered table-striped">
125
+ <% if @records.columns.any? %>
126
+ <thead>
127
+ <tr>
128
+ <% @records.columns.each do |column_name| %>
129
+ <th><%= column_name %></th>
130
+ <% end %>
131
+ </tr>
132
+ </thead>
133
+ <tbody>
134
+ <% if @records.rows.any? %>
135
+ <% @records.rows.each do |row| %>
136
+ <tr>
137
+ <% row.each do |cell| %>
138
+ <td><%= format_cell_value(cell) %></td>
139
+ <% end %>
140
+ </tr>
141
+ <% end %>
142
+ <% else %>
143
+ <tr>
144
+ <td colspan="<%= @records.columns.count %>">Query executed successfully, but returned no rows.</td>
145
+ </tr>
146
+ <% end %>
147
+ </tbody>
148
+ <% else %>
149
+ <tr>
150
+ <td>Query executed successfully, but returned no columns.</td>
151
+ </tr>
152
+ <% end %>
153
+ </table>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ <% end %>
520
158
 
521
- // Setup editor keybindings
522
- editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, function() {
523
- // Get the query value from the editor and set it to the hidden input
524
- const queryValue = editor.getValue();
525
- document.getElementById('query-input').value = queryValue;
526
-
527
- // Log for debugging
528
- console.log('Submitting query via keyboard shortcut:', queryValue);
529
-
530
- // Submit the form
531
- document.getElementById('sql-query-form').submit();
532
- });
533
-
534
- // Add keyboard shortcuts for common SQL statements
535
- editor.addAction({
536
- id: 'insert-select-all',
537
- label: 'Insert SELECT * FROM statement',
538
- keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyS],
539
- run: function() {
540
- editor.trigger('keyboard', 'type', { text: `SELECT * FROM ${tableName} LIMIT 100` });
541
- return null;
542
- }
543
- });
544
-
545
- editor.addAction({
546
- id: 'insert-where',
547
- label: 'Insert WHERE clause',
548
- keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyW],
549
- run: function() {
550
- editor.trigger('keyboard', 'type', { text: ' WHERE ' });
551
- return null;
552
- }
553
- });
554
-
555
- editor.addAction({
556
- id: 'toggle-table-structure',
557
- label: 'Toggle Table Structure Reference',
558
- keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyT],
559
- run: function() {
560
- // Use Bootstrap's collapse API to toggle
561
- bootstrap.Collapse.getOrCreateInstance(document.getElementById('tableStructureContent')).toggle();
562
- return null;
563
- }
564
- });
565
-
566
- // Create a status bar showing cursor position and columns info
567
- const statusBarDiv = document.createElement('div');
568
- statusBarDiv.className = 'monaco-status-bar';
569
- statusBarDiv.innerHTML = `<div class="status-info">Ready</div>
570
- <div class="column-info">Table: ${tableName} (${columns.length} columns)</div>`;
571
- document.getElementById('monaco-editor').after(statusBarDiv);
572
-
573
- // Apply initial theme to status bar
574
- const currentTheme = document.documentElement.getAttribute('data-bs-theme') || 'light';
575
- updateStatusBarTheme(currentTheme);
576
-
577
- // Update status bar with cursor position
578
- editor.onDidChangeCursorPosition(e => {
579
- const position = `Ln ${e.position.lineNumber}, Col ${e.position.column}`;
580
- statusBarDiv.querySelector('.status-info').textContent = position;
581
- });
582
-
583
- // Focus the editor when page loads
584
- window.addEventListener('load', () => {
585
- editor.focus();
586
- });
587
-
588
- // Toggle icon when table structure collapses or expands
589
- document.getElementById('tableStructureContent').addEventListener('show.bs.collapse', function() {
590
- document.querySelector('#tableStructureHeader button i').classList.replace('bi-chevron-down', 'bi-chevron-up');
591
- });
592
-
593
- document.getElementById('tableStructureContent').addEventListener('hide.bs.collapse', function() {
594
- document.querySelector('#tableStructureHeader button i').classList.replace('bi-chevron-up', 'bi-chevron-down');
595
- });
596
- </script>
159
+ <input type="text" id="table_name" class="d-none" value="<%= @table_name %>">
160
+ <input type="text" id="columns_data" class="d-none" value="<%= @columns.map { |column| { name: column[:name], type: column[:type] } }.to_json %>">