ab_admin 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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 +1 -1
  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 +15 -3
  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 +33 -16
  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 +28 -26
@@ -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