avo 0.2.2 → 0.3.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 +49 -48
- data/Gemfile.lock +19 -2
- data/README.md +30 -9
- 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 +14 -7
- data/app/controllers/avo/resources_controller.rb +66 -142
- 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 +1 -0
- data/app/views/layouts/avo/application.html.erb +34 -17
- data/app/views/partials/_header.html.erb +1 -1
- data/avo.gemspec +4 -2
- data/config/credentials.yml.enc +1 -0
- data/config/routes.rb +11 -7
- data/lib/avo.rb +2 -0
- data/lib/avo/app/app.rb +18 -29
- data/lib/avo/app/authorization_service.rb +40 -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/id_field.rb +4 -4
- data/lib/avo/app/licensing/community_license.rb +4 -0
- data/lib/avo/app/licensing/hq.rb +85 -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 +32 -12
- data/lib/avo/configuration.rb +14 -0
- data/lib/avo/engine.rb +3 -3
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/templates/initializer.rb +2 -0
- data/lib/generators/avo/templates/views/_header.html.erb +1 -1
- data/lib/generators/avo/templates/views/_scripts.html.erb +0 -0
- data/public/avo-packs/css/application-73e568bc.css +3 -0
- data/public/avo-packs/css/application-73e568bc.css.br +0 -0
- data/public/avo-packs/css/application-73e568bc.css.gz +0 -0
- data/public/avo-packs/js/application-725ee54e3adbcd950843.js +3 -0
- data/public/avo-packs/js/{application-9a0dde96ad9918852965.js.LICENSE.txt → application-725ee54e3adbcd950843.js.LICENSE.txt} +0 -0
- data/public/avo-packs/js/application-725ee54e3adbcd950843.js.br +0 -0
- data/public/avo-packs/js/application-725ee54e3adbcd950843.js.gz +0 -0
- data/public/avo-packs/js/application-725ee54e3adbcd950843.js.map +1 -0
- data/public/avo-packs/js/application-725ee54e3adbcd950843.js.map.br +0 -0
- data/public/avo-packs/js/application-725ee54e3adbcd950843.js.map.gz +0 -0
- data/public/avo-packs/manifest.json +8 -6
- data/public/avo-packs/manifest.json.br +0 -0
- data/public/avo-packs/manifest.json.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 +61 -21
- 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
@@ -0,0 +1,55 @@
|
|
1
|
+
require_dependency 'avo/application_controller'
|
2
|
+
|
3
|
+
module Avo
|
4
|
+
class SearchController < ApplicationController
|
5
|
+
before_action :authorize_user
|
6
|
+
|
7
|
+
def index
|
8
|
+
resources = []
|
9
|
+
|
10
|
+
resources_to_search_through = App.get_resources
|
11
|
+
.select { |resource| resource.search.present? }
|
12
|
+
.select { |resource| AuthorizationService.authorize_action current_user, resource.model, 'index' }
|
13
|
+
.each do |resource_model|
|
14
|
+
found_resources = add_link_to_search_results(search_resource(resource_model), resource_model)
|
15
|
+
resources.push({
|
16
|
+
label: resource_model.name,
|
17
|
+
resources: found_resources
|
18
|
+
})
|
19
|
+
end
|
20
|
+
|
21
|
+
render json: {
|
22
|
+
resources: resources
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def resource
|
27
|
+
render json: {
|
28
|
+
resources: add_link_to_search_results(search_resource(avo_resource), avo_resource)
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def add_link_to_search_results(resources, avo_resource)
|
34
|
+
resources.map do |model|
|
35
|
+
{
|
36
|
+
id: model.id,
|
37
|
+
search_label: model.send(avo_resource.title),
|
38
|
+
link: "/resources/#{model.class.to_s.singularize.underscore}/#{model.id}",
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def search_resource(avo_resource)
|
44
|
+
avo_resource.query_search(query: params[:q], via_resource_name: params[:via_resource_name], via_resource_id: params[:via_resource_id], user: current_user)
|
45
|
+
end
|
46
|
+
|
47
|
+
def authorize_user
|
48
|
+
return if params[:action] == 'index'
|
49
|
+
|
50
|
+
action = params[:action] == 'resource' ? :index : params[:action]
|
51
|
+
|
52
|
+
return render_unauthorized unless AuthorizationService::authorize_action current_user, avo_resource.model, action
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -10,12 +10,16 @@ module Avo
|
|
10
10
|
render partial: 'vendor/avo/partials/logo' rescue render partial: 'partials/logo'
|
11
11
|
end
|
12
12
|
|
13
|
+
def render_header
|
14
|
+
render partial: 'vendor/avo/partials/header' rescue render partial: 'partials/header'
|
15
|
+
end
|
16
|
+
|
13
17
|
def render_footer
|
14
18
|
render partial: 'vendor/avo/partials/footer' rescue render partial: 'partials/footer'
|
15
19
|
end
|
16
20
|
|
17
|
-
def
|
18
|
-
render partial: 'vendor/avo/partials/
|
21
|
+
def render_scripts
|
22
|
+
render partial: 'vendor/avo/partials/scripts' rescue ''
|
19
23
|
end
|
20
24
|
end
|
21
25
|
end
|
@@ -13,27 +13,44 @@
|
|
13
13
|
<body class="bg-gray-200">
|
14
14
|
|
15
15
|
<div id="app" class="flex min-h-screen flex-row h-full">
|
16
|
-
<
|
17
|
-
|
18
|
-
|
16
|
+
<app-layout class="flex flex-1 w-full"
|
17
|
+
:router-key="routerKey"
|
18
|
+
:layout="layout"
|
19
|
+
inline-template
|
20
|
+
>
|
21
|
+
<div>
|
22
|
+
<application-sidebar :resources='<%= Avo::App.get_resources_navigation(current_user).as_json.html_safe %>' v-if="layout !== 'blank'">
|
23
|
+
<template #logo>
|
24
|
+
<%= render_logo %>
|
25
|
+
</template>
|
26
|
+
<template #licensing>
|
27
|
+
<license-warnings></license-warnings>
|
28
|
+
</template>
|
29
|
+
</application-sidebar>
|
19
30
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
31
|
+
<div class="flex-1 h-full overflow-auto">
|
32
|
+
<div class="relative bg-white p-2 shadow-md h-16 w-full flex items-center z-40" v-if="layout !== 'blank'">
|
33
|
+
<div class="ml-6">
|
34
|
+
<%= render_header %>
|
35
|
+
</div>
|
36
|
+
<div class="flex-1 flex justify-center">
|
37
|
+
<div class="w-64">
|
38
|
+
<resources-search :global="true">
|
39
|
+
</div>
|
40
|
+
</div>
|
28
41
|
</div>
|
29
|
-
</div>
|
30
|
-
</div>
|
31
42
|
|
32
|
-
|
33
|
-
|
34
|
-
|
43
|
+
<div v-if="layout === 'blank'" class="h-full w-full flex justify-center items-center">
|
44
|
+
<%= yield %>
|
45
|
+
</div>
|
46
|
+
<div class="content p-8" v-else>
|
47
|
+
<%= yield %>
|
48
|
+
<%= render_footer %>
|
49
|
+
</div>
|
50
|
+
</div>
|
35
51
|
</div>
|
36
|
-
</
|
52
|
+
</app-layout>
|
37
53
|
</div>
|
54
|
+
<%= render_scripts %>
|
38
55
|
</body>
|
39
56
|
</html>
|
@@ -1 +1 @@
|
|
1
|
-
<%= link_to Avo.configuration.app_name,
|
1
|
+
<%= link_to Avo.configuration.app_name, '/', class: 'text-green-600 font-semibold', target: :_blank %>
|
data/avo.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.name = 'avo'
|
9
9
|
spec.version = Avo::VERSION
|
10
10
|
spec.authors = ['Adrian Marin', 'Mihai Marin']
|
11
|
-
spec.email = ['
|
11
|
+
spec.email = ['avo@avohq.io']
|
12
12
|
spec.homepage = 'https://avohq.io'
|
13
13
|
spec.summary = 'Configuration-based, no-maintenance, extendable Ruby on Rails admin.'
|
14
14
|
spec.description = 'Avo is a beautiful next-generation framework that empowers you, the developer, to create fantastic admin panels for your Ruby on Rails apps with the flexibility to fit your needs as you grow.'
|
@@ -32,10 +32,12 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.files = Dir['{bin,app,config,db,lib,public}/**/*', 'MIT-LICENSE', 'Rakefile', 'README.md', 'avo.gemspec', 'Gemfile', 'Gemfile.lock']
|
33
33
|
.reject { |file| file.start_with? 'app/frontend' }
|
34
34
|
|
35
|
-
spec.add_dependency 'rails', '
|
35
|
+
spec.add_dependency 'rails', '>= 6.0'
|
36
36
|
spec.add_dependency 'kaminari'
|
37
37
|
spec.add_dependency 'zeitwerk'
|
38
38
|
spec.add_dependency 'inline_svg'
|
39
39
|
spec.add_dependency 'webpacker'
|
40
40
|
spec.add_dependency 'countries'
|
41
|
+
spec.add_dependency 'pundit'
|
42
|
+
spec.add_dependency 'httparty'
|
41
43
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
u5m9Je6F3j/LCnOwcN7cXW37M63Bs0KNZC1fHRObWx4YQkV9DJl1k9H9+mlvV3irAC7NZB9H0BExhxquReVe2N0v6s3E3EvOWY8wfZhFONJpK8V0qerYJIhJvCOOPv0hfS/HQdcvUQx/i1faXEgr8QVR7nGVBHUzulwgab0lUBSAV0jjEbT6o+amTojFudelgtw0zmlZW9JL7OZ/IwZ7zxbi8/yoWB52lU4VKOxZV7AEWAVDzL/0ZogLRG12BsTSmqC0jS5ocdnOgV0X7oNeYL9HINqLLM5suBB+BOf3xL6vgngu7UDRAAQ/mN2TDGsvklF8bwVz0dDVQ0RuvdeQ9TWEeM75hhzGKG6wPKBjh4ebrKp7g9TT76F6omLe/hG30MbohGxaLVHIJjyJixfKlLlYwdNWeOjXwYsn--7CEWrUIlraWa8R0S--ia1zVj/2AXISMmbinw6S7g==
|
data/config/routes.rb
CHANGED
@@ -1,20 +1,24 @@
|
|
1
1
|
Avo::Engine.routes.draw do
|
2
2
|
root 'home#index'
|
3
3
|
|
4
|
-
get '/avo-api/
|
5
|
-
|
6
|
-
get '/avo-api/:resource_name', to: 'resources#index'
|
7
|
-
get '/avo-api/:resource_name/filters', to: 'resources#filters'
|
4
|
+
get '/avo-api/:resource_name/filters', to: 'filters#index'
|
5
|
+
|
8
6
|
get '/avo-api/:resource_name/actions', to: 'actions#index'
|
9
7
|
post '/avo-api/:resource_name/actions', to: 'actions#handle'
|
8
|
+
|
9
|
+
get '/avo-api/search', to: 'search#index'
|
10
|
+
get '/avo-api/:resource_name/search', to: 'search#resource'
|
11
|
+
|
12
|
+
get '/avo-api/:resource_name', to: 'resources#index'
|
10
13
|
post '/avo-api/:resource_name', to: 'resources#create'
|
11
|
-
get '/avo-api/:resource_name/
|
14
|
+
get '/avo-api/:resource_name/new', to: 'resources#new'
|
12
15
|
get '/avo-api/:resource_name/:id', to: 'resources#show'
|
13
16
|
get '/avo-api/:resource_name/:id/edit', to: 'resources#edit'
|
14
17
|
put '/avo-api/:resource_name/:id', to: 'resources#update'
|
15
18
|
delete '/avo-api/:resource_name/:id', to: 'resources#destroy'
|
16
|
-
|
17
|
-
post '/avo-api/:resource_name/:id/
|
19
|
+
|
20
|
+
post '/avo-api/:resource_name/:id/attach/:attachment_name/:attachment_id', to: 'relations#attach'
|
21
|
+
post '/avo-api/:resource_name/:id/detach/:attachment_name/:attachment_id', to: 'relations#detach'
|
18
22
|
|
19
23
|
# Tools
|
20
24
|
get '/avo-tools/resource-overview', to: 'resource_overview#index'
|
data/lib/avo.rb
CHANGED
data/lib/avo/app/app.rb
CHANGED
@@ -4,37 +4,35 @@ require_relative 'filters/select_filter'
|
|
4
4
|
require_relative 'filters/boolean_filter'
|
5
5
|
require_relative 'resource'
|
6
6
|
require_relative 'tool'
|
7
|
+
require_relative 'authorization_service'
|
7
8
|
|
8
9
|
module Avo
|
9
10
|
class App
|
10
11
|
@@app = {
|
11
12
|
root_path: '',
|
12
|
-
tools: [],
|
13
|
-
tool_classes: [],
|
14
13
|
resources: [],
|
15
14
|
field_names: {},
|
16
15
|
}
|
16
|
+
@@license = nil
|
17
17
|
|
18
18
|
class << self
|
19
|
-
def
|
19
|
+
def boot
|
20
20
|
@@app[:root_path] = Pathname.new(File.join(__dir__, '..', '..'))
|
21
|
-
# get_tools
|
22
|
-
# init_tools
|
23
21
|
init_fields
|
22
|
+
end
|
23
|
+
|
24
|
+
def init(current_request = nil)
|
24
25
|
init_resources
|
26
|
+
@@license = LicenseManager.new(HQ.new(current_request).response).license
|
25
27
|
end
|
26
28
|
|
27
29
|
def app
|
28
30
|
@@app
|
29
31
|
end
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
# def get_tools
|
36
|
-
# @@app[:tool_classes] = ToolsManager.get_tools
|
37
|
-
# end
|
33
|
+
def license
|
34
|
+
@@license
|
35
|
+
end
|
38
36
|
|
39
37
|
# This method will take all fields available in the Avo::Fields namespace and create a method for them.
|
40
38
|
#
|
@@ -129,23 +127,14 @@ module Avo
|
|
129
127
|
name.to_s.camelize.singularize
|
130
128
|
end
|
131
129
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
# @@app[:tools].each do |tool|
|
141
|
-
# navigation.push(tool.render_navigation) if tool.class.method_defined?(:render_navigation)
|
142
|
-
# end
|
143
|
-
|
144
|
-
# navigation.join('')
|
145
|
-
# end
|
146
|
-
|
147
|
-
def get_resources_navigation
|
148
|
-
App.get_resources.map { |resource| { label: resource.resource_name_plural.humanize, resource_name: resource.url.pluralize } }.to_json.to_s.html_safe
|
130
|
+
def get_resources_navigation(user)
|
131
|
+
App.get_resources
|
132
|
+
.select { |resource| AuthorizationService::authorize user, resource.model, Avo.configuration.authorization_methods.stringify_keys['index'] }
|
133
|
+
.map { |resource| { label: resource.resource_name_plural.humanize, resource_name: resource.url.pluralize } }
|
134
|
+
.reject { |i| i.blank? }
|
135
|
+
.to_json
|
136
|
+
.to_s
|
137
|
+
.html_safe
|
149
138
|
end
|
150
139
|
end
|
151
140
|
end
|
@@ -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
|
@@ -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,85 @@
|
|
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
|
+
end
|
12
|
+
|
13
|
+
def response
|
14
|
+
@hq_response or request
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def request
|
19
|
+
return cached_response if has_cached_response
|
20
|
+
|
21
|
+
begin
|
22
|
+
perform_and_cache_request
|
23
|
+
rescue HTTParty::Error => exception
|
24
|
+
cache_and_return_error 'HTTP client error.', exception.message
|
25
|
+
rescue Net::OpenTimeout => exception
|
26
|
+
cache_and_return_error 'Request timeout.', exception.message
|
27
|
+
rescue SocketError => exception
|
28
|
+
cache_and_return_error 'Connection error.', exception.message
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def perform_and_cache_request
|
33
|
+
hq_response = perform_request
|
34
|
+
|
35
|
+
return cache_and_return_error 'Avo HQ Internal server error.', hq_response.body if hq_response.code == 500
|
36
|
+
|
37
|
+
cache_response 1.hour.to_i, hq_response.parsed_response if hq_response.code == 200
|
38
|
+
end
|
39
|
+
|
40
|
+
def cache_response(time, response)
|
41
|
+
response.merge!(
|
42
|
+
expiry: time,
|
43
|
+
**payload,
|
44
|
+
).stringify_keys!
|
45
|
+
|
46
|
+
Rails.cache.write(CACHE_KEY, response, expires_in: time)
|
47
|
+
|
48
|
+
@hq_response = response
|
49
|
+
|
50
|
+
response
|
51
|
+
end
|
52
|
+
|
53
|
+
def perform_request
|
54
|
+
puts 'Performing request to avohq.io API to check license availability.'.inspect if Rails.env.development?
|
55
|
+
|
56
|
+
HTTParty.post ENDPOINT, body: payload.to_json, headers: { 'Content-type': 'application/json' }, timeout: REQUEST_TIMEOUT
|
57
|
+
end
|
58
|
+
|
59
|
+
def payload
|
60
|
+
{
|
61
|
+
license: Avo.configuration.license,
|
62
|
+
license_key: Avo.configuration.license_key,
|
63
|
+
avo_version: Avo::VERSION,
|
64
|
+
rails_version: Rails::VERSION::STRING,
|
65
|
+
ruby_version: RUBY_VERSION,
|
66
|
+
environment: Rails.env,
|
67
|
+
ip: current_request.ip,
|
68
|
+
host: current_request.host,
|
69
|
+
port: current_request.port,
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def cache_and_return_error(error, exception_message = '')
|
74
|
+
cache_response 5.minutes.to_i, { error: error, exception_message: exception_message }.stringify_keys
|
75
|
+
end
|
76
|
+
|
77
|
+
def has_cached_response
|
78
|
+
Rails.cache.exist? CACHE_KEY
|
79
|
+
end
|
80
|
+
|
81
|
+
def cached_response
|
82
|
+
Rails.cache.read CACHE_KEY
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|