avo 2.3.1.pre.1 → 2.3.1.pre.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.lock +3 -1
- data/app/assets/builds/avo.css +18 -5
- data/app/assets/builds/avo.js +58 -58
- data/app/assets/builds/avo.js.map +3 -3
- data/app/components/avo/sidebar/base_item_component.rb +1 -1
- data/app/components/avo/sidebar/group_component.html.erb +15 -13
- data/app/components/avo/sidebar/section_component.html.erb +1 -1
- data/app/components/avo/sidebar_component.html.erb +1 -1
- data/app/components/avo/views/resource_index_component.html.erb +19 -21
- data/app/controllers/avo/actions_controller.rb +1 -1
- data/app/controllers/avo/application_controller.rb +5 -1
- data/app/controllers/avo/base_controller.rb +9 -14
- data/app/controllers/avo/debug_controller.rb +24 -0
- data/app/javascript/js/controllers/copy_to_clipboard_controller.js +37 -0
- data/app/javascript/js/controllers/filter_controller.js +1 -11
- data/app/javascript/js/controllers/multiple_select_filter_controller.js +1 -3
- data/app/javascript/js/controllers/select_filter_controller.js +1 -1
- data/app/javascript/js/controllers.js +2 -0
- data/app/views/avo/base/_boolean_filter.html.erb +14 -1
- data/app/views/avo/base/_multiple_select_filter.html.erb +13 -2
- data/app/views/avo/base/_select_filter.html.erb +9 -1
- data/app/views/avo/base/_text_filter.html.erb +10 -2
- data/app/views/avo/debug/index.html.erb +87 -0
- data/config/routes.rb +4 -1
- data/lib/avo/app.rb +18 -0
- data/lib/avo/filters/base_filter.rb +2 -17
- data/lib/avo/filters/boolean_filter.rb +0 -12
- data/lib/avo/filters/multiple_select_filter.rb +0 -15
- data/lib/avo/filters/text_filter.rb +0 -4
- data/lib/avo/licensing/h_q.rb +53 -48
- data/lib/avo/licensing/license.rb +8 -0
- data/lib/avo/licensing/license_manager.rb +4 -0
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/templates/initializer/avo.tt +23 -3
- data/public/avo-assets/avo.css +18 -5
- data/public/avo-assets/avo.js +58 -58
- data/public/avo-assets/avo.js.map +3 -3
- metadata +5 -2
@@ -12,7 +12,7 @@ class Avo::Sidebar::BaseItemComponent < ViewComponent::Base
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def key
|
15
|
-
result = "avo.#{request.host}.main_menu.#{item.name.underscore}"
|
15
|
+
result = "avo.#{request.host}.main_menu.#{item.name.to_s.underscore}"
|
16
16
|
|
17
17
|
if item.icon.present?
|
18
18
|
result += ".#{item.icon.parameterize.underscore}"
|
@@ -6,20 +6,22 @@
|
|
6
6
|
data-menu-default-collapsed-state="<%= collapsed ? 'collapsed' : 'expanded' %>"
|
7
7
|
<% end %>
|
8
8
|
>
|
9
|
-
|
10
|
-
<div class="flex
|
11
|
-
|
12
|
-
|
13
|
-
<% if collapsable %>
|
14
|
-
<div class="cursor-pointer <%= 'rotate-90' if collapsed %>"
|
15
|
-
data-action="click->menu#triggerCollapse"
|
16
|
-
data-menu-target="svg"
|
17
|
-
data-menu-key-param="<%= key %>"
|
18
|
-
>
|
19
|
-
<%= helpers.svg 'heroicons/outline/chevron-down', class: "h-4"%>
|
9
|
+
<% if item.name.present? %>
|
10
|
+
<div class="flex justify-between px-10 pt-2 pb-0 text-gray-600 ">
|
11
|
+
<div class="flex items-center text-xs uppercase font-semibold leading-none">
|
12
|
+
<%= item.name %>
|
20
13
|
</div>
|
21
|
-
|
22
|
-
|
14
|
+
<% if collapsable %>
|
15
|
+
<div class="cursor-pointer <%= 'rotate-90' if collapsed %>"
|
16
|
+
data-action="click->menu#triggerCollapse"
|
17
|
+
data-menu-target="svg"
|
18
|
+
data-menu-key-param="<%= key %>"
|
19
|
+
>
|
20
|
+
<%= helpers.svg 'heroicons/outline/chevron-down', class: "h-4"%>
|
21
|
+
</div>
|
22
|
+
<% end %>
|
23
|
+
</div>
|
24
|
+
<% end %>
|
23
25
|
<div class="w-full space-y-1 <%= 'hidden' if collapsed %>" data-menu-target="items">
|
24
26
|
<% items.each do |item| %>
|
25
27
|
<%= render Avo::Sidebar::ItemSwitcherComponent.new item: item %>
|
@@ -11,7 +11,7 @@
|
|
11
11
|
<%= render Avo::Sidebar::LinkComponent.new label: 'Get started', path: helpers.avo.root_path, active: :exclusive if Rails.env.development? && Avo.configuration.home_path.nil? %>
|
12
12
|
|
13
13
|
<% if Avo::App.license.has_with_trial(:menu_editor) && Avo.configuration.main_menu.present? %>
|
14
|
-
<div class="text-black
|
14
|
+
<div class="text-black">
|
15
15
|
<% Avo::App.main_menu.items.each do |item| %>
|
16
16
|
<%= render Avo::Sidebar::ItemSwitcherComponent.new item: item %>
|
17
17
|
<% end %>
|
@@ -16,27 +16,25 @@
|
|
16
16
|
<% end %>
|
17
17
|
<% end %>
|
18
18
|
<% c.body do %>
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
</div>
|
39
|
-
<% end %>
|
19
|
+
<div class="flex flex-col xs:flex-row xs:justify-between space-y-2 xs:space-y-0 py-4 <%= 'hidden' if @resource.search_query.nil? && @filters.empty? && available_view_types.count <= 1 %>"
|
20
|
+
data-selected-resources-name="<%= @resource.model_key %>"
|
21
|
+
data-selected-resources="[]"
|
22
|
+
>
|
23
|
+
<% if @resource.search_query.present? %>
|
24
|
+
<div class="flex items-center px-4 w-64">
|
25
|
+
<%= render partial: 'avo/partials/resource_search', locals: {resource: @resource.model_key} %>
|
26
|
+
</div>
|
27
|
+
<% else %>
|
28
|
+
<%# Offset for the space-y-2 property when the serach is missing %>
|
29
|
+
<div class="-mb-2"></div>
|
30
|
+
<% end %>
|
31
|
+
<% if @filters.present? || available_view_types.count > 1 %>
|
32
|
+
<div class="justify-self-end flex justify-start xs:justify-end items-center px-4 space-x-3">
|
33
|
+
<%= render Avo::FiltersComponent.new filters: @filters, resource: @resource %>
|
34
|
+
<%= render partial: 'avo/partials/view_toggle_button', locals: { available_view_types: available_view_types, view_type: view_type, turbo_frame: @turbo_frame } if available_view_types.count > 1 %>
|
35
|
+
</div>
|
36
|
+
<% end %>
|
37
|
+
</div>
|
40
38
|
<% if view_type.to_sym == :table %>
|
41
39
|
<% if @resources.present? %>
|
42
40
|
<div class="w-full overflow-auto flex flex-col mt-0">
|
@@ -275,8 +275,12 @@ module Avo
|
|
275
275
|
request.original_url.match?(/.*#{Avo::App.root_path}\/dashboards\/.*/)
|
276
276
|
end
|
277
277
|
|
278
|
+
def on_debug_path
|
279
|
+
request.original_url.match?(/.*#{Avo::App.root_path}\/avo_private\/debug.*/)
|
280
|
+
end
|
281
|
+
|
278
282
|
def on_custom_tool_page
|
279
|
-
!(on_root_path || on_resources_path || on_api_path || on_dashboards_path)
|
283
|
+
!(on_root_path || on_resources_path || on_api_path || on_dashboards_path || on_debug_path)
|
280
284
|
end
|
281
285
|
|
282
286
|
def model_param_key
|
@@ -5,7 +5,6 @@ module Avo
|
|
5
5
|
before_action :set_resource_name
|
6
6
|
before_action :set_resource
|
7
7
|
before_action :hydrate_resource
|
8
|
-
before_action :set_applied_filters, only: :index
|
9
8
|
before_action :set_model, only: [:show, :edit, :destroy, :update, :order]
|
10
9
|
before_action :set_model_to_fill
|
11
10
|
before_action :set_edit_title_and_breadcrumbs, only: [:edit, :update]
|
@@ -56,8 +55,8 @@ module Avo
|
|
56
55
|
end
|
57
56
|
end
|
58
57
|
|
59
|
-
# Apply filters
|
60
|
-
|
58
|
+
# Apply filters
|
59
|
+
applied_filters.each do |filter_class, filter_value|
|
61
60
|
@query = filter_class.safe_constantize.new.apply_query request, @query, filter_value
|
62
61
|
end
|
63
62
|
|
@@ -309,32 +308,28 @@ module Avo
|
|
309
308
|
.select { |action| action.visible_in_view }
|
310
309
|
end
|
311
310
|
|
312
|
-
def
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
end
|
311
|
+
def applied_filters
|
312
|
+
if params[:filters].present?
|
313
|
+
return JSON.parse(Base64.decode64(params[:filters]))
|
314
|
+
end
|
317
315
|
|
318
|
-
# Get the default state of the filters and override with the user applied filters
|
319
|
-
def filters_to_be_applied
|
320
316
|
filter_defaults = {}
|
321
317
|
|
322
318
|
@resource.get_filters.each do |filter_class|
|
323
319
|
filter = filter_class.new
|
324
320
|
|
325
|
-
|
321
|
+
if filter.default.present?
|
326
322
|
filter_defaults[filter_class.to_s] = filter.default
|
327
323
|
end
|
328
324
|
end
|
329
325
|
|
330
|
-
filter_defaults
|
326
|
+
filter_defaults
|
331
327
|
end
|
332
328
|
|
333
|
-
# Caching these so we know when the filters have changed so we reset the pagination
|
334
329
|
def cache_applied_filters
|
335
330
|
::Avo::App.cache_store.delete applied_filters_cache_key if params[:filters].nil?
|
336
331
|
|
337
|
-
::Avo::App.cache_store.write(applied_filters_cache_key, params[:filters], expires_in:
|
332
|
+
::Avo::App.cache_store.write(applied_filters_cache_key, params[:filters], expires_in: 7.days)
|
338
333
|
end
|
339
334
|
|
340
335
|
def reset_pagination_if_filters_changed
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_dependency "avo/application_controller"
|
2
|
+
|
3
|
+
module Avo
|
4
|
+
class DebugController < ApplicationController
|
5
|
+
def index
|
6
|
+
end
|
7
|
+
|
8
|
+
def refresh_license
|
9
|
+
license = Licensing::LicenseManager.refresh_license request
|
10
|
+
|
11
|
+
if license.valid?
|
12
|
+
flash[:notice] = "avohq.io responded: \"#{license.id.humanize} license is valid\"."
|
13
|
+
else
|
14
|
+
if license.response['reason'].present?
|
15
|
+
flash[:error] = "avohq.io responded: \"#{license.response['reason']}\"."
|
16
|
+
else
|
17
|
+
flash[:error] = license.response['error']
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
redirect_back fallback_location: avo.avo_private_debug_index_path
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
copy() {
|
5
|
+
const str = this.context.element.dataset.text
|
6
|
+
/* ——— Derived from: https://hackernoon.com/copying-text-to-clipboard-with-javascript-df4d4988697f
|
7
|
+
improved to add iOS device compatibility——— */
|
8
|
+
const el = document.createElement('textarea') // Create a <textarea> element
|
9
|
+
|
10
|
+
const storeContentEditable = el.contentEditable
|
11
|
+
const storeReadOnly = el.readOnly
|
12
|
+
|
13
|
+
el.value = str // Set its value to the string that you want copied
|
14
|
+
el.contentEditable = true
|
15
|
+
el.readOnly = false
|
16
|
+
el.setAttribute('readonly', false) // Make it readonly false for iOS compatability
|
17
|
+
el.setAttribute('contenteditable', true) // Make it editable for iOS
|
18
|
+
el.style.position = 'absolute'
|
19
|
+
el.style.left = '-9999px' // Move outside the screen to make it invisible
|
20
|
+
document.body.appendChild(el) // Append the <textarea> element to the HTML document
|
21
|
+
const selected = document.getSelection().rangeCount > 0 // Check if there is any content selected previously
|
22
|
+
? document.getSelection().getRangeAt(0) // Store selection if found
|
23
|
+
: false // Mark as false to know no selection existed before
|
24
|
+
el.select() // Select the <textarea> content
|
25
|
+
el.setSelectionRange(0, 999999)
|
26
|
+
document.execCommand('copy') // Copy - only works as a result of a user action (e.g. click events)
|
27
|
+
document.body.removeChild(el) // Remove the <textarea> element
|
28
|
+
if (selected) {
|
29
|
+
// If a selection existed before copying
|
30
|
+
document.getSelection().removeAllRanges() // Unselect everything on the HTML document
|
31
|
+
document.getSelection().addRange(selected) // Restore the original selection
|
32
|
+
}
|
33
|
+
|
34
|
+
el.contentEditable = storeContentEditable
|
35
|
+
el.readOnly = storeReadOnly
|
36
|
+
}
|
37
|
+
}
|
@@ -38,22 +38,18 @@ export default class extends Controller {
|
|
38
38
|
const value = this.getFilterValue()
|
39
39
|
const filterClass = this.getFilterClass()
|
40
40
|
|
41
|
-
// Get the `filters` param for all params
|
42
41
|
let filters = this.uriParams()[this.uriParam('filters')]
|
43
42
|
|
44
|
-
// Decode the filters
|
45
43
|
if (filters) {
|
46
44
|
filters = JSON.parse(this.b64DecodeUnicode(filters))
|
47
45
|
} else {
|
48
46
|
filters = {}
|
49
47
|
}
|
50
48
|
|
51
|
-
// Get the values for this particular filter
|
52
49
|
filters[filterClass] = value
|
53
50
|
|
54
51
|
const filtered = Object.keys(filters)
|
55
|
-
|
56
|
-
.filter((key) => filters[key] !== null)
|
52
|
+
.filter((key) => filters[key] !== '')
|
57
53
|
.reduce((obj, key) => {
|
58
54
|
obj[key] = filters[key]
|
59
55
|
|
@@ -62,16 +58,10 @@ export default class extends Controller {
|
|
62
58
|
|
63
59
|
let encodedFilters
|
64
60
|
|
65
|
-
// Encode the filters and their values
|
66
61
|
if (filtered && Object.keys(filtered).length > 0) {
|
67
62
|
encodedFilters = this.b64EncodeUnicode(JSON.stringify(filtered))
|
68
63
|
}
|
69
64
|
|
70
|
-
this.navigateToURLWithFilters(encodedFilters)
|
71
|
-
}
|
72
|
-
|
73
|
-
navigateToURLWithFilters(encodedFilters) {
|
74
|
-
// Create a new URI with them
|
75
65
|
const url = new URI(this.urlRedirectTarget.href)
|
76
66
|
|
77
67
|
const query = {
|
@@ -4,9 +4,7 @@ export default class extends BaseFilterController {
|
|
4
4
|
static targets = ['selector']
|
5
5
|
|
6
6
|
getFilterValue() {
|
7
|
-
|
8
|
-
|
9
|
-
return filterValue.length === 0 ? null : filterValue
|
7
|
+
return Array.from(this.selectorTarget.selectedOptions).map(({ value }) => value)
|
10
8
|
}
|
11
9
|
|
12
10
|
getFilterClass() {
|
@@ -7,6 +7,7 @@ import AttachmentsController from './controllers/attachments_controller'
|
|
7
7
|
import BelongsToFieldController from './controllers/fields/belongs_to_field_controller'
|
8
8
|
import BooleanFilterController from './controllers/boolean_filter_controller'
|
9
9
|
import CodeFieldController from './controllers/fields/code_field_controller'
|
10
|
+
import CopyToClipboardController from './controllers/copy_to_clipboard_controller'
|
10
11
|
import DashboardCardController from './controllers/dashboard_card_controller'
|
11
12
|
import DateFieldController from './controllers/fields/date_field_controller'
|
12
13
|
import FilterController from './controllers/filter_controller'
|
@@ -34,6 +35,7 @@ application.register('actions-picker', ActionsPickerController)
|
|
34
35
|
application.register('alerts', AlertsController)
|
35
36
|
application.register('attachments', AttachmentsController)
|
36
37
|
application.register('boolean-filter', BooleanFilterController)
|
38
|
+
application.register('copy-to-clipboard', CopyToClipboardController)
|
37
39
|
application.register('dashboard-card', DashboardCardController)
|
38
40
|
application.register('filter', FilterController)
|
39
41
|
application.register('hidden-input', HiddenInputController)
|
@@ -1,10 +1,23 @@
|
|
1
|
+
<%
|
2
|
+
begin
|
3
|
+
decoded_filters_param = JSON.parse(Base64.decode64(params[:filters]))
|
4
|
+
set_value = decoded_filters_param[filter.class.to_s]
|
5
|
+
rescue => exception
|
6
|
+
if filter.default.present?
|
7
|
+
set_value = filter.default.stringify_keys
|
8
|
+
else
|
9
|
+
set_value = {}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
set_value = {} if set_value.nil?
|
13
|
+
%>
|
1
14
|
<div data-controller="boolean-filter" data-filter-name="<%= filter.name %>">
|
2
15
|
<%= filter_wrapper name: filter.name do %>
|
3
16
|
<div class="flex items-center">
|
4
17
|
<div class="space-y-2">
|
5
18
|
<% filter.options.each do |value, label| %>
|
6
19
|
<label class="flex items-center text-gray-700 text-sm">
|
7
|
-
<%= check_box_tag filter.id, value,
|
20
|
+
<%= check_box_tag filter.id, value, set_value[value.to_s],
|
8
21
|
class: 'mr-2 text-lg h-4 w-4',
|
9
22
|
id: "avo_filters_#{filter.id.parameterize.underscore}",
|
10
23
|
'data-filter-class': filter.class,
|
@@ -1,6 +1,17 @@
|
|
1
|
+
<%
|
2
|
+
set_value = filter.default.present? ? filter.default.select { |key, value| value }.keys.map(&:to_sym) : {}
|
3
|
+
|
4
|
+
begin
|
5
|
+
decoded_filters_param = JSON.parse(Base64.decode64(params[:filters]))
|
6
|
+
if decoded_filters_param[filter.class.to_s].present?
|
7
|
+
set_value = decoded_filters_param[filter.class.to_s]
|
8
|
+
end
|
9
|
+
rescue
|
10
|
+
end
|
11
|
+
%>
|
1
12
|
<div data-controller="multiple-select-filter" data-filter-name="<%= filter.name %>">
|
2
13
|
<%= filter_wrapper name: filter.name do %>
|
3
|
-
<%= select_tag filter.id, options_for_select(filter.options.invert,
|
14
|
+
<%= select_tag filter.id, options_for_select(filter.options.invert, set_value),
|
4
15
|
class: input_classes('w-full mb-0'),
|
5
16
|
multiple: true,
|
6
17
|
id: "avo_filters_#{filter.id.parameterize.underscore}",
|
@@ -8,7 +19,7 @@
|
|
8
19
|
'data-multiple-select-filter-target': 'selector'
|
9
20
|
%>
|
10
21
|
<div class="flex justify-end">
|
11
|
-
<%= a_button class: 'mt-4', color: :blue, size: :
|
22
|
+
<%= a_button class: 'mt-4', color: :blue, size: :sm, data: { action: "multiple-select-filter#changeFilter" } do %>
|
12
23
|
Filter by <%=filter.name %>
|
13
24
|
<% end %>
|
14
25
|
</div>
|
@@ -1,6 +1,14 @@
|
|
1
|
+
<%
|
2
|
+
begin
|
3
|
+
decoded_filters_param = JSON.parse(Base64.decode64(params[:filters]))
|
4
|
+
set_value = decoded_filters_param[filter.class.to_s]
|
5
|
+
rescue => exception
|
6
|
+
set_value = filter.default
|
7
|
+
end
|
8
|
+
%>
|
1
9
|
<div data-controller="select-filter" data-filter-name="<%= filter.name %>">
|
2
10
|
<%= filter_wrapper name: filter.name do %>
|
3
|
-
<%= select_tag filter.id, options_for_select(filter.options.invert,
|
11
|
+
<%= select_tag filter.id, options_for_select(filter.options.invert, set_value),
|
4
12
|
class: input_classes('w-full mb-0'),
|
5
13
|
include_blank: '—',
|
6
14
|
id: "avo_filters_#{filter.id.parameterize.underscore}",
|
@@ -1,6 +1,14 @@
|
|
1
|
+
<%
|
2
|
+
begin
|
3
|
+
decoded_filters_param = JSON.parse(Base64.decode64(params[:filters]))
|
4
|
+
set_value = decoded_filters_param[filter.class.to_s]
|
5
|
+
rescue => exception
|
6
|
+
set_value = filter.default
|
7
|
+
end
|
8
|
+
%>
|
1
9
|
<div data-controller="text-filter" data-filter-name="<%= filter.name %>">
|
2
10
|
<%= filter_wrapper name: filter.name do %>
|
3
|
-
<%= text_field_tag filter.id,
|
11
|
+
<%= text_field_tag filter.id, set_value,
|
4
12
|
class: input_classes('w-full mb-0'),
|
5
13
|
id: "avo_filters_#{filter.id.parameterize.underscore}",
|
6
14
|
'data-filter-class': filter.class.to_s,
|
@@ -8,7 +16,7 @@
|
|
8
16
|
'data-action': 'keypress->text-filter#tryToSubmit'
|
9
17
|
%>
|
10
18
|
<div class="flex justify-end">
|
11
|
-
<%= a_button class: 'mt-4', color: :blue, data: { action: "text-filter#changeFilter" }, size: :
|
19
|
+
<%= a_button class: 'mt-4', color: :blue, data: { action: "text-filter#changeFilter" }, size: :sm do %>
|
12
20
|
<%= filter.button_label || "Filter by #{filter.name}" %>
|
13
21
|
<% end %>
|
14
22
|
</div>
|
@@ -0,0 +1,87 @@
|
|
1
|
+
<%
|
2
|
+
license = Avo::App.license
|
3
|
+
information_items = [
|
4
|
+
'license',
|
5
|
+
'license_key',
|
6
|
+
'ruby_version',
|
7
|
+
'rails_version',
|
8
|
+
'environment',
|
9
|
+
'host',
|
10
|
+
'port',
|
11
|
+
'ip',
|
12
|
+
'app_name',
|
13
|
+
]
|
14
|
+
hq_payload = Avo::Licensing::HQ.new(request).payload
|
15
|
+
%>
|
16
|
+
<div class="flex flex-col">
|
17
|
+
<%= render Avo::PanelComponent.new title: 'Debug Avo', description: 'Use this page to debug the Avo license.' do |c| %>
|
18
|
+
<% c.tools do %>
|
19
|
+
<% end %>
|
20
|
+
<% c.bare_content do %>
|
21
|
+
<div class="grid gap-4 sm:grid-cols-3">
|
22
|
+
<div class="relative flex flex-col bg-white rounded shadow-panel p-4 space-y-4 h-full col-span-1">
|
23
|
+
<div class="font-semibold">License info</div>
|
24
|
+
<div class="flex flex-col justify-between flex-1">
|
25
|
+
<div>
|
26
|
+
<div class="text-xl font-semibold"><%= license.name %></div>
|
27
|
+
<% if license.response['reason'] %>
|
28
|
+
<dl>
|
29
|
+
<dt class="font-semibold text-sm">Error</dt>
|
30
|
+
<dd class="text-red-600"><%= license.response['reason'] %></dd>
|
31
|
+
</dl>
|
32
|
+
<% end %>
|
33
|
+
<% if license.response['error'] %>
|
34
|
+
<dl>
|
35
|
+
<dt class="font-semibold text-sm">Error</dt>
|
36
|
+
<dd class="text-red-600"><%= license.response['error'] %></dd>
|
37
|
+
<dt class="font-semibold text-sm">Exception message</dt>
|
38
|
+
<dd class="text-red-600"><%= license.response['exception_message'] %></dd>
|
39
|
+
</dl>
|
40
|
+
<% end %>
|
41
|
+
<div class="mt-4">
|
42
|
+
<dl>
|
43
|
+
<% information_items.each do |item| %>
|
44
|
+
<dt class="font-semibold text-sm"><%= item.humanize %></dt>
|
45
|
+
<dd><%= license.response[item] %></dd>
|
46
|
+
<% end %>
|
47
|
+
<dt class="font-semibold text-sm">Last fetched at</dt>
|
48
|
+
<dd><%= license.response['fetched_at'] %> <br>
|
49
|
+
(<%= time_ago_in_words license.response['fetched_at'] %> ago)</dd>
|
50
|
+
<dt class="font-semibold text-sm">Cache store</dt>
|
51
|
+
<dd><%= Avo::App.cache_store.class.to_s %> - <%= Avo::App.cache_store.options.inspect %></dd>
|
52
|
+
</dl>
|
53
|
+
</div>
|
54
|
+
</div>
|
55
|
+
<div class="flex justify-end mt-4">
|
56
|
+
<%= a_button style: :outline,
|
57
|
+
color: :blue,
|
58
|
+
url: '/admin/avo_private/debug/refresh_license',
|
59
|
+
method: :post,
|
60
|
+
loading: true,
|
61
|
+
icon: 'heroicons/outline/refresh' do %>
|
62
|
+
Refresh license
|
63
|
+
<% end %>
|
64
|
+
</div>
|
65
|
+
</div>
|
66
|
+
</div>
|
67
|
+
<div class="relative bg-white rounded shadow-panel p-4 space-y-4 col-span-2">
|
68
|
+
<div class="font-semibold">Debug report</div>
|
69
|
+
<div>
|
70
|
+
<%== ap Avo::App.debug_report(request) %>
|
71
|
+
</div>
|
72
|
+
<div class="flex justify-end">
|
73
|
+
<%= a_button icon: 'heroicons/outline/clipboard',
|
74
|
+
style: :primary,
|
75
|
+
data: {
|
76
|
+
controller: 'copy-to-clipboard',
|
77
|
+
action: "click->copy-to-clipboard#copy",
|
78
|
+
text: Avo::App.debug_report(request),
|
79
|
+
} do %>
|
80
|
+
Copy debug info
|
81
|
+
<% end %>
|
82
|
+
</div>
|
83
|
+
</div>
|
84
|
+
</div>
|
85
|
+
<% end %>
|
86
|
+
<% end %>
|
87
|
+
</div>
|
data/config/routes.rb
CHANGED
@@ -39,8 +39,11 @@ Avo::Engine.routes.draw do
|
|
39
39
|
delete "/:resource_name/:id/:related_name/:related_id", to: "associations#destroy", as: "associations_destroy"
|
40
40
|
end
|
41
41
|
|
42
|
+
get "/debug", to: "debug#index", as: "debug_index"
|
43
|
+
post "/debug/refresh_license", to: "debug#refresh_license"
|
44
|
+
|
42
45
|
if Rails.env.development? or Rails.env.staging?
|
43
|
-
scope "avo_private", as: "avo_private" do
|
46
|
+
scope "/avo_private", as: "avo_private" do
|
44
47
|
get "/design", to: "private#design"
|
45
48
|
end
|
46
49
|
end
|
data/lib/avo/app.rb
CHANGED
@@ -115,6 +115,24 @@ module Avo
|
|
115
115
|
|
116
116
|
Avo::Menu::Builder.parse_menu(&Avo.configuration.profile_menu)
|
117
117
|
end
|
118
|
+
|
119
|
+
def debug_report(request)
|
120
|
+
payload = {}
|
121
|
+
hq = Avo::Licensing::HQ.new(request)
|
122
|
+
|
123
|
+
payload[:hq_payload] = hq.payload
|
124
|
+
payload[:license_id] = Avo::App.license.id
|
125
|
+
payload[:license_valid] = Avo::App.license.valid?
|
126
|
+
payload[:license_payload] = Avo::App.license.payload
|
127
|
+
payload[:license_response] = Avo::App.license.response
|
128
|
+
payload[:cache_store] = self.cache_store.class.to_s
|
129
|
+
payload[:avo_metadata] = hq.avo_metadata
|
130
|
+
payload[:app_timezone] = Time.now.zone
|
131
|
+
|
132
|
+
payload
|
133
|
+
rescue => e
|
134
|
+
e
|
135
|
+
end
|
118
136
|
end
|
119
137
|
end
|
120
138
|
end
|
@@ -3,11 +3,11 @@ module Avo
|
|
3
3
|
class BaseFilter
|
4
4
|
class_attribute :name, default: "Filter"
|
5
5
|
class_attribute :component, default: "boolean-filter"
|
6
|
-
class_attribute :default, default:
|
6
|
+
class_attribute :default, default: ""
|
7
7
|
class_attribute :template, default: "avo/base/select_filter"
|
8
8
|
|
9
9
|
def apply_query(request, query, value)
|
10
|
-
value.
|
10
|
+
value.symbolize_keys! if value.is_a? Hash
|
11
11
|
|
12
12
|
apply(request, query, value)
|
13
13
|
end
|
@@ -15,21 +15,6 @@ module Avo
|
|
15
15
|
def id
|
16
16
|
self.class.name.underscore.tr("/", "_")
|
17
17
|
end
|
18
|
-
|
19
|
-
# Get the applied value this filter.
|
20
|
-
# If it's not present return the default value.
|
21
|
-
def applied_or_default_value(applied_filters)
|
22
|
-
# Get the values for this particular filter
|
23
|
-
applied_value = applied_filters[self.class.to_s]
|
24
|
-
|
25
|
-
# Return that value if present
|
26
|
-
return applied_value unless applied_value.nil?
|
27
|
-
|
28
|
-
# Return that default
|
29
|
-
default
|
30
|
-
rescue
|
31
|
-
default
|
32
|
-
end
|
33
18
|
end
|
34
19
|
end
|
35
20
|
end
|
@@ -2,18 +2,6 @@ module Avo
|
|
2
2
|
module Filters
|
3
3
|
class BooleanFilter < BaseFilter
|
4
4
|
self.template = "avo/base/boolean_filter"
|
5
|
-
|
6
|
-
def selected_value(item, applied_filters)
|
7
|
-
# See if there are any applied rules for this particular filter
|
8
|
-
if applied_filters[self.class.to_s].present?
|
9
|
-
# Symbolize the keys because they are returned from de-serialization (JSON and Base64)
|
10
|
-
applied_filters[self.class.to_s].stringify_keys.dig(item.to_s)
|
11
|
-
else
|
12
|
-
applied_or_default_value(applied_filters).stringify_keys.dig(item.to_s)
|
13
|
-
end
|
14
|
-
rescue
|
15
|
-
false
|
16
|
-
end
|
17
5
|
end
|
18
6
|
end
|
19
7
|
end
|
@@ -2,21 +2,6 @@ module Avo
|
|
2
2
|
module Filters
|
3
3
|
class MultipleSelectFilter < BaseFilter
|
4
4
|
self.template = "avo/base/multiple_select_filter"
|
5
|
-
|
6
|
-
# The input expects an array of strings for the value
|
7
|
-
# Ex: ['admins', 'non_admins']
|
8
|
-
def selected_value(applied_filters)
|
9
|
-
# Get the values for this particular filter
|
10
|
-
applied_value = applied_filters[self.class.to_s]
|
11
|
-
|
12
|
-
# Return that value if present
|
13
|
-
return applied_value unless applied_value.nil?
|
14
|
-
|
15
|
-
# Return that default
|
16
|
-
return default unless default.nil?
|
17
|
-
|
18
|
-
[]
|
19
|
-
end
|
20
5
|
end
|
21
6
|
end
|
22
7
|
end
|