dbviewer 0.4.7 → 0.5.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 +4 -20
- data/app/controllers/concerns/dbviewer/database_operations.rb +1 -30
- data/app/controllers/dbviewer/api/base_controller.rb +19 -0
- data/app/controllers/dbviewer/api/database_controller.rb +14 -0
- data/app/controllers/dbviewer/api/queries_controller.rb +18 -0
- data/app/controllers/dbviewer/api/tables_controller.rb +39 -0
- data/app/controllers/dbviewer/home_controller.rb +0 -92
- data/app/controllers/dbviewer/tables_controller.rb +20 -4
- data/app/views/dbviewer/home/index.html.erb +182 -28
- data/app/views/dbviewer/tables/show.html.erb +135 -2
- data/app/views/layouts/dbviewer/application.html.erb +184 -84
- data/app/views/layouts/dbviewer/shared/_sidebar.html.erb +0 -1
- data/config/routes.rb +18 -7
- data/lib/dbviewer/configuration.rb +4 -0
- data/lib/dbviewer/version.rb +1 -1
- data/lib/generators/dbviewer/structured_api_generator.rb +11 -0
- data/lib/generators/dbviewer/templates/initializer.rb +3 -0
- 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: 628884438be23468b7910e2a13c14b2aa00cc6361f01cb09b53ecdcf1d8a0b78
|
4
|
+
data.tar.gz: e9952d0c11ae3669b2604ac208f1a4ae224c37eb4d5f2b1074c4f284bb0bc24a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 369f106d728156712416e6d1f65c51e156ec1357e717c5cc4f95e9fe16dbe696e3a06b8c70a2ef2fd6ae559dd0e7ad7b73fc6a5b2e0ccc396a756633d733c2dd
|
7
|
+
data.tar.gz: 273df74ea6e69b327b01faecb4b3572983fbd461ee042c41ba2402729482ead2b9ebaed117c95158ff01def2e1a1373aaf0fed75d8268d8546b0f3e38d2ff2ae
|
data/README.md
CHANGED
@@ -5,7 +5,8 @@
|
|
5
5
|
DBViewer is a powerful Rails engine that provides a comprehensive interface to view and explore database tables, records, and schema.
|
6
6
|
It's designed for development, debugging, and database analysis, offering a clean and intuitive way to interact with your application's database.
|
7
7
|
|
8
|
-
<img width="1470" alt="image" src="https://github.com/user-attachments/assets/
|
8
|
+
<img width="1470" alt="image" src="https://github.com/user-attachments/assets/0d2719ad-f5b4-4818-891d-5bff7be6c5c3" />
|
9
|
+
|
9
10
|
|
10
11
|
## ✨ Features
|
11
12
|
|
@@ -45,27 +46,10 @@ It's designed for development, debugging, and database analysis, offering a clea
|
|
45
46
|
|
46
47
|
<details>
|
47
48
|
<summary>Click to see more screenshots</summary>
|
48
|
-
|
49
|
-
#### Dashboard Overview
|
50
|
-
|
51
|
-
<img width="1470" alt="image" src="https://github.com/user-attachments/assets/4e803d51-9a5b-4c80-bb4c-a761dba15a40" />
|
52
|
-
|
53
|
-
#### Table Details
|
54
|
-
|
55
|
-
<img width="1470" alt="image" src="https://github.com/user-attachments/assets/fe425ab4-5b22-4839-87bc-050b80ad4cf0" />
|
56
|
-
|
57
|
-
#### Query Editor
|
58
|
-
|
59
|
-
<img width="1470" alt="image" src="https://github.com/user-attachments/assets/392c73c7-0724-4a39-8ffa-8ff5115c5d5f" />
|
60
|
-
|
61
|
-
#### Query Logs
|
62
|
-
|
63
|
-
<img width="1470" alt="image" src="https://github.com/user-attachments/assets/7fcf3355-be3c-4d6a-9ab0-811333be5bbc" />
|
64
49
|
|
65
|
-
|
50
|
+
<img width="1470" alt="image" src="https://github.com/user-attachments/assets/7d708c14-5f78-42c4-b769-2167546b3aad" />
|
51
|
+
<img width="1470" alt="image" src="https://github.com/user-attachments/assets/f6d9a39a-a571-4328-908a-d96b3148f707" />
|
66
52
|
|
67
|
-
<img width="1470" alt="image" src="https://github.com/user-attachments/assets/0a2f838f-4ca6-4592-b939-7c7f8ac40f48" />
|
68
|
-
|
69
53
|
</details>
|
70
54
|
|
71
55
|
## 📥 Installation
|
@@ -50,7 +50,6 @@ module Dbviewer
|
|
50
50
|
database_manager.tables.map do |table_name|
|
51
51
|
table_stats = {
|
52
52
|
name: table_name
|
53
|
-
# columns_count: database_manager.column_count(table_name)
|
54
53
|
}
|
55
54
|
|
56
55
|
# Only fetch record counts if explicitly requested
|
@@ -72,36 +71,8 @@ module Dbviewer
|
|
72
71
|
largest_tables: tables.sort_by { |t| -t[:record_count] }.first(10),
|
73
72
|
empty_tables: tables.select { |t| t[:record_count] == 0 }
|
74
73
|
}
|
75
|
-
|
76
|
-
# Calculate total foreign key relationships
|
77
|
-
begin
|
78
|
-
total_relationships = 0
|
79
|
-
tables.each do |table|
|
80
|
-
metadata = fetch_table_metadata(table[:name])
|
81
|
-
total_relationships += metadata[:foreign_keys].size if metadata && metadata[:foreign_keys]
|
82
|
-
end
|
83
|
-
analytics[:total_relationships] = total_relationships
|
84
|
-
rescue => e
|
85
|
-
Rails.logger.error("Error calculating relationship count: #{e.message}")
|
86
|
-
analytics[:total_relationships] = 0
|
87
|
-
end
|
88
|
-
|
89
74
|
# Calculate schema size if possible
|
90
|
-
|
91
|
-
analytics[:schema_size] = calculate_schema_size
|
92
|
-
rescue => e
|
93
|
-
Rails.logger.error("Error calculating schema size: #{e.message}")
|
94
|
-
analytics[:schema_size] = nil
|
95
|
-
end
|
96
|
-
|
97
|
-
# Calculate average rows per table
|
98
|
-
if tables.any?
|
99
|
-
analytics[:avg_records_per_table] = (analytics[:total_records].to_f / tables.size).round(1)
|
100
|
-
analytics[:avg_columns_per_table] = (analytics[:total_columns].to_f / tables.size).round(1)
|
101
|
-
else
|
102
|
-
analytics[:avg_records_per_table] = 0
|
103
|
-
analytics[:avg_columns_per_table] = 0
|
104
|
-
end
|
75
|
+
analytics[:schema_size] = calculate_schema_size
|
105
76
|
|
106
77
|
analytics
|
107
78
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Dbviewer
|
2
|
+
module Api
|
3
|
+
class BaseController < ApplicationController
|
4
|
+
# Skip setting the tables instance variable for API endpoints since we don't need it
|
5
|
+
skip_before_action :set_tables
|
6
|
+
|
7
|
+
# Common API response handling for errors
|
8
|
+
def render_error(error_message, status = :internal_server_error)
|
9
|
+
Rails.logger.error(error_message)
|
10
|
+
render json: { error: error_message }, status: status
|
11
|
+
end
|
12
|
+
|
13
|
+
# Common API response handling for success
|
14
|
+
def render_success(data)
|
15
|
+
render json: data
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Dbviewer
|
2
|
+
module Api
|
3
|
+
class DatabaseController < BaseController
|
4
|
+
def size
|
5
|
+
begin
|
6
|
+
size = calculate_schema_size
|
7
|
+
render_success(schema_size: size)
|
8
|
+
rescue => e
|
9
|
+
render_error("Error calculating schema size: #{e.message}")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Dbviewer
|
2
|
+
module Api
|
3
|
+
class QueriesController < BaseController
|
4
|
+
def recent
|
5
|
+
@recent_queries = if Dbviewer.configuration.enable_query_logging
|
6
|
+
Dbviewer::Logger.instance.recent_queries(limit: 10)
|
7
|
+
else
|
8
|
+
[]
|
9
|
+
end
|
10
|
+
|
11
|
+
render_success({
|
12
|
+
enabled: Dbviewer.configuration.enable_query_logging,
|
13
|
+
queries: @recent_queries
|
14
|
+
})
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Dbviewer
|
2
|
+
module Api
|
3
|
+
class TablesController < BaseController
|
4
|
+
def index
|
5
|
+
tables = fetch_tables_with_stats(include_record_counts: false)
|
6
|
+
render_success(total_tables: tables.size)
|
7
|
+
end
|
8
|
+
|
9
|
+
def records
|
10
|
+
tables = fetch_tables_with_stats(include_record_counts: true)
|
11
|
+
|
12
|
+
records_data = {
|
13
|
+
total_records: tables.sum { |t| t[:record_count] },
|
14
|
+
largest_tables: tables.sort_by { |t| -t[:record_count] }.first(10),
|
15
|
+
empty_tables: tables.select { |t| t[:record_count] == 0 },
|
16
|
+
avg_records_per_table: tables.any? ? (tables.sum { |t| t[:record_count] }.to_f / tables.size).round(1) : 0
|
17
|
+
}
|
18
|
+
|
19
|
+
render_success(records_data)
|
20
|
+
end
|
21
|
+
|
22
|
+
def relationships_count
|
23
|
+
begin
|
24
|
+
tables = fetch_tables_with_stats(include_record_counts: false)
|
25
|
+
total_relationships = 0
|
26
|
+
|
27
|
+
tables.each do |table|
|
28
|
+
metadata = fetch_table_metadata(table[:name])
|
29
|
+
total_relationships += metadata[:foreign_keys].size if metadata && metadata[:foreign_keys]
|
30
|
+
end
|
31
|
+
|
32
|
+
render_success(total_relationships: total_relationships)
|
33
|
+
rescue => e
|
34
|
+
render_error("Error calculating relationship count: #{e.message}")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,98 +1,6 @@
|
|
1
1
|
module Dbviewer
|
2
2
|
class HomeController < ApplicationController
|
3
3
|
def index
|
4
|
-
# Load page immediately without heavy data
|
5
|
-
# Data will be loaded asynchronously via AJAX
|
6
|
-
end
|
7
|
-
|
8
|
-
def analytics
|
9
|
-
# This method is deprecated but kept for backward compatibility
|
10
|
-
analytics_data = fetch_database_analytics
|
11
|
-
# Remove record data which will be served by the records endpoint
|
12
|
-
analytics_data.delete(:total_records)
|
13
|
-
analytics_data.delete(:largest_tables)
|
14
|
-
analytics_data.delete(:empty_tables)
|
15
|
-
analytics_data.delete(:avg_records_per_table)
|
16
|
-
|
17
|
-
respond_to do |format|
|
18
|
-
format.json { render json: analytics_data }
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def tables_count
|
23
|
-
tables = fetch_tables_with_stats(include_record_counts: false)
|
24
|
-
|
25
|
-
respond_to do |format|
|
26
|
-
format.json { render json: { total_tables: tables.size } }
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def relationships_count
|
31
|
-
begin
|
32
|
-
tables = fetch_tables_with_stats(include_record_counts: false)
|
33
|
-
total_relationships = 0
|
34
|
-
|
35
|
-
tables.each do |table|
|
36
|
-
metadata = fetch_table_metadata(table[:name])
|
37
|
-
total_relationships += metadata[:foreign_keys].size if metadata && metadata[:foreign_keys]
|
38
|
-
end
|
39
|
-
|
40
|
-
respond_to do |format|
|
41
|
-
format.json { render json: { total_relationships: total_relationships } }
|
42
|
-
end
|
43
|
-
rescue => e
|
44
|
-
Rails.logger.error("Error calculating relationship count: #{e.message}")
|
45
|
-
respond_to do |format|
|
46
|
-
format.json { render json: { total_relationships: 0, error: e.message }, status: :internal_server_error }
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def database_size
|
52
|
-
begin
|
53
|
-
size = calculate_schema_size
|
54
|
-
|
55
|
-
respond_to do |format|
|
56
|
-
format.json { render json: { schema_size: size } }
|
57
|
-
end
|
58
|
-
rescue => e
|
59
|
-
Rails.logger.error("Error calculating schema size: #{e.message}")
|
60
|
-
respond_to do |format|
|
61
|
-
format.json { render json: { schema_size: nil, error: e.message }, status: :internal_server_error }
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def records
|
67
|
-
tables = fetch_tables_with_stats(include_record_counts: true)
|
68
|
-
|
69
|
-
records_data = {
|
70
|
-
total_records: tables.sum { |t| t[:record_count] },
|
71
|
-
largest_tables: tables.sort_by { |t| -t[:record_count] }.first(10),
|
72
|
-
empty_tables: tables.select { |t| t[:record_count] == 0 },
|
73
|
-
avg_records_per_table: tables.any? ? (tables.sum { |t| t[:record_count] }.to_f / tables.size).round(1) : 0
|
74
|
-
}
|
75
|
-
|
76
|
-
respond_to do |format|
|
77
|
-
format.json { render json: records_data }
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def recent_queries
|
82
|
-
@recent_queries = if Dbviewer.configuration.enable_query_logging
|
83
|
-
Dbviewer::Logger.instance.recent_queries(limit: 10)
|
84
|
-
else
|
85
|
-
[]
|
86
|
-
end
|
87
|
-
|
88
|
-
respond_to do |format|
|
89
|
-
format.json do
|
90
|
-
render json: {
|
91
|
-
enabled: Dbviewer.configuration.enable_query_logging,
|
92
|
-
queries: @recent_queries
|
93
|
-
}
|
94
|
-
end
|
95
|
-
end
|
96
4
|
end
|
97
5
|
|
98
6
|
private
|
@@ -103,11 +103,9 @@ module Dbviewer
|
|
103
103
|
@current_page = [ 1, params[:page].to_i ].max
|
104
104
|
@per_page = params[:per_page] ? params[:per_page].to_i : self.class.default_per_page
|
105
105
|
@per_page = self.class.default_per_page unless self.class.per_page_options.include?(@per_page)
|
106
|
-
@order_by = params[:order_by].presence ||
|
107
|
-
database_manager.primary_key(@table_name).presence ||
|
108
|
-
(@columns.first ? @columns.first[:name] : nil)
|
106
|
+
@order_by = params[:order_by].presence || determine_default_order_column
|
109
107
|
@order_direction = params[:order_direction].upcase if params[:order_direction].present?
|
110
|
-
@order_direction = "
|
108
|
+
@order_direction = "DESC" unless self.class::VALID_SORT_DIRECTIONS.include?(@order_direction)
|
111
109
|
@column_filters = params[:column_filters].presence ? params[:column_filters].to_enum.to_h : {}
|
112
110
|
end
|
113
111
|
|
@@ -158,5 +156,23 @@ module Dbviewer
|
|
158
156
|
end
|
159
157
|
end
|
160
158
|
end
|
159
|
+
|
160
|
+
# Determine the default order column using configurable ordering logic
|
161
|
+
def determine_default_order_column
|
162
|
+
# Get the table columns to check what's available
|
163
|
+
columns = @columns || fetch_table_columns(@table_name)
|
164
|
+
column_names = columns.map { |col| col[:name] }
|
165
|
+
|
166
|
+
# Try the configured default order column first
|
167
|
+
default_column = Dbviewer.configuration.default_order_column
|
168
|
+
return default_column if default_column && column_names.include?(default_column)
|
169
|
+
|
170
|
+
# Fall back to primary key
|
171
|
+
primary_key = database_manager.primary_key(@table_name)
|
172
|
+
return primary_key if primary_key.present?
|
173
|
+
|
174
|
+
# Final fallback to first column
|
175
|
+
columns.first ? columns.first[:name] : nil
|
176
|
+
end
|
161
177
|
end
|
162
178
|
end
|
@@ -8,7 +8,7 @@
|
|
8
8
|
</div>
|
9
9
|
</div>
|
10
10
|
|
11
|
-
<div class="row g-3 mb-4
|
11
|
+
<div class="row g-3 mb-4 dashboard-analytics-cards">
|
12
12
|
<div class="col-md-4">
|
13
13
|
<div class="card h-100 border-0 shadow-sm <%= stat_card_bg_class %>">
|
14
14
|
<div class="card-body d-flex align-items-center">
|
@@ -299,7 +299,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
299
299
|
}
|
300
300
|
|
301
301
|
// Load tables count data
|
302
|
-
fetch('<%= api_tables_path %>', {
|
302
|
+
fetch('<%= dbviewer.api_tables_path %>', {
|
303
303
|
headers: {
|
304
304
|
'Accept': 'application/json',
|
305
305
|
'X-Requested-With': 'XMLHttpRequest'
|
@@ -324,7 +324,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
324
324
|
});
|
325
325
|
|
326
326
|
// Load database size data
|
327
|
-
fetch('<%=
|
327
|
+
fetch('<%= dbviewer.size_api_database_path %>', {
|
328
328
|
headers: {
|
329
329
|
'Accept': 'application/json',
|
330
330
|
'X-Requested-With': 'XMLHttpRequest'
|
@@ -349,7 +349,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
349
349
|
});
|
350
350
|
|
351
351
|
// Load records data separately
|
352
|
-
fetch('<%=
|
352
|
+
fetch('<%= dbviewer.records_api_tables_path %>', {
|
353
353
|
headers: {
|
354
354
|
'Accept': 'application/json',
|
355
355
|
'X-Requested-With': 'XMLHttpRequest'
|
@@ -377,7 +377,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
377
377
|
});
|
378
378
|
|
379
379
|
// Load recent queries data
|
380
|
-
fetch('<%=
|
380
|
+
fetch('<%= dbviewer.recent_api_queries_path %>', {
|
381
381
|
headers: {
|
382
382
|
'Accept': 'application/json',
|
383
383
|
'X-Requested-With': 'XMLHttpRequest'
|
@@ -400,73 +400,173 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
400
400
|
</script>
|
401
401
|
|
402
402
|
<style>
|
403
|
+
/* ================================================
|
404
|
+
CSS Custom Properties (CSS Variables)
|
405
|
+
================================================ */
|
406
|
+
:root {
|
407
|
+
/* Colors */
|
408
|
+
--dbviewer-code-bg: rgba(0, 0, 0, 0.05);
|
409
|
+
--dbviewer-code-border: rgba(0, 0, 0, 0.1);
|
410
|
+
--dbviewer-muted-color: #6c757d;
|
411
|
+
--dbviewer-success-color: #28a745;
|
412
|
+
--dbviewer-danger-color: #dc3545;
|
413
|
+
--dbviewer-warning-color: #ffc107;
|
414
|
+
|
415
|
+
/* Skeleton loader colors */
|
416
|
+
--skeleton-base-color: #f0f0f0;
|
417
|
+
--skeleton-highlight-color: #e0e0e0;
|
418
|
+
|
419
|
+
/* Typography */
|
420
|
+
--dbviewer-monospace-font: 'Courier New', Courier, monospace;
|
421
|
+
--dbviewer-code-font-size: 0.85rem;
|
422
|
+
|
423
|
+
/* Spacing and sizing */
|
424
|
+
--dbviewer-border-radius: 4px;
|
425
|
+
--dbviewer-border-radius-sm: 3px;
|
426
|
+
--dbviewer-padding-sm: 2px 4px;
|
427
|
+
}
|
428
|
+
|
429
|
+
/* ================================================
|
430
|
+
Dark Mode Support
|
431
|
+
================================================ */
|
432
|
+
@media (prefers-color-scheme: dark) {
|
433
|
+
:root {
|
434
|
+
--dbviewer-code-bg: rgba(255, 255, 255, 0.1);
|
435
|
+
--dbviewer-code-border: rgba(255, 255, 255, 0.15);
|
436
|
+
--dbviewer-muted-color: #adb5bd;
|
437
|
+
--skeleton-base-color: #2a2a2a;
|
438
|
+
--skeleton-highlight-color: #404040;
|
439
|
+
}
|
440
|
+
}
|
441
|
+
|
442
|
+
/* Bootstrap dark mode support */
|
443
|
+
[data-bs-theme="dark"] {
|
444
|
+
--dbviewer-code-bg: rgba(255, 255, 255, 0.1);
|
445
|
+
--dbviewer-code-border: rgba(255, 255, 255, 0.15);
|
446
|
+
--dbviewer-muted-color: #adb5bd;
|
447
|
+
--skeleton-base-color: #2a2a2a;
|
448
|
+
--skeleton-highlight-color: #404040;
|
449
|
+
}
|
450
|
+
|
451
|
+
/* ================================================
|
452
|
+
SQL Query Styling
|
453
|
+
================================================ */
|
403
454
|
.sql-query-code {
|
404
|
-
font-family:
|
405
|
-
font-size:
|
406
|
-
background-color:
|
407
|
-
padding:
|
408
|
-
border-radius:
|
455
|
+
font-family: var(--dbviewer-monospace-font);
|
456
|
+
font-size: var(--dbviewer-code-font-size);
|
457
|
+
background-color: var(--dbviewer-code-bg);
|
458
|
+
padding: var(--dbviewer-padding-sm);
|
459
|
+
border-radius: var(--dbviewer-border-radius-sm);
|
460
|
+
border: 1px solid var(--dbviewer-code-border);
|
461
|
+
transition: background-color 0.2s ease, border-color 0.2s ease;
|
409
462
|
}
|
410
|
-
|
463
|
+
|
464
|
+
.sql-query-code:hover {
|
465
|
+
background-color: var(--dbviewer-code-bg);
|
466
|
+
filter: brightness(0.95);
|
467
|
+
}
|
468
|
+
|
469
|
+
/* ================================================
|
470
|
+
Query Performance Indicators
|
471
|
+
================================================ */
|
411
472
|
.query-duration {
|
412
|
-
color:
|
473
|
+
color: var(--dbviewer-success-color);
|
413
474
|
font-weight: 500;
|
475
|
+
font-variant-numeric: tabular-nums;
|
476
|
+
transition: color 0.2s ease;
|
414
477
|
}
|
415
478
|
|
416
479
|
.query-duration-slow {
|
417
|
-
color:
|
480
|
+
color: var(--dbviewer-danger-color);
|
418
481
|
font-weight: 600;
|
482
|
+
font-variant-numeric: tabular-nums;
|
483
|
+
transition: color 0.2s ease;
|
419
484
|
}
|
420
485
|
|
421
486
|
.query-timestamp {
|
422
|
-
color:
|
487
|
+
color: var(--dbviewer-muted-color);
|
488
|
+
font-variant-numeric: tabular-nums;
|
489
|
+
transition: color 0.2s ease;
|
423
490
|
}
|
424
|
-
|
491
|
+
|
492
|
+
/* ================================================
|
493
|
+
Empty States and Messages
|
494
|
+
================================================ */
|
425
495
|
.empty-data-message {
|
426
|
-
color:
|
496
|
+
color: var(--dbviewer-muted-color);
|
497
|
+
transition: color 0.2s ease;
|
427
498
|
}
|
428
|
-
|
429
|
-
|
499
|
+
|
500
|
+
.empty-data-message p {
|
501
|
+
margin-bottom: 0.5rem;
|
502
|
+
font-weight: 500;
|
503
|
+
}
|
504
|
+
|
505
|
+
.empty-data-message small {
|
506
|
+
opacity: 0.8;
|
507
|
+
}
|
508
|
+
|
509
|
+
/* ================================================
|
510
|
+
Loading States
|
511
|
+
================================================ */
|
430
512
|
.spinner-border-sm {
|
431
513
|
width: 1rem;
|
432
514
|
height: 1rem;
|
433
515
|
}
|
434
|
-
|
435
|
-
/*
|
516
|
+
|
517
|
+
/* ================================================
|
518
|
+
Skeleton Loader System
|
519
|
+
================================================ */
|
436
520
|
.skeleton-loader {
|
437
521
|
display: inline-block;
|
438
522
|
height: 1.2em;
|
439
523
|
width: 100%;
|
440
|
-
background: linear-gradient(
|
524
|
+
background: linear-gradient(
|
525
|
+
90deg,
|
526
|
+
var(--skeleton-base-color) 25%,
|
527
|
+
var(--skeleton-highlight-color) 37%,
|
528
|
+
var(--skeleton-base-color) 63%
|
529
|
+
);
|
441
530
|
background-size: 400% 100%;
|
442
531
|
animation: skeleton-loading 1.2s ease-in-out infinite;
|
443
|
-
border-radius:
|
532
|
+
border-radius: var(--dbviewer-border-radius);
|
444
533
|
}
|
445
|
-
|
534
|
+
|
535
|
+
/* Skeleton loader variants */
|
536
|
+
.skeleton-loader.number-loader {
|
446
537
|
width: 2.5em;
|
447
538
|
height: 1.5em;
|
448
539
|
margin-bottom: 0.2em;
|
449
540
|
}
|
450
|
-
|
541
|
+
|
542
|
+
.skeleton-loader.table-cell-loader {
|
451
543
|
width: 6em;
|
452
544
|
height: 1.2em;
|
453
545
|
}
|
454
|
-
|
546
|
+
|
547
|
+
.skeleton-loader.records-loader {
|
455
548
|
width: 3em;
|
456
549
|
height: 1.2em;
|
457
550
|
}
|
458
|
-
|
551
|
+
|
552
|
+
.skeleton-loader.query-cell-loader {
|
459
553
|
width: 12em;
|
460
554
|
height: 1.2em;
|
461
555
|
}
|
462
|
-
|
556
|
+
|
557
|
+
.skeleton-loader.duration-cell-loader {
|
463
558
|
width: 4em;
|
464
559
|
height: 1.2em;
|
465
560
|
}
|
466
|
-
|
561
|
+
|
562
|
+
.skeleton-loader.time-cell-loader {
|
467
563
|
width: 7em;
|
468
564
|
height: 1.2em;
|
469
565
|
}
|
566
|
+
|
567
|
+
/* ================================================
|
568
|
+
Animations
|
569
|
+
================================================ */
|
470
570
|
@keyframes skeleton-loading {
|
471
571
|
0% {
|
472
572
|
background-position: 100% 50%;
|
@@ -475,4 +575,58 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
475
575
|
background-position: 0 50%;
|
476
576
|
}
|
477
577
|
}
|
578
|
+
|
579
|
+
/* ================================================
|
580
|
+
Table Enhancements
|
581
|
+
================================================ */
|
582
|
+
.table-hover tbody tr:hover .sql-query-code {
|
583
|
+
background-color: var(--dbviewer-code-bg);
|
584
|
+
filter: brightness(0.9);
|
585
|
+
}
|
586
|
+
|
587
|
+
/* ================================================
|
588
|
+
Responsive Design
|
589
|
+
================================================ */
|
590
|
+
@media (max-width: 768px) {
|
591
|
+
.sql-query-code {
|
592
|
+
font-size: 0.75rem;
|
593
|
+
padding: 1px 3px;
|
594
|
+
}
|
595
|
+
|
596
|
+
.query-cell-loader {
|
597
|
+
width: 8em;
|
598
|
+
}
|
599
|
+
|
600
|
+
.duration-cell-loader {
|
601
|
+
width: 3em;
|
602
|
+
}
|
603
|
+
|
604
|
+
.time-cell-loader {
|
605
|
+
width: 5em;
|
606
|
+
}
|
607
|
+
}
|
608
|
+
|
609
|
+
/* ================================================
|
610
|
+
Accessibility Improvements
|
611
|
+
================================================ */
|
612
|
+
@media (prefers-reduced-motion: reduce) {
|
613
|
+
.skeleton-loader {
|
614
|
+
animation: none;
|
615
|
+
background: var(--skeleton-base-color);
|
616
|
+
}
|
617
|
+
|
618
|
+
.sql-query-code,
|
619
|
+
.query-duration,
|
620
|
+
.query-duration-slow,
|
621
|
+
.query-timestamp,
|
622
|
+
.empty-data-message {
|
623
|
+
transition: none;
|
624
|
+
}
|
625
|
+
}
|
626
|
+
|
627
|
+
/* Focus states for better keyboard navigation */
|
628
|
+
.sql-query-code:focus-visible {
|
629
|
+
outline: 2px solid var(--dbviewer-success-color);
|
630
|
+
outline-offset: 2px;
|
631
|
+
}
|
478
632
|
</style>
|