avo 2.2.2 → 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 +2 -0
- data/Gemfile.lock +7 -1
- data/app/assets/builds/avo.css +8840 -0
- data/app/assets/builds/avo.js +423 -0
- data/app/assets/builds/avo.js.map +7 -0
- data/app/components/avo/sidebar/base_item_component.rb +1 -1
- data/app/components/avo/sidebar/group_component.html.erb +17 -15
- data/app/components/avo/sidebar/heading_component.html.erb +1 -1
- data/app/components/avo/sidebar/heading_component.rb +3 -1
- data/app/components/avo/sidebar/section_component.html.erb +4 -4
- 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 -1
- data/app/controllers/avo/debug_controller.rb +24 -0
- data/app/javascript/avo.js +13 -0
- data/app/javascript/js/controllers/copy_to_clipboard_controller.js +37 -0
- data/app/javascript/js/controllers/menu_controller.js +23 -18
- data/app/javascript/js/controllers.js +2 -0
- 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/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/services/authorization_service.rb +8 -2
- 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 +10 -4
@@ -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}"
|
@@ -3,24 +3,26 @@
|
|
3
3
|
data-controller="menu"
|
4
4
|
data-menu-target="self"
|
5
5
|
data-menu-key-param="<%= key %>"
|
6
|
-
data-menu-collapsed-
|
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"
|
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
|
-
|
23
|
-
|
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 %>
|
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 %>
|
26
28
|
<% end %>
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<span class="min-w-[20px]"><%= icon %></span> <span><%= label %></span>
|
4
4
|
</div>
|
5
5
|
<% if collapsable %>
|
6
|
-
<div class="cursor-pointer"
|
6
|
+
<div class="cursor-pointer <%= 'rotate-90' if collapsed %>"
|
7
7
|
data-action="click->menu#triggerCollapse"
|
8
8
|
data-menu-key-param="<%= key %>"
|
9
9
|
data-menu-target="svg"
|
@@ -2,12 +2,14 @@
|
|
2
2
|
|
3
3
|
class Avo::Sidebar::HeadingComponent < ViewComponent::Base
|
4
4
|
attr_reader :collapsable
|
5
|
+
attr_reader :collapsed
|
5
6
|
attr_reader :icon
|
6
7
|
attr_reader :key
|
7
8
|
attr_reader :label
|
8
9
|
|
9
|
-
def initialize(label: nil, icon: nil, collapsable: false, key: nil)
|
10
|
+
def initialize(label: nil, icon: nil, collapsable: false, collapsed: false, key: nil)
|
10
11
|
@collapsable = collapsable
|
12
|
+
@collapsed = collapsed
|
11
13
|
@icon = icon
|
12
14
|
@key = key
|
13
15
|
@label = label
|
@@ -1,13 +1,13 @@
|
|
1
|
-
<div class="space-y-1"
|
1
|
+
<div class="space-y-1 mb-4"
|
2
2
|
<% if collapsable %>
|
3
3
|
data-controller="menu"
|
4
4
|
data-menu-target="self"
|
5
5
|
data-menu-key-param="<%= key %>"
|
6
|
-
data-menu-collapsed-
|
6
|
+
data-menu-default-collapsed-state="<%= collapsed ? 'collapsed' : 'expanded' %>"
|
7
7
|
<% end %>
|
8
8
|
>
|
9
|
-
<%= render Avo::Sidebar::HeadingComponent.new label: item.name, icon: helpers.svg(icon, class:
|
10
|
-
<div class="w-full space-y-1" data-menu-target="items">
|
9
|
+
<%= render Avo::Sidebar::HeadingComponent.new label: item.name, icon: helpers.svg(icon, class: "h-5"), collapsable: item.collapsable, collapsed: collapsed, key: key %>
|
10
|
+
<div class="w-full space-y-1 <%= 'hidden' if collapsed %>" data-menu-target="items">
|
11
11
|
<% items.each do |item| %>
|
12
12
|
<%= render Avo::Sidebar::ItemSwitcherComponent.new item: item %>
|
13
13
|
<% end %>
|
@@ -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
|
@@ -44,7 +44,15 @@ module Avo
|
|
44
44
|
unless @index_params[:sort_by].eql? :created_at
|
45
45
|
@query = @query.unscope(:order)
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
|
+
# Check if the sortable field option is actually a proc and we need to do a custom sort
|
49
|
+
field_id = @index_params[:sort_by].to_sym
|
50
|
+
field = @resource.get_field_definitions.find { |field| field.id == field_id }
|
51
|
+
if field&.sortable.is_a?(Proc)
|
52
|
+
@query = field.sortable.call(@query, @index_params[:sort_direction])
|
53
|
+
else
|
54
|
+
@query = @query.order("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
|
55
|
+
end
|
48
56
|
end
|
49
57
|
|
50
58
|
# Apply filters
|
@@ -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
|
data/app/javascript/avo.js
CHANGED
@@ -84,3 +84,16 @@ document.addEventListener('turbo:submit-end', () => document.body.classList.remo
|
|
84
84
|
document.addEventListener('turbo:before-cache', () => {
|
85
85
|
document.querySelectorAll('[data-turbo-remove-before-cache]').forEach((element) => element.remove())
|
86
86
|
})
|
87
|
+
|
88
|
+
window.Avo = window.Avo || { configuration: {} }
|
89
|
+
|
90
|
+
window.Avo.menus = {
|
91
|
+
resetCollapsedState() {
|
92
|
+
Array.from(document.querySelectorAll('[data-menu-key-param]'))
|
93
|
+
.map((i) => i.getAttribute('data-menu-key-param'))
|
94
|
+
.filter(Boolean)
|
95
|
+
.forEach((key) => {
|
96
|
+
window.localStorage.removeItem(key)
|
97
|
+
})
|
98
|
+
},
|
99
|
+
}
|
@@ -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
|
+
}
|
@@ -1,31 +1,46 @@
|
|
1
1
|
import { Controller } from '@hotwired/stimulus'
|
2
|
-
import isNull from 'lodash/isNull'
|
3
2
|
|
4
3
|
export default class extends Controller {
|
5
4
|
static targets = ['svg', 'items', 'self'];
|
6
5
|
|
7
|
-
collapsed =
|
6
|
+
collapsed = true;
|
8
7
|
|
9
8
|
get key() {
|
10
9
|
return this.selfTarget.getAttribute('data-menu-key-param')
|
11
10
|
}
|
12
11
|
|
13
|
-
defaultState() {
|
14
|
-
return this.selfTarget.getAttribute('data-menu-collapsed-
|
12
|
+
get defaultState() {
|
13
|
+
return this.selfTarget.getAttribute('data-menu-default-collapsed-state')
|
14
|
+
}
|
15
|
+
|
16
|
+
get userState() {
|
17
|
+
return window.localStorage.getItem(this.key)
|
18
|
+
}
|
19
|
+
|
20
|
+
set userState(payload) {
|
21
|
+
window.localStorage.setItem(this.key, payload)
|
22
|
+
}
|
23
|
+
|
24
|
+
get initiallyCollapsed() {
|
25
|
+
if (this.userState === 'collapsed' || this.defaultState === 'collapsed') return true
|
26
|
+
if (this.userState === 'expanded' || this.defaultState === 'expanded') return false
|
27
|
+
|
28
|
+
return false
|
15
29
|
}
|
16
30
|
|
17
31
|
connect() {
|
18
|
-
if (this.
|
19
|
-
this.collapsed = true
|
20
|
-
this.markCollapsed()
|
21
|
-
} else if (isNull(this.getState()) && this.defaultState()) {
|
32
|
+
if (this.initiallyCollapsed) {
|
22
33
|
this.collapsed = true
|
23
34
|
this.markCollapsed()
|
35
|
+
} else {
|
36
|
+
this.collapsed = false
|
37
|
+
this.markExpanded()
|
24
38
|
}
|
25
39
|
}
|
26
40
|
|
27
41
|
triggerCollapse() {
|
28
42
|
this.collapsed = !this.collapsed
|
43
|
+
this.userState = this.collapsed ? 'collapsed' : 'expanded'
|
29
44
|
|
30
45
|
this.updateDom()
|
31
46
|
}
|
@@ -41,20 +56,10 @@ export default class extends Controller {
|
|
41
56
|
markCollapsed() {
|
42
57
|
this.svgTarget.classList.add('rotate-90')
|
43
58
|
this.itemsTarget.classList.add('hidden')
|
44
|
-
this.storeState('collapsed')
|
45
59
|
}
|
46
60
|
|
47
61
|
markExpanded() {
|
48
62
|
this.svgTarget.classList.remove('rotate-90')
|
49
63
|
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
64
|
}
|
60
65
|
}
|
@@ -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)
|
@@ -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
|
data/lib/avo/licensing/h_q.rb
CHANGED
@@ -13,13 +13,62 @@ module Avo
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def response
|
16
|
-
|
16
|
+
make_request
|
17
|
+
end
|
18
|
+
|
19
|
+
def fresh_response
|
20
|
+
make_request(fresh: true)
|
21
|
+
end
|
22
|
+
|
23
|
+
def payload
|
24
|
+
{
|
25
|
+
license: Avo.configuration.license,
|
26
|
+
license_key: Avo.configuration.license_key,
|
27
|
+
avo_version: Avo::VERSION,
|
28
|
+
rails_version: Rails::VERSION::STRING,
|
29
|
+
ruby_version: RUBY_VERSION,
|
30
|
+
environment: Rails.env,
|
31
|
+
ip: current_request.ip,
|
32
|
+
host: current_request.host,
|
33
|
+
port: current_request.port,
|
34
|
+
app_name: app_name
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def avo_metadata
|
39
|
+
resources = App.resources
|
40
|
+
field_definitions = resources.map(&:get_field_definitions)
|
41
|
+
fields_count = field_definitions.map(&:count).sum
|
42
|
+
fields_per_resource = sprintf("%0.01f", fields_count / (resources.count + 0.0))
|
43
|
+
|
44
|
+
field_types = {}
|
45
|
+
custom_fields_count = 0
|
46
|
+
field_definitions.each do |fields|
|
47
|
+
fields.each do |field|
|
48
|
+
field_types[field.type] ||= 0
|
49
|
+
field_types[field.type] += 1
|
50
|
+
|
51
|
+
custom_fields_count += 1 if field.custom?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
{
|
56
|
+
resources_count: resources.count,
|
57
|
+
fields_count: fields_count,
|
58
|
+
fields_per_resource: fields_per_resource,
|
59
|
+
custom_fields_count: custom_fields_count,
|
60
|
+
field_types: field_types,
|
61
|
+
**other_metadata(:actions),
|
62
|
+
**other_metadata(:filters),
|
63
|
+
main_menu_present: Avo::App.main_menu.present?,
|
64
|
+
profile_menu_present: Avo::App.profile_menu.present?,
|
65
|
+
}
|
17
66
|
end
|
18
67
|
|
19
68
|
private
|
20
69
|
|
21
|
-
def
|
22
|
-
return cached_response if has_cached_response
|
70
|
+
def make_request(fresh: false)
|
71
|
+
return cached_response if has_cached_response && !fresh
|
23
72
|
|
24
73
|
begin
|
25
74
|
perform_and_cache_request
|
@@ -51,13 +100,12 @@ module Avo
|
|
51
100
|
def cache_response(time, response)
|
52
101
|
response.merge!(
|
53
102
|
expiry: time,
|
103
|
+
fetched_at: Time.now,
|
54
104
|
**payload
|
55
105
|
).stringify_keys!
|
56
106
|
|
57
107
|
@cache_store.write(CACHE_KEY, response, expires_in: time)
|
58
108
|
|
59
|
-
@hq_response = response
|
60
|
-
|
61
109
|
response
|
62
110
|
end
|
63
111
|
|
@@ -67,55 +115,12 @@ module Avo
|
|
67
115
|
HTTParty.post ENDPOINT, body: payload.to_json, headers: {'Content-type': "application/json"}, timeout: REQUEST_TIMEOUT
|
68
116
|
end
|
69
117
|
|
70
|
-
def payload
|
71
|
-
{
|
72
|
-
license: Avo.configuration.license,
|
73
|
-
license_key: Avo.configuration.license_key,
|
74
|
-
avo_version: Avo::VERSION,
|
75
|
-
rails_version: Rails::VERSION::STRING,
|
76
|
-
ruby_version: RUBY_VERSION,
|
77
|
-
environment: Rails.env,
|
78
|
-
ip: current_request.ip,
|
79
|
-
host: current_request.host,
|
80
|
-
port: current_request.port,
|
81
|
-
app_name: app_name
|
82
|
-
}
|
83
|
-
end
|
84
|
-
|
85
118
|
def app_name
|
86
119
|
Rails.application.class.to_s.split("::").first
|
87
120
|
rescue
|
88
121
|
nil
|
89
122
|
end
|
90
123
|
|
91
|
-
def avo_metadata
|
92
|
-
resources = App.resources
|
93
|
-
field_definitions = resources.map(&:get_field_definitions)
|
94
|
-
fields_count = field_definitions.map(&:count).sum
|
95
|
-
fields_per_resource = sprintf("%0.01f", fields_count / (resources.count + 0.0))
|
96
|
-
|
97
|
-
field_types = {}
|
98
|
-
custom_fields_count = 0
|
99
|
-
field_definitions.each do |fields|
|
100
|
-
fields.each do |field|
|
101
|
-
field_types[field.type] ||= 0
|
102
|
-
field_types[field.type] += 1
|
103
|
-
|
104
|
-
custom_fields_count += 1 if field.custom?
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
{
|
109
|
-
resources_count: resources.count,
|
110
|
-
fields_count: fields_count,
|
111
|
-
fields_per_resource: fields_per_resource,
|
112
|
-
custom_fields_count: custom_fields_count,
|
113
|
-
field_types: field_types,
|
114
|
-
**other_metadata(:actions),
|
115
|
-
**other_metadata(:filters),
|
116
|
-
}
|
117
|
-
end
|
118
|
-
|
119
124
|
def other_metadata(type = :actions)
|
120
125
|
resources = App.resources
|
121
126
|
|