dbviewer 0.5.7 → 0.6.0
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 +33 -6
- data/app/assets/javascripts/dbviewer/connections.js +70 -0
- data/app/controllers/concerns/dbviewer/database_operations.rb +110 -6
- data/app/controllers/dbviewer/api/entity_relationship_diagrams_controller.rb +30 -60
- data/app/controllers/dbviewer/api/tables_controller.rb +20 -0
- data/app/controllers/dbviewer/connections_controller.rb +35 -0
- data/app/controllers/dbviewer/home_controller.rb +5 -0
- data/app/helpers/dbviewer/application_helper.rb +46 -8
- data/app/views/dbviewer/connections/index.html.erb +119 -0
- data/app/views/dbviewer/connections/new.html.erb +79 -0
- data/app/views/dbviewer/shared/_tables_sidebar.html.erb +49 -0
- data/app/views/dbviewer/tables/index.html.erb +0 -8
- data/app/views/dbviewer/tables/show.html.erb +132 -8
- data/app/views/layouts/dbviewer/application.html.erb +22 -1
- data/config/routes.rb +15 -0
- data/lib/dbviewer/configuration.rb +17 -0
- data/lib/dbviewer/database/dynamic_model_factory.rb +4 -12
- data/lib/dbviewer/database/manager.rb +19 -10
- data/lib/dbviewer/datatable/query_operations.rb +21 -1
- data/lib/dbviewer/version.rb +1 -1
- data/lib/dbviewer.rb +37 -4
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53152d4109f427b7d3d32f38292f6c7add1aecc15e7bf9ce8db74458f24f8de8
|
4
|
+
data.tar.gz: dc556f4746314538c53a16cd1eabd811b91cfb6c35bee177971de3eae40f9f93
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4b267eee75e4a92a6a139e226834ff051e306524e1450833b644467b3661c83bffcadb9920450f1196c12919b474f6dca7c3cc6b12f352899c5803984c930e7
|
7
|
+
data.tar.gz: f62a2d830f539f8636522035fa60e08535f27c8b0cb77eed42c4baf1103171533e1b8327975a696f5d3c7125bfbc9ae4dc20c1a21d145b3f7b56117f95ccb742
|
data/README.md
CHANGED
@@ -32,6 +32,11 @@ It's designed for development, debugging, and database analysis, offering a clea
|
|
32
32
|
- View table structure reference while writing queries
|
33
33
|
- Protection against potentially harmful SQL operations
|
34
34
|
- Query execution statistics and timing
|
35
|
+
- **Multiple Database Connections**:
|
36
|
+
- Connect to multiple databases within your application
|
37
|
+
- Switch between connections on-the-fly to view different database schemas
|
38
|
+
- Add new database connections from the UI without code changes
|
39
|
+
- Test connections to verify they're working properly
|
35
40
|
- **Enhanced UI Features**:
|
36
41
|
- Responsive, Bootstrap-based interface that works on desktop and mobile
|
37
42
|
- Fixed header navigation with quick access to all features
|
@@ -142,6 +147,34 @@ You can also create this file manually if you prefer.
|
|
142
147
|
|
143
148
|
The configuration is accessed through `Dbviewer.configuration` throughout the codebase. You can also access it via `Dbviewer.config` which is an alias for backward compatibility.
|
144
149
|
|
150
|
+
### Multiple Database Connections
|
151
|
+
|
152
|
+
DBViewer supports working with multiple database connections in your application. This is useful for applications that connect to multiple databases or use different connection pools.
|
153
|
+
|
154
|
+
To configure multiple database connections, set them up in your initializer:
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
# config/initializers/dbviewer.rb
|
158
|
+
Dbviewer.configure do |config|
|
159
|
+
# Multiple database connections configuration
|
160
|
+
config.database_connections = {
|
161
|
+
primary: {
|
162
|
+
connection_class: "ActiveRecord::Base",
|
163
|
+
name: "Primary Database"
|
164
|
+
},
|
165
|
+
secondary: {
|
166
|
+
connection_class: "SecondaryDatabase",
|
167
|
+
name: "Blog Database"
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
# Set the default active connection
|
172
|
+
config.current_connection = :primary
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
Each connection needs to reference an ActiveRecord class that establishes a database connection. For more details, see [Multiple Database Connections](docs/multiple_connections.md).
|
177
|
+
|
145
178
|
## 🪵 Query Logging
|
146
179
|
|
147
180
|
DBViewer includes a powerful SQL query logging system that captures and analyzes database queries. You can access this log through the `/dbviewer/logs` endpoint. The logging system offers two storage backends:
|
@@ -413,12 +446,6 @@ graph TB
|
|
413
446
|
Engine -.->|"setup()"| QueryLogger
|
414
447
|
Config -.->|"logging settings"| QueryLogger
|
415
448
|
|
416
|
-
classDef decoupled fill:#e1f5fe,stroke:#01579b,stroke-width:2px
|
417
|
-
classDef controller fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
|
418
|
-
classDef database fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px
|
419
|
-
classDef query fill:#fff3e0,stroke:#e65100,stroke-width:2px
|
420
|
-
classDef storage fill:#fce4ec,stroke:#880e4f,stroke-width:2px
|
421
|
-
|
422
449
|
class CacheManager,QueryLogger decoupled
|
423
450
|
class HomeController,TablesController,LogsController,ERDController,APIController controller
|
424
451
|
class Manager,MetadataManager,DynamicModelFactory database
|
@@ -0,0 +1,70 @@
|
|
1
|
+
|
2
|
+
document.addEventListener('DOMContentLoaded', function() {
|
3
|
+
// Add connection form validation
|
4
|
+
const forms = document.querySelectorAll('.needs-validation');
|
5
|
+
|
6
|
+
Array.from(forms).forEach(form => {
|
7
|
+
form.addEventListener('submit', event => {
|
8
|
+
if (!form.checkValidity()) {
|
9
|
+
event.preventDefault();
|
10
|
+
event.stopPropagation();
|
11
|
+
}
|
12
|
+
|
13
|
+
form.classList.add('was-validated');
|
14
|
+
}, false);
|
15
|
+
});
|
16
|
+
|
17
|
+
// Handle connection test
|
18
|
+
const testButtons = document.querySelectorAll('.test-connection-btn');
|
19
|
+
|
20
|
+
Array.from(testButtons).forEach(button => {
|
21
|
+
button.addEventListener('click', async function(event) {
|
22
|
+
event.preventDefault();
|
23
|
+
const connectionKey = this.dataset.connectionKey;
|
24
|
+
const statusElement = document.getElementById(`connection-status-${connectionKey}`);
|
25
|
+
|
26
|
+
if (!statusElement) return;
|
27
|
+
|
28
|
+
statusElement.innerHTML = '<i class="bi bi-arrow-repeat spin me-1"></i> Testing connection...';
|
29
|
+
statusElement.classList.remove('text-success', 'text-danger');
|
30
|
+
|
31
|
+
try {
|
32
|
+
const response = await fetch(`/dbviewer/api/connections/${connectionKey}/test`, {
|
33
|
+
method: 'GET',
|
34
|
+
headers: {
|
35
|
+
'Accept': 'application/json',
|
36
|
+
'X-Requested-With': 'XMLHttpRequest'
|
37
|
+
}
|
38
|
+
});
|
39
|
+
|
40
|
+
const data = await response.json();
|
41
|
+
|
42
|
+
if (data.success) {
|
43
|
+
statusElement.innerHTML = '<i class="bi bi-check-circle-fill me-1"></i> Connection successful';
|
44
|
+
statusElement.classList.add('text-success');
|
45
|
+
} else {
|
46
|
+
statusElement.innerHTML = `<i class="bi bi-x-circle-fill me-1"></i> ${data.error || 'Connection failed'}`;
|
47
|
+
statusElement.classList.add('text-danger');
|
48
|
+
}
|
49
|
+
} catch (error) {
|
50
|
+
statusElement.innerHTML = '<i class="bi bi-x-circle-fill me-1"></i> Error testing connection';
|
51
|
+
statusElement.classList.add('text-danger');
|
52
|
+
}
|
53
|
+
});
|
54
|
+
});
|
55
|
+
});
|
56
|
+
|
57
|
+
// Auto-generate connection key from name
|
58
|
+
function setupConnectionKeyGenerator() {
|
59
|
+
const nameInput = document.getElementById('connection_name');
|
60
|
+
const keyInput = document.getElementById('connection_key');
|
61
|
+
|
62
|
+
if (nameInput && keyInput) {
|
63
|
+
nameInput.addEventListener('input', function() {
|
64
|
+
keyInput.value = this.value
|
65
|
+
.toLowerCase()
|
66
|
+
.replace(/\s+/g, '_')
|
67
|
+
.replace(/[^a-z0-9_]/g, '');
|
68
|
+
});
|
69
|
+
}
|
70
|
+
}
|
@@ -5,12 +5,100 @@ module Dbviewer
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
-
helper_method :current_table?, :get_database_name, :get_adapter_name
|
8
|
+
helper_method :current_table?, :get_database_name, :get_adapter_name,
|
9
|
+
:current_connection_key, :available_connections if respond_to?(:helper_method)
|
9
10
|
end
|
10
11
|
|
11
|
-
#
|
12
|
+
# Get the current active connection key
|
13
|
+
def current_connection_key
|
14
|
+
# Get the connection key from the session or fall back to the default
|
15
|
+
key = session[:dbviewer_connection] || Dbviewer.configuration.current_connection
|
16
|
+
|
17
|
+
# Ensure the key actually exists in our configured connections
|
18
|
+
if key && Dbviewer.configuration.database_connections.key?(key.to_sym)
|
19
|
+
return key.to_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
# If the key doesn't exist, fall back to any available connection
|
23
|
+
first_key = Dbviewer.configuration.database_connections.keys.first
|
24
|
+
if first_key
|
25
|
+
session[:dbviewer_connection] = first_key # Update the session
|
26
|
+
return first_key
|
27
|
+
end
|
28
|
+
|
29
|
+
# If there are no connections configured, use a default key
|
30
|
+
# This should never happen in normal operation, but it's a safety measure
|
31
|
+
:default
|
32
|
+
end
|
33
|
+
|
34
|
+
# Set the current connection to use
|
35
|
+
def switch_connection(connection_key)
|
36
|
+
connection_key = connection_key.to_sym if connection_key.respond_to?(:to_sym)
|
37
|
+
|
38
|
+
if connection_key && Dbviewer.configuration.database_connections.key?(connection_key)
|
39
|
+
session[:dbviewer_connection] = connection_key
|
40
|
+
# Clear the database manager to force it to be recreated with the new connection
|
41
|
+
@database_manager = nil
|
42
|
+
return true
|
43
|
+
else
|
44
|
+
# If the connection key doesn't exist, reset to default connection
|
45
|
+
if Dbviewer.configuration.database_connections.key?(Dbviewer.configuration.current_connection)
|
46
|
+
session[:dbviewer_connection] = Dbviewer.configuration.current_connection
|
47
|
+
@database_manager = nil
|
48
|
+
return true
|
49
|
+
else
|
50
|
+
# If even the default connection isn't valid, try the first available connection
|
51
|
+
first_key = Dbviewer.configuration.database_connections.keys.first
|
52
|
+
if first_key
|
53
|
+
session[:dbviewer_connection] = first_key
|
54
|
+
@database_manager = nil
|
55
|
+
return true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
false # Return false if we couldn't set a valid connection
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get list of available connections
|
64
|
+
def available_connections
|
65
|
+
connections = Dbviewer.configuration.database_connections.map do |key, config|
|
66
|
+
# Try to determine the adapter name if it's not already stored
|
67
|
+
adapter_name = nil
|
68
|
+
if config[:adapter_name].present?
|
69
|
+
adapter_name = config[:adapter_name]
|
70
|
+
elsif config[:connection].present?
|
71
|
+
begin
|
72
|
+
adapter_name = config[:connection].connection.adapter_name
|
73
|
+
rescue => e
|
74
|
+
Rails.logger.error("Error getting adapter name: #{e.message}")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
{
|
79
|
+
key: key,
|
80
|
+
name: config[:name] || key.to_s.humanize,
|
81
|
+
adapter_name: adapter_name,
|
82
|
+
current: key.to_sym == current_connection_key.to_sym
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
# Ensure at least one connection is marked as current
|
87
|
+
unless connections.any? { |c| c[:current] }
|
88
|
+
# If no connection is current, mark the first one as current
|
89
|
+
if connections.any?
|
90
|
+
connections.first[:current] = true
|
91
|
+
# Also update the session
|
92
|
+
session[:dbviewer_connection] = connections.first[:key]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
connections
|
97
|
+
end
|
98
|
+
|
99
|
+
# Initialize the database manager with the current connection
|
12
100
|
def database_manager
|
13
|
-
@database_manager
|
101
|
+
@database_manager = ::Dbviewer::Database::Manager.new(current_connection_key)
|
14
102
|
end
|
15
103
|
|
16
104
|
# Initialize the table query operations manager
|
@@ -21,6 +109,12 @@ module Dbviewer
|
|
21
109
|
|
22
110
|
# Get the name of the current database
|
23
111
|
def get_database_name
|
112
|
+
# First check if this connection has a name in the configuration
|
113
|
+
current_conn_config = Dbviewer.configuration.database_connections[current_connection_key]
|
114
|
+
if current_conn_config && current_conn_config[:name].present?
|
115
|
+
return current_conn_config[:name]
|
116
|
+
end
|
117
|
+
|
24
118
|
adapter = database_manager.connection.adapter_name.downcase
|
25
119
|
|
26
120
|
case adapter
|
@@ -34,7 +128,7 @@ module Dbviewer
|
|
34
128
|
result ? result["db_name"] : "Database"
|
35
129
|
when /sqlite/
|
36
130
|
# For SQLite, extract the database name from the connection_config
|
37
|
-
database_path =
|
131
|
+
database_path = database_manager.connection.pool.spec.config[:database] || ""
|
38
132
|
File.basename(database_path, ".*") || "SQLite Database"
|
39
133
|
else
|
40
134
|
"Database" # Default fallback
|
@@ -68,11 +162,21 @@ module Dbviewer
|
|
68
162
|
name: table_name
|
69
163
|
}
|
70
164
|
|
71
|
-
# Only fetch record
|
72
|
-
|
165
|
+
# Only fetch record count if specifically requested
|
166
|
+
if include_record_counts
|
167
|
+
begin
|
168
|
+
table_stats[:record_count] = database_manager.record_count(table_name)
|
169
|
+
rescue => e
|
170
|
+
Rails.logger.error("Error fetching record count for #{table_name}: #{e.message}")
|
171
|
+
table_stats[:record_count] = 0
|
172
|
+
end
|
173
|
+
end
|
73
174
|
|
74
175
|
table_stats
|
75
176
|
end
|
177
|
+
rescue => e
|
178
|
+
Rails.logger.error("Error fetching tables: #{e.message}")
|
179
|
+
[]
|
76
180
|
end
|
77
181
|
|
78
182
|
# Gather database analytics information
|
@@ -4,74 +4,44 @@ module Dbviewer
|
|
4
4
|
before_action :set_tables
|
5
5
|
|
6
6
|
def relationships
|
7
|
-
|
8
|
-
|
9
|
-
@table_relationships
|
10
|
-
|
11
|
-
|
12
|
-
status: "success"
|
13
|
-
})
|
14
|
-
rescue => e
|
15
|
-
Rails.logger.error("[DBViewer] Error fetching relationships: #{e.message}")
|
16
|
-
render json: {
|
17
|
-
relationships: [],
|
18
|
-
status: "error",
|
19
|
-
error: e.message
|
20
|
-
}, status: :internal_server_error
|
21
|
-
end
|
7
|
+
@table_relationships = fetch_table_relationships
|
8
|
+
render_success({
|
9
|
+
relationships: @table_relationships,
|
10
|
+
status: "success"
|
11
|
+
})
|
22
12
|
end
|
23
13
|
|
24
14
|
def table_relationships
|
25
|
-
# Fetch relationships for specific tables
|
26
15
|
table_names = params[:tables]&.split(",") || []
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
begin
|
44
|
-
metadata = fetch_table_metadata(table_name)
|
45
|
-
if metadata && metadata[:foreign_keys].present?
|
46
|
-
metadata[:foreign_keys].each do |fk|
|
47
|
-
relationships << {
|
48
|
-
from_table: table_name,
|
49
|
-
to_table: fk[:to_table],
|
50
|
-
from_column: fk[:column],
|
51
|
-
to_column: fk[:primary_key],
|
52
|
-
name: fk[:name]
|
53
|
-
}
|
54
|
-
end
|
16
|
+
relationships = []
|
17
|
+
|
18
|
+
table_names.each do |table_name|
|
19
|
+
next unless @tables.any? { |t| t[:name] == table_name }
|
20
|
+
|
21
|
+
begin
|
22
|
+
metadata = fetch_table_metadata(table_name)
|
23
|
+
if metadata && metadata[:foreign_keys].present?
|
24
|
+
metadata[:foreign_keys].each do |fk|
|
25
|
+
relationships << {
|
26
|
+
from_table: table_name,
|
27
|
+
to_table: fk[:to_table],
|
28
|
+
from_column: fk[:column],
|
29
|
+
to_column: fk[:primary_key],
|
30
|
+
name: fk[:name]
|
31
|
+
}
|
55
32
|
end
|
56
|
-
rescue => e
|
57
|
-
Rails.logger.error("[DBViewer] Error fetching relationships for #{table_name}: #{e.message}")
|
58
|
-
# Continue with other tables even if one fails
|
59
33
|
end
|
34
|
+
rescue => e
|
35
|
+
Rails.logger.error("[DBViewer] Error fetching relationships for #{table_name}: #{e.message}")
|
36
|
+
# Continue with other tables even if one fails
|
60
37
|
end
|
61
|
-
|
62
|
-
render_success({
|
63
|
-
relationships: relationships,
|
64
|
-
status: "success",
|
65
|
-
processed_tables: table_names
|
66
|
-
})
|
67
|
-
rescue => e
|
68
|
-
Rails.logger.error("[DBViewer] Error in table_relationships: #{e.message}")
|
69
|
-
render json: {
|
70
|
-
relationships: [],
|
71
|
-
status: "error",
|
72
|
-
error: e.message
|
73
|
-
}, status: :internal_server_error
|
74
38
|
end
|
39
|
+
|
40
|
+
render_success({
|
41
|
+
relationships: relationships,
|
42
|
+
status: "success",
|
43
|
+
processed_tables: table_names
|
44
|
+
})
|
75
45
|
end
|
76
46
|
|
77
47
|
private
|
@@ -16,6 +16,26 @@ module Dbviewer
|
|
16
16
|
render_success(total_relationships: total_relationships)
|
17
17
|
end
|
18
18
|
|
19
|
+
def relationship_counts
|
20
|
+
table_name = params[:id]
|
21
|
+
record_id = params[:record_id]
|
22
|
+
|
23
|
+
reverse_foreign_keys = fetch_table_metadata(table_name).dig(:reverse_foreign_keys) || []
|
24
|
+
relationship_counts = reverse_foreign_keys.map do |rel|
|
25
|
+
{
|
26
|
+
table: rel[:from_table],
|
27
|
+
foreign_key: rel[:column],
|
28
|
+
count: database_manager.get_model_for(rel[:from_table]).where(rel[:column] => record_id).count
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
render_success({
|
33
|
+
table_name: table_name,
|
34
|
+
record_id: record_id,
|
35
|
+
relationships: relationship_counts
|
36
|
+
})
|
37
|
+
end
|
38
|
+
|
19
39
|
private
|
20
40
|
|
21
41
|
def fetch_tables_count
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Dbviewer
|
2
|
+
class ConnectionsController < ApplicationController
|
3
|
+
# GET /dbviewer/connections
|
4
|
+
def index
|
5
|
+
@connections = available_connections
|
6
|
+
|
7
|
+
respond_to do |format|
|
8
|
+
format.html
|
9
|
+
format.json { render json: @connections }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# POST /dbviewer/connections/:id
|
14
|
+
def update
|
15
|
+
connection_key = params[:id]
|
16
|
+
|
17
|
+
if switch_connection(connection_key)
|
18
|
+
# Safely get the connection name
|
19
|
+
connection_config = Dbviewer.configuration.database_connections[connection_key.to_sym]
|
20
|
+
connection_name = connection_config && connection_config[:name] ? connection_config[:name] : "selected"
|
21
|
+
|
22
|
+
respond_to do |format|
|
23
|
+
format.html { redirect_to root_path, notice: "Switched to #{connection_name} database." }
|
24
|
+
format.json { render json: { success: true, current: connection_key } }
|
25
|
+
end
|
26
|
+
else
|
27
|
+
# Handle the case where switching failed
|
28
|
+
respond_to do |format|
|
29
|
+
format.html { redirect_to connections_path, alert: "Failed to switch connection. The connection may be invalid." }
|
30
|
+
format.json { render json: { success: false, error: "Invalid connection" }, status: :unprocessable_entity }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -33,7 +33,13 @@ module Dbviewer
|
|
33
33
|
|
34
34
|
# Generate operator options based on column type
|
35
35
|
def operator_options_for_column_type(column_type)
|
36
|
-
|
36
|
+
# Common operators for all types
|
37
|
+
common_operators = [
|
38
|
+
[ "is null", "is_null" ],
|
39
|
+
[ "is not null", "is_not_null" ]
|
40
|
+
]
|
41
|
+
|
42
|
+
type_specific_operators = if column_type && (column_type =~ /datetime/ || column_type =~ /^date$/ || column_type =~ /^time$/)
|
37
43
|
# Date/Time operators
|
38
44
|
[
|
39
45
|
[ "=", "eq" ],
|
@@ -64,28 +70,58 @@ module Dbviewer
|
|
64
70
|
[ "ends with", "ends_with" ]
|
65
71
|
]
|
66
72
|
end
|
73
|
+
|
74
|
+
# Return type-specific operators first, then common operators
|
75
|
+
type_specific_operators + common_operators
|
67
76
|
end
|
68
77
|
|
69
78
|
# Render column filter input based on column type
|
70
79
|
def render_column_filter_input(form, column_name, column_type, column_filters)
|
71
|
-
if
|
80
|
+
# Get selected operator to check if it's a null operator
|
81
|
+
operator = column_filters["#{column_name}_operator"]
|
82
|
+
is_null_operator = operator == "is_null" || operator == "is_not_null"
|
83
|
+
|
84
|
+
# Clean up the value for non-null operators if the value contains a null operator
|
85
|
+
# This ensures we don't carry over 'is_null' or 'is_not_null' values when switching operators
|
86
|
+
value = column_filters[column_name]
|
87
|
+
if !is_null_operator && value.present? && (value == "is_null" || value == "is_not_null")
|
88
|
+
value = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
# For null operators, display a non-editable field without placeholder
|
92
|
+
if is_null_operator
|
93
|
+
# Keep a hidden field for the actual value
|
94
|
+
hidden_field = form.hidden_field("column_filters[#{column_name}]",
|
95
|
+
value: operator,
|
96
|
+
class: "null-filter-value",
|
97
|
+
data: { column: column_name })
|
98
|
+
|
99
|
+
# Add a visible but disabled text field with no placeholder or value
|
100
|
+
visible_field = form.text_field("column_filters[#{column_name}_display]",
|
101
|
+
disabled: true,
|
102
|
+
value: "",
|
103
|
+
class: "form-control form-control-sm column-filter rounded-0 disabled-filter",
|
104
|
+
data: { column: "#{column_name}_display" })
|
105
|
+
|
106
|
+
hidden_field + visible_field
|
107
|
+
elsif column_type && column_type =~ /datetime/
|
72
108
|
form.datetime_local_field("column_filters[#{column_name}]",
|
73
|
-
value:
|
109
|
+
value: value,
|
74
110
|
class: "form-control form-control-sm column-filter rounded-0",
|
75
111
|
data: { column: column_name })
|
76
112
|
elsif column_type && column_type =~ /^date$/
|
77
113
|
form.date_field("column_filters[#{column_name}]",
|
78
|
-
value:
|
114
|
+
value: value,
|
79
115
|
class: "form-control form-control-sm column-filter rounded-0",
|
80
116
|
data: { column: column_name })
|
81
117
|
elsif column_type && column_type =~ /^time$/
|
82
118
|
form.time_field("column_filters[#{column_name}]",
|
83
|
-
value:
|
119
|
+
value: value,
|
84
120
|
class: "form-control form-control-sm column-filter rounded-0",
|
85
121
|
data: { column: column_name })
|
86
122
|
else
|
87
123
|
form.text_field("column_filters[#{column_name}]",
|
88
|
-
value:
|
124
|
+
value: value,
|
89
125
|
placeholder: "",
|
90
126
|
class: "form-control form-control-sm column-filter rounded-0",
|
91
127
|
data: { column: column_name })
|
@@ -113,8 +149,10 @@ module Dbviewer
|
|
113
149
|
column_type = column_type_from_info(column_name, columns)
|
114
150
|
|
115
151
|
content_tag(:div, class: "filter-input-group") do
|
116
|
-
render_operator_select(form, column_name, column_type, column_filters)
|
117
|
-
render_column_filter_input(form, column_name, column_type, column_filters)
|
152
|
+
operator_select = render_operator_select(form, column_name, column_type, column_filters)
|
153
|
+
input_field = render_column_filter_input(form, column_name, column_type, column_filters)
|
154
|
+
|
155
|
+
operator_select + input_field
|
118
156
|
end
|
119
157
|
end
|
120
158
|
|