dbviewer 0.7.11 โ 0.8.1
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 +59 -0
- data/app/helpers/dbviewer/datatable_ui_table_helper.rb +10 -9
- data/app/helpers/dbviewer/formatting_helper.rb +6 -1
- data/app/views/dbviewer/tables/query.html.erb +3 -2
- data/app/views/dbviewer/tables/show.html.erb +1 -1
- data/lib/dbviewer/configuration.rb +21 -0
- data/lib/dbviewer/data_privacy/pii_masker.rb +125 -0
- data/lib/dbviewer/version.rb +1 -1
- data/lib/dbviewer.rb +45 -0
- data/lib/generators/dbviewer/install_generator.rb +4 -0
- data/lib/generators/dbviewer/templates/dbviewer_pii.rb +26 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 12d0c138efd718011eda36a79ea33a9e96f6609032b1160249e19876df86d00e
|
4
|
+
data.tar.gz: 11150deaa07278a6073f9977000fc4075ac0d24f671601a3525d19afd27b2e25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e0bfde143b99b1412a405d46eee0e13efb3711d7b33a297c0327f712dfb946acb808052b377df970dfae65e97656a826af3cab7896a0e544831ad9942a048a4
|
7
|
+
data.tar.gz: 342ee2c29f04c64d68272f843c45860bf60a51e61e338d1a9ff5ae579d85ec6a5684d4bbd12f95b46a1f452100eb01796d57a0caa2a96b0b5732aecb050a4896
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|

|
2
2
|
|
3
3
|
# ๐๏ธ DBViewer
|
4
|
+
|
4
5
|
> **The fastest way to visualize and explore your database**
|
5
6
|
|
6
7
|
DBViewer is a powerful Rails engine that provides a comprehensive interface to view and explore database tables, records, and schema.
|
@@ -17,6 +18,7 @@ It's designed for development, debugging, and database analysis, offering a clea
|
|
17
18
|
- **Data Browsing**
|
18
19
|
- **SQL Queries**
|
19
20
|
- **Multiple Database Connections**
|
21
|
+
- **PII Data Masking** - Protect sensitive data with configurable masking rules
|
20
22
|
- **Enhanced UI Features**
|
21
23
|
|
22
24
|
## ๐งช Demo Application
|
@@ -258,6 +260,63 @@ end
|
|
258
260
|
|
259
261
|
When disabled, all DBViewer routes return 404 responses, making it appear as if the tool was never installed. This is the recommended approach for production systems where database admin tools should not be accessible.
|
260
262
|
|
263
|
+
## ๐ PII Data Masking
|
264
|
+
|
265
|
+
DBViewer includes built-in support for masking Personally Identifiable Information (PII) to protect sensitive data while allowing developers to browse database contents.
|
266
|
+
|
267
|
+
### Quick Setup
|
268
|
+
|
269
|
+
Configure PII masking in your Rails initializer (e.g., `config/initializers/dbviewer.rb`):
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
# Enable PII masking (enabled by default)
|
273
|
+
Dbviewer.configure do |config|
|
274
|
+
config.enable_pii_masking = true
|
275
|
+
end
|
276
|
+
|
277
|
+
# Define masking rules
|
278
|
+
Dbviewer.configure_pii do |pii|
|
279
|
+
# Built-in masking types
|
280
|
+
pii.mask 'users.email', with: :email # john@example.com โ jo***@example.com
|
281
|
+
pii.mask 'users.phone', with: :phone # +1234567890 โ +1***90
|
282
|
+
pii.mask 'users.ssn', with: :ssn # 123456789 โ ***-**-6789
|
283
|
+
pii.mask 'payments.card_number', with: :credit_card # 1234567890123456 โ ****-****-****-3456
|
284
|
+
pii.mask 'users.api_key', with: :full_redact # any_value โ ***REDACTED***
|
285
|
+
|
286
|
+
# Custom masking with lambda
|
287
|
+
pii.mask 'users.salary', with: ->(value) { value ? '$***,***' : value }
|
288
|
+
|
289
|
+
# Define reusable custom masks
|
290
|
+
pii.custom_mask :ip_mask, ->(value) {
|
291
|
+
return value if value.nil?
|
292
|
+
parts = value.split('.')
|
293
|
+
"#{parts[0]}.#{parts[1]}.***.***.***"
|
294
|
+
}
|
295
|
+
pii.mask 'logs.ip_address', with: :ip_mask
|
296
|
+
end
|
297
|
+
```
|
298
|
+
|
299
|
+
### Built-in Masking Types
|
300
|
+
|
301
|
+
- **`:email`** - Masks email addresses while preserving domain
|
302
|
+
- **`:phone`** - Masks phone numbers keeping first and last digits
|
303
|
+
- **`:ssn`** - Masks Social Security Numbers showing only last 4 digits
|
304
|
+
- **`:credit_card`** - Masks credit card numbers showing only last 4 digits
|
305
|
+
- **`:full_redact`** - Completely redacts the value
|
306
|
+
- **`:partial`** - Partial masking (default behavior)
|
307
|
+
|
308
|
+
### Generate Example Configuration
|
309
|
+
|
310
|
+
Use the generator to create an example PII configuration:
|
311
|
+
|
312
|
+
```bash
|
313
|
+
rails generate dbviewer:install
|
314
|
+
```
|
315
|
+
|
316
|
+
This creates `config/initializers/dbviewer_pii_example.rb` with comprehensive examples.
|
317
|
+
|
318
|
+
For detailed PII masking documentation, see [PII_MASKING.md](docs/PII_MASKING.md).
|
319
|
+
|
261
320
|
## ๐ Security Note
|
262
321
|
|
263
322
|
โ ๏ธ **Warning**: This engine provides direct access to your database contents, which contains sensitive information. Always protect it with HTTP Basic Authentication by configuring strong credentials as shown above.
|
@@ -40,8 +40,8 @@ module Dbviewer
|
|
40
40
|
end
|
41
41
|
|
42
42
|
# Render a cell that may include a foreign key link
|
43
|
-
def render_table_cell(cell, column_name, metadata)
|
44
|
-
cell_value = format_cell_value(cell)
|
43
|
+
def render_table_cell(cell, column_name, metadata, table_name = nil)
|
44
|
+
cell_value = format_cell_value(cell, table_name, column_name)
|
45
45
|
foreign_key = metadata && metadata[:foreign_keys] ?
|
46
46
|
metadata[:foreign_keys].find { |fk| fk[:column] == column_name } :
|
47
47
|
nil
|
@@ -61,15 +61,15 @@ module Dbviewer
|
|
61
61
|
end
|
62
62
|
|
63
63
|
# Render a table row with cells
|
64
|
-
def render_table_row(row, records, metadata)
|
64
|
+
def render_table_row(row, records, metadata, table_name = nil)
|
65
65
|
content_tag(:tr) do
|
66
66
|
# Start with action column (sticky first column)
|
67
|
-
cells = [ render_action_cell(row, records.columns, metadata) ]
|
67
|
+
cells = [ render_action_cell(row, records.columns, metadata, table_name) ]
|
68
68
|
|
69
69
|
# Add all data cells
|
70
70
|
cells += row.each_with_index.map do |cell, cell_index|
|
71
71
|
column_name = records.columns[cell_index]
|
72
|
-
render_table_cell(cell, column_name, metadata)
|
72
|
+
render_table_cell(cell, column_name, metadata, table_name)
|
73
73
|
end
|
74
74
|
|
75
75
|
cells.join.html_safe
|
@@ -77,7 +77,7 @@ module Dbviewer
|
|
77
77
|
end
|
78
78
|
|
79
79
|
# Render the entire table body with rows
|
80
|
-
def render_table_body(records, metadata)
|
80
|
+
def render_table_body(records, metadata, table_name = nil)
|
81
81
|
if records.nil? || records.rows.nil? || records.empty?
|
82
82
|
content_tag(:tbody) do
|
83
83
|
content_tag(:tr) do
|
@@ -89,19 +89,20 @@ module Dbviewer
|
|
89
89
|
else
|
90
90
|
content_tag(:tbody) do
|
91
91
|
records.rows.map do |row|
|
92
|
-
render_table_row(row, records, metadata)
|
92
|
+
render_table_row(row, records, metadata, table_name)
|
93
93
|
end.join.html_safe
|
94
94
|
end
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
98
98
|
# Render action buttons for a record
|
99
|
-
def render_action_cell(row_data, columns, metadata = nil)
|
99
|
+
def render_action_cell(row_data, columns, metadata = nil, table_name = nil)
|
100
100
|
data_attributes = {}
|
101
101
|
|
102
102
|
# Create a hash of column_name: value pairs for data attributes
|
103
|
+
# Apply the same formatting logic used in table cells
|
103
104
|
columns.each_with_index do |column_name, index|
|
104
|
-
data_attributes[column_name] = row_data[index]
|
105
|
+
data_attributes[column_name] = format_cell_value(row_data[index], table_name, column_name)
|
105
106
|
end
|
106
107
|
|
107
108
|
content_tag(:td, class: "text-center action-column") do
|
@@ -1,6 +1,11 @@
|
|
1
1
|
module Dbviewer
|
2
2
|
module FormattingHelper
|
3
|
-
def format_cell_value(value)
|
3
|
+
def format_cell_value(value, table_name = nil, column_name = nil)
|
4
|
+
# Apply PII masking if configured
|
5
|
+
if table_name && column_name
|
6
|
+
value = Dbviewer::DataPrivacy::PiiMasker.mask_value(value, table_name, column_name)
|
7
|
+
end
|
8
|
+
|
4
9
|
return "NULL" if value.nil?
|
5
10
|
return format_default_value(value) unless value.is_a?(String)
|
6
11
|
|
@@ -142,8 +142,9 @@
|
|
142
142
|
<% if @records.rows.any? %>
|
143
143
|
<% @records.rows.each do |row| %>
|
144
144
|
<tr>
|
145
|
-
<% row.
|
146
|
-
|
145
|
+
<% row.each_with_index do |cell, index| %>
|
146
|
+
<% column_name = @records.columns[index] %>
|
147
|
+
<td><%= format_cell_value(cell, @table_name, column_name) %></td>
|
147
148
|
<% end %>
|
148
149
|
</tr>
|
149
150
|
<% end %>
|
@@ -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>
|
@@ -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
|
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
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Dbviewer.configure do |config|
|
2
|
+
# Enable/disable PII masking globally (default: true)
|
3
|
+
# config.enable_pii_masking = true
|
4
|
+
end
|
5
|
+
|
6
|
+
# Configure PII masking rules
|
7
|
+
Dbviewer.configure_pii do |pii|
|
8
|
+
# pii.mask "users.email", with: :email
|
9
|
+
# pii.mask "customers.email_address", with: :email
|
10
|
+
|
11
|
+
# # Custom masking with lambda/proc:
|
12
|
+
# pii.mask "users.address", with: ->(value) {
|
13
|
+
# return value if value.nil?
|
14
|
+
# "#{value.split(' ').first} ***" # Show only first word
|
15
|
+
# }
|
16
|
+
|
17
|
+
# # Define custom masking functions that can be reused:
|
18
|
+
# pii.custom_mask :ip_mask, ->(value) {
|
19
|
+
# return value if value.nil?
|
20
|
+
# parts = value.split(".")
|
21
|
+
# return value if parts.length != 4
|
22
|
+
# "#{parts[0]}.#{parts[1]}.***.***.***"
|
23
|
+
# }
|
24
|
+
# pii.mask "logs.ip_address", with: :ip_mask
|
25
|
+
# pii.mask "sessions.client_ip", with: :ip_mask
|
26
|
+
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.1
|
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
|
@@ -144,6 +144,7 @@ files:
|
|
144
144
|
- lib/dbviewer/cache/base.rb
|
145
145
|
- lib/dbviewer/cache/in_memory.rb
|
146
146
|
- lib/dbviewer/configuration.rb
|
147
|
+
- lib/dbviewer/data_privacy/pii_masker.rb
|
147
148
|
- lib/dbviewer/database/dynamic_model_factory.rb
|
148
149
|
- lib/dbviewer/database/manager.rb
|
149
150
|
- lib/dbviewer/database/metadata_manager.rb
|
@@ -165,6 +166,7 @@ files:
|
|
165
166
|
- lib/dbviewer/validator/sql/validation_result.rb
|
166
167
|
- lib/dbviewer/version.rb
|
167
168
|
- lib/generators/dbviewer/install_generator.rb
|
169
|
+
- lib/generators/dbviewer/templates/dbviewer_pii.rb
|
168
170
|
- lib/generators/dbviewer/templates/initializer.rb
|
169
171
|
- lib/tasks/dbviewer_tasks.rake
|
170
172
|
homepage: https://github.com/wailantirajoh/dbviewer
|