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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +250 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/stylesheets/dbviewer/application.css +21 -0
  6. data/app/assets/stylesheets/dbviewer/dbviewer.css +0 -0
  7. data/app/assets/stylesheets/dbviewer/enhanced.css +0 -0
  8. data/app/controllers/concerns/dbviewer/database_operations.rb +354 -0
  9. data/app/controllers/concerns/dbviewer/error_handling.rb +42 -0
  10. data/app/controllers/concerns/dbviewer/pagination_concern.rb +43 -0
  11. data/app/controllers/dbviewer/application_controller.rb +21 -0
  12. data/app/controllers/dbviewer/databases_controller.rb +0 -0
  13. data/app/controllers/dbviewer/entity_relationship_diagrams_controller.rb +24 -0
  14. data/app/controllers/dbviewer/home_controller.rb +10 -0
  15. data/app/controllers/dbviewer/logs_controller.rb +39 -0
  16. data/app/controllers/dbviewer/tables_controller.rb +73 -0
  17. data/app/helpers/dbviewer/application_helper.rb +118 -0
  18. data/app/jobs/dbviewer/application_job.rb +4 -0
  19. data/app/mailers/dbviewer/application_mailer.rb +6 -0
  20. data/app/models/dbviewer/application_record.rb +5 -0
  21. data/app/services/dbviewer/file_storage.rb +0 -0
  22. data/app/services/dbviewer/in_memory_storage.rb +0 -0
  23. data/app/services/dbviewer/query_analyzer.rb +0 -0
  24. data/app/services/dbviewer/query_collection.rb +0 -0
  25. data/app/services/dbviewer/query_logger.rb +0 -0
  26. data/app/services/dbviewer/query_parser.rb +82 -0
  27. data/app/services/dbviewer/query_storage.rb +0 -0
  28. data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +564 -0
  29. data/app/views/dbviewer/home/index.html.erb +237 -0
  30. data/app/views/dbviewer/logs/index.html.erb +614 -0
  31. data/app/views/dbviewer/shared/_sidebar.html.erb +177 -0
  32. data/app/views/dbviewer/tables/_table_structure.html.erb +102 -0
  33. data/app/views/dbviewer/tables/index.html.erb +128 -0
  34. data/app/views/dbviewer/tables/query.html.erb +600 -0
  35. data/app/views/dbviewer/tables/show.html.erb +271 -0
  36. data/app/views/layouts/dbviewer/application.html.erb +728 -0
  37. data/config/routes.rb +22 -0
  38. data/lib/dbviewer/configuration.rb +79 -0
  39. data/lib/dbviewer/database_manager.rb +450 -0
  40. data/lib/dbviewer/engine.rb +20 -0
  41. data/lib/dbviewer/initializer.rb +23 -0
  42. data/lib/dbviewer/logger.rb +102 -0
  43. data/lib/dbviewer/query_analyzer.rb +109 -0
  44. data/lib/dbviewer/query_collection.rb +41 -0
  45. data/lib/dbviewer/query_parser.rb +82 -0
  46. data/lib/dbviewer/sql_validator.rb +194 -0
  47. data/lib/dbviewer/storage/base.rb +31 -0
  48. data/lib/dbviewer/storage/file_storage.rb +96 -0
  49. data/lib/dbviewer/storage/in_memory_storage.rb +59 -0
  50. data/lib/dbviewer/version.rb +3 -0
  51. data/lib/dbviewer.rb +65 -0
  52. data/lib/tasks/dbviewer_tasks.rake +4 -0
  53. metadata +126 -0
@@ -0,0 +1,271 @@
1
+ <% content_for :title do %>
2
+ Table: <%= @table_name %>
3
+ <% end %>
4
+
5
+ <% content_for :sidebar_active do %>active<% end %>
6
+
7
+ <% content_for :sidebar do %>
8
+ <%= render 'dbviewer/shared/sidebar' %>
9
+ <% end %>
10
+
11
+ <div class="d-flex justify-content-between align-items-center mb-4">
12
+ <h1>Table: <%= @table_name %></h1>
13
+ <div class="d-flex gap-2">
14
+ <button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#csvExportModal">
15
+ <i class="bi bi-file-earmark-spreadsheet me-1"></i> Export CSV
16
+ </button>
17
+ <%= link_to query_table_path(@table_name), class: "btn btn-primary" do %>
18
+ <i class="bi bi-code-square me-1"></i> Run SQL Query
19
+ <% end %>
20
+ </div>
21
+ </div>
22
+
23
+ <!-- CSV Export Modal -->
24
+ <div class="modal fade" id="csvExportModal" tabindex="-1" aria-labelledby="csvExportModalLabel" aria-hidden="true">
25
+ <div class="modal-dialog">
26
+ <div class="modal-content">
27
+ <div class="modal-header">
28
+ <h5 class="modal-title" id="csvExportModalLabel">Export <strong><%= @table_name %></strong> to CSV</h5>
29
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
30
+ </div>
31
+ <div class="modal-body">
32
+ <%= form_with url: export_csv_table_path(@table_name), method: :get, id: "csvExportForm" do |form| %>
33
+ <div class="mb-3">
34
+ <label for="limit" class="form-label">Maximum number of records</label>
35
+ <input type="number" class="form-control" id="limit" name="limit" value="10000" min="1" max="100000">
36
+ <div class="form-text">Limit the number of records to export. Large exports may take some time.</div>
37
+ </div>
38
+
39
+ <% if @total_count > 10000 %>
40
+ <div class="alert alert-warning">
41
+ <i class="bi bi-exclamation-triangle-fill me-2"></i>
42
+ This table has <%= number_with_delimiter(@total_count) %> records. Exporting all records may be slow.
43
+ </div>
44
+ <% end %>
45
+
46
+ <div class="mb-3 form-check">
47
+ <input type="checkbox" class="form-check-input" id="includeHeaders" name="include_headers" checked>
48
+ <label class="form-check-label" for="includeHeaders">Include column headers</label>
49
+ </div>
50
+ <% end %>
51
+ </div>
52
+ <div class="modal-footer">
53
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
54
+ <button type="submit" form="csvExportForm" class="btn btn-success">
55
+ <i class="bi bi-download me-1"></i> Export CSV
56
+ </button>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <!-- Records Section -->
63
+ <div class="dbviewer-card card mb-4">
64
+ <div class="card-header d-flex justify-content-between align-items-center">
65
+ <h5 class="mb-0"><i class="bi bi-table me-2"></i>Records</h5>
66
+ <div class="d-flex align-items-center">
67
+ <div class="me-3">
68
+ <label for="per-page-select" class="me-2">Per page:</label>
69
+ <select id="per-page-select" class="form-select form-select-sm" onchange="window.location.href='<%= table_path(@table_name) %>?per_page=' + this.value + '&page=1&order_by=<%= @order_by %>&order_direction=<%= @order_direction %>'">
70
+ <% Dbviewer::TablesController.per_page_options.each do |option| %>
71
+ <option value="<%= option %>" <%= 'selected' if @per_page == option %>><%= option %></option>
72
+ <% end %>
73
+ </select>
74
+ </div>
75
+ <span class="badge bg-secondary">Total: <%= @total_count %> records</span>
76
+ </div>
77
+ </div>
78
+ <div class="card-body p-0">
79
+ <div class="table-responsive dbviewer-scrollable">
80
+ <table class="table table-bordered table-striped rounded-none">
81
+ <% if @records.present? && @records.columns.any? %>
82
+ <thead class="dbviewer-table-header">
83
+ <tr>
84
+ <% @records.columns.each do |column_name| %>
85
+ <th>
86
+ <%= column_name %>
87
+ </th>
88
+ <% end %>
89
+ </tr>
90
+ </thead>
91
+ <tbody>
92
+ <% @records.rows.each do |row| %>
93
+ <tr>
94
+ <% row.each do |cell| %>
95
+ <% cell_value = format_cell_value(cell) %>
96
+ <td title="<%= cell_value %>"><%= cell_value %></td>
97
+ <% end %>
98
+ </tr>
99
+ <% end %>
100
+ </tbody>
101
+ <% else %>
102
+ <tr>
103
+ <td colspan="100%">No records found or table is empty.</td>
104
+ </tr>
105
+ <% end %>
106
+ </table>
107
+ </div>
108
+
109
+ <% if @total_pages > 1 %>
110
+ <nav aria-label="Page navigation">
111
+ <ul class="pagination justify-content-center">
112
+ <li class="page-item <%= 'disabled' if @current_page == 1 %>">
113
+ <%= link_to '«', table_path(@table_name, page: [@current_page - 1, 1].max, order_by: @order_by, order_direction: @order_direction, per_page: @per_page), class: 'page-link' %>
114
+ </li>
115
+
116
+ <% start_page = [1, @current_page - 2].max %>
117
+ <% end_page = [start_page + 4, @total_pages].min %>
118
+ <% start_page = [1, end_page - 4].max %>
119
+
120
+ <% (start_page..end_page).each do |page_num| %>
121
+ <li class="page-item <%= 'active' if page_num == @current_page %>">
122
+ <%= link_to page_num, table_path(@table_name, page: page_num, order_by: @order_by, order_direction: @order_direction, per_page: @per_page), class: 'page-link' %>
123
+ </li>
124
+ <% end %>
125
+
126
+ <li class="page-item <%= 'disabled' if @current_page == @total_pages %>">
127
+ <%= link_to '»', table_path(@table_name, page: [@current_page + 1, @total_pages].min, order_by: @order_by, order_direction: @order_direction, per_page: @per_page), class: 'page-link' %>
128
+ </li>
129
+ </ul>
130
+ </nav>
131
+ <% end %>
132
+ </div>
133
+ </div>
134
+ </div>
135
+
136
+ <!-- Two-column layout for Timeline and Structure -->
137
+ <div class="row two-column-layout">
138
+ <!-- Timeline Column -->
139
+ <div class="col-md-6 mb-4">
140
+ <% if @timestamp_data.present? %>
141
+ <div class="dbviewer-card card h-100">
142
+ <div class="card-header d-flex justify-content-between align-items-center">
143
+ <h5 class="mb-0"><i class="bi bi-graph-up me-2"></i>Record Creation Timeline</h5>
144
+ <div>
145
+ <div class="btn-group btn-group-sm" role="group" aria-label="Time grouping">
146
+ <%= link_to "Hourly", table_path(@table_name, time_group: "hourly", page: @current_page, order_by: @order_by, order_direction: @order_direction, per_page: @per_page), class: "btn btn-outline-primary #{@time_grouping == 'hourly' ? 'active' : ''}" %>
147
+ <%= link_to "Daily", table_path(@table_name, time_group: "daily", page: @current_page, order_by: @order_by, order_direction: @order_direction, per_page: @per_page), class: "btn btn-outline-primary #{@time_grouping == 'daily' ? 'active' : ''}" %>
148
+ <%= link_to "Weekly", table_path(@table_name, time_group: "weekly", page: @current_page, order_by: @order_by, order_direction: @order_direction, per_page: @per_page), class: "btn btn-outline-primary #{@time_grouping == 'weekly' ? 'active' : ''}" %>
149
+ </div>
150
+ </div>
151
+ </div>
152
+ <div class="card-body">
153
+ <div class="chart-container">
154
+ <canvas id="timestampChart"></canvas>
155
+ </div>
156
+ <div class="mt-3 text-center">
157
+ <small class="text-muted">
158
+ <i class="bi bi-info-circle"></i>
159
+ Timeline shows <%= @time_grouping %> record creation patterns based on <code>created_at</code> column.
160
+ </small>
161
+ </div>
162
+ </div>
163
+ </div>
164
+ <% else %>
165
+ <div class="dbviewer-card card h-100">
166
+ <div class="card-header">
167
+ <h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>Creation Timeline</h5>
168
+ </div>
169
+ <div class="card-body d-flex justify-content-center align-items-center text-center text-muted">
170
+ <div>
171
+ <i class="bi bi-calendar-x display-4 mb-3"></i>
172
+ <p>No creation timestamp data available for this table.</p>
173
+ <small>Timeline visualization is only available for tables with a <code>created_at</code> column.</small>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ <% end %>
178
+ </div>
179
+
180
+ <!-- Structure Column -->
181
+ <div class="col-md-6 mb-4">
182
+ <div class="dbviewer-card card h-100">
183
+ <div class="card-header">
184
+ <h5 class="mb-0"><i class="bi bi-diagram-3 me-2"></i>Table Structure</h5>
185
+ </div>
186
+ <div class="card-body structure-container">
187
+ <%= render 'table_structure' %>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ </div>
192
+
193
+ <% if @timestamp_data.present? %>
194
+ <script>
195
+ document.addEventListener('DOMContentLoaded', function() {
196
+ const timeGrouping = '<%= @time_grouping %>';
197
+ const chartData = <%= raw @timestamp_data.to_json %>;
198
+
199
+ // Reverse the data so it's chronological
200
+ const labels = chartData.map(item => item.label).reverse();
201
+ const values = chartData.map(item => item.value).reverse();
202
+
203
+ // Chart colors based on time grouping
204
+ let chartColor;
205
+ let chartTitle;
206
+
207
+ switch(timeGrouping) {
208
+ case 'hourly':
209
+ chartColor = 'rgba(75, 192, 192, 0.7)';
210
+ chartTitle = 'Hourly Record Creation';
211
+ break;
212
+ case 'weekly':
213
+ chartColor = 'rgba(153, 102, 255, 0.7)';
214
+ chartTitle = 'Weekly Record Creation';
215
+ break;
216
+ default:
217
+ chartColor = 'rgba(54, 162, 235, 0.7)';
218
+ chartTitle = 'Daily Record Creation';
219
+ }
220
+
221
+ const ctx = document.getElementById('timestampChart').getContext('2d');
222
+ new Chart(ctx, {
223
+ type: 'bar',
224
+ data: {
225
+ labels: labels,
226
+ datasets: [{
227
+ label: 'Records Created',
228
+ data: values,
229
+ backgroundColor: chartColor,
230
+ borderColor: chartColor.replace('0.7', '1.0'),
231
+ borderWidth: 1
232
+ }]
233
+ },
234
+ options: {
235
+ responsive: true,
236
+ maintainAspectRatio: false,
237
+ plugins: {
238
+ legend: {
239
+ display: false
240
+ },
241
+ title: {
242
+ display: true,
243
+ text: chartTitle,
244
+ font: {
245
+ size: 16
246
+ }
247
+ }
248
+ },
249
+ scales: {
250
+ y: {
251
+ beginAtZero: true,
252
+ title: {
253
+ display: true,
254
+ text: 'Number of Records'
255
+ },
256
+ ticks: {
257
+ precision: 0
258
+ }
259
+ },
260
+ x: {
261
+ title: {
262
+ display: true,
263
+ text: timeGrouping.charAt(0).toUpperCase() + timeGrouping.slice(1)
264
+ }
265
+ }
266
+ }
267
+ }
268
+ });
269
+ });
270
+ </script>
271
+ <% end %>