avo 0.2.4 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of avo might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +52 -48
- data/Gemfile.lock +25 -1
- data/README.md +5 -3
- data/app/controllers/avo/application_controller.rb +34 -1
- data/app/controllers/avo/filters_controller.rb +19 -0
- data/app/controllers/avo/relations_controller.rb +34 -0
- data/app/controllers/avo/resource_overview_controller.rb +15 -7
- data/app/controllers/avo/resources_controller.rb +71 -145
- data/app/controllers/avo/search_controller.rb +55 -0
- data/app/helpers/avo/application_helper.rb +6 -2
- data/app/views/layouts/avo/_javascript.html.erb +3 -0
- data/app/views/layouts/avo/_translations.html.erb +5 -0
- data/app/views/layouts/avo/application.html.erb +40 -18
- data/avo.gemspec +4 -1
- data/config/credentials.yml.enc +1 -0
- data/config/routes.rb +11 -7
- data/lib/avo.rb +4 -0
- data/lib/avo/app/action.rb +8 -5
- data/lib/avo/app/app.rb +35 -28
- data/lib/avo/app/fields/belongs_to.rb +2 -2
- data/lib/avo/app/fields/code_field.rb +2 -0
- data/lib/avo/app/fields/country_field.rb +1 -1
- data/lib/avo/app/fields/field.rb +3 -1
- data/lib/avo/app/fields/field_extensions/visible_in_different_views.rb +4 -0
- data/lib/avo/app/fields/has_and_belongs_to_many.rb +1 -0
- data/lib/avo/app/fields/has_many.rb +1 -0
- data/lib/avo/app/fields/has_one.rb +2 -2
- data/lib/avo/app/fields/id_field.rb +4 -4
- data/lib/avo/app/fields/markdown_field.rb +27 -0
- data/lib/avo/app/fields/password_field.rb +3 -1
- data/lib/avo/app/fields/select_field.rb +1 -1
- data/lib/avo/app/licensing/community_license.rb +4 -0
- data/lib/avo/app/licensing/hq.rb +86 -0
- data/lib/avo/app/licensing/license.rb +48 -0
- data/lib/avo/app/licensing/license_manager.rb +25 -0
- data/lib/avo/app/licensing/null_license.rb +12 -0
- data/lib/avo/app/licensing/pro_license.rb +9 -0
- data/lib/avo/app/resource.rb +58 -20
- data/lib/avo/app/services/authorization_service.rb +40 -0
- data/lib/avo/configuration.rb +28 -2
- data/lib/avo/engine.rb +7 -7
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/install_generator.rb +2 -1
- data/lib/generators/avo/templates/{initializer.rb → initializer/avo.rb} +2 -0
- data/lib/generators/avo/templates/locales/avo.en.yml +60 -0
- data/lib/generators/avo/templates/views/_scripts.html.erb +0 -0
- data/public/avo-packs/css/application-d405b262.css +3 -0
- data/public/avo-packs/css/application-d405b262.css.br +0 -0
- data/public/avo-packs/css/application-d405b262.css.gz +0 -0
- data/public/avo-packs/js/application-d7382d9da26e7470d7b2.js +3 -0
- data/public/avo-packs/js/{application-9a0dde96ad9918852965.js.LICENSE.txt → application-d7382d9da26e7470d7b2.js.LICENSE.txt} +0 -0
- data/public/avo-packs/js/application-d7382d9da26e7470d7b2.js.br +0 -0
- data/public/avo-packs/js/application-d7382d9da26e7470d7b2.js.gz +0 -0
- data/public/avo-packs/js/application-d7382d9da26e7470d7b2.js.map +1 -0
- data/public/avo-packs/js/application-d7382d9da26e7470d7b2.js.map.br +0 -0
- data/public/avo-packs/js/application-d7382d9da26e7470d7b2.js.map.gz +0 -0
- data/public/avo-packs/manifest.json +13 -6
- data/public/avo-packs/manifest.json.br +0 -0
- data/public/avo-packs/manifest.json.gz +0 -0
- data/public/avo-packs/media/font/fontello-068ca2b3.ttf +0 -0
- data/public/avo-packs/media/font/fontello-068ca2b3.ttf.br +0 -0
- data/public/avo-packs/media/font/fontello-068ca2b3.ttf.gz +0 -0
- data/public/avo-packs/media/font/fontello-8d4a4e6f.woff2 +0 -0
- data/public/avo-packs/media/font/fontello-9354499c.svg +72 -0
- data/public/avo-packs/media/font/fontello-9354499c.svg.br +0 -0
- data/public/avo-packs/media/font/fontello-9354499c.svg.gz +0 -0
- data/public/avo-packs/media/font/fontello-a782baa8.woff +0 -0
- data/public/avo-packs/media/font/fontello-e73a0647.eot +0 -0
- data/public/avo-packs/media/font/fontello-e73a0647.eot.br +0 -0
- data/public/avo-packs/media/font/fontello-e73a0647.eot.gz +0 -0
- data/public/avo-packs/media/svgs/arrow-circle-right-1ad1e15ec9a7aa54b67d126566a5aa2d.svg +1 -0
- data/public/avo-packs/media/svgs/arrow-circle-right-1ad1e15ec9a7aa54b67d126566a5aa2d.svg.br +0 -0
- data/public/avo-packs/media/svgs/arrow-circle-right-1ad1e15ec9a7aa54b67d126566a5aa2d.svg.gz +0 -0
- data/public/avo-packs/media/svgs/exclamation-8d1c0baa390a8df9bb52176011eb5892.svg +1 -0
- data/public/avo-packs/media/svgs/exclamation-8d1c0baa390a8df9bb52176011eb5892.svg.br +0 -0
- data/public/avo-packs/media/svgs/exclamation-8d1c0baa390a8df9bb52176011eb5892.svg.gz +0 -0
- metadata +88 -14
- data/public/avo-packs/css/application-5dc4dd78.css +0 -3
- data/public/avo-packs/css/application-5dc4dd78.css.br +0 -0
- data/public/avo-packs/css/application-5dc4dd78.css.gz +0 -0
- data/public/avo-packs/js/application-9a0dde96ad9918852965.js +0 -3
- data/public/avo-packs/js/application-9a0dde96ad9918852965.js.br +0 -0
- data/public/avo-packs/js/application-9a0dde96ad9918852965.js.gz +0 -0
- data/public/avo-packs/js/application-9a0dde96ad9918852965.js.map +0 -1
- data/public/avo-packs/js/application-9a0dde96ad9918852965.js.map.br +0 -0
- data/public/avo-packs/js/application-9a0dde96ad9918852965.js.map.gz +0 -0
@@ -13,7 +13,7 @@ module Avo
|
|
13
13
|
|
14
14
|
hide_on :create
|
15
15
|
|
16
|
-
@placeholder =
|
16
|
+
@placeholder = I18n.t 'avo.choose_an_option'
|
17
17
|
|
18
18
|
@relation_method = name.to_s.parameterize.underscore
|
19
19
|
end
|
@@ -43,7 +43,7 @@ module Avo
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
fields[:
|
46
|
+
fields[:plural_name] = target_resource.plural_name
|
47
47
|
|
48
48
|
fields
|
49
49
|
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module Avo
|
2
2
|
module Fields
|
3
3
|
class IdField < Field
|
4
|
-
DEFAULT_VALUE = 'id'
|
5
|
-
|
6
4
|
def initialize(name, **args, &block)
|
5
|
+
default_value = 'id'
|
6
|
+
|
7
7
|
if name.nil?
|
8
|
-
@name = name =
|
8
|
+
@name = name = default_value
|
9
9
|
elsif !name.is_a? String and !name.is_a? Symbol
|
10
10
|
args_copy = name
|
11
|
-
@name = name =
|
11
|
+
@name = name = default_value
|
12
12
|
args = args_copy
|
13
13
|
end
|
14
14
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative 'field'
|
2
|
+
|
3
|
+
module Avo
|
4
|
+
module Fields
|
5
|
+
class MarkdownField < Field
|
6
|
+
def initialize(name, **args, &block)
|
7
|
+
@defaults = {
|
8
|
+
component: 'markdown-field',
|
9
|
+
}
|
10
|
+
|
11
|
+
super(name, **args, &block)
|
12
|
+
|
13
|
+
hide_on :index
|
14
|
+
|
15
|
+
@always_show = args[:always_show].present? ? args[:always_show] : false
|
16
|
+
@height = args[:height].present? ? args[:height].to_s : 'auto'
|
17
|
+
end
|
18
|
+
|
19
|
+
def hydrate_field(fields, model, resource, view)
|
20
|
+
{
|
21
|
+
always_show: @always_show,
|
22
|
+
height: @height
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -12,7 +12,7 @@ module Avo
|
|
12
12
|
@options = args[:options].present? ? args[:options] : {}
|
13
13
|
@enum = args[:enum].present? ? args[:enum] : nil
|
14
14
|
@display_value = args[:display_value].present? ? args[:display_value] : false
|
15
|
-
@placeholder = args[:placeholder].present? ? args[:placeholder].to_s : '
|
15
|
+
@placeholder = args[:placeholder].present? ? args[:placeholder].to_s : I18n.t('avo.choose_an_option')
|
16
16
|
end
|
17
17
|
|
18
18
|
def hydrate_field(fields, model, resource, view)
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Avo
|
2
|
+
class HQ
|
3
|
+
attr_accessor :current_request
|
4
|
+
|
5
|
+
ENDPOINT = 'https://avohq.io/api/v1/licenses/check'
|
6
|
+
CACHE_KEY = 'avo.hq.response'
|
7
|
+
REQUEST_TIMEOUT = 5 # seconds
|
8
|
+
|
9
|
+
def initialize(current_request)
|
10
|
+
@current_request = current_request
|
11
|
+
@cache_store = Avo::App.cache_store
|
12
|
+
end
|
13
|
+
|
14
|
+
def response
|
15
|
+
@hq_response or request
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def request
|
20
|
+
return cached_response if has_cached_response
|
21
|
+
|
22
|
+
begin
|
23
|
+
perform_and_cache_request
|
24
|
+
rescue HTTParty::Error => exception
|
25
|
+
cache_and_return_error 'HTTP client error.', exception.message
|
26
|
+
rescue Net::OpenTimeout => exception
|
27
|
+
cache_and_return_error 'Request timeout.', exception.message
|
28
|
+
rescue SocketError => exception
|
29
|
+
cache_and_return_error 'Connection error.', exception.message
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def perform_and_cache_request
|
34
|
+
hq_response = perform_request
|
35
|
+
|
36
|
+
return cache_and_return_error 'Avo HQ Internal server error.', hq_response.body if hq_response.code == 500
|
37
|
+
|
38
|
+
cache_response 1.hour.to_i, hq_response.parsed_response if hq_response.code == 200
|
39
|
+
end
|
40
|
+
|
41
|
+
def cache_response(time, response)
|
42
|
+
response.merge!(
|
43
|
+
expiry: time,
|
44
|
+
**payload,
|
45
|
+
).stringify_keys!
|
46
|
+
|
47
|
+
@cache_store.write(CACHE_KEY, response, expires_in: time)
|
48
|
+
|
49
|
+
@hq_response = response
|
50
|
+
|
51
|
+
response
|
52
|
+
end
|
53
|
+
|
54
|
+
def perform_request
|
55
|
+
puts 'Performing request to avohq.io API to check license availability.'.inspect if Rails.env.development?
|
56
|
+
|
57
|
+
HTTParty.post ENDPOINT, body: payload.to_json, headers: { 'Content-type': 'application/json' }, timeout: REQUEST_TIMEOUT
|
58
|
+
end
|
59
|
+
|
60
|
+
def payload
|
61
|
+
{
|
62
|
+
license: Avo.configuration.license,
|
63
|
+
license_key: Avo.configuration.license_key,
|
64
|
+
avo_version: Avo::VERSION,
|
65
|
+
rails_version: Rails::VERSION::STRING,
|
66
|
+
ruby_version: RUBY_VERSION,
|
67
|
+
environment: Rails.env,
|
68
|
+
ip: current_request.ip,
|
69
|
+
host: current_request.host,
|
70
|
+
port: current_request.port,
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def cache_and_return_error(error, exception_message = '')
|
75
|
+
cache_response 5.minutes.to_i, { error: error, exception_message: exception_message }.stringify_keys
|
76
|
+
end
|
77
|
+
|
78
|
+
def has_cached_response
|
79
|
+
@cache_store.exist? CACHE_KEY
|
80
|
+
end
|
81
|
+
|
82
|
+
def cached_response
|
83
|
+
@cache_store.read CACHE_KEY
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Avo
|
2
|
+
class License
|
3
|
+
attr_accessor :id
|
4
|
+
attr_accessor :response
|
5
|
+
attr_accessor :valid
|
6
|
+
|
7
|
+
def initialize(response)
|
8
|
+
@response = response
|
9
|
+
@id = response['id']
|
10
|
+
@valid = response['valid']
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid?
|
14
|
+
valid
|
15
|
+
end
|
16
|
+
|
17
|
+
def invalid?
|
18
|
+
!valid?
|
19
|
+
end
|
20
|
+
|
21
|
+
def pro?
|
22
|
+
id == 'pro'
|
23
|
+
end
|
24
|
+
|
25
|
+
def error
|
26
|
+
@response['error']
|
27
|
+
end
|
28
|
+
|
29
|
+
def properties
|
30
|
+
@response.slice 'valid', 'id', 'error'
|
31
|
+
end
|
32
|
+
|
33
|
+
def abilities
|
34
|
+
[]
|
35
|
+
end
|
36
|
+
|
37
|
+
def can(ability)
|
38
|
+
abilities.include? ability
|
39
|
+
end
|
40
|
+
|
41
|
+
def cant(ability)
|
42
|
+
!can ability
|
43
|
+
end
|
44
|
+
|
45
|
+
alias_method :has, :can
|
46
|
+
alias_method :lacks, :cant
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative 'license'
|
2
|
+
require_relative 'community_license'
|
3
|
+
require_relative 'pro_license'
|
4
|
+
require_relative 'null_license'
|
5
|
+
|
6
|
+
module Avo
|
7
|
+
class LicenseManager
|
8
|
+
def initialize(hq_response)
|
9
|
+
@hq_response = hq_response
|
10
|
+
end
|
11
|
+
|
12
|
+
def license
|
13
|
+
return NullLicense.new if Rails.env.test? and ENV['RUN_WITH_NULL_LICENSE'] == '1'
|
14
|
+
|
15
|
+
case @hq_response['id']
|
16
|
+
when 'community'
|
17
|
+
CommunityLicense.new @hq_response
|
18
|
+
when 'pro'
|
19
|
+
ProLicense.new @hq_response
|
20
|
+
else
|
21
|
+
NullLicense.new @hq_response
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/avo/app/resource.rb
CHANGED
@@ -12,18 +12,28 @@ module Avo
|
|
12
12
|
attr_reader :default_view_type
|
13
13
|
|
14
14
|
class << self
|
15
|
-
def hydrate_resource(model
|
16
|
-
|
15
|
+
def hydrate_resource(model:, resource:, view: :index, user:)
|
16
|
+
case view
|
17
|
+
when :show
|
18
|
+
panel_name = I18n.t 'avo.resource_details', name: resource.name.downcase.upcase_first
|
19
|
+
when :edit
|
20
|
+
panel_name = I18n.t('avo.edit_item', item: resource.name.downcase).upcase_first
|
21
|
+
when :create
|
22
|
+
panel_name = I18n.t('avo.create_new_item', item: resource.name.downcase).upcase_first
|
23
|
+
end
|
17
24
|
|
18
25
|
resource_with_fields = {
|
19
26
|
id: model.id,
|
20
|
-
|
21
|
-
|
27
|
+
authorization: get_authorization(user, model),
|
28
|
+
singular_name: resource.singular_name,
|
29
|
+
plural_name: resource.plural_name,
|
22
30
|
title: model[resource.title],
|
31
|
+
translation_key: resource.translation_key,
|
32
|
+
path: resource.url,
|
23
33
|
fields: [],
|
24
34
|
grid_fields: {},
|
25
35
|
panels: [{
|
26
|
-
name:
|
36
|
+
name: panel_name,
|
27
37
|
component: 'panel',
|
28
38
|
}]
|
29
39
|
}
|
@@ -39,9 +49,11 @@ module Avo
|
|
39
49
|
|
40
50
|
furnished_field = field.fetch_for_resource(model, resource, view)
|
41
51
|
|
52
|
+
next unless field_resource_authorized field, furnished_field, user
|
53
|
+
|
42
54
|
next if furnished_field.blank?
|
43
55
|
|
44
|
-
furnished_field[:panel_name] =
|
56
|
+
furnished_field[:panel_name] = panel_name
|
45
57
|
furnished_field[:show_on_show] = field.show_on_show
|
46
58
|
|
47
59
|
if field.has_own_panel?
|
@@ -74,19 +86,41 @@ module Avo
|
|
74
86
|
|
75
87
|
"/resources/#{url}"
|
76
88
|
end
|
89
|
+
|
90
|
+
def get_authorization(user, model)
|
91
|
+
[:create, :edit, :update, :show, :destroy].map do |action|
|
92
|
+
[action, AuthorizationService::authorize_action(user, model, action)]
|
93
|
+
end.to_h
|
94
|
+
end
|
95
|
+
|
96
|
+
def field_resource_authorized(field, furnished_field, user)
|
97
|
+
if [Avo::Fields::HasManyField, Avo::Fields::HasAndBelongsToManyField].include? field.class
|
98
|
+
return true if furnished_field[:relationship_model].nil?
|
99
|
+
|
100
|
+
AuthorizationService.authorize user, furnished_field[:relationship_model].safe_constantize, Avo.configuration.authorization_methods.stringify_keys['index']
|
101
|
+
else
|
102
|
+
true
|
103
|
+
end
|
104
|
+
end
|
77
105
|
end
|
78
106
|
|
79
107
|
def name
|
80
108
|
return @name if @name.present?
|
81
109
|
|
110
|
+
return I18n.t(@translation_key, count: 1).capitalize if @translation_key
|
111
|
+
|
82
112
|
self.class.name.demodulize.titlecase
|
83
113
|
end
|
84
114
|
|
85
|
-
def
|
115
|
+
def singular_name
|
116
|
+
return I18n.t(@translation_key, count: 1).capitalize if @translation_key
|
117
|
+
|
86
118
|
name
|
87
119
|
end
|
88
120
|
|
89
|
-
def
|
121
|
+
def plural_name
|
122
|
+
return I18n.t(@translation_key, count: 2).capitalize if @translation_key
|
123
|
+
|
90
124
|
name.pluralize
|
91
125
|
end
|
92
126
|
|
@@ -108,6 +142,10 @@ module Avo
|
|
108
142
|
'id'
|
109
143
|
end
|
110
144
|
|
145
|
+
def translation_key
|
146
|
+
@translation_key
|
147
|
+
end
|
148
|
+
|
111
149
|
def model
|
112
150
|
return @model if @model.present?
|
113
151
|
|
@@ -138,26 +176,26 @@ module Avo
|
|
138
176
|
@search
|
139
177
|
end
|
140
178
|
|
141
|
-
def query_search(query: '', via_resource_name: , via_resource_id:)
|
142
|
-
|
179
|
+
def query_search(query: '', via_resource_name: , via_resource_id:, user:)
|
180
|
+
model_class = self.model
|
181
|
+
|
182
|
+
db_query = AuthorizationService.with_policy(user, model_class)
|
143
183
|
|
144
184
|
if via_resource_name.present?
|
145
|
-
|
146
|
-
|
147
|
-
db_query = related_model.find(via_resource_id).public_send(self.
|
185
|
+
related_model = App.get_resource_by_name(via_resource_name).model
|
186
|
+
|
187
|
+
db_query = related_model.find(via_resource_id).public_send(self.plural_name.downcase)
|
148
188
|
end
|
149
189
|
|
190
|
+
new_query = []
|
191
|
+
|
150
192
|
[self.search].flatten.each_with_index do |search_by, index|
|
151
|
-
|
193
|
+
new_query.push 'or' if index != 0
|
152
194
|
|
153
|
-
|
154
|
-
db_query = db_query.where query_string
|
155
|
-
else
|
156
|
-
db_query = db_query.or(self.model.where query_string)
|
157
|
-
end
|
195
|
+
new_query.push "text(#{search_by}) ILIKE '%#{query}%'"
|
158
196
|
end
|
159
197
|
|
160
|
-
db_query.
|
198
|
+
db_query.where(new_query.join(' '))
|
161
199
|
end
|
162
200
|
|
163
201
|
def model
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Avo
|
2
|
+
class AuthorizationService
|
3
|
+
class << self
|
4
|
+
def authorize(user, record, action)
|
5
|
+
return true if skip_authorization
|
6
|
+
|
7
|
+
begin
|
8
|
+
if Pundit.policy user, record
|
9
|
+
Pundit.authorize user, record, action
|
10
|
+
end
|
11
|
+
true
|
12
|
+
rescue Pundit::NotAuthorizedError => error
|
13
|
+
false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def authorize_action(user, record, action)
|
18
|
+
action = Avo.configuration.authorization_methods.stringify_keys[action.to_s]
|
19
|
+
|
20
|
+
return true if action.nil?
|
21
|
+
|
22
|
+
authorize user, record, action
|
23
|
+
end
|
24
|
+
|
25
|
+
def with_policy(user, model)
|
26
|
+
return model if skip_authorization
|
27
|
+
|
28
|
+
begin
|
29
|
+
Pundit.policy_scope! user, model
|
30
|
+
rescue => exception
|
31
|
+
model
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def skip_authorization
|
36
|
+
Avo::App.license.lacks :authorization
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|