ab_admin 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/ab_admin/components/in_place_edit.js.coffee +28 -24
  3. data/app/assets/javascripts/ab_admin/core/batch_actions.js.coffee +1 -1
  4. data/app/assets/javascripts/ab_admin/core/init.js.coffee +1 -0
  5. data/app/assets/javascripts/ab_admin/core/ui_utils.js.coffee +21 -1
  6. data/app/assets/javascripts/ab_admin/core/utils.js.coffee +8 -0
  7. data/app/assets/javascripts/ab_admin/inputs/datetime_input.js.coffee +1 -0
  8. data/app/assets/javascripts/ab_admin/main.js +1 -2
  9. data/app/assets/stylesheets/ab_admin/bootstrap_and_overrides.scss +44 -23
  10. data/app/assets/stylesheets/ab_admin/components/_colored_tabs.scss +1 -1
  11. data/app/assets/stylesheets/ab_admin/components/_form.scss +3 -1
  12. data/app/assets/stylesheets/ab_admin/components/_navigation.scss +7 -2
  13. data/app/assets/stylesheets/ab_admin/components/_table_view.scss +75 -9
  14. data/app/assets/stylesheets/ab_admin/components/_tooltip.scss +1 -0
  15. data/app/assets/stylesheets/ab_admin/main.scss +1 -1
  16. data/app/controllers/admin/assets_controller.rb +6 -2
  17. data/app/controllers/admin/base_controller.rb +87 -107
  18. data/app/controllers/admin/manager_controller.rb +17 -47
  19. data/app/views/ab_admin/devise/sessions/new.html.slim +2 -0
  20. data/app/views/admin/assets/batch_edit.html.slim +2 -1
  21. data/app/views/admin/base/_search_layout.html.slim +1 -1
  22. data/app/views/admin/base/create.js.erb +1 -1
  23. data/app/views/admin/base/edit.js.erb +1 -1
  24. data/app/views/admin/base/new.js.erb +1 -1
  25. data/app/views/admin/base/update.js.erb +3 -3
  26. data/app/views/admin/manager/_show_table.html.slim +1 -1
  27. data/app/views/admin/manager/_stats.html.slim +4 -0
  28. data/app/views/admin/manager/_table.html.slim +6 -3
  29. data/app/views/admin/shared/_content_actions.html.slim +22 -15
  30. data/app/views/admin/shared/_save_buttons.html.slim +10 -1
  31. data/app/views/admin/users/_form.html.slim +2 -2
  32. data/config/locales/en.yml +8 -13
  33. data/config/locales/it.yml +1 -0
  34. data/db/migrate/20130101000001_create_users.rb +1 -4
  35. data/db/migrate/20130101000003_create_assets.rb +1 -1
  36. data/db/migrate/20130101000004_create_headers.rb +5 -5
  37. data/db/migrate/20130101000005_create_static_pages.rb +2 -5
  38. data/db/migrate/20130101000006_create_structures.rb +1 -1
  39. data/db/migrate/20130101000007_base_translations.rb +43 -12
  40. data/db/migrate/20130101000008_create_admin_comments.rb +2 -7
  41. data/db/migrate/20130101000009_create_tracks.rb +4 -8
  42. data/lib/ab_admin/abstract_resource.rb +6 -5
  43. data/lib/ab_admin/carrierwave/base_uploader.rb +87 -74
  44. data/lib/ab_admin/carrierwave/glue.rb +0 -5
  45. data/lib/ab_admin/concerns/admin_addition.rb +19 -1
  46. data/lib/ab_admin/concerns/utilities.rb +1 -1
  47. data/lib/ab_admin/config/base.rb +20 -4
  48. data/lib/ab_admin/core_ext/array.rb +0 -5
  49. data/lib/ab_admin/core_ext/string.rb +1 -6
  50. data/lib/ab_admin/devise.rb +7 -0
  51. data/lib/ab_admin/engine.rb +1 -1
  52. data/lib/ab_admin/hooks/ckeditor_lazy.rb +13 -0
  53. data/lib/ab_admin/menu/base_group.rb +0 -1
  54. data/lib/ab_admin/menu/group.rb +2 -4
  55. data/lib/ab_admin/menu/item.rb +4 -8
  56. data/lib/ab_admin/models/asset.rb +7 -10
  57. data/lib/ab_admin/models/locator.rb +1 -1
  58. data/lib/ab_admin/models/settings.rb +2 -2
  59. data/lib/ab_admin/models/structure.rb +3 -3
  60. data/lib/ab_admin/models/track.rb +17 -5
  61. data/lib/ab_admin/utils/csv_document.rb +4 -4
  62. data/lib/ab_admin/utils/eval_helpers.rb +0 -16
  63. data/lib/ab_admin/utils/logger.rb +12 -2
  64. data/lib/ab_admin/utils/mysql.rb +2 -3
  65. data/lib/ab_admin/utils/xls_document.rb +1 -3
  66. data/lib/ab_admin/utils.rb +0 -5
  67. data/lib/ab_admin/version.rb +1 -1
  68. data/lib/ab_admin/views/admin_helpers.rb +49 -33
  69. data/lib/ab_admin/views/admin_navigation_helpers.rb +12 -9
  70. data/lib/ab_admin/views/inputs/ckeditor_input.rb +1 -5
  71. data/lib/ab_admin/views/manager_helpers.rb +9 -3
  72. data/lib/ab_admin/views/search_form_builder.rb +12 -12
  73. data/lib/ab_admin.rb +13 -2
  74. data/lib/generators/ab_admin/install/templates/models/user.rb +1 -2
  75. data/lib/generators/ab_admin/install/templates/uploaders/attachment_file_uploader.rb +1 -1
  76. data/lib/generators/ab_admin/install/templates/uploaders/avatar_uploader.rb +1 -1
  77. data/lib/generators/ab_admin/install/templates/uploaders/picture_uploader.rb +16 -3
  78. data/lib/generators/ab_admin/resource/resource_generator.rb +0 -4
  79. data/lib/generators/ab_admin/resource/templates/controller.erb +0 -7
  80. data/lib/tasks/assets.rake +5 -5
  81. metadata +24 -22
@@ -1,44 +1,39 @@
1
1
  class Admin::BaseController < ::InheritedResources::Base
2
- use Rack::Pjax, only: :index
3
-
4
- layout :set_layout
5
-
6
2
  include AbAdmin::Controllers::Fv
7
3
  include AbAdmin::Controllers::Callbacks
8
4
 
9
- define_admin_callbacks :save, :create
5
+ class_attribute :export_builder, :batch_actions, :button_scopes, instance_reader: true, instance_writer: false
6
+ attr_reader :settings
10
7
 
11
- before_action :authenticate_user!, :require_admin_access, :build_settings, :set_user_vars
8
+ before_action :authenticate_user!, :require_admin_access, :manager, :build_settings, :set_user_vars
12
9
  before_action :add_breadcrumbs, :set_title, unless: :xhr?
10
+ before_action :preflight_batch_action, only: :batch
13
11
 
14
- class_attribute :export_builder, :batch_action_list, :button_scopes, instance_reader: false, instance_writer: false
15
-
16
- defaults finder: :friendly_find
17
-
18
- has_scope :by_ids, type: :array
19
-
20
- helper_method :admin?, :moderator?
21
-
22
- attr_reader :settings
23
- helper_method :button_scopes, :collection_action?, :action_items, :resource_action_items, :query_params,
24
- :preview_resource_path, :get_subject, :settings, :batch_action_list, :tree_node_renderer,
12
+ helper_method :admin?, :moderator?, :button_scopes, :collection_action?, :action_items, :resource_action_items, :query_params,
13
+ :settings, :batch_actions, :tree_node_renderer,
25
14
  :pjax?, :xhr?, :params_for_links, :resource_list_id, :ransack_collection, :search_collection
26
15
 
16
+ define_admin_callbacks :save, :create
17
+ use Rack::Pjax, only: :index
18
+ defaults finder: :friendly_find
27
19
  rescue_from ::CanCan::AccessDenied, with: :render_unauthorized
20
+ layout :set_layout
28
21
 
29
22
  def index
30
23
  super do |format|
31
24
  format.js { render layout: false }
32
- format.csv do
33
- authorize! :export, resource_class
34
- doc = AbAdmin::Utils::CsvDocument.new(collection, export_options)
35
- send_data(doc.render(self, locale: params[:locale]), filename: doc.filename, type: Mime[:csv], disposition: 'attachment')
36
- end
37
- if Mime[:xlsx]
38
- format.xlsx do
25
+ if settings[:export]
26
+ format.csv do
39
27
  authorize! :export, resource_class
40
- doc = AbAdmin::Utils::XlsDocument.new(collection, export_options)
41
- send_data(doc.render(self, locale: params[:locale]), filename: doc.filename, type: Mime[:xlsx], disposition: 'attachment')
28
+ doc = AbAdmin::Utils::CsvDocument.new(collection, export_options)
29
+ send_data(doc.render(self, locale: params[:locale]), filename: doc.filename, type: Mime[:csv], disposition: 'attachment')
30
+ end
31
+ if Mime[:xlsx]
32
+ format.xlsx do
33
+ authorize! :export, resource_class
34
+ doc = AbAdmin::Utils::XlsDocument.new(collection, export_options)
35
+ send_data(doc.render(self, locale: params[:locale]), filename: doc.filename, type: Mime[:xlsx], disposition: 'attachment')
36
+ end
42
37
  end
43
38
  end
44
39
  end
@@ -49,10 +44,15 @@ class Admin::BaseController < ::InheritedResources::Base
49
44
  success.html { redirect_to redirect_to_on_success }
50
45
  success.js { render layout: false }
51
46
  failure.js { render :new, layout: false }
47
+ unless respond_to_format?(:json)
48
+ success.json { head :no_content }
49
+ failure.json { render json: {errors: resource.errors}, status: :unprocessable_entity }
50
+ end
52
51
  end
53
52
  end
54
53
 
55
54
  def update
55
+ resource.last_updated_timestamp = params[:last_updated_timestamp].to_i if settings[:safe_save] && !params[:_force_save] && params[:last_updated_timestamp]
56
56
  update! do |success, failure|
57
57
  success.html { redirect_to redirect_to_on_success }
58
58
  failure.html { render :edit }
@@ -60,7 +60,7 @@ class Admin::BaseController < ::InheritedResources::Base
60
60
  failure.js { render :edit, layout: false }
61
61
  unless respond_to_format?(:json)
62
62
  success.json { head :no_content }
63
- failure.json { head :unprocessable }
63
+ failure.json { render json: {errors: resource.errors}, status: :unprocessable_entity }
64
64
  end
65
65
  end
66
66
  end
@@ -91,32 +91,26 @@ class Admin::BaseController < ::InheritedResources::Base
91
91
  end
92
92
 
93
93
  def batch
94
- raise 'No ids specified for batch action' unless params[:by_ids].present?
95
- batch_action = params[:batch_action].to_sym
96
- if allow_batch_action?(batch_action) && collection.all?{|item| can?(batch_action, item) }
97
- if batch_action.to_s.end_with?('_collection')
98
- count = collection.size
99
- resource_class.public_send(batch_action, collection, *[params[:batch_params]].compact)
100
- if @settings[:history]
101
- if Object.const_defined?('ActiveRecord::Import') && Track.respond_to?(:import)
102
- tracks = collection.map { |item| track_action("batch_#{batch_action}", item) }
103
- Track.import_from_batch_collection_action(tracks)
104
- else
105
- collection.each { |item| track_action!("batch_#{batch_action}", item) }
106
- end
107
- end
108
- else
109
- count = collection.inject(0) { |c, item| apply_batch_action(item, batch_action, *[params[:batch_params]].compact) ? c + 1 : c }
110
- end
111
- batch_action_name = I18n.t("admin.actions.batch_#{batch_action}.title", default: batch_action.to_s.humanize)
112
- flash[:success] = I18n.t('admin.batch_actions.status', count: count, action: batch_action_name)
113
- else
114
- raise CanCan::AccessDenied
115
- end
94
+ batch_action = batch_actions.detect{|a| a.name == params[:batch_action].to_sym }
95
+ count = collection.inject(0) { |c, item| apply_batch_action(item, batch_action, *params[:batch_params].presence) ? c + 1 : c }
96
+ flash[:success] = I18n.t('admin.batch_actions.status', count: count, action: batch_action.title)
116
97
  redirect_to_back_or_root
117
98
  end
118
99
 
119
- protected
100
+ private
101
+
102
+ def manager
103
+ end
104
+
105
+ def set_layout
106
+ pjax? ? false : 'admin/application'
107
+ end
108
+
109
+ def preflight_batch_action
110
+ batch_action = params[:batch_action].to_sym
111
+ head :unprocessable_entity unless allow_batch_action?(batch_action) && params[:q].try!(:[], :id_in).present?
112
+ raise CanCan::AccessDenied unless collection.all?{|item| can?(batch_action, item) }
113
+ end
120
114
 
121
115
  def build_resource_params
122
116
  permitted_params || params[resource_class.model_name.param_key]
@@ -133,22 +127,26 @@ class Admin::BaseController < ::InheritedResources::Base
133
127
  def default_url_options
134
128
  options = {format: nil}
135
129
  options.update instance_exec(&AbAdmin.default_url_options) if AbAdmin.default_url_options
136
- options.update instance_exec(&@settings[:default_url_options]) if @settings[:default_url_options]
130
+ options.update instance_exec(&@settings[:default_url_options]) if @settings && @settings[:default_url_options]
137
131
  options
138
132
  end
139
133
 
134
+ def self.batch_action(name, options={}, &block)
135
+ if options
136
+ self.batch_actions << AbAdmin::Config::BatchAction.new(name.to_sym, options, &block)
137
+ else
138
+ self.batch_actions.reject!{|a| a.name == name.to_sym }
139
+ end
140
+ end
141
+
140
142
  def apply_batch_action(item, batch_action, *batch_params)
141
- success = item.send(batch_action, *batch_params)
142
- track_action!("batch_#{batch_action}", item) if @settings[:history]
143
+ success = batch_action.data.is_a?(Symbol) ? item.public_send(batch_action.data, *batch_params) : batch_action.data.call(item)
144
+ track_action!("batch_#{batch_action.name}", item) if settings[:history]
143
145
  success
144
146
  end
145
147
 
146
148
  def allow_batch_action?(batch_action)
147
- batch_action_list.detect { |a| a.name == batch_action }
148
- end
149
-
150
- def redirect_to_back_or_root
151
- redirect_back fallback_location: admin_root_path
149
+ batch_actions.detect { |a| a.name == batch_action }
152
150
  end
153
151
 
154
152
  def track_action(key=nil, item=nil)
@@ -159,19 +157,6 @@ class Admin::BaseController < ::InheritedResources::Base
159
157
  track_action(*args).save!
160
158
  end
161
159
 
162
- def batch_action_list
163
- self.class.batch_action_list ||= begin
164
- resource_class.batch_actions.map do |a|
165
- opts = a == :destroy ? {confirm: I18n.t('admin.delete_confirmation')} : {}
166
- if a.is_a?(Hash)
167
- opts.merge!(a.except(:name))
168
- a = a[:name]
169
- end
170
- AbAdmin::Config::BatchAction.new(a, opts)
171
- end
172
- end
173
- end
174
-
175
160
  def self.inherited(base)
176
161
  super
177
162
  base.class_eval do
@@ -179,6 +164,8 @@ class Admin::BaseController < ::InheritedResources::Base
179
164
  before_save :bind_current_updater
180
165
  before_save :track_current_action
181
166
  end
167
+ base.button_scopes = []
168
+ base.batch_actions = [AbAdmin::Config::BatchAction.new(:destroy, confirm: I18n.t('admin.delete_confirmation'))]
182
169
  end
183
170
 
184
171
  def track_current_action(*)
@@ -202,10 +189,6 @@ class Admin::BaseController < ::InheritedResources::Base
202
189
  export_builder.render_options
203
190
  end
204
191
 
205
- def preview_resource_path(item)
206
- nil
207
- end
208
-
209
192
  def custom_settings
210
193
  {}
211
194
  end
@@ -219,7 +202,8 @@ class Admin::BaseController < ::InheritedResources::Base
219
202
  @settings[:per_page] ||= per_page
220
203
  @settings[:per_page_variants] ||= @settings[:per_page_variants].find_all{|n| n <= @settings[:max_per_page] }
221
204
  @settings[:sidebar] = true unless @settings.key?(:sidebar)
222
- @settings[:pagination] = @settings[:pagination_index_views].include?(@settings[:current_index_view])
205
+ @settings[:pagination] = @settings[:pagination_index_views].include?(@settings[:current_index_view]) unless @settings.key?(:pagination)
206
+ @settings[:order] = active_scopes.filter_map{|sc| sc.options[:default_order] }.first || @settings[:default_order] || ('id desc' unless @settings[:current_index_view] == :tree)
223
207
  end
224
208
  @settings[:well] = (collection_action? || %w(show history).include?(action_name)) && @settings[:current_index_view] != :tree unless @settings.key?(:well)
225
209
  end
@@ -243,19 +227,6 @@ class Admin::BaseController < ::InheritedResources::Base
243
227
  @settings[:collection_actions].include?(action_name)
244
228
  end
245
229
 
246
- def self.scope(name, options={}, &block)
247
- has_scope name, options.without(:badge, :if), &block
248
- options[:badge] = {} if options[:badge] && !options[:badge].is_a?(Hash)
249
- options[:block] = block
250
- self.button_scopes ||= []
251
- self.button_scopes << [name, options]
252
- end
253
-
254
- def button_scopes
255
- self.class.button_scopes ||= self.class.scopes_configuration.except(:by_ids).find_all{|_, s| s[:type] == :boolean }
256
- self.class.button_scopes.to_h
257
- end
258
-
259
230
  def add_breadcrumbs
260
231
  @breadcrumbs = []
261
232
  if parent?
@@ -306,18 +277,28 @@ class Admin::BaseController < ::InheritedResources::Base
306
277
  end
307
278
 
308
279
  def query_params
309
- query = params[:q].try! {|q| q.permit!.to_h} || {}
310
- nested = resource_class.respond_to?(:acts_as_nested_set_options) && @settings[:current_index_view] == :tree
311
- query[:s] ||= @settings[:default_order] || ('id desc' unless nested)
312
- query.reject_blank
280
+ @query_params ||= begin
281
+ query = params[:q].try! {|q| q.permit!.to_h} || {}
282
+ query[:s] ||= @settings[:order]
283
+ query.reverse_merge!(@settings[:default_q].to_h{ [_1.to_s, _2.to_s] }) if @settings[:default_q] && !params[:q]
284
+ query.reject_blank
285
+ end
286
+ end
287
+
288
+ def self.scope(name, options={}, &block)
289
+ self.button_scopes << ::AbAdmin::Config::Scope.new(name, options, &block)
290
+ end
291
+
292
+ def active_scopes
293
+ button_scopes.find_all{|scope| params[scope.name].present? }
313
294
  end
314
295
 
315
296
  def with_scopes(relation)
316
- relation
297
+ active_scopes.inject(relation) { |result, scope| scope.apply(self, result) }
317
298
  end
318
299
 
319
- def set_layout
320
- pjax? ? false : 'admin/application'
300
+ def redirect_to_back_or_root
301
+ redirect_back fallback_location: admin_root_path, turbolinks: false
321
302
  end
322
303
 
323
304
  def back_or_collection
@@ -382,7 +363,7 @@ class Admin::BaseController < ::InheritedResources::Base
382
363
  end
383
364
 
384
365
  def require_admin_access
385
- raise CanCan::AccessDenied unless current_user.admin_access?
366
+ raise CanCan::AccessDenied unless current_user&.admin_access?
386
367
  end
387
368
 
388
369
  def bind_current_user(*)
@@ -393,13 +374,14 @@ class Admin::BaseController < ::InheritedResources::Base
393
374
  resource.updater_id = current_user.id if !@settings[:skip_bind_current_updater] && resource.respond_to?(:updater_id)
394
375
  end
395
376
 
396
- def get_subject
397
- params[:id] ? resource : resource_class
377
+ def authenticate_user!
378
+ Rails.logger.debug "Redirect unauthorized user"
379
+ super
398
380
  end
399
381
 
400
382
  def render_unauthorized(exception)
401
- Rails.logger.debug "Access denied on #{exception.action} #{exception.subject.inspect}, user: #{current_user.try(:id)}"
402
-
383
+ Rails.logger.debug "Access denied on #{exception.action} #{exception.subject.inspect}, user: #{current_user&.id}"
384
+ render_not_found and return unless AbAdmin.render_unauthorized
403
385
  if pjax?
404
386
  render partial: 'admin/shared/flash', locals: {flash: {alert: exception.message}}
405
387
  elsif request.format.try(:html?)
@@ -409,11 +391,11 @@ class Admin::BaseController < ::InheritedResources::Base
409
391
  end
410
392
  end
411
393
 
412
- def default_serializer_options
413
- if resource_class
414
- {root: resource_class.model_name.plural}
394
+ def render_not_found
395
+ if request.format.try(:html?)
396
+ render file: Rails.root.join('public/404.html'), layout: false, status: :not_found
415
397
  else
416
- {root: false}
398
+ head :not_found
417
399
  end
418
400
  end
419
401
 
@@ -421,8 +403,6 @@ class Admin::BaseController < ::InheritedResources::Base
421
403
  "list_#{resource_instance_name}_#{resource.id}"
422
404
  end
423
405
 
424
- private
425
-
426
406
  def per_page
427
407
  request_per_page = (params[:per_page].presence || cookies[:pp].presence).to_i.nonzero?
428
408
  params[:per_page] = [request_per_page || @settings[:view_default_per_page][@settings[:current_index_view]], @settings[:max_per_page]].min
@@ -1,9 +1,6 @@
1
1
  class ::Admin::ManagerController < ::Admin::BaseController
2
- include AbAdmin::Utils::EvalHelpers
3
2
  include AbAdmin::Controllers::Tree
4
3
 
5
- prepend_before_action :manager
6
-
7
4
  load_and_authorize_resource
8
5
 
9
6
  helper_method :manager, :admin_partial_name, :history_resource_path, :fetch_admin_template
@@ -11,23 +8,19 @@ class ::Admin::ManagerController < ::Admin::BaseController
11
8
  def custom_action
12
9
  custom_action = manager.custom_action_for(params[:custom_action], self)
13
10
  if custom_action.options[:method] && custom_action.options[:method] != request.method_symbol
14
- raise ActionController::RoutingError.new("AbAdmin custom action for #{params[:custom_action]} not found")
11
+ raise ActionController::RoutingError.new("AbAdmin custom action #{custom_action.options[:method].to_s.upcase} #{params[:custom_action]} not allowed for #{request.method_symbol} method")
15
12
  end
16
13
  instance_exec(&custom_action.data)
17
14
  end
18
15
 
19
- protected
16
+ private
20
17
 
21
18
  def button_scopes
22
- manager.scopes.map{|scope| [scope.name, scope.options] }
23
- end
24
-
25
- def with_scopes(relation)
26
- scopes_to_apply.inject(relation) { |result, scope| scope.apply(result, params) }
19
+ manager.scopes
27
20
  end
28
21
 
29
- def scopes_to_apply
30
- manager.scopes.find_all{|scope| params[scope.name].present? }
22
+ def batch_actions
23
+ manager.batch_actions
31
24
  end
32
25
 
33
26
  def begin_of_association_chain
@@ -80,21 +73,6 @@ class ::Admin::ManagerController < ::Admin::BaseController
80
73
  manager.default_action_items_for(action_name.to_sym, for_resource) + manager.action_items_for(action_name.to_sym)
81
74
  end
82
75
 
83
- def apply_batch_action(item, batch_action, *batch_params)
84
- data = manager.batch_action_list.detect{|a| a.name == batch_action }.data
85
- success = call_method_or_proc_on item, data, exec: false, attrs: batch_params
86
- track_action!("batch_#{batch_action}", item) if settings[:history]
87
- success
88
- end
89
-
90
- def allow_batch_action?(batch_action)
91
- manager.batch_action_list.detect { |a| a.name == batch_action }
92
- end
93
-
94
- def batch_action_list
95
- manager.batch_action_list
96
- end
97
-
98
76
  def custom_settings
99
77
  manager.custom_settings || {}
100
78
  end
@@ -107,13 +85,11 @@ class ::Admin::ManagerController < ::Admin::BaseController
107
85
  @manager ||= begin
108
86
  manager_class_name = "AbAdmin#{resource_class.name}"
109
87
  manager_instance = manager_class_name.constantize.instance
110
- unless manager_instance.allow_action?(action_name)
111
- raise ActionController::RoutingError.new("AbAdmin action #{action_name} for #{resource_class.name} not found")
112
- end
88
+ raise ActionController::RoutingError.new("AbAdmin action #{action_name} not found") unless manager_instance.allow_action?(action_name)
113
89
  manager_instance
114
90
  rescue NameError => e
115
- if e.message.include?(manager_class_name)
116
- raise ActionController::RoutingError.new("AbAdmin manager_model for #{resource_class.name} not found (#{e.message})")
91
+ if e.message.include?("uninitialized constant #{manager_class_name}")
92
+ raise ActionController::RoutingError.new("AbAdmin manager for #{resource_class.name} not found (#{e.message})")
117
93
  else
118
94
  raise
119
95
  end
@@ -122,9 +98,14 @@ class ::Admin::ManagerController < ::Admin::BaseController
122
98
 
123
99
  def resource_class
124
100
  @model ||= begin
125
- params[:model_name].classify.constantize
126
- rescue NameError => e
127
- raise ActionController::RoutingError.new("AbAdmin model #{params[:model_name]} not found (#{e.message})")
101
+ model_name = params[:model_name].classify
102
+ model_name.constantize
103
+ rescue NameError => e
104
+ if e.message.include?("uninitialized constant #{model_name}")
105
+ raise ActionController::RoutingError.new("AbAdmin model #{model_name} not found (#{e.message})")
106
+ else
107
+ raise
108
+ end
128
109
  end
129
110
  end
130
111
 
@@ -140,19 +121,8 @@ class ::Admin::ManagerController < ::Admin::BaseController
140
121
  end
141
122
  end
142
123
 
143
- def preview_resource_path(item)
144
- return unless manager.preview_path
145
- return if manager.preview_path[:options][:if] && !call_method_or_proc_on(item, manager.preview_path[:options][:if])
146
- I18n.with_locale I18n.default_locale do
147
- manager.preview_path[:value].is_a?(Proc) ? instance_exec(item, &manager.preview_path[:value]) : send(manager.preview_path[:value], item)
148
- end
149
- end
150
-
151
124
  def admin_partial_name(builder)
152
- builder.partial ||= begin
153
- #if template_exists?(builder.partial_name, "admin/#{resource_collection_name}", true)
154
- fetch_admin_template(builder.partial_name, true)
155
- end
125
+ builder.partial ||= fetch_admin_template(builder.partial_name, true)
156
126
  end
157
127
 
158
128
  def fetch_admin_template(template_name, partial=false)
@@ -3,6 +3,8 @@
3
3
  .form-inputs
4
4
  = f.input :email, required: false, autofocus: true
5
5
  = f.input :password, required: false
6
+ - if devise_mapping.try(:two_factor_authenticatable?)
7
+ = f.input :otp_attempt, required: false
6
8
  = f.input :remember_me, as: :boolean, wrapper: :default if devise_mapping.rememberable?
7
9
  = f.button :submit, t('admin.auth.sessions.button', default: 'Sign in'), class: 'btn-primary btn-large'
8
10
  br
@@ -7,7 +7,8 @@
7
7
  a.thumbnail href=asset.url target='_blank' = image_tag(asset.thumb_url)
8
8
  .span8
9
9
  - if first_locale == l
10
- p= text_field :data, :base_filename, index: asset.id, value: asset.base_filename, placeholder: Asset.han(:data_file_name), style: 'width: 95%;'
10
+ .muted= asset.original_name
11
+ div= text_field :data, :human_filename, index: asset.id, value: asset.human_filename, placeholder: Asset.han(:data_file_name), style: 'width: 95%;'
11
12
  - [:alt, :name].each do |attr|
12
13
  - loc_attr = "#{attr}_#{l}"
13
14
  p= text_field :data, loc_attr, index: asset.id, value: asset.send(loc_attr), placeholder: Asset.han(attr), style: 'width: 95%;'
@@ -5,7 +5,7 @@
5
5
  div
6
6
  = f.submit t('admin.search.submit'), class: 'btn btn-success search-submit'
7
7
  button.js-search-cancel.btn.search-cancel= icon('remove')
8
- = render 'search_form', f: f
8
+ = render settings[:search_form_name] || 'search_form', f: f
9
9
  div
10
10
  = f.submit t('admin.search.submit'), class: 'btn btn-success search-submit'
11
11
  button.js-search-cancel.btn.search-cancel= icon('remove')
@@ -10,7 +10,7 @@
10
10
  $('#<%= form_wrap_id %>').remove();
11
11
  $('#list tbody:first').prepend('<%= j html %>');
12
12
  $(document).trigger('admin:list_init');
13
- $('#<%= dom_id(resource, 'list') %>').addClass('success').scrollToEl();
13
+ $('#<%= dom_id(resource, 'list') %>').addClass('bg-lemon');
14
14
  <% end %>
15
15
 
16
16
  window.ab_admin_last_created = <%= raw resource.for_input_token.to_json %>;
@@ -10,7 +10,7 @@
10
10
  %>
11
11
  $('#<%= form_wrap_id %>').remove();
12
12
  $('#<%= el_id %>').after('<%= j html %>');
13
- $('#<%= form_wrap_id %> td:first').attr('colspan', $('#<%= form_wrap_id %>').prev('tr').find('> td').length);
13
+ $('#<%= form_wrap_id %> > td').attr('colspan', $('#list > thead > tr:first th').length);
14
14
  $('#<%= form_wrap_id %> form').trigger('admin:form_init');
15
15
  $('#<%= el_id %>').scrollToEl();
16
16
  <% end %>
@@ -9,7 +9,7 @@
9
9
  %>
10
10
  $('#<%= form_wrap_id %>').remove();
11
11
  $('#list tbody:first').prepend('<%= j html %>');
12
- $('#<%= form_wrap_id %> td:first').attr('colspan', $('#list tr:first th').length);
12
+ $('#<%= form_wrap_id %> > td').attr('colspan', $('#list > thead > tr > th').length);
13
13
  $('#<%= form_wrap_id %> form').trigger('admin:form_init');
14
14
  <% end %>
15
15
 
@@ -1,8 +1,9 @@
1
+ <%= render 'admin/shared/flash' %>
2
+
1
3
  <% if params[:flash] %>
2
4
  flash("<%= t("flash.admin.actions.#{action_name}.notice", resource_name: resource_class.model_name.human(count: 1)) %>")
3
5
  <% end %>
4
6
 
5
-
6
7
  <% unless params[:modal] %>
7
8
  <%
8
9
  form_wrap_id = dom_id(resource, 'list_edit')
@@ -12,8 +13,7 @@
12
13
  $('#<%= dom_id(resource, 'list') %>').replaceWith('<%= j html %>');
13
14
  var $el = $('#<%= dom_id(resource, 'list') %>');
14
15
  $(document).trigger('admin:list_init');
15
- if (!$el[0].className) $el.addClass('success');
16
- $el.scrollToEl();
16
+ $el.addClass('bg-lemon');
17
17
  <% end %>
18
18
 
19
19
  window.ab_admin_last_updated = <%= raw resource.for_input_token.to_json %>;
@@ -4,4 +4,4 @@
4
4
  - show_builder.fields.each do |field|
5
5
  tr
6
6
  td= ha(field.name)
7
- td= admin_pretty_data call_method_or_proc_on(resource, field.data)
7
+ td= admin_pretty_data method_or_proc_on(field.data, resource)
@@ -0,0 +1,4 @@
1
+ - if tmpl = admin_partial_name(stats_builder)
2
+ = render partial: tmpl, locals: local_assigns
3
+ - else
4
+ h2 Generic stats template is not implemented yet. It can be here `admin/#{resource_collection_name}/_stats.html.slim`
@@ -19,12 +19,15 @@
19
19
  tbody
20
20
  - is_edit_link = manager.actions.include?(:edit)
21
21
  - collection.each do |item|
22
- tr id=dom_id(item, 'list') class=(call_method_or_proc_on(item, table_builder.options[:row_class]) if table_builder.options[:row_class])
22
+ tr id=dom_id(item, 'list') class=method_or_proc_on(table_builder.row_html_class, item)
23
23
  = batch_action_item(item)
24
- td= id_link(item, edit: is_edit_link)
24
+ td
25
+ = id_link(item, edit: is_edit_link)
26
+ - if settings[:id_column_with_created_at]
27
+ div: i= l(item.created_at, format: AbAdmin.datetime_format)
25
28
  = item_index_actions_panel(item)
26
29
  - table_builder.fields.each do |field|
27
- td class=(field.options[:cell_class] if field.options[:cell_class])
30
+ td class=field.options[:cell_html_class]
28
31
  - field_content = table_item_field(item, field)
29
32
  - if field_content.present? && field.options[:copy_button]
30
33
  - field_id = dom_id(item, field.name)
@@ -5,22 +5,21 @@
5
5
  = t 'admin.batch_actions.title'
6
6
  span.caret
7
7
  ul.dropdown-menu
8
- - batch_action_list.each do |ba|
8
+ - batch_actions.each do |ba|
9
9
  li: a.batch_action_link href='#' data-form=ba.form data-action=ba.name data-confirm=ba.confirm = ba.title
10
10
  - if settings[:button_scopes] && button_scopes.present?
11
11
  .btn-group
12
- - button_scopes.each do |name, opts|
13
- - next if opts[:if] && !call_method_or_proc_on(controller, opts[:if])
14
- - param_name = opts[:as] || name
15
- - active = params[param_name]
16
- - url = url_for(param_name => (active ? nil : 1), index_view: params[:index_view])
17
- a.btn href=url title=opts[:title] class=[opts[:class], ('active' if active), ('tool tool-bottom' if opts[:title])]
18
- = opts[:label] || opts[:title] || t("admin.scopes.#{name}", default: name.to_s.titleize)
19
- - if opts[:badge]
20
- - scope = opts[:block] ? opts[:block].call(controller, resource_class) : resource_class.send(name)
21
- - scope_count = scope.send(*[opts[:badge][:value_type] || :count, opts[:badge][:column]].compact)
12
+ - button_scopes.each do |s|
13
+ - next unless option_conditions_met?(s.options)
14
+ - active = params[s.param_name]
15
+ - url = url_for(s.param_name => (active ? nil : 1), index_view: params[:index_view])
16
+ a.btn href=url title=s.options[:title] class=[s.options[:class], ('active' if active), ('tool tool-bottom' if s.options[:title])]
17
+ = s.options[:label] || s.options[:title] || t("admin.scopes.#{s.name}", default: s.name.to_s.titleize)
18
+ - if s.options[:badge]
19
+ - scope = s.apply(self, resource_class.accessible_by(current_ability))
20
+ - scope_count = scope.send(*[s.options[:badge][:value_type] || :count, s.options[:badge][:column]].compact)
22
21
  - unless scope_count.zero?
23
- span.badge< class=("badge-#{opts[:badge].try(:[], :type) || 'important'}") = scope_count
22
+ span.badge< class=("badge-#{s.options.dig(:badge, :type) || 'important'} #{'pulse' if s.options.dig(:badge, :pulse)}") = scope_count
24
23
 
25
24
  - if settings[:current_index_view] == :table
26
25
  .btn-group#columns_hider_wrap
@@ -38,7 +37,7 @@
38
37
  - settings[:index_views].each do |index_view|
39
38
  = index_view_link(index_view)
40
39
 
41
- - if can?(:export, resource_class) && !settings[:skip_export]
40
+ - if settings[:export] && can?(:export, resource_class)
42
41
  .btn-group.downloads
43
42
  = icon('download-alt')
44
43
  - %w(csv xlsx).each do |format|
@@ -56,6 +55,14 @@
56
55
  .label> class=(ag[:css] || 'label-success')
57
56
  => ag[:label] || "#{ag[:column].to_s.humanize}:"
58
57
  - if !ag[:type] || ag[:type] == :sum
59
- = search_collection.distinct(false).sum(ag[:column]).try!(:round, 2)
58
+ - res = search_collection.unscope(:includes).distinct(false).sum(ag[:column])
59
+ = number_with_delimiter(res.round(2), delimiter: ' ') if res
60
60
  - elsif ag[:type] == :average
61
- = search_collection.average(ag[:column]).try!(:round, 2)
61
+ - res = search_collection.unscope(:includes).average(ag[:column])
62
+ = number_with_delimiter(res.round(2), delimiter: ' ') if res
63
+ - elsif ag[:type] == :pct
64
+ - sc_count = search_collection.count
65
+ - if sc_count.zero?
66
+ | 0%
67
+ - else
68
+ = "#{(search_collection.where.not(ag[:column] => nil).count.to_f / search_collection.count * 100).try!(:round, 2)}%"
@@ -1,10 +1,19 @@
1
1
  - return_to = params[:return_to].presence || request.referer || collection_path
2
- - return_to = "#{return_to}##{dom_id(resource, 'list')}" if resource.persisted?
2
+ - return_to = "#{return_to.split('#', 2).first}##{dom_id(resource, 'list')}" if resource.persisted?
3
3
  input type='hidden' name='return_to' value=return_to
4
+ - if settings[:safe_save] && resource.updated_at
5
+ input type='hidden' name='last_updated_timestamp' value=(resource.last_updated_timestamp || resource.updated_timestamp.to_i)
4
6
  .form-actions
5
7
  button.btn.btn-primary type="submit" name="_save" data-disable-with=t('admin.form.save')
6
8
  = icon('ok', true)
7
9
  = t('admin.form.save')
10
+ - if resource.errors.of_kind?(:base, :changed)
11
+ a.btn href=edit_resource_path(resource, return_to: params[:return_to])
12
+ = icon('repeat')
13
+ = t('admin.form.refresh')
14
+ button.btn.btn-warning type="submit" name="_force_save" data-disable-with='Force'
15
+ = icon('fire', true)
16
+ = t('admin.form.force_save')
8
17
  span.extra_buttons
9
18
  - unless resource.new_record?
10
19
  span.next_prev_buttons
@@ -18,8 +18,8 @@
18
18
  = f.input :locale, collection: AbAdmin.translated_locales
19
19
  = f.input :bg_color, as: :color
20
20
 
21
- = f.input :password, input_html: {autocomplete: 'off'}
22
- = f.input :password_confirmation, input_html: {autocomplete: 'off'}
21
+ = f.input :password, input_html: {autocomplete: 'new-password'}
22
+ = f.input :password_confirmation, input_html: {autocomplete: 'new-password'}
23
23
 
24
24
  = f.input :avatar, as: :uploader, edit_meta: true
25
25