dbviewer 0.3.15 → 0.3.16
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/README.md +34 -2
- data/app/controllers/concerns/dbviewer/database_operations.rb +23 -21
- data/app/controllers/concerns/dbviewer/pagination_concern.rb +8 -0
- data/app/controllers/dbviewer/tables_controller.rb +100 -21
- data/app/helpers/dbviewer/application_helper.rb +73 -0
- data/app/views/dbviewer/shared/_sidebar.html.erb +146 -1
- data/app/views/dbviewer/tables/index.html.erb +7 -1
- data/app/views/dbviewer/tables/show.html.erb +361 -28
- data/lib/dbviewer/database_manager.rb +31 -115
- data/lib/dbviewer/query_analyzer.rb +130 -0
- data/lib/dbviewer/table_query_operations.rb +621 -0
- data/lib/dbviewer/table_query_params.rb +39 -0
- data/lib/dbviewer/version.rb +1 -1
- data/lib/dbviewer.rb +1 -0
- metadata +4 -9
- 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 +0 -82
- data/app/services/dbviewer/query_storage.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72e7257a74294ca4a6ad8b53fc497ca9eb0727772e798b2c4d7fa2c164b35531
|
4
|
+
data.tar.gz: d01a7c934e3e859f06f94a746671974dd93813e832c6f5f54df49b3071e95178
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79511e9ac29ca71e4834b958c54b36ee05f83e831f1426745282139ea6b46d5e0e1bcd2a2b31df956987218bfc44239fa624dba86f74ec4858cc35aea4af8536
|
7
|
+
data.tar.gz: e63f51557b5f2431f63c920989a1b13f7ed5c1cbe5cd708091fc8f728e649caae11a6c09a754d6522b2dbbb3408cb380a2ded2f438770e8b9c2d49b782bdcd7f
|
data/README.md
CHANGED
@@ -44,7 +44,8 @@ It's designed for development, debugging, and database analysis, offering a clea
|
|
44
44
|
<details>
|
45
45
|
<summary>Click to see more screenshots</summary>
|
46
46
|
|
47
|
-
|
47
|
+
#### Dashboard Overview
|
48
|
+
|
48
49
|
<img width="1470" alt="image" src="https://github.com/user-attachments/assets/4e803d51-9a5b-4c80-bb4c-a761dba15a40" />
|
49
50
|
|
50
51
|
#### Table Details
|
@@ -76,7 +77,7 @@ gem "dbviewer", group: :development
|
|
76
77
|
And then execute:
|
77
78
|
|
78
79
|
```bash
|
79
|
-
|
80
|
+
bundle
|
80
81
|
```
|
81
82
|
|
82
83
|
## 🔧 Usage
|
@@ -274,6 +275,7 @@ With the addition of Basic Authentication, DBViewer can now be used in any envir
|
|
274
275
|
```
|
275
276
|
|
276
277
|
3. Access the tool through your regular application URL:
|
278
|
+
|
277
279
|
```
|
278
280
|
https://yourdomain.com/dbviewer?override_env_check=your_secure_random_key
|
279
281
|
```
|
@@ -288,6 +290,36 @@ When used in production, ensure:
|
|
288
290
|
- You access DBViewer over HTTPS connections only
|
289
291
|
- Access is limited to trusted administrators only
|
290
292
|
|
293
|
+
## 🔄 Updating DBViewer
|
294
|
+
|
295
|
+
To keep DBViewer up to date with the latest features, security patches, and bug fixes, follow these steps:
|
296
|
+
|
297
|
+
### Using Bundler
|
298
|
+
|
299
|
+
The simplest way to update is using Bundler:
|
300
|
+
|
301
|
+
- Update your Gemfile with the desired version:
|
302
|
+
|
303
|
+
```ruby
|
304
|
+
# For the latest version
|
305
|
+
gem "dbviewer", group: :development
|
306
|
+
|
307
|
+
# Or specify a version
|
308
|
+
gem "dbviewer", "~> 0.3.2", group: :development
|
309
|
+
```
|
310
|
+
|
311
|
+
- Run bundle update:
|
312
|
+
|
313
|
+
```ruby
|
314
|
+
bundle update dbviewer
|
315
|
+
```
|
316
|
+
|
317
|
+
- Restart your Rails server to apply the changes:
|
318
|
+
|
319
|
+
```ruby
|
320
|
+
rails server
|
321
|
+
```
|
322
|
+
|
291
323
|
## 🤌🏻 Contributing
|
292
324
|
|
293
325
|
Bug reports and pull requests are welcome.
|
@@ -11,6 +11,12 @@ module Dbviewer
|
|
11
11
|
@database_manager ||= ::Dbviewer::DatabaseManager.new
|
12
12
|
end
|
13
13
|
|
14
|
+
# Initialize the table query operations manager
|
15
|
+
# This gives direct access to table query operations when needed
|
16
|
+
def table_query_operations
|
17
|
+
@table_query_operations ||= database_manager.table_query_operations
|
18
|
+
end
|
19
|
+
|
14
20
|
# Get the name of the current database
|
15
21
|
def get_database_name
|
16
22
|
adapter = database_manager.connection.adapter_name.downcase
|
@@ -151,19 +157,8 @@ module Dbviewer
|
|
151
157
|
end
|
152
158
|
|
153
159
|
# Fetch records for a table with pagination and sorting
|
154
|
-
def fetch_table_records(table_name)
|
155
|
-
|
156
|
-
# Clean up blank filters
|
157
|
-
column_filters.reject! { |_, v| v.blank? }
|
158
|
-
|
159
|
-
database_manager.table_records(
|
160
|
-
table_name,
|
161
|
-
@current_page,
|
162
|
-
@order_by,
|
163
|
-
@order_direction,
|
164
|
-
@per_page,
|
165
|
-
column_filters || {}
|
166
|
-
)
|
160
|
+
def fetch_table_records(table_name, query_params)
|
161
|
+
database_manager.table_records(table_name, query_params)
|
167
162
|
end
|
168
163
|
|
169
164
|
# Get filtered record count for a table
|
@@ -377,17 +372,24 @@ module Dbviewer
|
|
377
372
|
end
|
378
373
|
|
379
374
|
# Export table data to CSV
|
380
|
-
def export_table_to_csv(table_name,
|
375
|
+
def export_table_to_csv(table_name, query_params = nil, include_headers = true)
|
381
376
|
require "csv"
|
382
377
|
|
383
378
|
begin
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
limit
|
390
|
-
|
379
|
+
if query_params.is_a?(Dbviewer::TableQueryParams)
|
380
|
+
# Use the query params object directly
|
381
|
+
records = database_manager.query_operations.table_records(table_name, query_params)
|
382
|
+
else
|
383
|
+
# Legacy support for the old method signature
|
384
|
+
limit = query_params.is_a?(Numeric) ? query_params : 10000
|
385
|
+
records = database_manager.table_records(
|
386
|
+
table_name,
|
387
|
+
1, # First page
|
388
|
+
nil, # Default sorting
|
389
|
+
"asc",
|
390
|
+
limit # Limit number of records
|
391
|
+
)
|
392
|
+
end
|
391
393
|
|
392
394
|
csv_data = CSV.generate do |csv|
|
393
395
|
# Add headers if requested
|
@@ -35,6 +35,14 @@ module Dbviewer
|
|
35
35
|
@order_direction = "ASC" unless self.class::VALID_SORT_DIRECTIONS.include?(@order_direction)
|
36
36
|
end
|
37
37
|
|
38
|
+
def fetch_total_count(table_name, query_params)
|
39
|
+
if query_params.column_filters.present? && query_params.column_filters.values.any?(&:present?)
|
40
|
+
fetch_filtered_record_count(table_name, query_params.column_filters)
|
41
|
+
else
|
42
|
+
fetch_table_record_count(table_name)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
38
46
|
# Calculate the total number of pages
|
39
47
|
def calculate_total_pages(total_count, per_page)
|
40
48
|
(total_count.to_f / per_page).ceil
|
@@ -2,34 +2,38 @@ module Dbviewer
|
|
2
2
|
class TablesController < ApplicationController
|
3
3
|
include Dbviewer::PaginationConcern
|
4
4
|
|
5
|
+
before_action :set_table_name, except: [ :index ]
|
6
|
+
|
5
7
|
def index
|
6
8
|
@tables = fetch_tables_with_stats(include_record_counts: true)
|
7
9
|
end
|
8
10
|
|
9
11
|
def show
|
10
|
-
@table_name = params[:id]
|
11
|
-
@columns = fetch_table_columns(@table_name)
|
12
|
-
@metadata = fetch_table_metadata(@table_name)
|
13
|
-
@tables = fetch_tables_with_stats # Fetch tables for sidebar
|
14
|
-
|
15
12
|
set_pagination_params
|
16
13
|
set_sorting_params
|
17
14
|
|
18
|
-
#
|
15
|
+
# Get column filters from params first
|
19
16
|
@column_filters = params[:column_filters].presence ? params[:column_filters].to_enum.to_h : {}
|
20
17
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
18
|
+
# Then apply global creation filters (this will modify @column_filters)
|
19
|
+
set_global_filters
|
20
|
+
|
21
|
+
# Now create the query params with the combined filters
|
22
|
+
query_params = Dbviewer::TableQueryParams.new(
|
23
|
+
page: @current_page,
|
24
|
+
per_page: @per_page,
|
25
|
+
order_by: @order_by,
|
26
|
+
direction: @order_direction,
|
27
|
+
column_filters: @column_filters.reject { |_, v| v.blank? }
|
28
|
+
)
|
29
|
+
@total_count = fetch_total_count(@table_name, query_params)
|
30
|
+
@records = fetch_table_records(@table_name, query_params)
|
27
31
|
@total_pages = calculate_total_pages(@total_count, @per_page)
|
28
|
-
@
|
32
|
+
@columns = fetch_table_columns(@table_name)
|
33
|
+
@metadata = fetch_table_metadata(@table_name)
|
29
34
|
|
30
|
-
# Ensure @records is never nil to prevent template errors
|
31
35
|
if @records.nil?
|
32
|
-
column_names =
|
36
|
+
column_names = @columns.map { |c| c[:name] }
|
33
37
|
@records = ActiveRecord::Result.new(column_names, [])
|
34
38
|
end
|
35
39
|
|
@@ -53,8 +57,6 @@ module Dbviewer
|
|
53
57
|
end
|
54
58
|
|
55
59
|
def mini_erd
|
56
|
-
@table_name = params[:id]
|
57
|
-
|
58
60
|
begin
|
59
61
|
@erd_data = fetch_mini_erd_for_table(@table_name)
|
60
62
|
|
@@ -81,7 +83,6 @@ module Dbviewer
|
|
81
83
|
end
|
82
84
|
|
83
85
|
def query
|
84
|
-
@table_name = params[:id]
|
85
86
|
@read_only_mode = true # Flag to indicate we're in read-only mode
|
86
87
|
@columns = fetch_table_columns(@table_name)
|
87
88
|
@tables = fetch_tables_with_stats # Fetch tables for sidebar
|
@@ -102,20 +103,98 @@ module Dbviewer
|
|
102
103
|
return
|
103
104
|
end
|
104
105
|
|
105
|
-
table_name = params[:id]
|
106
106
|
limit = (params[:limit] || 10000).to_i
|
107
107
|
include_headers = params[:include_headers] != "0"
|
108
108
|
|
109
|
-
|
109
|
+
# Apply global creation filters
|
110
|
+
set_global_filters
|
111
|
+
|
112
|
+
# Create query parameters for export
|
113
|
+
query_params = Dbviewer::TableQueryParams.new(
|
114
|
+
page: 1,
|
115
|
+
per_page: limit,
|
116
|
+
order_by: nil,
|
117
|
+
direction: "asc",
|
118
|
+
column_filters: @column_filters.reject { |_, v| v.blank? }
|
119
|
+
)
|
120
|
+
|
121
|
+
# Get filtered data for export
|
122
|
+
csv_data = export_table_to_csv(@table_name, query_params, include_headers)
|
110
123
|
|
111
124
|
# Set filename with timestamp for uniqueness
|
112
125
|
timestamp = Time.now.strftime("%Y%m%d%H%M%S")
|
113
|
-
|
126
|
+
|
127
|
+
# Add filter info to filename if filters are applied
|
128
|
+
filename_suffix = ""
|
129
|
+
if @creation_filter_start.present? || @creation_filter_end.present?
|
130
|
+
filename_suffix = "_filtered"
|
131
|
+
end
|
132
|
+
|
133
|
+
filename = "#{@table_name}#{filename_suffix}_#{timestamp}.csv"
|
114
134
|
|
115
135
|
# Send data as file
|
116
136
|
send_data csv_data,
|
117
137
|
type: "text/csv; charset=utf-8; header=present",
|
118
138
|
disposition: "attachment; filename=#{filename}"
|
119
139
|
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def set_table_name
|
144
|
+
@table_name = params[:id]
|
145
|
+
end # Handle global creation datetime filters across tables
|
146
|
+
def set_global_filters
|
147
|
+
# Store creation filter datetimes in session to persist between table navigation
|
148
|
+
if params[:creation_filter_start].present?
|
149
|
+
session[:creation_filter_start] = params[:creation_filter_start]
|
150
|
+
end
|
151
|
+
|
152
|
+
if params[:creation_filter_end].present?
|
153
|
+
session[:creation_filter_end] = params[:creation_filter_end]
|
154
|
+
end
|
155
|
+
|
156
|
+
# Clear filters if explicitly requested
|
157
|
+
if params[:clear_creation_filter] == "true"
|
158
|
+
session.delete(:creation_filter_start)
|
159
|
+
session.delete(:creation_filter_end)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Set instance variables for view access
|
163
|
+
@creation_filter_start = session[:creation_filter_start]
|
164
|
+
@creation_filter_end = session[:creation_filter_end]
|
165
|
+
|
166
|
+
# Initialize column_filters if not present
|
167
|
+
@column_filters ||= {}
|
168
|
+
|
169
|
+
# Apply creation filters to column_filters if the table has a created_at column
|
170
|
+
if has_timestamp_column?(@table_name) && (@creation_filter_start.present? || @creation_filter_end.present?)
|
171
|
+
# Clear any existing created_at filters to avoid conflicts
|
172
|
+
@column_filters.delete("created_at")
|
173
|
+
@column_filters.delete("created_at_operator")
|
174
|
+
@column_filters.delete("created_at_end")
|
175
|
+
|
176
|
+
if @creation_filter_start.present? && @creation_filter_end.present?
|
177
|
+
# If both start and end are present, set up a range filter
|
178
|
+
@column_filters["created_at"] = @creation_filter_start
|
179
|
+
@column_filters["created_at_end"] = @creation_filter_end
|
180
|
+
# No need for operator when using start and end together
|
181
|
+
Rails.logger.info("[DBViewer] Setting creation filter range: #{@creation_filter_start} to #{@creation_filter_end}")
|
182
|
+
elsif @creation_filter_start.present?
|
183
|
+
# Only start date is present
|
184
|
+
@column_filters["created_at"] = @creation_filter_start
|
185
|
+
@column_filters["created_at_operator"] = "gte" # Greater than or equal
|
186
|
+
Rails.logger.info("[DBViewer] Setting creation filter start: #{@creation_filter_start}")
|
187
|
+
elsif @creation_filter_end.present?
|
188
|
+
# Only end date is present
|
189
|
+
@column_filters["created_at"] = @creation_filter_end
|
190
|
+
@column_filters["created_at_operator"] = "lte" # Less than or equal
|
191
|
+
Rails.logger.info("[DBViewer] Setting creation filter end: #{@creation_filter_end}")
|
192
|
+
end
|
193
|
+
else
|
194
|
+
if @creation_filter_start.present? || @creation_filter_end.present?
|
195
|
+
Rails.logger.info("[DBViewer] Creation filter present but table #{@table_name} has no created_at column")
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
120
199
|
end
|
121
200
|
end
|
@@ -1,5 +1,19 @@
|
|
1
1
|
module Dbviewer
|
2
2
|
module ApplicationHelper
|
3
|
+
# Check if a table has a created_at column
|
4
|
+
def has_timestamp_column?(table_name)
|
5
|
+
return false unless table_name.present?
|
6
|
+
|
7
|
+
# Get the columns for the table directly using DatabaseManager
|
8
|
+
columns = get_database_manager.table_columns(table_name)
|
9
|
+
columns.any? { |col| col[:name] == "created_at" && [ :datetime, :timestamp ].include?(col[:type]) }
|
10
|
+
end
|
11
|
+
|
12
|
+
# Helper to access the database manager
|
13
|
+
def get_database_manager
|
14
|
+
@database_manager ||= ::Dbviewer::DatabaseManager.new
|
15
|
+
end
|
16
|
+
|
3
17
|
def format_cell_value(value)
|
4
18
|
return "NULL" if value.nil?
|
5
19
|
return value.to_s.truncate(100) unless value.is_a?(String)
|
@@ -114,5 +128,64 @@ module Dbviewer
|
|
114
128
|
def logs_nav_class
|
115
129
|
active_nav_class("logs")
|
116
130
|
end
|
131
|
+
|
132
|
+
# Returns a sort icon based on the current sort direction
|
133
|
+
def sort_icon(column_name, current_order_by, current_direction)
|
134
|
+
if column_name == current_order_by
|
135
|
+
direction = current_direction == "ASC" ? "up" : "down"
|
136
|
+
"<i class='bi bi-sort-#{direction}'></i>".html_safe
|
137
|
+
else
|
138
|
+
"<i class='bi bi-filter invisible sort-icon'></i>".html_safe
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Determine the next sort direction based on the current one
|
143
|
+
def next_sort_direction(column_name, current_order_by, current_direction)
|
144
|
+
if column_name == current_order_by && current_direction == "ASC"
|
145
|
+
"DESC"
|
146
|
+
else
|
147
|
+
"ASC"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Generate a sortable column header link
|
152
|
+
def sortable_column_header(column_name, current_order_by, current_direction, table_name, current_page, per_page, column_filters)
|
153
|
+
is_sorted = column_name == current_order_by
|
154
|
+
sort_direction = next_sort_direction(column_name, current_order_by, current_direction)
|
155
|
+
|
156
|
+
aria_sort = if is_sorted
|
157
|
+
current_direction.downcase == "asc" ? "ascending" : "descending"
|
158
|
+
else
|
159
|
+
"none"
|
160
|
+
end
|
161
|
+
|
162
|
+
# Build parameters for the sort link
|
163
|
+
sort_params = {
|
164
|
+
order_by: column_name,
|
165
|
+
order_direction: sort_direction,
|
166
|
+
page: current_page,
|
167
|
+
per_page: per_page,
|
168
|
+
column_filters: column_filters
|
169
|
+
}
|
170
|
+
|
171
|
+
# Add creation filter parameters if they're in the controller
|
172
|
+
if defined?(@creation_filter_start) && @creation_filter_start.present?
|
173
|
+
sort_params[:creation_filter_start] = @creation_filter_start
|
174
|
+
end
|
175
|
+
|
176
|
+
if defined?(@creation_filter_end) && @creation_filter_end.present?
|
177
|
+
sort_params[:creation_filter_end] = @creation_filter_end
|
178
|
+
end
|
179
|
+
|
180
|
+
link_to table_path(table_name, sort_params),
|
181
|
+
class: "d-flex align-items-center text-decoration-none text-reset column-sort-link",
|
182
|
+
title: "Sort by #{column_name} (#{sort_direction.downcase})",
|
183
|
+
"aria-sort": aria_sort,
|
184
|
+
role: "button",
|
185
|
+
tabindex: "0" do
|
186
|
+
content_tag(:span, column_name, class: "column-name") +
|
187
|
+
content_tag(:span, sort_icon(column_name, current_order_by, current_direction), class: "sort-icon-container")
|
188
|
+
end
|
189
|
+
end
|
117
190
|
end
|
118
191
|
end
|
@@ -5,13 +5,158 @@
|
|
5
5
|
id="tableSearch" placeholder="Filter tables..." aria-label="Filter tables">
|
6
6
|
</div>
|
7
7
|
|
8
|
+
<div class="p-2">
|
9
|
+
<div class="accordion accordion-flush" id="creationFilterAccordion">
|
10
|
+
<div class="accordion-item border-0">
|
11
|
+
<h2 class="accordion-header" id="creationFilterHeading">
|
12
|
+
<button class="accordion-button p-2 collapsed" type="button" data-bs-toggle="collapse"
|
13
|
+
data-bs-target="#creationFilterCollapse" aria-expanded="false"
|
14
|
+
aria-controls="creationFilterCollapse">
|
15
|
+
<i class="bi bi-calendar-range me-2"></i> Creation Filter
|
16
|
+
<% if @creation_filter_start.present? || @creation_filter_end.present? %>
|
17
|
+
<% if @table_name.present? && has_timestamp_column?(@table_name) %>
|
18
|
+
<span class="badge bg-success ms-2">Active</span>
|
19
|
+
<% else %>
|
20
|
+
<span class="badge bg-secondary ms-2">Set</span>
|
21
|
+
<% end %>
|
22
|
+
<% end %>
|
23
|
+
</button>
|
24
|
+
</h2>
|
25
|
+
<div id="creationFilterCollapse" class="accordion-collapse collapse" aria-labelledby="creationFilterHeading">
|
26
|
+
<div class="accordion-body p-2">
|
27
|
+
<form id="creationFilterForm" action="<%= request.path %>" method="get" class="mb-0">
|
28
|
+
<!-- Preserve existing query parameters -->
|
29
|
+
<input type="hidden" name="page" value="<%= @current_page %>">
|
30
|
+
<input type="hidden" name="per_page" value="<%= @per_page %>">
|
31
|
+
<input type="hidden" name="order_by" value="<%= @order_by %>">
|
32
|
+
<input type="hidden" name="order_direction" value="<%= @order_direction %>">
|
33
|
+
|
34
|
+
<!-- Datetime range fields -->
|
35
|
+
<div class="mb-2">
|
36
|
+
<label for="creationFilterStart" class="form-label mb-1 small">Start Date/Time</label>
|
37
|
+
<input type="datetime-local" id="creationFilterStart" name="creation_filter_start"
|
38
|
+
class="form-control form-control-sm" value="<%= @creation_filter_start %>">
|
39
|
+
</div>
|
40
|
+
<div class="mb-2">
|
41
|
+
<label for="creationFilterEnd" class="form-label mb-1 small">End Date/Time</label>
|
42
|
+
<input type="datetime-local" id="creationFilterEnd" name="creation_filter_end"
|
43
|
+
class="form-control form-control-sm" value="<%= @creation_filter_end %>">
|
44
|
+
</div>
|
45
|
+
|
46
|
+
<div class="d-flex justify-content-between">
|
47
|
+
<button type="submit" class="btn btn-primary btn-sm">Apply</button>
|
48
|
+
<% if @creation_filter_start.present? || @creation_filter_end.present? %>
|
49
|
+
<%
|
50
|
+
# Preserve other query params when clearing creation filter
|
51
|
+
clear_params = {
|
52
|
+
clear_creation_filter: true,
|
53
|
+
page: @current_page,
|
54
|
+
per_page: @per_page,
|
55
|
+
order_by: @order_by,
|
56
|
+
order_direction: @order_direction
|
57
|
+
}
|
58
|
+
%>
|
59
|
+
<a href="<%= request.path %>?<%= clear_params.to_query %>" class="btn btn-outline-secondary btn-sm">Clear</a>
|
60
|
+
<% end %>
|
61
|
+
</div>
|
62
|
+
<div class="mt-2 small">
|
63
|
+
<% if @table_name.present? && has_timestamp_column?(@table_name) && (@creation_filter_start.present? || @creation_filter_end.present?) %>
|
64
|
+
<div class="text-success">
|
65
|
+
<i class="bi bi-check-circle-fill"></i>
|
66
|
+
Filter active on this table.
|
67
|
+
<% if @current_page == 1 && @records && @records.rows && @records.rows.empty? %>
|
68
|
+
<div class="alert alert-warning p-1 mt-2 small">
|
69
|
+
<i class="bi bi-exclamation-triangle-fill"></i>
|
70
|
+
No records match the filter criteria.
|
71
|
+
</div>
|
72
|
+
<% end %>
|
73
|
+
</div>
|
74
|
+
<% elsif @table_name.present? && (@creation_filter_start.present? || @creation_filter_end.present?) %>
|
75
|
+
<div class="text-warning">
|
76
|
+
<i class="bi bi-exclamation-circle-fill"></i>
|
77
|
+
This table has no <code>created_at</code> column.
|
78
|
+
</div>
|
79
|
+
<% elsif !@table_name.present? && (@creation_filter_start.present? || @creation_filter_end.present?) %>
|
80
|
+
<div class="text-info">
|
81
|
+
<i class="bi bi-info-circle-fill"></i>
|
82
|
+
Filter will be applied on tables with <code>created_at</code> column.
|
83
|
+
</div>
|
84
|
+
<% else %>
|
85
|
+
<div class="text-muted">
|
86
|
+
<i class="bi bi-info-circle-fill"></i>
|
87
|
+
Filters apply to tables with a <code>created_at</code> column.
|
88
|
+
</div>
|
89
|
+
<% end %>
|
90
|
+
</div>
|
91
|
+
</form>
|
92
|
+
</div>
|
93
|
+
</div>
|
94
|
+
</div>
|
95
|
+
</div>
|
96
|
+
</div>
|
97
|
+
|
98
|
+
<!-- Add custom styling for datetime inputs -->
|
99
|
+
<style>
|
100
|
+
/* Better datetime input styling */
|
101
|
+
input[type="datetime-local"] {
|
102
|
+
padding-right: 0.5rem;
|
103
|
+
}
|
104
|
+
|
105
|
+
/* Dark mode support for datetime inputs */
|
106
|
+
[data-bs-theme="dark"] input[type="datetime-local"] {
|
107
|
+
background-color: rgba(255,255,255,0.1);
|
108
|
+
color: #fff;
|
109
|
+
border-color: rgba(255,255,255,0.15);
|
110
|
+
}
|
111
|
+
|
112
|
+
[data-bs-theme="dark"] input[type="datetime-local"]::-webkit-calendar-picker-indicator {
|
113
|
+
filter: invert(1);
|
114
|
+
}
|
115
|
+
</style>
|
116
|
+
|
117
|
+
<script>
|
118
|
+
// Set default values for datetime inputs when empty
|
119
|
+
document.addEventListener('DOMContentLoaded', function() {
|
120
|
+
const startInput = document.getElementById('creationFilterStart');
|
121
|
+
const endInput = document.getElementById('creationFilterEnd');
|
122
|
+
|
123
|
+
// When applying filter with empty start date, default to beginning of current month
|
124
|
+
if (startInput) {
|
125
|
+
startInput.addEventListener('click', function() {
|
126
|
+
if (!this.value) {
|
127
|
+
const now = new Date();
|
128
|
+
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
|
129
|
+
const formattedDate = firstDay.toISOString().slice(0, 16); // Format: YYYY-MM-DDTHH:MM
|
130
|
+
this.value = formattedDate;
|
131
|
+
}
|
132
|
+
});
|
133
|
+
}
|
134
|
+
|
135
|
+
// When applying filter with empty end date, default to current datetime
|
136
|
+
if (endInput) {
|
137
|
+
endInput.addEventListener('click', function() {
|
138
|
+
if (!this.value) {
|
139
|
+
const now = new Date();
|
140
|
+
const formattedDate = now.toISOString().slice(0, 16); // Format: YYYY-MM-DDTHH:MM
|
141
|
+
this.value = formattedDate;
|
142
|
+
}
|
143
|
+
});
|
144
|
+
}
|
145
|
+
});
|
146
|
+
</script>
|
8
147
|
</div>
|
9
148
|
|
10
149
|
<div class="dbviewer-sidebar-content">
|
11
150
|
<% if @tables.any? %>
|
12
151
|
<div class="list-group list-group-flush" id="tablesList">
|
13
152
|
<% @tables.each do |table| %>
|
14
|
-
|
153
|
+
<%
|
154
|
+
# Build table URL with creation filter params if they exist
|
155
|
+
table_url_params = {}
|
156
|
+
table_url_params[:creation_filter_start] = @creation_filter_start if @creation_filter_start.present?
|
157
|
+
table_url_params[:creation_filter_end] = @creation_filter_end if @creation_filter_end.present?
|
158
|
+
%>
|
159
|
+
<%= link_to table_path(table[:name], table_url_params), title: table[:name],
|
15
160
|
class: "list-group-item list-group-item-action d-flex align-items-center #{'active' if current_table?(table[:name])}",
|
16
161
|
tabindex: "0",
|
17
162
|
data: { table_name: table[:name] },
|
@@ -44,7 +44,13 @@
|
|
44
44
|
<% @tables.each do |table| %>
|
45
45
|
<tr>
|
46
46
|
<td class="fw-medium">
|
47
|
-
|
47
|
+
<%
|
48
|
+
# Include creation filter params in table links
|
49
|
+
filter_params = {}
|
50
|
+
filter_params[:creation_filter_start] = session[:creation_filter_start] if session[:creation_filter_start].present?
|
51
|
+
filter_params[:creation_filter_end] = session[:creation_filter_end] if session[:creation_filter_end].present?
|
52
|
+
%>
|
53
|
+
<%= link_to table[:name], table_path(table[:name], filter_params), class: "text-decoration-none" %>
|
48
54
|
</td>
|
49
55
|
<td class="text-end">
|
50
56
|
<span class="badge bg-secondary-subtle"><%= table[:record_count] %></span>
|