dbviewer 0.7.10 → 0.8.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 +60 -0
- data/app/assets/images/dbviewer/emoji-favicon.txt +1 -0
- data/app/assets/images/dbviewer/favicon.ico +4 -0
- data/app/assets/images/dbviewer/favicon.png +4 -0
- data/app/assets/images/dbviewer/favicon.svg +10 -0
- data/app/assets/javascripts/dbviewer/entity_relationship_diagram.js +38 -42
- data/app/assets/javascripts/dbviewer/error_handler.js +58 -0
- data/app/assets/javascripts/dbviewer/home.js +25 -34
- data/app/assets/javascripts/dbviewer/layout.js +100 -129
- data/app/assets/javascripts/dbviewer/query.js +309 -246
- data/app/assets/javascripts/dbviewer/sidebar.js +170 -183
- data/app/assets/javascripts/dbviewer/utility.js +124 -0
- data/app/assets/stylesheets/dbviewer/application.css +8 -146
- data/app/assets/stylesheets/dbviewer/entity_relationship_diagram.css +0 -34
- data/app/assets/stylesheets/dbviewer/logs.css +0 -11
- data/app/assets/stylesheets/dbviewer/query.css +21 -9
- data/app/assets/stylesheets/dbviewer/table.css +49 -131
- data/app/controllers/concerns/dbviewer/database_operations/connection_management.rb +90 -0
- data/app/controllers/concerns/dbviewer/database_operations/data_export.rb +31 -0
- data/app/controllers/concerns/dbviewer/database_operations/database_information.rb +54 -0
- data/app/controllers/concerns/dbviewer/database_operations/datatable_operations.rb +37 -0
- data/app/controllers/concerns/dbviewer/database_operations/query_operations.rb +37 -0
- data/app/controllers/concerns/dbviewer/database_operations/relationship_management.rb +175 -0
- data/app/controllers/concerns/dbviewer/database_operations/table_operations.rb +46 -0
- data/app/controllers/concerns/dbviewer/database_operations.rb +4 -9
- data/app/controllers/dbviewer/api/tables_controller.rb +12 -0
- data/app/controllers/dbviewer/entity_relationship_diagrams_controller.rb +0 -15
- data/app/controllers/dbviewer/tables_controller.rb +4 -33
- data/app/helpers/dbviewer/datatable_ui_table_helper.rb +10 -9
- data/app/helpers/dbviewer/formatting_helper.rb +6 -1
- data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +1 -1
- data/app/views/dbviewer/tables/query.html.erb +24 -8
- data/app/views/dbviewer/tables/show.html.erb +3 -3
- data/app/views/layouts/dbviewer/application.html.erb +12 -3
- data/config/routes.rb +2 -2
- data/lib/dbviewer/configuration.rb +21 -0
- data/lib/dbviewer/data_privacy/pii_masker.rb +125 -0
- data/lib/dbviewer/database/manager.rb +2 -2
- data/lib/dbviewer/datatable/query_operations.rb +1 -17
- data/lib/dbviewer/engine.rb +29 -0
- data/lib/dbviewer/version.rb +1 -1
- data/lib/dbviewer.rb +45 -0
- data/lib/generators/dbviewer/install_generator.rb +6 -0
- data/lib/generators/dbviewer/templates/pii_configuration_example.rb +99 -0
- metadata +17 -10
- data/app/controllers/concerns/dbviewer/connection_management.rb +0 -88
- data/app/controllers/concerns/dbviewer/data_export.rb +0 -32
- data/app/controllers/concerns/dbviewer/database_information.rb +0 -62
- data/app/controllers/concerns/dbviewer/datatable_support.rb +0 -47
- data/app/controllers/concerns/dbviewer/pagination_concern.rb +0 -34
- data/app/controllers/concerns/dbviewer/query_operations.rb +0 -28
- data/app/controllers/concerns/dbviewer/relationship_management.rb +0 -173
- data/app/controllers/concerns/dbviewer/table_operations.rb +0 -56
@@ -100,7 +100,7 @@
|
|
100
100
|
<div class="card-header d-flex justify-content-between align-items-center">
|
101
101
|
<h5 class="mb-0">
|
102
102
|
<select id="per-page-select" class="form-select form-select-sm" onchange="window.location.href='<%= table_path(@table_name) %>?<%= per_page_url_params(@table_name) %>'">
|
103
|
-
<% Dbviewer
|
103
|
+
<% Dbviewer.configuration.per_page_options.each do |option| %>
|
104
104
|
<option value="<%= option %>" <%= 'selected' if @per_page == option %>><%= option %></option>
|
105
105
|
<% end %>
|
106
106
|
</select>
|
@@ -136,7 +136,7 @@
|
|
136
136
|
<%= render_sortable_header_row(@records, @order_by, @order_direction, @table_name, @current_page, @per_page, @column_filters) %>
|
137
137
|
<%= render_column_filters_row(form, @records, @columns, @column_filters) %>
|
138
138
|
</thead>
|
139
|
-
<%= render_table_body(@records, @metadata) %>
|
139
|
+
<%= render_table_body(@records, @metadata, @table_name) %>
|
140
140
|
</table>
|
141
141
|
<% end %> <!-- End of form_with -->
|
142
142
|
</div>
|
@@ -348,5 +348,5 @@
|
|
348
348
|
</div>
|
349
349
|
</div>
|
350
350
|
<% end %>
|
351
|
-
<input type="hidden" id="mini_erd_table_path" name="mini_erd_table_path" value="<%= dbviewer.
|
351
|
+
<input type="hidden" id="mini_erd_table_path" name="mini_erd_table_path" value="<%= dbviewer.mini_erd_api_table_path(@table_name, format: :json) %>">
|
352
352
|
<input type="hidden" id="table_name" name="table_name" value="<%= @table_name %>">
|
@@ -5,6 +5,13 @@
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
6
6
|
<%= csrf_meta_tags %>
|
7
7
|
<%= csp_meta_tag %>
|
8
|
+
|
9
|
+
<!-- Favicon -->
|
10
|
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>👁️</text></svg>">
|
11
|
+
<link rel="icon" href="<%= asset_path('dbviewer/favicon.ico') %>" type="image/x-icon">
|
12
|
+
<link rel="shortcut icon" href="<%= asset_path('dbviewer/favicon.ico') %>" type="image/x-icon">
|
13
|
+
<link rel="icon" type="image/svg+xml" href="<%= asset_path('dbviewer/favicon.svg') %>">
|
14
|
+
<link rel="icon" type="image/png" href="<%= asset_path('dbviewer/favicon.png') %>">
|
8
15
|
|
9
16
|
<!-- Prevent theme flash during page load -->
|
10
17
|
<script>
|
@@ -37,6 +44,8 @@
|
|
37
44
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
|
38
45
|
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
39
46
|
|
47
|
+
<%= javascript_include_tag "dbviewer/utility", "data-turbo-track": "reload" %>
|
48
|
+
<%= javascript_include_tag "dbviewer/error_handler", "data-turbo-track": "reload" %>
|
40
49
|
<%= javascript_include_tag "dbviewer/layout", "data-turbo-track": "reload" %>
|
41
50
|
<%= javascript_include_tag "dbviewer/sidebar", "data-turbo-track": "reload" %>
|
42
51
|
<%= stylesheet_link_tag "dbviewer/application", "data-turbo-track": "reload" %>
|
@@ -47,8 +56,8 @@
|
|
47
56
|
<div class="dbviewer-wrapper">
|
48
57
|
<!-- Top Navigation Bar (Fixed) -->
|
49
58
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary dbviewer-navbar fixed-top">
|
50
|
-
<div class="container-fluid">
|
51
|
-
<a class="navbar-brand" href="<%= dbviewer.root_path %>"><
|
59
|
+
<div class="container-fluid px-0">
|
60
|
+
<a class="navbar-brand d-flex" href="<%= dbviewer.root_path %>"><span class="me-2">👁️</span> DB Viewer</a>
|
52
61
|
<div class="d-flex align-items-center">
|
53
62
|
<button class="navbar-toggler border-0 px-2 d-lg-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarOffcanvas"
|
54
63
|
aria-controls="navbarOffcanvas" aria-expanded="false" aria-label="Toggle navigation">
|
@@ -110,7 +119,7 @@
|
|
110
119
|
<!-- Offcanvas sidebar for mobile/tablet view that slides from right -->
|
111
120
|
<div class="offcanvas offcanvas-end d-lg-none" tabindex="-1" id="navbarOffcanvas" aria-labelledby="offcanvasNavbarLabel">
|
112
121
|
<div class="offcanvas-header bg-light-subtle">
|
113
|
-
<h5 class="offcanvas-title" id="offcanvasNavbarLabel"
|
122
|
+
<h5 class="offcanvas-title" id="offcanvasNavbarLabel">👁️ DB Viewer</h5>
|
114
123
|
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
115
124
|
</div>
|
116
125
|
<div class="offcanvas-body bg-body-tertiary">
|
data/config/routes.rb
CHANGED
@@ -4,7 +4,6 @@ Dbviewer::Engine.routes.draw do
|
|
4
4
|
get "query"
|
5
5
|
post "query"
|
6
6
|
get "export_csv"
|
7
|
-
get "mini_erd"
|
8
7
|
end
|
9
8
|
end
|
10
9
|
|
@@ -26,13 +25,14 @@ Dbviewer::Engine.routes.draw do
|
|
26
25
|
get "dashboard", to: "home#index", as: :dashboard
|
27
26
|
|
28
27
|
namespace :api do
|
29
|
-
resources :tables, only: [ :index ] do
|
28
|
+
resources :tables, only: [ :index, :show ] do
|
30
29
|
collection do
|
31
30
|
get "records"
|
32
31
|
get "relationships_count"
|
33
32
|
end
|
34
33
|
member do
|
35
34
|
get "relationship_counts"
|
35
|
+
get "mini_erd"
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
@@ -59,6 +59,24 @@ module Dbviewer
|
|
59
59
|
# When enabled, all DBViewer routes will return 404 responses
|
60
60
|
attr_accessor :disabled
|
61
61
|
|
62
|
+
# PII (Personally Identifiable Information) masking configuration
|
63
|
+
# Hash of table.column => masking rule
|
64
|
+
# @example {
|
65
|
+
# 'users.email' => :email,
|
66
|
+
# 'users.phone' => :phone,
|
67
|
+
# 'customers.ssn' => :custom_mask
|
68
|
+
# }
|
69
|
+
attr_accessor :pii_rules
|
70
|
+
|
71
|
+
# Enable/disable PII masking globally
|
72
|
+
attr_accessor :enable_pii_masking
|
73
|
+
|
74
|
+
# Custom PII masking blocks
|
75
|
+
# @example {
|
76
|
+
# custom_mask: ->(value) { value ? '***REDACTED***' : value }
|
77
|
+
# }
|
78
|
+
attr_accessor :custom_pii_masks
|
79
|
+
|
62
80
|
def initialize
|
63
81
|
@per_page_options = [ 10, 20, 50, 100 ]
|
64
82
|
@default_per_page = 20
|
@@ -82,6 +100,9 @@ module Dbviewer
|
|
82
100
|
}
|
83
101
|
}
|
84
102
|
@current_connection = :default
|
103
|
+
@pii_rules = {}
|
104
|
+
@enable_pii_masking = true
|
105
|
+
@custom_pii_masks = {}
|
85
106
|
end
|
86
107
|
end
|
87
108
|
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Dbviewer
|
2
|
+
module DataPrivacy
|
3
|
+
class PiiMasker
|
4
|
+
BUILT_IN_MASKS = {
|
5
|
+
email: ->(value) { mask_email(value) },
|
6
|
+
phone: ->(value) { mask_phone(value) },
|
7
|
+
credit_card: ->(value) { mask_credit_card(value) },
|
8
|
+
ssn: ->(value) { mask_ssn(value) },
|
9
|
+
full_redact: ->(value) { value ? "***REDACTED***" : value },
|
10
|
+
partial: ->(value) { mask_partial(value) }
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
def self.mask_value(value, table_name, column_name)
|
14
|
+
return value unless should_mask?(table_name, column_name)
|
15
|
+
|
16
|
+
rule = get_masking_rule(table_name, column_name)
|
17
|
+
apply_mask(value, rule)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def self.should_mask?(table_name, column_name)
|
23
|
+
return false unless Dbviewer.configuration.enable_pii_masking
|
24
|
+
return false if Dbviewer.configuration.pii_rules.empty?
|
25
|
+
|
26
|
+
key = "#{table_name}.#{column_name}"
|
27
|
+
Dbviewer.configuration.pii_rules.key?(key)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.get_masking_rule(table_name, column_name)
|
31
|
+
key = "#{table_name}.#{column_name}"
|
32
|
+
Dbviewer.configuration.pii_rules[key]
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.apply_mask(value, rule)
|
36
|
+
return value if value.nil?
|
37
|
+
|
38
|
+
case rule
|
39
|
+
when Symbol
|
40
|
+
apply_built_in_mask(value, rule)
|
41
|
+
when Proc
|
42
|
+
rule.call(value)
|
43
|
+
else
|
44
|
+
apply_built_in_mask(value, :partial)
|
45
|
+
end
|
46
|
+
rescue => e
|
47
|
+
Rails.logger.warn("PII masking failed for rule #{rule}: #{e.message}")
|
48
|
+
"***ERROR***"
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.apply_built_in_mask(value, mask_type)
|
52
|
+
mask_proc = BUILT_IN_MASKS[mask_type]
|
53
|
+
if mask_proc
|
54
|
+
mask_proc.call(value)
|
55
|
+
else
|
56
|
+
# Check if it's a custom mask
|
57
|
+
custom_mask = Dbviewer.configuration.custom_pii_masks[mask_type]
|
58
|
+
if custom_mask && custom_mask.respond_to?(:call)
|
59
|
+
custom_mask.call(value)
|
60
|
+
else
|
61
|
+
mask_partial(value)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.mask_email(value)
|
67
|
+
return value unless value.to_s.include?("@")
|
68
|
+
|
69
|
+
parts = value.to_s.split("@")
|
70
|
+
username = parts[0]
|
71
|
+
domain = parts[1]
|
72
|
+
|
73
|
+
if username.length <= 1
|
74
|
+
masked_username = "*"
|
75
|
+
elsif username.length <= 2
|
76
|
+
masked_username = username
|
77
|
+
else
|
78
|
+
masked_username = "#{username[0..1]}***"
|
79
|
+
end
|
80
|
+
|
81
|
+
"#{masked_username}@#{domain}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.mask_phone(value)
|
85
|
+
# Remove all non-digit characters
|
86
|
+
digits = value.to_s.gsub(/\D/, "")
|
87
|
+
return value if digits.length < 4
|
88
|
+
|
89
|
+
# Keep first and last 2 digits, mask the middle
|
90
|
+
if digits.length <= 6
|
91
|
+
"#{digits[0]}#{'*' * (digits.length - 2)}#{digits[-1]}"
|
92
|
+
else
|
93
|
+
"#{digits[0..1]}#{'*' * 3}#{digits[-2..-1]}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.mask_credit_card(value)
|
98
|
+
digits = value.to_s.gsub(/\D/, "")
|
99
|
+
return value if digits.length < 4
|
100
|
+
|
101
|
+
# Show last 4 digits only
|
102
|
+
"****-****-****-#{digits[-4..-1]}"
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.mask_ssn(value)
|
106
|
+
digits = value.to_s.gsub(/\D/, "")
|
107
|
+
return value if digits.length != 9
|
108
|
+
|
109
|
+
# Show last 4 digits only
|
110
|
+
"***-**-#{digits[-4..-1]}"
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.mask_partial(value)
|
114
|
+
str = value.to_s
|
115
|
+
return str if str.length <= 2
|
116
|
+
|
117
|
+
if str.length <= 4
|
118
|
+
"#{str[0]}#{'*' * (str.length - 2)}#{str[-1]}"
|
119
|
+
else
|
120
|
+
"#{str[0..1]}#{'*' * 3}#{str[-2..-1]}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -75,8 +75,8 @@ module Dbviewer
|
|
75
75
|
# @param table_name [String] Name of the table
|
76
76
|
# @param column_filters [Hash] Hash of column_name => filter_value for filtering
|
77
77
|
# @return [Integer] Number of filtered records
|
78
|
-
def
|
79
|
-
@table_query_operations.
|
78
|
+
def table_record_count(table_name, column_filters = {})
|
79
|
+
@table_query_operations.table_record_count(table_name, column_filters)
|
80
80
|
end
|
81
81
|
|
82
82
|
# Get the number of columns in a table
|
@@ -48,9 +48,6 @@ module Dbviewer
|
|
48
48
|
|
49
49
|
# Format results
|
50
50
|
to_result_set(records, column_names)
|
51
|
-
rescue => e
|
52
|
-
Rails.logger.error("[DBViewer] Error executing table query: #{e.message}")
|
53
|
-
raise e
|
54
51
|
end
|
55
52
|
|
56
53
|
# Get the total count of records in a table
|
@@ -58,23 +55,13 @@ module Dbviewer
|
|
58
55
|
# @return [Integer] Number of records
|
59
56
|
def table_count(table_name)
|
60
57
|
get_model_for(table_name).count
|
61
|
-
rescue => e
|
62
|
-
Rails.logger.error("[DBViewer] Error counting records in table #{table_name}: #{e.message}")
|
63
|
-
0
|
64
|
-
end
|
65
|
-
|
66
|
-
# Get the number of records in a table (alias for table_count)
|
67
|
-
# @param table_name [String] Name of the table
|
68
|
-
# @return [Integer] Number of records
|
69
|
-
def record_count(table_name)
|
70
|
-
table_count(table_name)
|
71
58
|
end
|
72
59
|
|
73
60
|
# Get the number of records in a table with filters applied
|
74
61
|
# @param table_name [String] Name of the table
|
75
62
|
# @param column_filters [Hash] Hash of column_name => filter_value for filtering
|
76
63
|
# @return [Integer] Number of filtered records
|
77
|
-
def
|
64
|
+
def table_record_count(table_name, column_filters = {})
|
78
65
|
return table_count(table_name) unless column_filters.present?
|
79
66
|
|
80
67
|
model = get_model_for(table_name)
|
@@ -83,9 +70,6 @@ module Dbviewer
|
|
83
70
|
# Apply filters in the same way as table_records
|
84
71
|
query = apply_column_filters(query, table_name, column_filters)
|
85
72
|
query.count
|
86
|
-
rescue => e
|
87
|
-
Rails.logger.error("[DBViewer] Error counting filtered records in table #{table_name}: #{e.message}")
|
88
|
-
0
|
89
73
|
end
|
90
74
|
|
91
75
|
## -- Delegator
|
data/lib/dbviewer/engine.rb
CHANGED
@@ -15,5 +15,34 @@ module Dbviewer
|
|
15
15
|
initializer "dbviewer.notifications" do
|
16
16
|
Dbviewer::Query::NotificationSubscriber.subscribe
|
17
17
|
end
|
18
|
+
|
19
|
+
# Configure assets for the engine
|
20
|
+
initializer "dbviewer.assets" do |app|
|
21
|
+
# Add engine assets path to asset load paths
|
22
|
+
app.config.assets.paths << root.join("app", "assets", "javascripts").to_s
|
23
|
+
app.config.assets.paths << root.join("app", "assets", "stylesheets").to_s
|
24
|
+
app.config.assets.paths << root.join("app", "assets", "images").to_s
|
25
|
+
|
26
|
+
# Make sure we precompile individual JavaScript files
|
27
|
+
app.config.assets.precompile += %w[
|
28
|
+
dbviewer/utility.js
|
29
|
+
dbviewer/error_handler.js
|
30
|
+
dbviewer/home.js
|
31
|
+
dbviewer/layout.js
|
32
|
+
dbviewer/sidebar.js
|
33
|
+
dbviewer/query.js
|
34
|
+
dbviewer/entity_relationship_diagram.js
|
35
|
+
dbviewer/table.js
|
36
|
+
]
|
37
|
+
|
38
|
+
# Make sure stylesheets are precompiled too
|
39
|
+
app.config.assets.precompile += %w[
|
40
|
+
dbviewer/application.css
|
41
|
+
dbviewer/home.css
|
42
|
+
dbviewer/query.css
|
43
|
+
dbviewer/table.css
|
44
|
+
dbviewer/entity_relationship_diagram.css
|
45
|
+
]
|
46
|
+
end
|
18
47
|
end
|
19
48
|
end
|
data/lib/dbviewer/version.rb
CHANGED
data/lib/dbviewer.rb
CHANGED
@@ -23,11 +23,40 @@ require "dbviewer/database/metadata_manager"
|
|
23
23
|
require "dbviewer/datatable/query_operations"
|
24
24
|
require "dbviewer/datatable/query_params"
|
25
25
|
|
26
|
+
require "dbviewer/data_privacy/pii_masker"
|
27
|
+
|
26
28
|
require "propshaft"
|
27
29
|
|
28
30
|
module Dbviewer
|
29
31
|
# Main module for the database viewer
|
30
32
|
|
33
|
+
# Helper class for configuring PII masking rules
|
34
|
+
class PiiConfigurator
|
35
|
+
def initialize(configuration)
|
36
|
+
@configuration = configuration
|
37
|
+
end
|
38
|
+
|
39
|
+
# Define a PII masking rule
|
40
|
+
# @param column_spec [String] Table and column in format "table.column"
|
41
|
+
# @param with [Symbol, Proc] Masking rule - either built-in symbol or custom proc
|
42
|
+
def mask(column_spec, with:)
|
43
|
+
@configuration.pii_rules[column_spec] = with
|
44
|
+
end
|
45
|
+
|
46
|
+
# Define a custom masking function
|
47
|
+
# @param name [Symbol] Name of the custom mask
|
48
|
+
# @param block [Proc] The masking function
|
49
|
+
def custom_mask(name, block)
|
50
|
+
@configuration.custom_pii_masks[name] = block
|
51
|
+
end
|
52
|
+
|
53
|
+
# Enable or disable PII masking globally
|
54
|
+
# @param enabled [Boolean] Whether to enable PII masking
|
55
|
+
def enabled=(enabled)
|
56
|
+
@configuration.enable_pii_masking = enabled
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
31
60
|
class << self
|
32
61
|
# Module accessor for configuration
|
33
62
|
attr_writer :configuration
|
@@ -73,6 +102,22 @@ module Dbviewer
|
|
73
102
|
connection_errors
|
74
103
|
end
|
75
104
|
|
105
|
+
# Configure PII masking rules using a block
|
106
|
+
#
|
107
|
+
# @example
|
108
|
+
# Dbviewer.configure_pii do |pii|
|
109
|
+
# pii.mask 'users.email', with: :email
|
110
|
+
# pii.mask 'users.phone', with: :phone
|
111
|
+
# pii.mask 'users.ssn', with: :ssn
|
112
|
+
# pii.mask 'customers.credit_card', with: :credit_card
|
113
|
+
# pii.mask 'profiles.secret', with: :full_redact
|
114
|
+
# pii.mask 'accounts.api_key', with: ->(value) { value ? 'api_***' : value }
|
115
|
+
# pii.custom_mask :my_custom, ->(value) { "CUSTOM: #{value[0]}***" }
|
116
|
+
# end
|
117
|
+
def configure_pii
|
118
|
+
yield(PiiConfigurator.new(configuration)) if block_given?
|
119
|
+
end
|
120
|
+
|
76
121
|
private
|
77
122
|
|
78
123
|
# Configure the query logger with current configuration settings
|
@@ -7,6 +7,12 @@ module Dbviewer
|
|
7
7
|
def copy_initializer_file
|
8
8
|
copy_file "initializer.rb", "config/initializers/dbviewer.rb"
|
9
9
|
end
|
10
|
+
|
11
|
+
def copy_pii_example
|
12
|
+
copy_file "pii_configuration_example.rb", "config/initializers/dbviewer_pii_example.rb"
|
13
|
+
say "Created example PII configuration at config/initializers/dbviewer_pii_example.rb", :green
|
14
|
+
say "Review and customize the PII masking rules, then rename to dbviewer_pii.rb to activate.", :yellow
|
15
|
+
end
|
10
16
|
end
|
11
17
|
end
|
12
18
|
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# Example DBViewer PII Configuration
|
2
|
+
#
|
3
|
+
# This file shows how to configure PII (Personally Identifiable Information) masking
|
4
|
+
# in DBViewer to protect sensitive data in your database views.
|
5
|
+
#
|
6
|
+
# Place this configuration in your Rails initializer file (e.g., config/initializers/dbviewer.rb)
|
7
|
+
|
8
|
+
Dbviewer.configure do |config|
|
9
|
+
# Enable/disable PII masking globally (default: true)
|
10
|
+
config.enable_pii_masking = true
|
11
|
+
|
12
|
+
# Other DBViewer configurations...
|
13
|
+
# config.default_per_page = 20
|
14
|
+
# config.enable_data_export = false
|
15
|
+
end
|
16
|
+
|
17
|
+
# Configure PII masking rules
|
18
|
+
Dbviewer.configure_pii do |pii|
|
19
|
+
# Built-in masking types:
|
20
|
+
|
21
|
+
# Email masking: john.doe@example.com -> jo***@example.com
|
22
|
+
pii.mask "users.email", with: :email
|
23
|
+
pii.mask "customers.email_address", with: :email
|
24
|
+
|
25
|
+
# Phone masking: +1234567890 -> +1***90
|
26
|
+
pii.mask "users.phone", with: :phone
|
27
|
+
pii.mask "profiles.mobile_number", with: :phone
|
28
|
+
|
29
|
+
# Social Security Number masking: 123456789 -> ***-**-6789
|
30
|
+
pii.mask "users.ssn", with: :ssn
|
31
|
+
pii.mask "employees.social_security", with: :ssn
|
32
|
+
|
33
|
+
# Credit card masking: 1234567890123456 -> ****-****-****-3456
|
34
|
+
pii.mask "payments.card_number", with: :credit_card
|
35
|
+
|
36
|
+
# Full redaction: any_value -> ***REDACTED***
|
37
|
+
pii.mask "users.api_key", with: :full_redact
|
38
|
+
pii.mask "accounts.secret_token", with: :full_redact
|
39
|
+
|
40
|
+
# Partial masking (default): john_doe -> jo***oe
|
41
|
+
pii.mask "users.username", with: :partial
|
42
|
+
|
43
|
+
# Custom masking with lambda/proc:
|
44
|
+
pii.mask "users.address", with: ->(value) {
|
45
|
+
return value if value.nil?
|
46
|
+
"#{value.split(' ').first} ***" # Show only first word
|
47
|
+
}
|
48
|
+
|
49
|
+
# Define custom masking functions that can be reused:
|
50
|
+
pii.custom_mask :ip_mask, ->(value) {
|
51
|
+
return value if value.nil?
|
52
|
+
parts = value.split(".")
|
53
|
+
return value if parts.length != 4
|
54
|
+
"#{parts[0]}.#{parts[1]}.***.***.***"
|
55
|
+
}
|
56
|
+
|
57
|
+
# Use custom mask:
|
58
|
+
pii.mask "logs.ip_address", with: :ip_mask
|
59
|
+
pii.mask "sessions.client_ip", with: :ip_mask
|
60
|
+
|
61
|
+
# More examples:
|
62
|
+
|
63
|
+
# Customer data
|
64
|
+
pii.mask "customers.first_name", with: :partial
|
65
|
+
pii.mask "customers.last_name", with: :partial
|
66
|
+
pii.mask "customers.date_of_birth", with: ->(value) {
|
67
|
+
return value if value.nil?
|
68
|
+
date = Date.parse(value.to_s) rescue nil
|
69
|
+
date ? "#{date.year}/***/**" : value
|
70
|
+
}
|
71
|
+
|
72
|
+
# Employee data
|
73
|
+
pii.mask "employees.salary", with: ->(value) { value ? "$***,***" : value }
|
74
|
+
pii.mask "employees.bank_account", with: :full_redact
|
75
|
+
|
76
|
+
# User profiles
|
77
|
+
pii.mask "profiles.biography", with: ->(value) {
|
78
|
+
return value if value.nil? || value.length <= 50
|
79
|
+
"#{value[0..50]}... [TRUNCATED FOR PRIVACY]"
|
80
|
+
}
|
81
|
+
|
82
|
+
# System logs with PII
|
83
|
+
pii.mask "audit_logs.user_data", with: :full_redact
|
84
|
+
pii.mask "error_logs.request_params", with: ->(value) {
|
85
|
+
# Redact JSON containing potential PII
|
86
|
+
return value if value.nil?
|
87
|
+
begin
|
88
|
+
JSON.parse(value)
|
89
|
+
"{ [REDACTED JSON DATA] }"
|
90
|
+
rescue
|
91
|
+
value
|
92
|
+
end
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
# You can also disable PII masking globally:
|
97
|
+
# Dbviewer.configure_pii do |pii|
|
98
|
+
# pii.enabled = false
|
99
|
+
# end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dbviewer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wailan Tirajoh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-06-
|
11
|
+
date: 2025-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -77,28 +77,33 @@ files:
|
|
77
77
|
- MIT-LICENSE
|
78
78
|
- README.md
|
79
79
|
- Rakefile
|
80
|
+
- app/assets/images/dbviewer/emoji-favicon.txt
|
81
|
+
- app/assets/images/dbviewer/favicon.ico
|
82
|
+
- app/assets/images/dbviewer/favicon.png
|
83
|
+
- app/assets/images/dbviewer/favicon.svg
|
80
84
|
- app/assets/javascripts/dbviewer/entity_relationship_diagram.js
|
85
|
+
- app/assets/javascripts/dbviewer/error_handler.js
|
81
86
|
- app/assets/javascripts/dbviewer/home.js
|
82
87
|
- app/assets/javascripts/dbviewer/layout.js
|
83
88
|
- app/assets/javascripts/dbviewer/query.js
|
84
89
|
- app/assets/javascripts/dbviewer/sidebar.js
|
85
90
|
- app/assets/javascripts/dbviewer/table.js
|
91
|
+
- app/assets/javascripts/dbviewer/utility.js
|
86
92
|
- app/assets/stylesheets/dbviewer/application.css
|
87
93
|
- app/assets/stylesheets/dbviewer/entity_relationship_diagram.css
|
88
94
|
- app/assets/stylesheets/dbviewer/home.css
|
89
95
|
- app/assets/stylesheets/dbviewer/logs.css
|
90
96
|
- app/assets/stylesheets/dbviewer/query.css
|
91
97
|
- app/assets/stylesheets/dbviewer/table.css
|
92
|
-
- app/controllers/concerns/dbviewer/connection_management.rb
|
93
|
-
- app/controllers/concerns/dbviewer/data_export.rb
|
94
|
-
- app/controllers/concerns/dbviewer/database_information.rb
|
95
98
|
- app/controllers/concerns/dbviewer/database_operations.rb
|
96
|
-
- app/controllers/concerns/dbviewer/
|
99
|
+
- app/controllers/concerns/dbviewer/database_operations/connection_management.rb
|
100
|
+
- app/controllers/concerns/dbviewer/database_operations/data_export.rb
|
101
|
+
- app/controllers/concerns/dbviewer/database_operations/database_information.rb
|
102
|
+
- app/controllers/concerns/dbviewer/database_operations/datatable_operations.rb
|
103
|
+
- app/controllers/concerns/dbviewer/database_operations/query_operations.rb
|
104
|
+
- app/controllers/concerns/dbviewer/database_operations/relationship_management.rb
|
105
|
+
- app/controllers/concerns/dbviewer/database_operations/table_operations.rb
|
97
106
|
- app/controllers/concerns/dbviewer/disabled_state_validation.rb
|
98
|
-
- app/controllers/concerns/dbviewer/pagination_concern.rb
|
99
|
-
- app/controllers/concerns/dbviewer/query_operations.rb
|
100
|
-
- app/controllers/concerns/dbviewer/relationship_management.rb
|
101
|
-
- app/controllers/concerns/dbviewer/table_operations.rb
|
102
107
|
- app/controllers/dbviewer/api/base_controller.rb
|
103
108
|
- app/controllers/dbviewer/api/database_controller.rb
|
104
109
|
- app/controllers/dbviewer/api/entity_relationship_diagrams_controller.rb
|
@@ -139,6 +144,7 @@ files:
|
|
139
144
|
- lib/dbviewer/cache/base.rb
|
140
145
|
- lib/dbviewer/cache/in_memory.rb
|
141
146
|
- lib/dbviewer/configuration.rb
|
147
|
+
- lib/dbviewer/data_privacy/pii_masker.rb
|
142
148
|
- lib/dbviewer/database/dynamic_model_factory.rb
|
143
149
|
- lib/dbviewer/database/manager.rb
|
144
150
|
- lib/dbviewer/database/metadata_manager.rb
|
@@ -161,6 +167,7 @@ files:
|
|
161
167
|
- lib/dbviewer/version.rb
|
162
168
|
- lib/generators/dbviewer/install_generator.rb
|
163
169
|
- lib/generators/dbviewer/templates/initializer.rb
|
170
|
+
- lib/generators/dbviewer/templates/pii_configuration_example.rb
|
164
171
|
- lib/tasks/dbviewer_tasks.rake
|
165
172
|
homepage: https://github.com/wailantirajoh/dbviewer
|
166
173
|
licenses:
|