avo 2.1.2.pre2 → 2.2.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 +4 -2
- data/Gemfile.lock +11 -5
- data/app/assets/svgs/{dashboards-icon.svg → dashboards.svg} +1 -1
- data/app/assets/svgs/{resources-icon.svg → resources.svg} +0 -0
- data/app/assets/svgs/{tools-icon.svg → tools.svg} +0 -0
- data/app/components/avo/button_component.rb +6 -2
- data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +1 -0
- data/app/components/avo/fields/belongs_to_field/edit_component.rb +5 -2
- data/app/components/avo/index/field_wrapper_component.rb +13 -0
- data/app/components/avo/index/grid_item_component.html.erb +3 -1
- data/app/components/avo/index/table_row_component.html.erb +7 -5
- data/app/components/avo/panel_component.html.erb +4 -4
- data/app/components/avo/profile_item_component.html.erb +2 -2
- data/app/components/avo/profile_item_component.rb +6 -0
- data/app/components/avo/sidebar/base_item_component.rb +31 -0
- data/app/components/avo/sidebar/group_component.html.erb +28 -0
- data/app/components/avo/sidebar/group_component.rb +4 -0
- data/app/components/avo/sidebar/heading_component.html.erb +14 -0
- data/app/components/avo/sidebar/heading_component.rb +15 -0
- data/app/components/avo/sidebar/item_switcher_component.html.erb +16 -0
- data/app/components/avo/sidebar/item_switcher_component.rb +15 -0
- data/app/components/avo/sidebar/link_component.html.erb +12 -0
- data/app/components/avo/sidebar/link_component.rb +28 -0
- data/app/components/avo/sidebar/section_component.html.erb +15 -0
- data/app/components/avo/sidebar/section_component.rb +9 -0
- data/app/components/avo/sidebar_component.html.erb +33 -24
- data/app/components/avo/sidebar_component.rb +3 -9
- data/app/components/avo/sidebar_profile_component.html.erb +10 -1
- data/app/components/avo/views/resource_edit_component.html.erb +2 -2
- data/app/components/avo/views/resource_new_component.html.erb +2 -2
- data/app/components/avo/views/resource_show_component.html.erb +2 -2
- data/app/controllers/avo/application_controller.rb +1 -2
- data/app/controllers/avo/base_controller.rb +1 -1
- data/app/helpers/avo/application_helper.rb +2 -0
- data/app/javascript/js/controllers/loading_button_controller.js +47 -10
- data/app/javascript/js/controllers/menu_controller.js +60 -0
- data/app/javascript/js/controllers/search_controller.js +28 -10
- data/app/javascript/js/controllers.js +2 -0
- data/app/views/avo/base/_boolean_filter.html.erb +1 -1
- data/app/views/avo/base/_multiple_select_filter.html.erb +7 -4
- data/app/views/avo/base/_select_filter.html.erb +1 -1
- data/app/views/avo/base/_text_filter.html.erb +1 -1
- data/app/views/avo/partials/_table_header.html.erb +12 -13
- data/app/views/layouts/avo/application.html.erb +3 -0
- data/avo.gemspec +1 -0
- data/bin/helpers.rb +7 -1
- data/bin/init +2 -2
- data/lib/avo/app.rb +8 -86
- data/lib/avo/base_resource.rb +1 -3
- data/lib/avo/concerns/fetches_things.rb +127 -0
- data/lib/avo/configuration.rb +4 -0
- data/lib/avo/dynamic_router.rb +1 -1
- data/lib/avo/engine.rb +0 -1
- data/lib/avo/fields/base_field.rb +2 -0
- data/lib/avo/fields/belongs_to_field.rb +2 -0
- data/lib/avo/hosts/base_host.rb +20 -0
- data/lib/avo/licensing/pro_license.rb +2 -1
- data/lib/avo/menu/base_item.rb +20 -0
- data/lib/avo/menu/builder.rb +77 -0
- data/lib/avo/menu/dashboard.rb +17 -0
- data/lib/avo/menu/group.rb +2 -0
- data/lib/avo/menu/link.rb +4 -0
- data/lib/avo/menu/menu.rb +2 -0
- data/lib/avo/menu/resource.rb +9 -0
- data/lib/avo/menu/section.rb +2 -0
- data/lib/avo/reloader.rb +15 -7
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/filter_generator.rb +2 -0
- data/lib/generators/avo/templates/filters/boolean_filter.tt +1 -1
- data/lib/generators/avo/templates/filters/multiple_select_filter.tt +11 -0
- data/lib/generators/avo/templates/filters/select_filter.tt +1 -1
- data/lib/generators/avo/templates/filters/text_filter.tt +1 -1
- data/lib/generators/avo/templates/tool/sidebar_item.tt +1 -1
- data/public/avo-assets/avo.css +23 -6
- data/public/avo-assets/avo.js +63 -63
- data/public/avo-assets/avo.js.map +3 -3
- metadata +44 -14
- data/app/assets/builds/avo.css +0 -8810
- data/app/assets/builds/avo.js +0 -423
- data/app/assets/builds/avo.js.map +0 -7
- data/app/components/avo/sidebar_heading_component.html.erb +0 -3
- data/app/components/avo/sidebar_heading_component.rb +0 -11
- data/app/components/avo/sidebar_item_component.html.erb +0 -3
- data/app/components/avo/sidebar_item_component.rb +0 -10
@@ -35,12 +35,12 @@
|
|
35
35
|
method: :delete,
|
36
36
|
local: true,
|
37
37
|
title: t('avo.delete_item', item: @resource.model.model_name.name.downcase).capitalize,
|
38
|
-
|
38
|
+
loading: true,
|
39
|
+
confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
|
39
40
|
color: :red,
|
40
41
|
icon: 'trash',
|
41
42
|
form_class: 'flex flex-col sm:flex-row sm:inline-flex',
|
42
43
|
data: {
|
43
|
-
confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
|
44
44
|
control: :destroy,
|
45
45
|
'resource-id': @resource.model.id,
|
46
46
|
'tippy': 'tooltip',
|
@@ -19,7 +19,6 @@ module Avo
|
|
19
19
|
before_action :set_container_classes
|
20
20
|
before_action :add_initial_breadcrumbs
|
21
21
|
before_action :set_view
|
22
|
-
before_action :set_model_to_fill
|
23
22
|
|
24
23
|
rescue_from Pundit::NotAuthorizedError, with: :render_unauthorized
|
25
24
|
rescue_from ActiveRecord::RecordInvalid, with: :exception_logger
|
@@ -285,7 +284,7 @@ module Avo
|
|
285
284
|
end
|
286
285
|
|
287
286
|
def set_locale
|
288
|
-
I18n.locale = params[:
|
287
|
+
I18n.locale = params[:set_locale] || I18n.default_locale
|
289
288
|
|
290
289
|
I18n.default_locale = I18n.locale
|
291
290
|
end
|
@@ -44,7 +44,7 @@ module Avo
|
|
44
44
|
unless @index_params[:sort_by].eql? :created_at
|
45
45
|
@query = @query.unscope(:order)
|
46
46
|
end
|
47
|
-
@query = @query.order(
|
47
|
+
@query = @query.order("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
|
48
48
|
end
|
49
49
|
|
50
50
|
# Apply filters
|
@@ -1,3 +1,4 @@
|
|
1
|
+
/* eslint-disable no-alert */
|
1
2
|
import { Controller } from '@hotwired/stimulus'
|
2
3
|
|
3
4
|
export default class extends Controller {
|
@@ -6,17 +7,53 @@ export default class extends Controller {
|
|
6
7
|
<div class="double-bounce2"></div>
|
7
8
|
</div>`;
|
8
9
|
|
10
|
+
confirmed = false
|
11
|
+
|
9
12
|
connect() {
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
this.context.scope.element.addEventListener('click', (e) => {
|
14
|
+
// If the user has to confirm the action
|
15
|
+
if (this.confirmationMessage) {
|
16
|
+
// Intervene only if not confirmed
|
17
|
+
if (!this.confirmed) {
|
18
|
+
e.preventDefault()
|
19
|
+
if (window.confirm(this.confirmationMessage)) {
|
20
|
+
this.applyLoader()
|
21
|
+
}
|
22
|
+
}
|
23
|
+
} else {
|
24
|
+
this.applyLoader()
|
25
|
+
}
|
20
26
|
})
|
21
27
|
}
|
28
|
+
|
29
|
+
get button() {
|
30
|
+
return this.context.scope.element
|
31
|
+
}
|
32
|
+
|
33
|
+
get confirmationMessage() {
|
34
|
+
return this.context.scope.element.getAttribute('data-avo-confirm')
|
35
|
+
}
|
36
|
+
|
37
|
+
applyLoader() {
|
38
|
+
const { button } = this
|
39
|
+
|
40
|
+
button.style.width = `${button.getBoundingClientRect().width}px`
|
41
|
+
button.style.height = `${button.getBoundingClientRect().height}px`
|
42
|
+
button.innerHTML = this.spinnerMarkup
|
43
|
+
button.classList.add('justify-center')
|
44
|
+
|
45
|
+
setTimeout(() => {
|
46
|
+
this.markConfirmed()
|
47
|
+
button.click()
|
48
|
+
button.setAttribute('disabled', 'disabled')
|
49
|
+
}, 1)
|
50
|
+
}
|
51
|
+
|
52
|
+
markConfirmed() {
|
53
|
+
this.confirmed = true
|
54
|
+
}
|
55
|
+
|
56
|
+
markUnconfirmed() {
|
57
|
+
this.confirmed = false
|
58
|
+
}
|
22
59
|
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
import isNull from 'lodash/isNull'
|
3
|
+
|
4
|
+
export default class extends Controller {
|
5
|
+
static targets = ['svg', 'items', 'self'];
|
6
|
+
|
7
|
+
collapsed = false;
|
8
|
+
|
9
|
+
get key() {
|
10
|
+
return this.selfTarget.getAttribute('data-menu-key-param')
|
11
|
+
}
|
12
|
+
|
13
|
+
defaultState() {
|
14
|
+
return this.selfTarget.getAttribute('data-menu-collapsed-param') === 'collapsed'
|
15
|
+
}
|
16
|
+
|
17
|
+
connect() {
|
18
|
+
if (this.getState() === 'collapsed') {
|
19
|
+
this.collapsed = true
|
20
|
+
this.markCollapsed()
|
21
|
+
} else if (isNull(this.getState()) && this.defaultState()) {
|
22
|
+
this.collapsed = true
|
23
|
+
this.markCollapsed()
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
triggerCollapse() {
|
28
|
+
this.collapsed = !this.collapsed
|
29
|
+
|
30
|
+
this.updateDom()
|
31
|
+
}
|
32
|
+
|
33
|
+
updateDom() {
|
34
|
+
if (this.collapsed) {
|
35
|
+
this.markCollapsed()
|
36
|
+
} else {
|
37
|
+
this.markExpanded()
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
markCollapsed() {
|
42
|
+
this.svgTarget.classList.add('rotate-90')
|
43
|
+
this.itemsTarget.classList.add('hidden')
|
44
|
+
this.storeState('collapsed')
|
45
|
+
}
|
46
|
+
|
47
|
+
markExpanded() {
|
48
|
+
this.svgTarget.classList.remove('rotate-90')
|
49
|
+
this.itemsTarget.classList.remove('hidden')
|
50
|
+
this.storeState('expanded')
|
51
|
+
}
|
52
|
+
|
53
|
+
getState() {
|
54
|
+
return window.localStorage.getItem(this.key)
|
55
|
+
}
|
56
|
+
|
57
|
+
storeState(payload) {
|
58
|
+
window.localStorage.setItem(this.key, payload)
|
59
|
+
}
|
60
|
+
}
|
@@ -88,6 +88,9 @@ export default class extends Controller {
|
|
88
88
|
} else {
|
89
89
|
Turbo.visit(item._url, { action: 'advance' })
|
90
90
|
}
|
91
|
+
|
92
|
+
// On searchable belongs to the class `aa-Detached` remains on the body making it unscrollable
|
93
|
+
document.body.classList.remove('aa-Detached')
|
91
94
|
}
|
92
95
|
|
93
96
|
addSource(resourceName, data) {
|
@@ -102,7 +105,7 @@ export default class extends Controller {
|
|
102
105
|
return `${data.header.toUpperCase()} ${data.help}`
|
103
106
|
},
|
104
107
|
item({ item, createElement }) {
|
105
|
-
|
108
|
+
const children = []
|
106
109
|
|
107
110
|
if (item._avatar) {
|
108
111
|
let classes
|
@@ -117,22 +120,37 @@ export default class extends Controller {
|
|
117
120
|
break
|
118
121
|
}
|
119
122
|
|
120
|
-
|
123
|
+
children.push(
|
124
|
+
createElement('img', {
|
125
|
+
src: item._avatar,
|
126
|
+
alt: item._label,
|
127
|
+
class: `flex-shrink-0 w-8 h-8 my-[2px] inline mr-2 ${classes}`,
|
128
|
+
}),
|
129
|
+
)
|
121
130
|
}
|
122
|
-
element += `<div>${item._label}`
|
123
131
|
|
132
|
+
const labelChildren = [item._label]
|
124
133
|
if (item._description) {
|
125
|
-
|
134
|
+
labelChildren.push(
|
135
|
+
createElement(
|
136
|
+
'div',
|
137
|
+
{
|
138
|
+
class: 'aa-ItemDescription',
|
139
|
+
},
|
140
|
+
item._description,
|
141
|
+
),
|
142
|
+
)
|
126
143
|
}
|
127
144
|
|
128
|
-
|
145
|
+
children.push(createElement('div', null, labelChildren))
|
129
146
|
|
130
|
-
return createElement(
|
131
|
-
|
132
|
-
|
133
|
-
|
147
|
+
return createElement(
|
148
|
+
'div',
|
149
|
+
{
|
150
|
+
class: 'flex',
|
134
151
|
},
|
135
|
-
|
152
|
+
children,
|
153
|
+
)
|
136
154
|
},
|
137
155
|
noResults() {
|
138
156
|
return that.translationKeys.no_item_found.replace(
|
@@ -15,6 +15,7 @@ import ItemSelectAllController from './controllers/item_select_all_controller'
|
|
15
15
|
import ItemSelectorController from './controllers/item_selector_controller'
|
16
16
|
import KeyValueController from './controllers/fields/key_value_controller'
|
17
17
|
import LoadingButtonController from './controllers/loading_button_controller'
|
18
|
+
import MenuController from './controllers/menu_controller'
|
18
19
|
import MobileController from './controllers/mobile_controller'
|
19
20
|
import ModalController from './controllers/modal_controller'
|
20
21
|
import MultipleSelectFilterController from './controllers/multiple_select_filter_controller'
|
@@ -39,6 +40,7 @@ application.register('hidden-input', HiddenInputController)
|
|
39
40
|
application.register('item-select-all', ItemSelectAllController)
|
40
41
|
application.register('item-selector', ItemSelectorController)
|
41
42
|
application.register('loading-button', LoadingButtonController)
|
43
|
+
application.register('menu', MenuController)
|
42
44
|
application.register('mobile', MobileController)
|
43
45
|
application.register('modal', ModalController)
|
44
46
|
application.register('multiple-select-filter', MultipleSelectFilterController)
|
@@ -11,7 +11,7 @@
|
|
11
11
|
end
|
12
12
|
set_value = {} if set_value.nil?
|
13
13
|
%>
|
14
|
-
<div data-controller="boolean-filter">
|
14
|
+
<div data-controller="boolean-filter" data-filter-name="<%= filter.name %>">
|
15
15
|
<%= filter_wrapper name: filter.name do %>
|
16
16
|
<div class="flex items-center">
|
17
17
|
<div class="space-y-2">
|
@@ -1,12 +1,15 @@
|
|
1
1
|
<%
|
2
|
+
set_value = filter.default.present? ? filter.default.select { |key, value| value }.keys.map(&:to_sym) : {}
|
3
|
+
|
2
4
|
begin
|
3
5
|
decoded_filters_param = JSON.parse(Base64.decode64(params[:filters]))
|
4
|
-
|
5
|
-
|
6
|
-
|
6
|
+
if decoded_filters_param[filter.class.to_s].present?
|
7
|
+
set_value = decoded_filters_param[filter.class.to_s]
|
8
|
+
end
|
9
|
+
rescue
|
7
10
|
end
|
8
11
|
%>
|
9
|
-
<div data-controller="multiple-select-filter">
|
12
|
+
<div data-controller="multiple-select-filter" data-filter-name="<%= filter.name %>">
|
10
13
|
<%= filter_wrapper name: filter.name do %>
|
11
14
|
<%= select_tag filter.id, options_for_select(filter.options.invert, set_value),
|
12
15
|
class: input_classes('w-full mb-0'),
|
@@ -6,7 +6,7 @@
|
|
6
6
|
set_value = filter.default
|
7
7
|
end
|
8
8
|
%>
|
9
|
-
<div data-controller="select-filter">
|
9
|
+
<div data-controller="select-filter" data-filter-name="<%= filter.name %>">
|
10
10
|
<%= filter_wrapper name: filter.name do %>
|
11
11
|
<%= select_tag filter.id, options_for_select(filter.options.invert, set_value),
|
12
12
|
class: input_classes('w-full mb-0'),
|
@@ -6,7 +6,7 @@
|
|
6
6
|
set_value = filter.default
|
7
7
|
end
|
8
8
|
%>
|
9
|
-
<div data-controller="text-filter">
|
9
|
+
<div data-controller="text-filter" data-filter-name="<%= filter.name %>">
|
10
10
|
<%= filter_wrapper name: filter.name do %>
|
11
11
|
<%= text_field_tag filter.id, set_value,
|
12
12
|
class: input_classes('w-full mb-0'),
|
@@ -1,10 +1,11 @@
|
|
1
|
-
|
2
1
|
<thead class="bg-white border-b border-gray-200 pb-1">
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
<% if @resource.record_selector %>
|
3
|
+
<th class="rounded-lg">
|
4
|
+
<%== item_select_all_input %>
|
5
|
+
</th>
|
6
|
+
<% end %>
|
7
|
+
<% fields.each_with_index do |field, index| %>
|
8
|
+
<%
|
8
9
|
if params[:sort_by] == field.id.to_s
|
9
10
|
if params[:sort_direction] == 'asc'
|
10
11
|
sort_by = nil
|
@@ -28,8 +29,7 @@
|
|
28
29
|
sort_by = field.id
|
29
30
|
sort_direction = 'desc'
|
30
31
|
end
|
31
|
-
classes = "text-gray-500 tracking-tight leading-tight text-sm font-semibold"
|
32
|
-
%>
|
32
|
+
classes = "text-gray-500 tracking-tight leading-tight text-sm font-semibold" %>
|
33
33
|
<th class="text-left uppercase px-3 py-4 whitespace-nowrap rounded-l">
|
34
34
|
<% if field.sortable %>
|
35
35
|
<%= link_to params.permit!.merge(sort_by: sort_by, sort_direction: sort_direction), class: "flex items-center #{classes} #{'cursor-pointer' if field.sortable}", 'data-turbo-frame': params[:turbo_frame] do %>
|
@@ -37,14 +37,13 @@
|
|
37
37
|
<%= render partial: 'avo/partials/sortable_component', locals: {field: field} %>
|
38
38
|
<% end %>
|
39
39
|
<% else %>
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
<div class="flex items-center <%= classes %>">
|
41
|
+
<%= field.name %>
|
42
|
+
</div>
|
43
43
|
<% end %>
|
44
44
|
</th>
|
45
45
|
<% end %>
|
46
|
-
|
47
46
|
<th class="w-24">
|
48
|
-
<!--
|
47
|
+
<!-- Item controls cell -->
|
49
48
|
</th>
|
50
49
|
</thead>
|
@@ -16,6 +16,9 @@
|
|
16
16
|
<% else %>
|
17
17
|
<%= javascript_include_tag "avo", "data-turbo-track": "reload", defer: true %>
|
18
18
|
<%= stylesheet_link_tag "avo", "data-turbo-track": "reload", defer: true %>
|
19
|
+
<% if Rails.env.development? %>
|
20
|
+
<%= javascript_include_tag "hotwire-livereload", defer: true %>
|
21
|
+
<% end %>
|
19
22
|
<% end %>
|
20
23
|
</head>
|
21
24
|
<body class="bg-application os-mac">
|
data/avo.gemspec
CHANGED
data/bin/helpers.rb
CHANGED
@@ -22,8 +22,14 @@ end
|
|
22
22
|
|
23
23
|
def ask(question:, valid_answers: [])
|
24
24
|
puts "\n#{question} (#{valid_answers.join('/')})"
|
25
|
+
# An uppercase option is treated as a default answer. Otherwise, we disregard case, and always
|
26
|
+
# return the answer in lowercase.
|
27
|
+
default_answer = valid_answers.select { |val| val == val.upcase }.first&.downcase
|
25
28
|
|
26
|
-
|
29
|
+
valid_answers.map!(&:downcase)
|
30
|
+
|
31
|
+
input = gets.downcase.chomp
|
32
|
+
input = default_answer if input == ''
|
27
33
|
|
28
34
|
while !valid_answers.include?(input)
|
29
35
|
puts 'Invalid input, please try again.'
|
data/bin/init
CHANGED
@@ -17,7 +17,7 @@ app_root do
|
|
17
17
|
header 'Installing Yarn packages'
|
18
18
|
run! 'yarn'
|
19
19
|
|
20
|
-
if use_docker
|
20
|
+
if use_docker == 'y'
|
21
21
|
header 'Creating the Docker volume'
|
22
22
|
run! 'docker volume create --name=avo-db-data'
|
23
23
|
|
@@ -28,7 +28,7 @@ app_root do
|
|
28
28
|
header 'Preparing the database'
|
29
29
|
run! 'bin/rails db:setup'
|
30
30
|
|
31
|
-
if use_docker
|
31
|
+
if use_docker == 'y'
|
32
32
|
header 'Stopping the Docker image'
|
33
33
|
run! 'docker-compose stop'
|
34
34
|
end
|
data/lib/avo/app.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module Avo
|
2
2
|
class App
|
3
|
+
include Avo::Concerns::FetchesThings
|
4
|
+
|
3
5
|
class_attribute :resources, default: []
|
4
6
|
class_attribute :dashboards, default: []
|
5
7
|
class_attribute :cache_store, default: nil
|
@@ -102,96 +104,16 @@ module Avo
|
|
102
104
|
end
|
103
105
|
end
|
104
106
|
|
105
|
-
|
106
|
-
|
107
|
-
dashboards.find do |dashboard|
|
108
|
-
dashboard.id == id
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
# Returns the Avo resource by camelized name
|
113
|
-
#
|
114
|
-
# get_resource_by_name('User') => UserResource
|
115
|
-
def get_resource(resource)
|
116
|
-
resources.find do |available_resource|
|
117
|
-
"#{resource}Resource".safe_constantize == available_resource.class
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
# Returns the Avo resource by singular snake_cased name
|
122
|
-
#
|
123
|
-
# get_resource_by_name('user') => UserResource
|
124
|
-
def get_resource_by_name(name)
|
125
|
-
get_resource name.singularize.camelize
|
126
|
-
end
|
127
|
-
|
128
|
-
# Returns the Avo resource by singular snake_cased name
|
129
|
-
#
|
130
|
-
# get_resource_by_name('User') => UserResource
|
131
|
-
# get_resource_by_name(User) => UserResource
|
132
|
-
def get_resource_by_model_name(name)
|
133
|
-
resources.find do |resource|
|
134
|
-
resource.model_class.model_name.name == name.to_s
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
# Returns the Avo resource by singular snake_cased name
|
139
|
-
#
|
140
|
-
# get_resource_by_controller_name('delayed_backend_active_record_jobs') => DelayedJobResource
|
141
|
-
# get_resource_by_controller_name('users') => UserResource
|
142
|
-
def get_resource_by_controller_name(name)
|
143
|
-
resources.find do |resource|
|
144
|
-
resource.model_class.to_s.pluralize.underscore.tr("/", "_") == name.to_s
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
# Returns the Rails model class by singular snake_cased name
|
149
|
-
#
|
150
|
-
# get_model_class_by_name('user') => User
|
151
|
-
def get_model_class_by_name(name)
|
152
|
-
name.to_s.camelize.singularize
|
153
|
-
end
|
107
|
+
def main_menu
|
108
|
+
return nil if Avo::App.license.lacks_with_trial(:menu_builder)
|
154
109
|
|
155
|
-
|
156
|
-
resources.select do |resource|
|
157
|
-
Services::AuthorizationService.authorize user, resource.model_class, Avo.configuration.authorization_methods.stringify_keys["index"], raise_exception: false
|
158
|
-
end
|
159
|
-
.sort_by { |r| r.name }
|
110
|
+
Avo::Menu::Builder.parse_menu(&Avo.configuration.main_menu)
|
160
111
|
end
|
161
112
|
|
162
|
-
def
|
163
|
-
|
164
|
-
end
|
113
|
+
def profile_menu
|
114
|
+
return nil if Avo::App.license.lacks_with_trial(:menu_builder)
|
165
115
|
|
166
|
-
|
167
|
-
get_available_resources(user)
|
168
|
-
.select do |resource|
|
169
|
-
resource.model_class.present?
|
170
|
-
end
|
171
|
-
.select do |resource|
|
172
|
-
resource.visible_on_sidebar
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
def get_dashboards(user = nil)
|
177
|
-
return [] unless App.license.has_with_trial(:resource_ordering)
|
178
|
-
|
179
|
-
get_available_dashboards(user)
|
180
|
-
end
|
181
|
-
|
182
|
-
# Insert any partials that we find in app/views/avo/sidebar/items.
|
183
|
-
def get_sidebar_partials
|
184
|
-
Dir.glob(Rails.root.join("app", "views", "avo", "sidebar", "items", "*.html.erb"))
|
185
|
-
.map do |path|
|
186
|
-
File.basename path
|
187
|
-
end
|
188
|
-
.map do |filename|
|
189
|
-
# remove the leading underscore (_)
|
190
|
-
filename[0] = ""
|
191
|
-
# remove the extension
|
192
|
-
filename.gsub!(".html.erb", "")
|
193
|
-
filename
|
194
|
-
end
|
116
|
+
Avo::Menu::Builder.parse_menu(&Avo.configuration.profile_menu)
|
195
117
|
end
|
196
118
|
end
|
197
119
|
end
|
data/lib/avo/base_resource.rb
CHANGED
@@ -43,6 +43,7 @@ module Avo
|
|
43
43
|
class_attribute :after_create_path, default: :show
|
44
44
|
class_attribute :after_update_path, default: :show
|
45
45
|
class_attribute :invalid_fields
|
46
|
+
class_attribute :record_selector, default: true
|
46
47
|
|
47
48
|
class << self
|
48
49
|
delegate :t, to: ::I18n
|
@@ -285,9 +286,6 @@ module Avo
|
|
285
286
|
end
|
286
287
|
|
287
288
|
def name
|
288
|
-
# return 'hwhwhw'
|
289
|
-
|
290
|
-
|
291
289
|
default = class_name_without_resource.titlecase
|
292
290
|
|
293
291
|
return @name if @name.present?
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Avo
|
2
|
+
module Concerns
|
3
|
+
module FetchesThings
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class_methods do
|
7
|
+
# Returns the Avo dashboard by id
|
8
|
+
#
|
9
|
+
# get_dashboard_by_id(:dashy) -> Dashy
|
10
|
+
def get_dashboard_by_id(id)
|
11
|
+
dashboards.find do |dashboard|
|
12
|
+
dashboard.id == id
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the Avo dashboard by name
|
17
|
+
#
|
18
|
+
# get_dashboard_by_name(:dashy) -> Dashy
|
19
|
+
def get_dashboard_by_name(name)
|
20
|
+
dashboards.find do |dashboard|
|
21
|
+
dashboard.name == name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the Avo resource by camelized name
|
26
|
+
#
|
27
|
+
# get_resource_by_name('User') => UserResource
|
28
|
+
def get_resource(resource)
|
29
|
+
possible_resource = "#{resource}Resource".gsub "ResourceResource", "Resource"
|
30
|
+
|
31
|
+
resources.find do |available_resource|
|
32
|
+
possible_resource.safe_constantize == available_resource.class
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the Avo resource by singular snake_cased name
|
37
|
+
#
|
38
|
+
# get_resource_by_name('user') => UserResource
|
39
|
+
def get_resource_by_name(name)
|
40
|
+
get_resource name.singularize.camelize
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the Avo resource by singular snake_cased name
|
44
|
+
#
|
45
|
+
# get_resource_by_name('User') => UserResource
|
46
|
+
# get_resource_by_name(User) => UserResource
|
47
|
+
def get_resource_by_model_name(name)
|
48
|
+
resources.find do |resource|
|
49
|
+
resource.model_class.model_name.name == name.to_s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the Avo resource by singular snake_cased name
|
54
|
+
#
|
55
|
+
# get_resource_by_controller_name('delayed_backend_active_record_jobs') => DelayedJobResource
|
56
|
+
# get_resource_by_controller_name('users') => UserResource
|
57
|
+
def get_resource_by_controller_name(name)
|
58
|
+
resources.find do |resource|
|
59
|
+
resource.model_class.to_s.pluralize.underscore.tr("/", "_") == name.to_s
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the Avo resource by some name
|
64
|
+
def guess_resource(name)
|
65
|
+
get_resource_by_name(name.to_s) || get_resource_by_model_name(name)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the Rails model class by singular snake_cased name
|
69
|
+
#
|
70
|
+
# get_model_class_by_name('user') => User
|
71
|
+
def get_model_class_by_name(name)
|
72
|
+
name.to_s.camelize.singularize
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_available_resources(user = nil)
|
76
|
+
resources.select do |resource|
|
77
|
+
Services::AuthorizationService.authorize user, resource.model_class, Avo.configuration.authorization_methods.stringify_keys["index"], raise_exception: false
|
78
|
+
end
|
79
|
+
.sort_by { |r| r.name }
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_available_dashboards(user = nil)
|
83
|
+
dashboards.sort_by { |r| r.name }
|
84
|
+
end
|
85
|
+
|
86
|
+
def resources_for_navigation(user = nil)
|
87
|
+
get_available_resources(current_user)
|
88
|
+
.select do |resource|
|
89
|
+
resource.model_class.present?
|
90
|
+
end
|
91
|
+
.select do |resource|
|
92
|
+
resource.visible_on_sidebar
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def dashboards_for_navigation(user = nil)
|
97
|
+
return [] if App.license.lacks_with_trial(:resource_ordering)
|
98
|
+
|
99
|
+
get_available_dashboards(user).select do |dashboard|
|
100
|
+
dashboard.is_visible?
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Insert any partials that we find in app/views/avo/sidebar/items.
|
105
|
+
def get_sidebar_partials
|
106
|
+
Dir.glob(Rails.root.join("app", "views", "avo", "sidebar", "items", "*.html.erb"))
|
107
|
+
.map do |path|
|
108
|
+
File.basename path
|
109
|
+
end
|
110
|
+
.map do |filename|
|
111
|
+
# remove the leading underscore (_)
|
112
|
+
filename[0] = ""
|
113
|
+
# remove the extension
|
114
|
+
filename.gsub!(".html.erb", "")
|
115
|
+
filename
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def tools_for_navigation
|
120
|
+
return [] if Avo::App.license.lacks_with_trial(:custom_tools)
|
121
|
+
|
122
|
+
get_sidebar_partials
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|