avo 2.1.0 → 2.1.2.pre2
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 +1 -1
- data/app/assets/builds/avo.css +8810 -0
- data/app/assets/builds/avo.js +423 -0
- data/app/assets/builds/avo.js.map +7 -0
- data/app/assets/stylesheets/avo.css +1 -1
- data/app/components/avo/alert_component.html.erb +1 -1
- data/app/components/avo/alert_component.rb +1 -1
- data/app/components/avo/card_component.html.erb +7 -2
- data/app/components/avo/filters_component.html.erb +19 -20
- data/app/components/avo/panel_component.html.erb +5 -3
- data/app/components/avo/sidebar_component.html.erb +2 -0
- data/app/components/avo/sidebar_profile_component.html.erb +14 -10
- data/app/components/avo/views/resource_show_component.html.erb +10 -0
- data/app/components/avo/views/resource_show_component.rb +5 -0
- data/app/controllers/avo/base_controller.rb +30 -10
- data/app/controllers/avo/dashboards_controller.rb +1 -5
- data/app/controllers/avo/search_controller.rb +1 -1
- data/app/helpers/avo/url_helpers.rb +6 -2
- data/app/views/avo/dashboards/show.html.erb +4 -1
- data/app/views/avo/partials/_sidebar_extra.html.erb +0 -0
- data/lib/avo/app.rb +7 -3
- data/lib/avo/base_action.rb +1 -0
- data/lib/avo/base_card.rb +175 -0
- data/lib/avo/base_resource.rb +12 -2
- data/lib/avo/dashboards/base_dashboard.rb +20 -2
- data/lib/avo/dashboards/base_divider.rb +3 -1
- data/lib/avo/dashboards/dashboard_card.rb +6 -0
- data/lib/avo/dashboards/partial_card.rb +1 -1
- data/lib/avo/dynamic_router.rb +1 -1
- data/lib/avo/engine.rb +7 -16
- data/lib/avo/fields/base_field.rb +2 -2
- data/lib/avo/fields/date_field.rb +1 -1
- data/lib/avo/fields_collector.rb +7 -2
- data/lib/avo/hosts/dashboard_card.rb +1 -0
- data/lib/avo/reloader.rb +51 -0
- data/lib/avo/version.rb +1 -1
- data/public/avo-assets/avo.css +18 -13
- metadata +11 -5
- data/lib/avo/dashboards/base_card.rb +0 -151
@@ -18,7 +18,7 @@ class Avo::AlertComponent < ViewComponent::Base
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def classes
|
21
|
-
result = "max-w-
|
21
|
+
result = "max-w-lg w-full shadow-lg rounded px-4 py-3 rounded relative border text-white pointer-events-auto"
|
22
22
|
|
23
23
|
result += if is_error?
|
24
24
|
" bg-red-400 border-red-700"
|
@@ -1,4 +1,9 @@
|
|
1
|
-
<div class="relative flex-1 flex flex-col justify-between h-full"
|
1
|
+
<div class="relative flex-1 flex flex-col justify-between h-full"
|
2
|
+
data-controller="dashboard-card"
|
3
|
+
data-dashboard-card-target="card"
|
4
|
+
data-refresh-every="<%= @card.refresh_every %>"
|
5
|
+
data-card-id="<%= @card.id %>"
|
6
|
+
data-card-index="<%= @card.index %>">
|
2
7
|
<% if @card.class.display_header %>
|
3
8
|
<div class="px-4 pt-4">
|
4
9
|
<div class="flex justify-between items-center min-h-6">
|
@@ -7,7 +12,7 @@
|
|
7
12
|
</div>
|
8
13
|
<div data-controller="select">
|
9
14
|
<% if @card.type == :metric && @card.parsed_ranges.present? %>
|
10
|
-
<%= select_tag "#{@card.id}_range", options_for_select(@card.parsed_ranges, @card.range),
|
15
|
+
<%= select_tag "#{@card.id}_#{@card.index}_range", options_for_select(@card.parsed_ranges, @card.range),
|
11
16
|
class: 'appearance-none inline-flex bg-blue-gray-100 disabled:bg-blue-gray-300 disabled:cursor-not-allowed focus:bg-white text-sm text-blue-gray-700 disabled:text-blue-gray-700 leading-none rounded-md py-px px-2 leading-tight border outline-none outline w-24',
|
12
17
|
data: {
|
13
18
|
target: 'select',
|
@@ -7,31 +7,30 @@
|
|
7
7
|
title: t('avo.click_to_reveal_filters'),
|
8
8
|
'data-button': 'resource-filters',
|
9
9
|
'data-action': 'click->toggle-panel#togglePanel',
|
10
|
-
'data-tippy': 'tooltip' do
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
'data-tippy': 'tooltip' do %>
|
11
|
+
<%= t 'avo.filters' %>
|
12
|
+
<% if params[:filters].present? %>
|
13
|
+
<span class="ml-1">(<%=JSON.parse(Base64.decode64(params[:filters])).count%> applied)</span>
|
14
|
+
<% end %>
|
15
15
|
<% end %>
|
16
|
-
|
17
|
-
<div
|
16
|
+
<div
|
18
17
|
class="absolute block inset-auto sm:right-0 top-full bg-white min-w-[300px] mt-2 z-20 shadow-modal rounded hidden divide-y divide-gray-300"
|
19
18
|
data-toggle-panel-target="panel"
|
20
19
|
>
|
21
|
-
|
22
|
-
|
23
|
-
<% end %>
|
24
|
-
<div class="p-4 border-gray-300 border-t">
|
25
|
-
<% if params[:filters].present? %>
|
26
|
-
<%= a_link helpers.resources_path(resource: @resource, filters: nil, keep_query_params: true), color: :gray, size: :sm, class: 'w-full justify-center' do %>
|
27
|
-
<%= t('avo.reset_filters') %>
|
28
|
-
<% end %>
|
29
|
-
<% else %>
|
30
|
-
<%= a_button class: 'w-full justify-center', disabled: true do %>
|
31
|
-
<%= t('avo.reset_filters') %>
|
32
|
-
<% end %>
|
20
|
+
<% @filters.each do |filter| %>
|
21
|
+
<%= render partial: filter.class.template, locals: {filter: filter} %>
|
33
22
|
<% end %>
|
23
|
+
<div class="p-4 border-gray-300 border-t">
|
24
|
+
<% if params[:filters].present? %>
|
25
|
+
<%= a_link helpers.resources_path(resource: @resource, filters: nil, keep_query_params: true), color: :gray, size: :sm, class: 'w-full justify-center' do %>
|
26
|
+
<%= t('avo.reset_filters') %>
|
27
|
+
<% end %>
|
28
|
+
<% else %>
|
29
|
+
<%= a_button class: 'w-full justify-center', disabled: true do %>
|
30
|
+
<%= t('avo.reset_filters') %>
|
31
|
+
<% end %>
|
32
|
+
<% end %>
|
33
|
+
</div>
|
34
34
|
</div>
|
35
35
|
</div>
|
36
36
|
</div>
|
37
|
-
</div>
|
@@ -19,9 +19,11 @@
|
|
19
19
|
<% end %>
|
20
20
|
</div>
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
<% if tools.present? %>
|
23
|
+
<div class="flex-1 w-full flex flex-col sm:flex-row xl:justify-end sm:items-end space-y-2 sm:space-y-0 sm:space-x-2 mt-4 xl:mt-0">
|
24
|
+
<%= tools %>
|
25
|
+
</div>
|
26
|
+
<% end %>
|
25
27
|
</div>
|
26
28
|
<% end %>
|
27
29
|
|
@@ -1,19 +1,23 @@
|
|
1
1
|
<div class="text-black border-t border-gray-200 p-4 flex">
|
2
2
|
<div class="flex-1 flex space-x-4">
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
<% if avatar.present? %>
|
4
|
+
<div class="relative aspect-square w-10 h-10 overflow-hidden rounded">
|
5
|
+
<%= image_tag avatar, class: "object-cover min-w-full min-h-full h-full" %>
|
6
|
+
</div>
|
7
|
+
<% end %>
|
6
8
|
<div class="flex flex-col">
|
7
9
|
<div class="font-medium">
|
8
10
|
<%= name %>
|
9
11
|
</div>
|
10
|
-
|
11
|
-
|
12
|
-
|
12
|
+
<% if title.present? %>
|
13
|
+
<div class="text-xs text-gray-500 uppercase">
|
14
|
+
<%= title %>
|
15
|
+
</div>
|
16
|
+
<% end %>
|
13
17
|
</div>
|
14
18
|
</div>
|
15
|
-
|
16
|
-
|
19
|
+
<% if can_destroy_user? %>
|
20
|
+
<div class="relative" data-controller="toggle-panel">
|
17
21
|
<a class="flex items-center h-full cursor-pointer" data-control="profile-dots" data-action="click->toggle-panel#togglePanel">
|
18
22
|
<%= helpers.svg 'three-dots', class: 'h-4' %>
|
19
23
|
</a>
|
@@ -31,6 +35,6 @@
|
|
31
35
|
<%= helpers.svg 'logout', class: 'h-4 mr-1' %> <%= t('avo.sign_out') %>
|
32
36
|
<% end %>
|
33
37
|
</div>
|
34
|
-
|
35
|
-
|
38
|
+
</div>
|
39
|
+
<% end %>
|
36
40
|
</div>
|
@@ -84,4 +84,14 @@
|
|
84
84
|
<% end %>
|
85
85
|
<% end %>
|
86
86
|
<% end %>
|
87
|
+
|
88
|
+
<% if should_display_invalid_fields_errors? %>
|
89
|
+
<turbo-stream action="append" target="alerts">
|
90
|
+
<template>
|
91
|
+
<% @resource.invalid_fields.each do |error| %>
|
92
|
+
<%= render Avo::AlertComponent.new :error, error[:message] %>
|
93
|
+
<% end %>
|
94
|
+
</template>
|
95
|
+
</turbo-stream>
|
96
|
+
<% end %>
|
87
97
|
</div>
|
@@ -90,4 +90,9 @@ class Avo::Views::ResourceShowComponent < Avo::ResourceComponent
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
end
|
93
|
+
|
94
|
+
# In development and test environments we shoudl show the invalid field errors
|
95
|
+
def should_display_invalid_fields_errors?
|
96
|
+
(Rails.env.development? || Rails.env.test?) && @resource.invalid_fields.present?
|
97
|
+
end
|
93
98
|
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("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
|
47
|
+
@query = @query.order(Arel.sql("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}"))
|
48
48
|
end
|
49
49
|
|
50
50
|
# Apply filters
|
@@ -128,14 +128,7 @@ module Avo
|
|
128
128
|
|
129
129
|
respond_to do |format|
|
130
130
|
if saved
|
131
|
-
|
132
|
-
|
133
|
-
if params[:via_relation_class].present? && params[:via_resource_id].present?
|
134
|
-
parent_resource = ::Avo::App.get_resource_by_model_name params[:via_relation_class].safe_constantize
|
135
|
-
redirect_path = resource_path(model: params[:via_relation_class].safe_constantize, resource: parent_resource, resource_id: params[:via_resource_id])
|
136
|
-
end
|
137
|
-
|
138
|
-
format.html { redirect_to redirect_path, notice: "#{@model.class.name} #{t("avo.was_successfully_created")}." }
|
131
|
+
format.html { redirect_to after_create_path, notice: "#{@model.class.name} #{t("avo.was_successfully_created")}." }
|
139
132
|
else
|
140
133
|
flash.now[:error] = t "avo.you_missed_something_check_form"
|
141
134
|
format.html { render :new, status: :unprocessable_entity }
|
@@ -153,7 +146,7 @@ module Avo
|
|
153
146
|
|
154
147
|
respond_to do |format|
|
155
148
|
if saved
|
156
|
-
format.html { redirect_to
|
149
|
+
format.html { redirect_to after_update_path, notice: "#{@model.class.name} #{t("avo.was_successfully_updated")}." }
|
157
150
|
else
|
158
151
|
flash.now[:error] = t "avo.you_missed_something_check_form"
|
159
152
|
format.html { render :edit, status: :unprocessable_entity }
|
@@ -357,5 +350,32 @@ module Avo
|
|
357
350
|
add_breadcrumb @resource.model_title, resource_path(model: @resource.model, resource: @resource)
|
358
351
|
add_breadcrumb t("avo.edit").humanize
|
359
352
|
end
|
353
|
+
|
354
|
+
def after_create_path
|
355
|
+
# If this is an associated record return to the association show page
|
356
|
+
if params[:via_relation_class].present? && params[:via_resource_id].present?
|
357
|
+
parent_resource = ::Avo::App.get_resource_by_model_name params[:via_relation_class].safe_constantize
|
358
|
+
|
359
|
+
return resource_path(model: params[:via_relation_class].safe_constantize, resource: parent_resource, resource_id: params[:via_resource_id])
|
360
|
+
end
|
361
|
+
|
362
|
+
redirect_path_from_resource_option || resource_path(model: @model, resource: @resource)
|
363
|
+
end
|
364
|
+
|
365
|
+
def after_update_path
|
366
|
+
return params[:referrer] if params[:referrer].present?
|
367
|
+
|
368
|
+
redirect_path_from_resource_option || resource_path(model: @model, resource: @resource)
|
369
|
+
end
|
370
|
+
|
371
|
+
def redirect_path_from_resource_option
|
372
|
+
return nil if @resource.class.after_update_path.blank?
|
373
|
+
|
374
|
+
if @resource.class.after_create_path == :index
|
375
|
+
resources_path(resource: @resource)
|
376
|
+
else
|
377
|
+
resource_path(model: @model, resource: @resource)
|
378
|
+
end
|
379
|
+
end
|
360
380
|
end
|
361
381
|
end
|
@@ -8,11 +8,7 @@ module Avo
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def card
|
11
|
-
@card = @dashboard.
|
12
|
-
next unless item.is_card?
|
13
|
-
|
14
|
-
item.id.to_s == params[:card_id]
|
15
|
-
end.tap do |card|
|
11
|
+
@card = @dashboard.item_at_index(params[:index].to_i).tap do |card|
|
16
12
|
card.hydrate(dashboard: @dashboard, params: params)
|
17
13
|
end
|
18
14
|
end
|
@@ -11,7 +11,7 @@ module Avo
|
|
11
11
|
raise ActionController::BadRequest.new("This feature requires the pro license https://avohq.io/purchase/pro") if App.license.lacks_with_trial(:global_search)
|
12
12
|
|
13
13
|
resources = Avo::App.resources.reject do |resource|
|
14
|
-
resource.class.
|
14
|
+
resource.class.hide_from_global_search
|
15
15
|
end
|
16
16
|
|
17
17
|
render json: search_resources(resources)
|
@@ -12,8 +12,12 @@ module Avo
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
#
|
16
|
-
|
15
|
+
# Create the `route_key` from the model key so the namespaced models get the proper path (SomeModule::Post -> some_module_post).
|
16
|
+
# Add the `_index` suffix for the uncountable models so they get the correct path (`fish_index`)
|
17
|
+
route_key = resource.model_key
|
18
|
+
route_key << "_index" if resource.model_name.singular == resource.model_name.plural
|
19
|
+
|
20
|
+
avo.send :"resources_#{route_key}_path", **existing_params, **args
|
17
21
|
end
|
18
22
|
|
19
23
|
def resource_path(
|
@@ -8,7 +8,10 @@
|
|
8
8
|
<%= render Avo::Dashboards::DividerComponent.new(divider: item) %>
|
9
9
|
<% elsif item.is_card? %>
|
10
10
|
<%= content_tag(:div, class: "relative bg-white rounded shadow-panel space-y-2 #{item.card_classes} overflow-hidden") do %>
|
11
|
-
<turbo-frame id="<%= item.turbo_frame %>"
|
11
|
+
<turbo-frame id="<%= item.turbo_frame %>"
|
12
|
+
src="<%= item.frame_url(enforced_range: @range, params: params) %>"
|
13
|
+
data-card-index="<%= item.index %>"
|
14
|
+
>
|
12
15
|
<%= render(Avo::LoadingComponent.new(title: item.label)) %>
|
13
16
|
</turbo-frame>
|
14
17
|
<% end %>
|
File without changes
|
data/lib/avo/app.rb
CHANGED
@@ -79,12 +79,16 @@ module Avo
|
|
79
79
|
def init_resources
|
80
80
|
self.resources = BaseResource.descendants
|
81
81
|
.select do |resource|
|
82
|
+
# Remove the BaseResource. We only need the descendants
|
82
83
|
resource != BaseResource
|
83
84
|
end
|
85
|
+
.uniq do |klass|
|
86
|
+
# On invalid resource configuration the resource classes get duplicated in `ObjectSpace`
|
87
|
+
# We need to de-duplicate them
|
88
|
+
klass.name
|
89
|
+
end
|
84
90
|
.map do |resource|
|
85
|
-
if resource.is_a? Class
|
86
|
-
resource.new
|
87
|
-
end
|
91
|
+
resource.new if resource.is_a? Class
|
88
92
|
end
|
89
93
|
end
|
90
94
|
|
data/lib/avo/base_action.rb
CHANGED
@@ -0,0 +1,175 @@
|
|
1
|
+
module Avo
|
2
|
+
class BaseCard
|
3
|
+
class_attribute :id
|
4
|
+
class_attribute :label
|
5
|
+
class_attribute :description
|
6
|
+
class_attribute :cols, default: 1
|
7
|
+
class_attribute :rows, default: 1
|
8
|
+
class_attribute :initial_range
|
9
|
+
class_attribute :ranges, default: []
|
10
|
+
class_attribute :refresh_every
|
11
|
+
class_attribute :display_header, default: true
|
12
|
+
# private
|
13
|
+
class_attribute :result_data
|
14
|
+
class_attribute :query_block
|
15
|
+
|
16
|
+
attr_accessor :dashboard
|
17
|
+
attr_accessor :options
|
18
|
+
attr_accessor :index
|
19
|
+
attr_accessor :params
|
20
|
+
|
21
|
+
delegate :context, to: ::Avo::App
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def query(&block)
|
25
|
+
self.query_block = block
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(dashboard:, options: {}, index: 0, cols: nil, rows: nil, label: nil, description: nil, refresh_every: nil)
|
30
|
+
@dashboard = dashboard
|
31
|
+
@options = options
|
32
|
+
@index = index
|
33
|
+
@cols = cols
|
34
|
+
@rows = rows
|
35
|
+
@label = label
|
36
|
+
@refresh_every = refresh_every
|
37
|
+
@description = description
|
38
|
+
end
|
39
|
+
|
40
|
+
def label
|
41
|
+
return @label.to_s if @label.present?
|
42
|
+
return self.class.label.to_s if self.class.label.present?
|
43
|
+
|
44
|
+
self.class.id.to_s.humanize
|
45
|
+
end
|
46
|
+
|
47
|
+
def description
|
48
|
+
@description || self.class.description
|
49
|
+
end
|
50
|
+
|
51
|
+
def refresh_every
|
52
|
+
@refresh_every || self.class.refresh_every
|
53
|
+
end
|
54
|
+
|
55
|
+
def translated_range(range)
|
56
|
+
return "#{range} days" if range.is_a? Integer
|
57
|
+
|
58
|
+
case range
|
59
|
+
when "MTD"
|
60
|
+
"Month to date"
|
61
|
+
when "QTD"
|
62
|
+
"Quarter to date"
|
63
|
+
when "YTD"
|
64
|
+
"Year to date"
|
65
|
+
when "TODAY"
|
66
|
+
"Today"
|
67
|
+
else
|
68
|
+
range
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def parsed_ranges
|
73
|
+
return unless ranges.present?
|
74
|
+
|
75
|
+
ranges.map { |range| [translated_range(range), range] }
|
76
|
+
end
|
77
|
+
|
78
|
+
def turbo_frame
|
79
|
+
"#{dashboard.id}_#{id}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def frame_url(enforced_range: nil, params: {})
|
83
|
+
enforced_range ||= initial_range || ranges.first
|
84
|
+
|
85
|
+
# append the parent params to the card request
|
86
|
+
begin
|
87
|
+
other_params = "&#{params.permit!.to_h.map { |k, v| "#{k}=#{v}" }.join("&")}"
|
88
|
+
rescue
|
89
|
+
end
|
90
|
+
|
91
|
+
"#{Avo::App.root_path}/dashboards/#{dashboard.id}/cards/#{id}?turbo_frame=#{turbo_frame}&index=#{index}&range=#{enforced_range}#{other_params}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def card_classes
|
95
|
+
result = ""
|
96
|
+
|
97
|
+
# Writing down the classes so TailwindCSS knows not to purge them
|
98
|
+
classes_for_cols = {
|
99
|
+
1 => " sm:col-span-1",
|
100
|
+
2 => " sm:col-span-2",
|
101
|
+
3 => " sm:col-span-3",
|
102
|
+
4 => " sm:col-span-4",
|
103
|
+
5 => " sm:col-span-5",
|
104
|
+
6 => " sm:col-span-6"
|
105
|
+
}
|
106
|
+
|
107
|
+
classes_for_rows = {
|
108
|
+
1 => " h-36",
|
109
|
+
2 => " h-72",
|
110
|
+
3 => " h-[27rem]",
|
111
|
+
4 => " h-[36rem]",
|
112
|
+
5 => " h-[45rem]",
|
113
|
+
6 => " h-[54rem]"
|
114
|
+
}
|
115
|
+
# puts ["cols->", cols, classes_for_cols, classes_for_rows, classes_for_cols[cols.to_i]].inspect
|
116
|
+
|
117
|
+
result += classes_for_cols[cols.to_i] if classes_for_cols[cols.to_i].present?
|
118
|
+
result += classes_for_rows[rows.to_i] if classes_for_rows[rows.to_i].present?
|
119
|
+
|
120
|
+
result
|
121
|
+
end
|
122
|
+
|
123
|
+
def type
|
124
|
+
return :metric if self.class.superclass == ::Avo::Dashboards::MetricCard
|
125
|
+
return :chartkick if self.class.superclass == ::Avo::Dashboards::ChartkickCard
|
126
|
+
return :partial if self.class.superclass == ::Avo::Dashboards::PartialCard
|
127
|
+
end
|
128
|
+
|
129
|
+
def compute_result
|
130
|
+
Avo::Hosts::DashboardCard.new(card: self, dashboard: dashboard, params: params, context: context, range: range, options: options)
|
131
|
+
.compute_result
|
132
|
+
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
def hydrate(dashboard: nil, params: nil)
|
137
|
+
@dashboard = dashboard if dashboard.present?
|
138
|
+
@params = params if params.present?
|
139
|
+
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
def range
|
144
|
+
return params[:range] if params.dig(:range).present?
|
145
|
+
|
146
|
+
return initial_range if initial_range.present?
|
147
|
+
|
148
|
+
ranges.first
|
149
|
+
end
|
150
|
+
|
151
|
+
def result(data)
|
152
|
+
self.result_data = data
|
153
|
+
|
154
|
+
self
|
155
|
+
end
|
156
|
+
|
157
|
+
def is_card?
|
158
|
+
true
|
159
|
+
end
|
160
|
+
|
161
|
+
def is_divider?
|
162
|
+
false
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def cols
|
168
|
+
@cols || self.class.cols
|
169
|
+
end
|
170
|
+
|
171
|
+
def rows
|
172
|
+
@rows || self.class.rows
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
data/lib/avo/base_resource.rb
CHANGED
@@ -39,7 +39,10 @@ module Avo
|
|
39
39
|
class_attribute :resolve_query_scope
|
40
40
|
class_attribute :resolve_find_scope
|
41
41
|
class_attribute :ordering
|
42
|
-
class_attribute :
|
42
|
+
class_attribute :hide_from_global_search, default: false
|
43
|
+
class_attribute :after_create_path, default: :show
|
44
|
+
class_attribute :after_update_path, default: :show
|
45
|
+
class_attribute :invalid_fields
|
43
46
|
|
44
47
|
class << self
|
45
48
|
delegate :t, to: ::I18n
|
@@ -282,6 +285,9 @@ module Avo
|
|
282
285
|
end
|
283
286
|
|
284
287
|
def name
|
288
|
+
# return 'hwhwhw'
|
289
|
+
|
290
|
+
|
285
291
|
default = class_name_without_resource.titlecase
|
286
292
|
|
287
293
|
return @name if @name.present?
|
@@ -427,11 +433,15 @@ module Avo
|
|
427
433
|
# This is used as the model class ID
|
428
434
|
# We use this instead of the route_key to maintain compatibility with uncountable models
|
429
435
|
# With uncountable models route key appends an _index suffix (Fish->fish_index)
|
430
|
-
# Example: User->users, MediaItem->
|
436
|
+
# Example: User->users, MediaItem->media_items, Fish->fish
|
431
437
|
def model_key
|
432
438
|
model_class.model_name.plural
|
433
439
|
end
|
434
440
|
|
441
|
+
def model_name
|
442
|
+
model_class.model_name
|
443
|
+
end
|
444
|
+
|
435
445
|
def singular_model_key
|
436
446
|
model_class.model_name.singular
|
437
447
|
end
|
@@ -9,12 +9,30 @@ module Avo
|
|
9
9
|
class_attribute :items_holder
|
10
10
|
class_attribute :grid_cols, default: 3
|
11
11
|
class_attribute :visible, default: true
|
12
|
+
class_attribute :index, default: 0
|
12
13
|
|
13
14
|
class << self
|
14
|
-
def card(klass)
|
15
|
+
def card(klass, label: nil, description: nil, cols: nil, rows: nil, refresh_every: nil, options: {})
|
15
16
|
self.items_holder ||= []
|
16
17
|
|
17
|
-
self.items_holder << klass.new(dashboard: self
|
18
|
+
self.items_holder << klass.new(dashboard: self,
|
19
|
+
label: label,
|
20
|
+
description: description,
|
21
|
+
cols: cols,
|
22
|
+
rows: rows,
|
23
|
+
refresh_every: refresh_every,
|
24
|
+
options: options,
|
25
|
+
index: index
|
26
|
+
)
|
27
|
+
self.index += 1
|
28
|
+
end
|
29
|
+
|
30
|
+
def item_at_index(index)
|
31
|
+
items.find do |item|
|
32
|
+
next if item.index.blank?
|
33
|
+
|
34
|
+
item.index == index
|
35
|
+
end
|
18
36
|
end
|
19
37
|
|
20
38
|
def divider(**args)
|
@@ -3,12 +3,14 @@ module Avo
|
|
3
3
|
class BaseDivider
|
4
4
|
attr_reader :label
|
5
5
|
attr_reader :invisible
|
6
|
+
attr_reader :index
|
6
7
|
|
7
8
|
class_attribute :id
|
8
9
|
|
9
|
-
def initialize(label: nil, invisible: false)
|
10
|
+
def initialize(label: nil, invisible: false, index: nil)
|
10
11
|
@label = label
|
11
12
|
@invisible = invisible
|
13
|
+
@index = index
|
12
14
|
end
|
13
15
|
|
14
16
|
def is_divider?
|