dbviewer 0.5.6 → 0.5.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.
- checksums.yaml +4 -4
- data/app/controllers/dbviewer/api/tables_controller.rb +55 -0
- data/app/views/dbviewer/tables/index.html.erb +1 -7
- data/app/views/dbviewer/tables/show.html.erb +1070 -2
- data/app/views/layouts/dbviewer/application.html.erb +4 -0
- data/app/views/layouts/dbviewer/shared/_sidebar.html.erb +95 -113
- data/config/routes.rb +3 -0
- data/lib/dbviewer/version.rb +1 -1
- metadata +1 -1
@@ -23,6 +23,8 @@
|
|
23
23
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
24
24
|
<!-- Bootstrap Icons -->
|
25
25
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
26
|
+
<!-- Flatpickr CSS for Date Range Picker -->
|
27
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
|
26
28
|
<!-- Google Fonts -->
|
27
29
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
|
28
30
|
<!-- Chart.js for Data Visualization -->
|
@@ -1407,6 +1409,8 @@
|
|
1407
1409
|
|
1408
1410
|
<!-- Bootstrap JS -->
|
1409
1411
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
1412
|
+
<!-- Flatpickr JS for Date Range Picker -->
|
1413
|
+
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
1410
1414
|
|
1411
1415
|
<!-- Dark Mode Toggle Script -->
|
1412
1416
|
<script>
|
@@ -4,95 +4,7 @@
|
|
4
4
|
id="tableSearch" placeholder="Filter tables..." aria-label="Filter tables">
|
5
5
|
</div>
|
6
6
|
|
7
|
-
|
8
|
-
<div class="accordion accordion-flush" id="creationFilterAccordion">
|
9
|
-
<div class="accordion-item border-0">
|
10
|
-
<h2 class="accordion-header" id="creationFilterHeading">
|
11
|
-
<button class="accordion-button p-2 collapsed" type="button" data-bs-toggle="collapse"
|
12
|
-
data-bs-target="#creationFilterCollapse" aria-expanded="false"
|
13
|
-
aria-controls="creationFilterCollapse">
|
14
|
-
<i class="bi bi-calendar-range me-2"></i> Creation Filter
|
15
|
-
<% if @creation_filter_start.present? || @creation_filter_end.present? %>
|
16
|
-
<% if @table_name.present? && has_timestamp_column?(@table_name) %>
|
17
|
-
<span class="badge bg-success ms-2">Active</span>
|
18
|
-
<% else %>
|
19
|
-
<span class="badge bg-secondary ms-2">Set</span>
|
20
|
-
<% end %>
|
21
|
-
<% end %>
|
22
|
-
</button>
|
23
|
-
</h2>
|
24
|
-
<div id="creationFilterCollapse" class="accordion-collapse collapse" aria-labelledby="creationFilterHeading">
|
25
|
-
<div class="accordion-body p-2">
|
26
|
-
<form id="creationFilterForm" action="<%= request.path %>" method="get" class="mb-0">
|
27
|
-
<!-- Preserve existing query parameters -->
|
28
|
-
<input type="hidden" name="page" value="<%= @current_page %>">
|
29
|
-
<input type="hidden" name="per_page" value="<%= @per_page %>">
|
30
|
-
<input type="hidden" name="order_by" value="<%= @order_by %>">
|
31
|
-
<input type="hidden" name="order_direction" value="<%= @order_direction %>">
|
32
|
-
|
33
|
-
<!-- Datetime range fields -->
|
34
|
-
<div class="mb-2">
|
35
|
-
<label for="creationFilterStart" class="form-label mb-1 small">Start Date/Time</label>
|
36
|
-
<input type="datetime-local" id="creationFilterStart" name="creation_filter_start"
|
37
|
-
class="form-control form-control-sm" value="<%= @creation_filter_start %>">
|
38
|
-
</div>
|
39
|
-
<div class="mb-2">
|
40
|
-
<label for="creationFilterEnd" class="form-label mb-1 small">End Date/Time</label>
|
41
|
-
<input type="datetime-local" id="creationFilterEnd" name="creation_filter_end"
|
42
|
-
class="form-control form-control-sm" value="<%= @creation_filter_end %>">
|
43
|
-
</div>
|
44
|
-
|
45
|
-
<div class="d-flex justify-content-between">
|
46
|
-
<button type="submit" class="btn btn-primary btn-sm">Apply</button>
|
47
|
-
<% if @creation_filter_start.present? || @creation_filter_end.present? %>
|
48
|
-
<%
|
49
|
-
# Preserve other query params when clearing creation filter
|
50
|
-
clear_params = {
|
51
|
-
clear_creation_filter: true,
|
52
|
-
page: @current_page,
|
53
|
-
per_page: @per_page,
|
54
|
-
order_by: @order_by,
|
55
|
-
order_direction: @order_direction
|
56
|
-
}
|
57
|
-
%>
|
58
|
-
<a href="<%= request.path %>?<%= clear_params.to_query %>" class="btn btn-outline-secondary btn-sm">Clear</a>
|
59
|
-
<% end %>
|
60
|
-
</div>
|
61
|
-
<div class="mt-2 small">
|
62
|
-
<% if @table_name.present? && has_timestamp_column?(@table_name) && (@creation_filter_start.present? || @creation_filter_end.present?) %>
|
63
|
-
<div class="text-success">
|
64
|
-
<i class="bi bi-check-circle-fill"></i>
|
65
|
-
Filter active on this table.
|
66
|
-
<% if @current_page == 1 && @records && @records.rows && @records.rows.empty? %>
|
67
|
-
<div class="alert alert-warning p-1 mt-2 small">
|
68
|
-
<i class="bi bi-exclamation-triangle-fill"></i>
|
69
|
-
No records match the filter criteria.
|
70
|
-
</div>
|
71
|
-
<% end %>
|
72
|
-
</div>
|
73
|
-
<% elsif @table_name.present? && (@creation_filter_start.present? || @creation_filter_end.present?) %>
|
74
|
-
<div class="text-warning">
|
75
|
-
<i class="bi bi-exclamation-circle-fill"></i>
|
76
|
-
This table has no <code>created_at</code> column.
|
77
|
-
</div>
|
78
|
-
<% elsif !@table_name.present? && (@creation_filter_start.present? || @creation_filter_end.present?) %>
|
79
|
-
<div class="text-info">
|
80
|
-
<i class="bi bi-info-circle-fill"></i>
|
81
|
-
Filter will be applied on tables with <code>created_at</code> column.
|
82
|
-
</div>
|
83
|
-
<% else %>
|
84
|
-
<div class="text-muted">
|
85
|
-
<i class="bi bi-info-circle-fill"></i>
|
86
|
-
Filters apply to tables with a <code>created_at</code> column.
|
87
|
-
</div>
|
88
|
-
<% end %>
|
89
|
-
</div>
|
90
|
-
</form>
|
91
|
-
</div>
|
92
|
-
</div>
|
93
|
-
</div>
|
94
|
-
</div>
|
95
|
-
</div>
|
7
|
+
|
96
8
|
|
97
9
|
<!-- Add custom styling for datetime inputs -->
|
98
10
|
<style>
|
@@ -207,6 +119,13 @@
|
|
207
119
|
<script>
|
208
120
|
document.addEventListener('DOMContentLoaded', function() {
|
209
121
|
const searchInput = document.getElementById('tableSearch');
|
122
|
+
const sidebarContent = document.querySelector('.dbviewer-sidebar-content');
|
123
|
+
|
124
|
+
// Storage keys for persistence
|
125
|
+
const STORAGE_KEYS = {
|
126
|
+
searchFilter: 'dbviewer_sidebar_search_filter',
|
127
|
+
scrollPosition: 'dbviewer_sidebar_scroll_position'
|
128
|
+
};
|
210
129
|
|
211
130
|
if (searchInput) {
|
212
131
|
// Debounce function to limit how often the filter runs
|
@@ -228,6 +147,9 @@
|
|
228
147
|
const tableItems = document.querySelectorAll('#tablesList .list-group-item-action');
|
229
148
|
let visibleCount = 0;
|
230
149
|
|
150
|
+
// Save the current search filter to localStorage
|
151
|
+
localStorage.setItem(STORAGE_KEYS.searchFilter, searchInput.value);
|
152
|
+
|
231
153
|
tableItems.forEach(function(item) {
|
232
154
|
// Get the table name from the title attribute for more accurate matching
|
233
155
|
const tableName = (item.getAttribute('title') || item.textContent).trim().toLowerCase();
|
@@ -266,26 +188,7 @@
|
|
266
188
|
}
|
267
189
|
}, 150); // Debounce for 150ms
|
268
190
|
|
269
|
-
// Set up
|
270
|
-
searchInput.addEventListener('input', filterTables);
|
271
|
-
searchInput.addEventListener('keyup', function(e) {
|
272
|
-
filterTables();
|
273
|
-
|
274
|
-
// Add keyboard navigation for the filtered list
|
275
|
-
if (e.key === 'Enter' || e.key === 'ArrowDown') {
|
276
|
-
e.preventDefault();
|
277
|
-
// Focus the first visible table item (not having d-none class)
|
278
|
-
const firstVisibleItem = document.querySelector('#tablesList .list-group-item-action:not(.d-none)');
|
279
|
-
if (firstVisibleItem) {
|
280
|
-
firstVisibleItem.focus();
|
281
|
-
// Make sure the item is visible in the scrollable area
|
282
|
-
firstVisibleItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
283
|
-
}
|
284
|
-
}
|
285
|
-
});
|
286
|
-
searchInput.addEventListener('search', filterTables); // For clearing via the "x" in some browsers
|
287
|
-
|
288
|
-
// Clear the search box when clicking the X button
|
191
|
+
// Set up clear button first
|
289
192
|
const clearButton = document.createElement('button');
|
290
193
|
clearButton.type = 'button';
|
291
194
|
clearButton.className = 'btn btn-sm btn-link position-absolute';
|
@@ -301,6 +204,8 @@
|
|
301
204
|
clearButton.innerHTML = '<i class="bi bi-x-circle"></i>';
|
302
205
|
clearButton.addEventListener('click', function() {
|
303
206
|
searchInput.value = '';
|
207
|
+
// Clear the saved filter from localStorage
|
208
|
+
localStorage.removeItem(STORAGE_KEYS.searchFilter);
|
304
209
|
// Call filter directly without debouncing for immediate feedback
|
305
210
|
filterTables();
|
306
211
|
this.style.display = 'none';
|
@@ -314,13 +219,90 @@
|
|
314
219
|
searchInput.addEventListener('input', function() {
|
315
220
|
clearButton.style.display = this.value ? 'block' : 'none';
|
316
221
|
});
|
222
|
+
}
|
223
|
+
|
224
|
+
// Restore saved search filter on page load and apply it immediately
|
225
|
+
const savedFilter = localStorage.getItem(STORAGE_KEYS.searchFilter);
|
226
|
+
if (savedFilter) {
|
227
|
+
searchInput.value = savedFilter;
|
228
|
+
// Show clear button immediately when filter is restored
|
229
|
+
clearButton.style.display = 'block';
|
230
|
+
// Apply filter immediately without debouncing to prevent blinking
|
231
|
+
const query = savedFilter.toLowerCase();
|
232
|
+
const tableItems = document.querySelectorAll('#tablesList .list-group-item-action');
|
233
|
+
let visibleCount = 0;
|
317
234
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
235
|
+
tableItems.forEach(function(item) {
|
236
|
+
const tableName = (item.getAttribute('title') || item.textContent).trim().toLowerCase();
|
237
|
+
const displayedText = item.textContent.trim().toLowerCase();
|
238
|
+
|
239
|
+
if (tableName.includes(query) || displayedText.includes(query)) {
|
240
|
+
item.classList.remove('d-none');
|
241
|
+
visibleCount++;
|
242
|
+
} else {
|
243
|
+
item.classList.add('d-none');
|
244
|
+
}
|
245
|
+
});
|
246
|
+
|
247
|
+
// Update the tables count immediately
|
248
|
+
const tableCountElement = document.getElementById('table-count');
|
249
|
+
if (tableCountElement) {
|
250
|
+
tableCountElement.textContent = visibleCount;
|
251
|
+
}
|
252
|
+
|
253
|
+
// Handle no results message immediately
|
254
|
+
let noResultsEl = document.getElementById('dbviewer-no-filter-results');
|
255
|
+
if (visibleCount === 0 && query !== '') {
|
256
|
+
if (!noResultsEl) {
|
257
|
+
noResultsEl = document.createElement('div');
|
258
|
+
noResultsEl.id = 'dbviewer-no-filter-results';
|
259
|
+
noResultsEl.className = 'list-group-item text-muted text-center py-3';
|
260
|
+
noResultsEl.innerHTML = '<i class="bi bi-search me-1"></i> No tables match "<span class="fw-bold"></span>"';
|
261
|
+
document.getElementById('tablesList').appendChild(noResultsEl);
|
262
|
+
}
|
263
|
+
noResultsEl.querySelector('.fw-bold').textContent = query;
|
264
|
+
noResultsEl.style.display = 'block';
|
265
|
+
} else if (noResultsEl) {
|
266
|
+
noResultsEl.style.display = 'none';
|
322
267
|
}
|
323
268
|
}
|
269
|
+
|
270
|
+
// Restore saved scroll position on page load
|
271
|
+
if (sidebarContent) {
|
272
|
+
const savedScrollPosition = localStorage.getItem(STORAGE_KEYS.scrollPosition);
|
273
|
+
if (savedScrollPosition) {
|
274
|
+
// Use requestAnimationFrame to ensure DOM is fully rendered
|
275
|
+
requestAnimationFrame(() => {
|
276
|
+
sidebarContent.scrollTop = parseInt(savedScrollPosition, 10);
|
277
|
+
});
|
278
|
+
}
|
279
|
+
|
280
|
+
// Save scroll position on scroll
|
281
|
+
const saveScrollPosition = debounce(function() {
|
282
|
+
localStorage.setItem(STORAGE_KEYS.scrollPosition, sidebarContent.scrollTop);
|
283
|
+
}, 100);
|
284
|
+
|
285
|
+
sidebarContent.addEventListener('scroll', saveScrollPosition);
|
286
|
+
}
|
287
|
+
|
288
|
+
// Set up event listeners for the search input
|
289
|
+
searchInput.addEventListener('input', filterTables);
|
290
|
+
searchInput.addEventListener('keyup', function(e) {
|
291
|
+
filterTables();
|
292
|
+
|
293
|
+
// Add keyboard navigation for the filtered list
|
294
|
+
if (e.key === 'Enter' || e.key === 'ArrowDown') {
|
295
|
+
e.preventDefault();
|
296
|
+
// Focus the first visible table item (not having d-none class)
|
297
|
+
const firstVisibleItem = document.querySelector('#tablesList .list-group-item-action:not(.d-none)');
|
298
|
+
if (firstVisibleItem) {
|
299
|
+
firstVisibleItem.focus();
|
300
|
+
// Make sure the item is visible in the scrollable area
|
301
|
+
firstVisibleItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
302
|
+
}
|
303
|
+
}
|
304
|
+
});
|
305
|
+
searchInput.addEventListener('search', filterTables); // For clearing via the "x" in some browsers
|
324
306
|
}
|
325
307
|
});
|
326
308
|
</script>
|
data/config/routes.rb
CHANGED
data/lib/dbviewer/version.rb
CHANGED