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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -0
  3. data/app/assets/images/dbviewer/emoji-favicon.txt +1 -0
  4. data/app/assets/images/dbviewer/favicon.ico +4 -0
  5. data/app/assets/images/dbviewer/favicon.png +4 -0
  6. data/app/assets/images/dbviewer/favicon.svg +10 -0
  7. data/app/assets/javascripts/dbviewer/entity_relationship_diagram.js +38 -42
  8. data/app/assets/javascripts/dbviewer/error_handler.js +58 -0
  9. data/app/assets/javascripts/dbviewer/home.js +25 -34
  10. data/app/assets/javascripts/dbviewer/layout.js +100 -129
  11. data/app/assets/javascripts/dbviewer/query.js +309 -246
  12. data/app/assets/javascripts/dbviewer/sidebar.js +170 -183
  13. data/app/assets/javascripts/dbviewer/utility.js +124 -0
  14. data/app/assets/stylesheets/dbviewer/application.css +8 -146
  15. data/app/assets/stylesheets/dbviewer/entity_relationship_diagram.css +0 -34
  16. data/app/assets/stylesheets/dbviewer/logs.css +0 -11
  17. data/app/assets/stylesheets/dbviewer/query.css +21 -9
  18. data/app/assets/stylesheets/dbviewer/table.css +49 -131
  19. data/app/controllers/concerns/dbviewer/database_operations/connection_management.rb +90 -0
  20. data/app/controllers/concerns/dbviewer/database_operations/data_export.rb +31 -0
  21. data/app/controllers/concerns/dbviewer/database_operations/database_information.rb +54 -0
  22. data/app/controllers/concerns/dbviewer/database_operations/datatable_operations.rb +37 -0
  23. data/app/controllers/concerns/dbviewer/database_operations/query_operations.rb +37 -0
  24. data/app/controllers/concerns/dbviewer/database_operations/relationship_management.rb +175 -0
  25. data/app/controllers/concerns/dbviewer/database_operations/table_operations.rb +46 -0
  26. data/app/controllers/concerns/dbviewer/database_operations.rb +4 -9
  27. data/app/controllers/dbviewer/api/tables_controller.rb +12 -0
  28. data/app/controllers/dbviewer/entity_relationship_diagrams_controller.rb +0 -15
  29. data/app/controllers/dbviewer/tables_controller.rb +4 -33
  30. data/app/helpers/dbviewer/datatable_ui_table_helper.rb +10 -9
  31. data/app/helpers/dbviewer/formatting_helper.rb +6 -1
  32. data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +1 -1
  33. data/app/views/dbviewer/tables/query.html.erb +24 -8
  34. data/app/views/dbviewer/tables/show.html.erb +3 -3
  35. data/app/views/layouts/dbviewer/application.html.erb +12 -3
  36. data/config/routes.rb +2 -2
  37. data/lib/dbviewer/configuration.rb +21 -0
  38. data/lib/dbviewer/data_privacy/pii_masker.rb +125 -0
  39. data/lib/dbviewer/database/manager.rb +2 -2
  40. data/lib/dbviewer/datatable/query_operations.rb +1 -17
  41. data/lib/dbviewer/engine.rb +29 -0
  42. data/lib/dbviewer/version.rb +1 -1
  43. data/lib/dbviewer.rb +45 -0
  44. data/lib/generators/dbviewer/install_generator.rb +6 -0
  45. data/lib/generators/dbviewer/templates/pii_configuration_example.rb +99 -0
  46. metadata +17 -10
  47. data/app/controllers/concerns/dbviewer/connection_management.rb +0 -88
  48. data/app/controllers/concerns/dbviewer/data_export.rb +0 -32
  49. data/app/controllers/concerns/dbviewer/database_information.rb +0 -62
  50. data/app/controllers/concerns/dbviewer/datatable_support.rb +0 -47
  51. data/app/controllers/concerns/dbviewer/pagination_concern.rb +0 -34
  52. data/app/controllers/concerns/dbviewer/query_operations.rb +0 -28
  53. data/app/controllers/concerns/dbviewer/relationship_management.rb +0 -173
  54. 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::TablesController.per_page_options.each do |option| %>
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.mini_erd_table_path(@table_name, format: :json) %>">
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 %>"><i class="bi bi-database-fill me-1"></i>DB Viewer</a>
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"><i class="bi bi-database-fill me-2 text-primary"></i>DB Viewer</h5>
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 filtered_record_count(table_name, column_filters = {})
79
- @table_query_operations.filtered_record_count(table_name, column_filters)
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 filtered_record_count(table_name, column_filters = {})
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Dbviewer
2
- VERSION = "0.7.10"
2
+ VERSION = "0.8.0"
3
3
  end
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.7.10
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-19 00:00:00.000000000 Z
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/datatable_support.rb
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: