ditty 0.7.1 → 0.10.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/.env.test +2 -0
- data/.gitignore +3 -0
- data/.pryrc +2 -0
- data/.rubocop.yml +24 -8
- data/.travis.yml +4 -8
- data/CNAME +1 -0
- data/Dockerfile +18 -0
- data/Gemfile.ci +0 -15
- data/Rakefile +5 -4
- data/Readme.md +24 -2
- data/_config.yml +1 -0
- data/config.ru +4 -4
- data/ditty.gemspec +31 -20
- data/docs/CNAME +1 -0
- data/docs/_config.yml +1 -0
- data/docs/index.md +34 -0
- data/exe/ditty +2 -0
- data/lib/ditty.rb +30 -4
- data/lib/ditty/cli.rb +38 -5
- data/lib/ditty/components/ditty.rb +82 -0
- data/lib/ditty/controllers/application_controller.rb +267 -0
- data/lib/ditty/controllers/{audit_logs.rb → audit_logs_controller.rb} +5 -7
- data/lib/ditty/controllers/{auth.rb → auth_controller.rb} +56 -32
- data/lib/ditty/controllers/{component.rb → component_controller.rb} +35 -24
- data/lib/ditty/controllers/{main.rb → main_controller.rb} +7 -7
- data/lib/ditty/controllers/roles_controller.rb +23 -0
- data/lib/ditty/controllers/user_login_traits_controller.rb +46 -0
- data/lib/ditty/controllers/{users.rb → users_controller.rb} +17 -20
- data/lib/ditty/db.rb +9 -5
- data/lib/ditty/emails/base.rb +48 -34
- data/lib/ditty/generators/crud_generator.rb +114 -0
- data/lib/ditty/generators/migration_generator.rb +26 -0
- data/lib/ditty/generators/project_generator.rb +52 -0
- data/lib/ditty/helpers/authentication.rb +6 -5
- data/lib/ditty/helpers/component.rb +11 -2
- data/lib/ditty/helpers/pundit.rb +24 -8
- data/lib/ditty/helpers/response.rb +38 -15
- data/lib/ditty/helpers/views.rb +48 -6
- data/lib/ditty/listener.rb +44 -14
- data/lib/ditty/memcached.rb +8 -0
- data/lib/ditty/middleware/accept_extension.rb +4 -2
- data/lib/ditty/middleware/error_catchall.rb +4 -2
- data/lib/ditty/models/audit_log.rb +1 -0
- data/lib/ditty/models/base.rb +13 -0
- data/lib/ditty/models/identity.rb +10 -7
- data/lib/ditty/models/role.rb +2 -0
- data/lib/ditty/models/user.rb +40 -3
- data/lib/ditty/models/user_login_trait.rb +17 -0
- data/lib/ditty/policies/audit_log_policy.rb +6 -6
- data/lib/ditty/policies/role_policy.rb +3 -3
- data/lib/ditty/policies/user_login_trait_policy.rb +45 -0
- data/lib/ditty/policies/user_policy.rb +3 -3
- data/lib/ditty/rubocop.rb +3 -0
- data/lib/ditty/seed.rb +2 -0
- data/lib/ditty/services/authentication.rb +31 -15
- data/lib/ditty/services/email.rb +22 -12
- data/lib/ditty/services/logger.rb +30 -13
- data/lib/ditty/services/pagination_wrapper.rb +9 -5
- data/lib/ditty/services/settings.rb +19 -7
- data/lib/ditty/tasks/ditty.rake +127 -0
- data/lib/ditty/tasks/omniauth-ldap.rake +43 -0
- data/lib/ditty/templates/.gitignore +5 -0
- data/lib/ditty/templates/.rspec +2 -0
- data/lib/ditty/templates/.rubocop.yml +7 -0
- data/lib/ditty/templates/Rakefile +12 -0
- data/lib/ditty/templates/application.rb +12 -0
- data/lib/ditty/templates/config.ru +37 -0
- data/lib/ditty/templates/controller.rb.erb +64 -0
- data/lib/ditty/templates/env.example +4 -0
- data/lib/ditty/templates/lib/project.rb.erb +5 -0
- data/lib/ditty/templates/migration.rb.erb +7 -0
- data/lib/ditty/templates/model.rb.erb +26 -0
- data/lib/ditty/templates/pids/.empty_directory +0 -0
- data/lib/ditty/templates/policy.rb.erb +48 -0
- data/{public → lib/ditty/templates/public}/browserconfig.xml +0 -0
- data/lib/ditty/templates/public/css/sb-admin-2.min.css +10 -0
- data/lib/ditty/templates/public/css/styles.css +13 -0
- data/lib/ditty/templates/public/favicon.ico +0 -0
- data/{public → lib/ditty/templates/public}/images/apple-icon.png +0 -0
- data/{public → lib/ditty/templates/public}/images/favicon-16x16.png +0 -0
- data/{public → lib/ditty/templates/public}/images/favicon-32x32.png +0 -0
- data/{public → lib/ditty/templates/public}/images/launcher-icon-1x.png +0 -0
- data/{public → lib/ditty/templates/public}/images/launcher-icon-2x.png +0 -0
- data/{public → lib/ditty/templates/public}/images/launcher-icon-4x.png +0 -0
- data/{public → lib/ditty/templates/public}/images/mstile-150x150.png +0 -0
- data/{public → lib/ditty/templates/public}/images/safari-pinned-tab.svg +0 -0
- data/lib/ditty/templates/public/js/sb-admin-2.min.js +7 -0
- data/lib/ditty/templates/public/js/scripts.js +1 -0
- data/{public/manifest.json → lib/ditty/templates/public/manifest.json.erb} +2 -2
- data/lib/ditty/templates/settings.yml.erb +19 -0
- data/lib/ditty/templates/sidekiq.rb +18 -0
- data/lib/ditty/templates/sidekiq.yml +9 -0
- data/lib/ditty/templates/spec_helper.rb +43 -0
- data/lib/ditty/templates/type.rb.erb +21 -0
- data/lib/ditty/templates/views/display.haml.tt +20 -0
- data/lib/ditty/templates/views/edit.haml.tt +10 -0
- data/lib/ditty/templates/views/form.haml.tt +11 -0
- data/lib/ditty/templates/views/index.haml.tt +29 -0
- data/lib/ditty/templates/views/new.haml.tt +10 -0
- data/lib/ditty/version.rb +1 -1
- data/lib/rubocop/cop/ditty/call_services_directly.rb +42 -0
- data/migrate/20181209_add_user_login_traits.rb +16 -0
- data/migrate/20181209_extend_audit_log.rb +12 -0
- data/migrate/20190220_add_parent_id_to_roles.rb +9 -0
- data/spec/ditty/api_spec.rb +51 -0
- data/spec/ditty/controllers/roles_spec.rb +67 -0
- data/spec/ditty/controllers/user_login_traits_spec.rb +72 -0
- data/spec/ditty/controllers/users_spec.rb +72 -0
- data/spec/ditty/emails/base_spec.rb +76 -0
- data/spec/ditty/emails/forgot_password_spec.rb +20 -0
- data/spec/ditty/helpers/component_spec.rb +85 -0
- data/spec/ditty/models/user_spec.rb +36 -0
- data/spec/ditty/services/email_spec.rb +36 -0
- data/spec/ditty/services/logger_spec.rb +68 -0
- data/spec/ditty/services/settings_spec.rb +63 -0
- data/spec/ditty_spec.rb +9 -0
- data/spec/factories.rb +46 -0
- data/spec/fixtures/logger.yml +17 -0
- data/spec/fixtures/section.yml +3 -0
- data/spec/fixtures/settings.yml +8 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/support/api_shared_examples.rb +250 -0
- data/spec/support/crud_shared_examples.rb +145 -0
- data/views/403.haml +2 -0
- data/views/404.haml +2 -4
- data/views/500.haml +11 -0
- data/views/audit_logs/index.haml +32 -28
- data/views/auth/forgot_password.haml +32 -16
- data/views/auth/identity.haml +14 -13
- data/views/auth/ldap.haml +17 -0
- data/views/auth/login.haml +23 -17
- data/views/auth/register.haml +20 -18
- data/views/auth/register_identity.haml +27 -12
- data/views/auth/reset_password.haml +36 -19
- data/views/blank.haml +43 -0
- data/views/emails/forgot_password.haml +1 -1
- data/views/emails/layouts/action.haml +10 -6
- data/views/emails/layouts/alert.haml +2 -1
- data/views/emails/layouts/billing.haml +2 -1
- data/views/embedded.haml +17 -11
- data/views/error.haml +8 -3
- data/views/index.haml +1 -1
- data/views/layout.haml +45 -30
- data/views/partials/actions.haml +15 -14
- data/views/partials/content_tag.haml +0 -0
- data/views/partials/delete_form.haml +1 -1
- data/views/partials/filter_control.haml +2 -2
- data/views/partials/footer.haml +13 -5
- data/views/partials/form_control.haml +30 -19
- data/views/partials/form_tag.haml +1 -1
- data/views/partials/navitems.haml +42 -0
- data/views/partials/notifications.haml +12 -8
- data/views/partials/pager.haml +44 -25
- data/views/partials/search.haml +15 -11
- data/views/partials/sidebar.haml +15 -37
- data/views/partials/sort_ui.haml +2 -0
- data/views/partials/timespan_selector.haml +64 -0
- data/views/partials/topbar.haml +53 -0
- data/views/partials/user_associations.haml +32 -0
- data/views/quick_start.haml +23 -0
- data/views/roles/display.haml +27 -6
- data/views/roles/edit.haml +3 -3
- data/views/roles/form.haml +1 -0
- data/views/roles/index.haml +23 -14
- data/views/roles/new.haml +2 -2
- data/views/user_login_traits/display.haml +32 -0
- data/views/user_login_traits/edit.haml +10 -0
- data/views/user_login_traits/form.haml +5 -0
- data/views/user_login_traits/index.haml +28 -0
- data/views/user_login_traits/new.haml +10 -0
- data/views/users/display.haml +15 -16
- data/views/users/edit.haml +3 -3
- data/views/users/form.haml +0 -0
- data/views/users/index.haml +31 -24
- data/views/users/login_traits.haml +25 -0
- data/views/users/new.haml +2 -2
- data/views/users/profile.haml +17 -15
- data/views/users/user.haml +1 -1
- metadata +314 -76
- data/lib/ditty/components/app.rb +0 -77
- data/lib/ditty/controllers/application.rb +0 -175
- data/lib/ditty/controllers/roles.rb +0 -16
- data/lib/ditty/rake_tasks.rb +0 -102
- data/views/partials/navbar.haml +0 -23
|
@@ -13,8 +13,10 @@ module Ditty
|
|
|
13
13
|
# param :count, Integer, min: 1, default: 10 # Can't do this, since count can be `all`
|
|
14
14
|
def check_count
|
|
15
15
|
return 10 if params[:count].nil?
|
|
16
|
+
|
|
16
17
|
count = params[:count].to_i
|
|
17
18
|
return count if count >= 1
|
|
19
|
+
|
|
18
20
|
excp = Sinatra::Param::InvalidParameterError.new 'Parameter cannot be less than 1'
|
|
19
21
|
excp.param = :count
|
|
20
22
|
raise excp
|
|
@@ -31,22 +33,25 @@ module Ditty
|
|
|
31
33
|
param :q, String
|
|
32
34
|
param :page, Integer, min: 1, default: 1
|
|
33
35
|
param :sort, String
|
|
34
|
-
param :order, String, in: %w[asc desc], transform: :downcase
|
|
36
|
+
param :order, String, in: %w[asc desc], transform: :downcase
|
|
35
37
|
# TODO: Can we dynamically validate the search / filter fields?
|
|
36
38
|
|
|
37
39
|
ds = dataset
|
|
38
40
|
ds = ds.dataset if ds.respond_to?(:dataset)
|
|
41
|
+
params[:order] ||= 'asc' if params[:sort]
|
|
39
42
|
return ds if params[:count] == 'all'
|
|
43
|
+
|
|
40
44
|
params[:count] = check_count
|
|
41
45
|
|
|
42
46
|
# Account for difference between sequel paginate and will paginate
|
|
43
47
|
return ds.paginate(page: params[:page], per_page: params[:count]) if ds.is_a?(Array)
|
|
48
|
+
|
|
44
49
|
ds.paginate(params[:page], params[:count])
|
|
45
50
|
end
|
|
46
51
|
|
|
47
52
|
def heading(action = nil)
|
|
48
53
|
@headings ||= begin
|
|
49
|
-
heading = settings.model_class.to_s.demodulize.titleize
|
|
54
|
+
heading = settings.heading || settings.model_class.to_s.demodulize.singularize.titleize
|
|
50
55
|
h = Hash.new(heading)
|
|
51
56
|
h[:new] = "New #{heading}"
|
|
52
57
|
h[:list] = pluralize heading
|
|
@@ -78,6 +83,7 @@ module Ditty
|
|
|
78
83
|
def filters
|
|
79
84
|
filter_fields.map do |filter|
|
|
80
85
|
next if params[filter[:name]].blank?
|
|
86
|
+
|
|
81
87
|
filter[:field] ||= filter[:name]
|
|
82
88
|
filter[:modifier] ||= :to_s # TODO: Do this with Sinatra Param?
|
|
83
89
|
filter
|
|
@@ -86,6 +92,7 @@ module Ditty
|
|
|
86
92
|
|
|
87
93
|
def ordering
|
|
88
94
|
return if params[:sort].blank?
|
|
95
|
+
|
|
89
96
|
Sequel.send(params[:order].to_sym, params[:sort].to_sym)
|
|
90
97
|
end
|
|
91
98
|
|
|
@@ -112,11 +119,13 @@ module Ditty
|
|
|
112
119
|
assoc = filter[:field].to_s.split('.').first.to_sym
|
|
113
120
|
assoc = settings.model_class.association_reflection(assoc)
|
|
114
121
|
raise "Unknown association #{assoc}" if assoc.nil?
|
|
122
|
+
|
|
115
123
|
assoc
|
|
116
124
|
end
|
|
117
125
|
|
|
118
126
|
def search_filters
|
|
119
127
|
return [] if params[:q].blank?
|
|
128
|
+
|
|
120
129
|
searchable_fields.map { |f| Sequel.ilike(f.to_sym, "%#{params[:q]}%") }
|
|
121
130
|
end
|
|
122
131
|
end
|
data/lib/ditty/helpers/pundit.rb
CHANGED
|
@@ -12,23 +12,39 @@ module Ditty
|
|
|
12
12
|
super
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def permitted_attributes(record, action)
|
|
16
|
-
param_key = PolicyFinder.new(record).param_key
|
|
15
|
+
def permitted_attributes(record, action = nil)
|
|
17
16
|
policy = policy(record)
|
|
17
|
+
action ||= record.new? ? :create : :update
|
|
18
18
|
method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
"permitted_attributes_for_#{action}"
|
|
20
|
+
else
|
|
21
|
+
'permitted_attributes'
|
|
22
|
+
end
|
|
23
|
+
policy.public_send(method_name)
|
|
24
|
+
end
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
def permitted_parameters(record, action = nil)
|
|
27
|
+
param_key = PolicyFinder.new(record).param_key
|
|
28
|
+
policy_fields = permitted_attributes(record, action)
|
|
25
29
|
request.params.fetch(param_key, {}).select do |key, _value|
|
|
26
30
|
policy_fields.include? key.to_sym
|
|
27
31
|
end
|
|
28
32
|
end
|
|
29
33
|
|
|
34
|
+
def permitted_response_attributes(record, method = :values)
|
|
35
|
+
policy = policy(record)
|
|
36
|
+
response = record.send(method)
|
|
37
|
+
|
|
38
|
+
return response unless policy.respond_to? :response_attributes
|
|
39
|
+
|
|
40
|
+
policy_fields = policy.response_attributes
|
|
41
|
+
response.select do |key, _value|
|
|
42
|
+
policy_fields.include? key.to_sym
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
30
46
|
def pundit_user
|
|
31
|
-
current_user
|
|
47
|
+
current_user unless current_user&.anonymous?
|
|
32
48
|
end
|
|
33
49
|
end
|
|
34
50
|
end
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'csv'
|
|
4
|
+
|
|
3
5
|
module Ditty
|
|
4
6
|
module Helpers
|
|
5
7
|
module Response
|
|
6
|
-
def list_response(result)
|
|
8
|
+
def list_response(result, view: 'index')
|
|
7
9
|
respond_to do |format|
|
|
8
10
|
format.html do
|
|
9
11
|
actions = {}
|
|
10
12
|
actions["#{base_path}/new"] = "New #{heading}" if policy(settings.model_class).create?
|
|
11
|
-
haml :"#{view_location}
|
|
13
|
+
haml :"#{view_location}/#{view}",
|
|
12
14
|
locals: { list: result, title: heading(:list), actions: actions },
|
|
13
15
|
layout: layout
|
|
14
16
|
end
|
|
@@ -16,12 +18,21 @@ module Ditty
|
|
|
16
18
|
# TODO: Add links defined by actions (New #{heading})
|
|
17
19
|
total = result.respond_to?(:pagination_record_count) ? result.pagination_record_count : result.count
|
|
18
20
|
json(
|
|
19
|
-
'items' => result.all.map(
|
|
21
|
+
'items' => result.all.map { |e| permitted_response_attributes(e, :for_json) },
|
|
20
22
|
'page' => (params['page'] || 1).to_i,
|
|
21
23
|
'count' => result.count,
|
|
22
24
|
'total' => total
|
|
23
25
|
)
|
|
24
26
|
end
|
|
27
|
+
format.csv do
|
|
28
|
+
attachment "#{base_path}.csv"
|
|
29
|
+
CSV.generate do |csv|
|
|
30
|
+
csv << result.first.for_csv.keys
|
|
31
|
+
result.all.each do |r|
|
|
32
|
+
csv << r.for_csv.values
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
25
36
|
end
|
|
26
37
|
end
|
|
27
38
|
|
|
@@ -29,29 +40,42 @@ module Ditty
|
|
|
29
40
|
respond_to do |format|
|
|
30
41
|
format.html do
|
|
31
42
|
flash[:success] = "#{heading} Created"
|
|
32
|
-
redirect with_layout("#{base_path}/#{entity.
|
|
43
|
+
redirect with_layout(params[:redirect_to] || flash[:redirect_to] || "#{base_path}/#{entity.display_id}")
|
|
33
44
|
end
|
|
34
45
|
format.json do
|
|
35
46
|
content_type :json
|
|
36
|
-
redirect "#{base_path}/#{entity.
|
|
47
|
+
redirect "#{base_path}/#{entity.display_id}", 201
|
|
37
48
|
end
|
|
38
49
|
end
|
|
39
50
|
end
|
|
40
51
|
|
|
52
|
+
def actions(entity = nil)
|
|
53
|
+
actions = {}
|
|
54
|
+
actions["#{base_path}/#{entity.display_id}/edit"] = "Edit #{heading}" if entity && policy(entity).update?
|
|
55
|
+
actions["#{base_path}/new"] = "New #{heading}" if policy(settings.model_class).create?
|
|
56
|
+
actions
|
|
57
|
+
end
|
|
58
|
+
|
|
41
59
|
def read_response(entity)
|
|
60
|
+
actions = actions(entity)
|
|
42
61
|
respond_to do |format|
|
|
43
62
|
format.html do
|
|
44
|
-
actions = {}
|
|
45
|
-
actions["#{base_path}/#{entity.id}/edit"] = "Edit #{heading}" if policy(entity).update?
|
|
46
|
-
actions["#{base_path}/new"] = "New #{heading}" if policy(entity).create?
|
|
47
63
|
title = heading(:read) + (entity.respond_to?(:name) ? ": #{entity.name}" : '')
|
|
64
|
+
last_modified entity.updated_at if entity.respond_to?(:updated_at)
|
|
65
|
+
etag entity.etag if entity.respond_to?(:etag)
|
|
48
66
|
haml :"#{view_location}/display",
|
|
49
67
|
locals: { entity: entity, title: title, actions: actions },
|
|
50
68
|
layout: layout
|
|
51
69
|
end
|
|
52
70
|
format.json do
|
|
53
71
|
# TODO: Add links defined by actions (Edit #{heading})
|
|
54
|
-
json entity
|
|
72
|
+
json permitted_response_attributes(entity, :for_json)
|
|
73
|
+
end
|
|
74
|
+
format.csv do
|
|
75
|
+
CSV.generate do |csv|
|
|
76
|
+
csv << entity.for_csv.keys
|
|
77
|
+
csv << entity.for_csv.values
|
|
78
|
+
end
|
|
55
79
|
end
|
|
56
80
|
end
|
|
57
81
|
end
|
|
@@ -61,11 +85,11 @@ module Ditty
|
|
|
61
85
|
format.html do
|
|
62
86
|
# TODO: Ability to customize the return path and message?
|
|
63
87
|
flash[:success] = "#{heading} Updated"
|
|
64
|
-
redirect with_layout("#{base_path}/#{entity.
|
|
88
|
+
redirect with_layout(params[:redirect_to] || flash[:redirect_to] || "#{base_path}/#{entity.display_id}")
|
|
65
89
|
end
|
|
66
90
|
format.json do
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
content_type :json
|
|
92
|
+
redirect "#{base_path}/#{entity.display_id}", 200, json(entity.for_json)
|
|
69
93
|
end
|
|
70
94
|
end
|
|
71
95
|
end
|
|
@@ -74,12 +98,11 @@ module Ditty
|
|
|
74
98
|
respond_to do |format|
|
|
75
99
|
format.html do
|
|
76
100
|
flash[:success] = "#{heading} Deleted"
|
|
77
|
-
redirect with_layout(base_path
|
|
101
|
+
redirect with_layout(params[:redirect_to] || flash[:redirect_to] || back || base_path)
|
|
78
102
|
end
|
|
79
103
|
format.json do
|
|
80
104
|
content_type :json
|
|
81
|
-
|
|
82
|
-
status 204
|
|
105
|
+
redirect base_path.to_s, 204
|
|
83
106
|
end
|
|
84
107
|
end
|
|
85
108
|
end
|
data/lib/ditty/helpers/views.rb
CHANGED
|
@@ -7,6 +7,7 @@ module Ditty
|
|
|
7
7
|
module Views
|
|
8
8
|
def layout
|
|
9
9
|
return :embedded if request.params['layout'] == 'embedded'
|
|
10
|
+
|
|
10
11
|
:layout
|
|
11
12
|
end
|
|
12
13
|
|
|
@@ -14,6 +15,7 @@ module Ditty
|
|
|
14
15
|
uri = URI.parse(url)
|
|
15
16
|
# Don't set the layout if there's none. Don't set the layout for external URIs
|
|
16
17
|
return url if params['layout'].nil? || uri.host
|
|
18
|
+
|
|
17
19
|
qs = { 'layout' => params['layout'] }.merge(uri.query ? CGI.parse(uri.query) : {})
|
|
18
20
|
uri.query = Rack::Utils.build_query qs
|
|
19
21
|
uri.to_s
|
|
@@ -22,9 +24,11 @@ module Ditty
|
|
|
22
24
|
def form_control(name, model, opts = {})
|
|
23
25
|
label = opts.delete(:label) || name.to_s.titlecase
|
|
24
26
|
klass = opts.delete(:class) || 'form-control' unless opts[:type] == 'file'
|
|
27
|
+
klass = "#{klass} is-invalid" if model.errors[name]
|
|
25
28
|
group = opts.delete(:group) || model.class.to_s.demodulize.underscore
|
|
26
29
|
field = opts.delete(:field) || name
|
|
27
30
|
default = opts.delete(:default) || nil
|
|
31
|
+
help_text = opts.delete(:help_text) || nil
|
|
28
32
|
|
|
29
33
|
attributes = { type: 'text', id: name, name: "#{group}[#{name}]", class: klass }.merge(opts)
|
|
30
34
|
haml :'partials/form_control', locals: {
|
|
@@ -34,33 +38,37 @@ module Ditty
|
|
|
34
38
|
name: name,
|
|
35
39
|
group: group,
|
|
36
40
|
field: field,
|
|
37
|
-
default: default
|
|
41
|
+
default: default,
|
|
42
|
+
help_text: help_text
|
|
38
43
|
}
|
|
39
44
|
end
|
|
40
45
|
|
|
41
46
|
def filter_control(filter, opts = {})
|
|
42
47
|
meth = "#{filter[:name]}_options".to_sym
|
|
43
48
|
return unless respond_to? meth
|
|
49
|
+
|
|
44
50
|
haml :'partials/filter_control', locals: {
|
|
45
51
|
name: filter[:name],
|
|
46
52
|
label: opts[:label] || filter[:name].to_s.titlecase,
|
|
47
|
-
options: send(meth)
|
|
53
|
+
options: send(meth),
|
|
54
|
+
total_filters: opts[:filters]
|
|
48
55
|
}
|
|
49
56
|
end
|
|
50
57
|
|
|
51
58
|
def flash_messages(key = :flash)
|
|
52
59
|
return '' if flash(key).empty?
|
|
60
|
+
|
|
53
61
|
id = (key == :flash ? 'flash' : "flash_#{key}")
|
|
54
62
|
messages = flash(key).collect do |message|
|
|
55
63
|
" <div class='alert alert-#{message[0]} alert-dismissable' role='alert'>#{message[1]}</div>\n"
|
|
56
64
|
end
|
|
57
|
-
"<div id='#{id}'>\n
|
|
65
|
+
"<div id='#{id}'>\n#{messages.join}</div>"
|
|
58
66
|
end
|
|
59
67
|
|
|
60
68
|
def query_string(add = {})
|
|
61
69
|
qs = params.clone.merge(add)
|
|
62
70
|
qs.delete('captures')
|
|
63
|
-
Rack::Utils.build_query
|
|
71
|
+
Rack::Utils.build_query(qs.delete_if { |_k, v| v == '' })
|
|
64
72
|
end
|
|
65
73
|
|
|
66
74
|
def delete_form(entity, label = 'Delete')
|
|
@@ -86,14 +94,15 @@ module Ditty
|
|
|
86
94
|
def form_tag(url, options = {}, &block)
|
|
87
95
|
options[:form_verb] ||= :post
|
|
88
96
|
options[:attributes] ||= {}
|
|
89
|
-
options[:attributes] = {
|
|
97
|
+
options[:attributes] = { class: 'form-horizontal' }.merge options[:attributes]
|
|
90
98
|
options[:url] = options[:form_verb].to_sym == :get ? url : with_layout(url)
|
|
91
99
|
haml :'partials/form_tag', locals: options.merge(block: block)
|
|
92
100
|
end
|
|
93
101
|
|
|
94
102
|
def pagination(list, base_path, qp = {})
|
|
95
103
|
return unless list.respond_to?(:pagination_record_count) || list.respond_to?(:total_entries)
|
|
96
|
-
|
|
104
|
+
|
|
105
|
+
list = ::Ditty::Services::PaginationWrapper.new(list)
|
|
97
106
|
locals = {
|
|
98
107
|
first_link: "#{base_path}?" + query_string(qp.merge(page: 1)),
|
|
99
108
|
next_link: list.last_page? ? '#' : "#{base_path}?" + query_string(qp.merge(page: list.next_page)),
|
|
@@ -114,6 +123,39 @@ module Ditty
|
|
|
114
123
|
value
|
|
115
124
|
end
|
|
116
125
|
end
|
|
126
|
+
|
|
127
|
+
def url_for(options = nil)
|
|
128
|
+
return options if options.is_a? String
|
|
129
|
+
return request.env['HTTP_REFERER'] if options == :back && request.env['HTTP_REFERER']
|
|
130
|
+
|
|
131
|
+
raise 'Unimplemented'
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def link_to(name = nil, options = nil, html_options = {})
|
|
135
|
+
html_options[:href] ||= url_for(options)
|
|
136
|
+
|
|
137
|
+
capture_haml do
|
|
138
|
+
haml_tag :a, name, html_options
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def sort_ui(field)
|
|
143
|
+
haml :'partials/sort_ui', locals: { field: field }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def sort_query(field)
|
|
147
|
+
query_string(
|
|
148
|
+
order: params[:sort] == field.to_s && params[:order] == 'asc' ? 'desc' : 'asc',
|
|
149
|
+
sort: field,
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def sort_icon(field)
|
|
154
|
+
return 'fa-sort' unless params[:sort] == field.to_s
|
|
155
|
+
return 'fa-sort-up' if params[:order] == 'asc'
|
|
156
|
+
|
|
157
|
+
'fa-sort-down'
|
|
158
|
+
end
|
|
117
159
|
end
|
|
118
160
|
end
|
|
119
161
|
end
|
data/lib/ditty/listener.rb
CHANGED
|
@@ -17,43 +17,73 @@ module Ditty
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def method_missing(method, *args)
|
|
20
|
-
|
|
20
|
+
unless args[0].is_a?(Hash) && args[0][:target].is_a?(Sinatra::Base) && args[0][:target].settings.track_actions
|
|
21
|
+
return
|
|
22
|
+
end
|
|
21
23
|
|
|
22
|
-
log_action(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
log_action(
|
|
25
|
+
user_traits(args[0][:target]).merge(
|
|
26
|
+
action: action_from(args[0][:target], method),
|
|
27
|
+
details: args[0][:details]
|
|
28
|
+
).merge(args[0][:values] || {})
|
|
29
|
+
)
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
def respond_to_missing?(method, _include_private = false)
|
|
30
33
|
EVENTS.include? method
|
|
31
34
|
end
|
|
32
35
|
|
|
36
|
+
def user_login(event)
|
|
37
|
+
log_action(
|
|
38
|
+
user_traits(event[:target]).merge(
|
|
39
|
+
action: action_from(event[:target], :user_login),
|
|
40
|
+
details: event[:details]
|
|
41
|
+
).merge(event[:values] || {})
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@mutex.synchronize do
|
|
45
|
+
UserLoginTrait.update_or_create(user_traits(event[:target]), updated_at: Time.now)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
33
49
|
def user_register(event)
|
|
34
50
|
user = event[:values][:user]
|
|
35
|
-
log_action(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
log_action(
|
|
52
|
+
user_traits(event[:target]).merge(
|
|
53
|
+
user_id: user.id,
|
|
54
|
+
action: action_from(event[:target], :user_register),
|
|
55
|
+
details: event[:details]
|
|
56
|
+
).merge(event[:values] || {})
|
|
57
|
+
)
|
|
40
58
|
|
|
41
59
|
# Create the SA user if none is present
|
|
42
60
|
sa = Role.find_or_create(name: 'super_admin')
|
|
43
61
|
return if User.where(roles: sa).count.positive?
|
|
62
|
+
|
|
44
63
|
user.add_role sa
|
|
45
64
|
end
|
|
46
65
|
|
|
47
66
|
def action_from(target, method)
|
|
48
67
|
return method unless method.to_s.start_with? 'component_'
|
|
49
|
-
|
|
68
|
+
|
|
69
|
+
"#{target.class.to_s.demodulize.underscore}_#{method.to_s.gsub(/^component_/, '')}"
|
|
50
70
|
end
|
|
51
71
|
|
|
52
72
|
def log_action(values)
|
|
53
73
|
values[:user] ||= values[:target].current_user if values[:target]
|
|
54
|
-
@mutex.synchronize { Ditty::AuditLog.create values }
|
|
74
|
+
@mutex.synchronize { ::Ditty::AuditLog.create values }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def user_traits(target)
|
|
78
|
+
{
|
|
79
|
+
user_id: target.current_user&.id,
|
|
80
|
+
platform: target.browser.platform.name,
|
|
81
|
+
device: target.browser.device.name,
|
|
82
|
+
browser: target.browser.name,
|
|
83
|
+
ip_address: target.request.ip
|
|
84
|
+
}
|
|
55
85
|
end
|
|
56
86
|
end
|
|
57
87
|
end
|
|
58
88
|
|
|
59
|
-
Wisper.subscribe(Ditty::Listener.new) unless ENV['RACK_ENV'] == 'test'
|
|
89
|
+
Wisper.subscribe(::Ditty::Listener.new) unless ENV['RACK_ENV'] == 'test'
|