rails_admin_next 1.0.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 (184) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +46 -0
  3. data/LICENSE.md +21 -0
  4. data/README.md +317 -0
  5. data/Rakefile +25 -0
  6. data/app/assets/javascripts/rails_admin/custom/ui.js +1 -0
  7. data/app/assets/stylesheets/rails_admin/framework.css +2375 -0
  8. data/app/assets/stylesheets/rails_admin/skin.css +472 -0
  9. data/app/assets/stylesheets/rails_admin/tokens.css +281 -0
  10. data/app/assets/stylesheets/rails_admin.css +47 -0
  11. data/app/controllers/rails_admin_next/application_controller.rb +98 -0
  12. data/app/controllers/rails_admin_next/main_controller.rb +185 -0
  13. data/app/helpers/rails_admin_next/application_helper.rb +254 -0
  14. data/app/helpers/rails_admin_next/form_builder.rb +173 -0
  15. data/app/helpers/rails_admin_next/main_helper.rb +63 -0
  16. data/app/views/layouts/rails_admin_next/_head.html.erb +29 -0
  17. data/app/views/layouts/rails_admin_next/_navigation.html.erb +18 -0
  18. data/app/views/layouts/rails_admin_next/_secondary_navigation.html.erb +27 -0
  19. data/app/views/layouts/rails_admin_next/_sidebar_navigation.html.erb +6 -0
  20. data/app/views/layouts/rails_admin_next/application.html.erb +42 -0
  21. data/app/views/layouts/rails_admin_next/content.html.erb +22 -0
  22. data/app/views/layouts/rails_admin_next/modal.js.erb +7 -0
  23. data/app/views/rails_admin_next/main/_dashboard_history.html.erb +46 -0
  24. data/app/views/rails_admin_next/main/_delete_notice.html.erb +36 -0
  25. data/app/views/rails_admin_next/main/_form_action_text.html.erb +2 -0
  26. data/app/views/rails_admin_next/main/_form_boolean.html.erb +15 -0
  27. data/app/views/rails_admin_next/main/_form_colorpicker.html.erb +6 -0
  28. data/app/views/rails_admin_next/main/_form_datetime.html.erb +14 -0
  29. data/app/views/rails_admin_next/main/_form_enumeration.html.erb +26 -0
  30. data/app/views/rails_admin_next/main/_form_field.html.erb +2 -0
  31. data/app/views/rails_admin_next/main/_form_file_upload.html.erb +18 -0
  32. data/app/views/rails_admin_next/main/_form_filtering_multiselect.html.erb +20 -0
  33. data/app/views/rails_admin_next/main/_form_filtering_select.html.erb +22 -0
  34. data/app/views/rails_admin_next/main/_form_multiple_file_upload.html.erb +21 -0
  35. data/app/views/rails_admin_next/main/_form_nested_many.html.erb +20 -0
  36. data/app/views/rails_admin_next/main/_form_nested_one.html.erb +19 -0
  37. data/app/views/rails_admin_next/main/_form_polymorphic_association.html.erb +24 -0
  38. data/app/views/rails_admin_next/main/_form_text.html.erb +2 -0
  39. data/app/views/rails_admin_next/main/_pagination.html.erb +54 -0
  40. data/app/views/rails_admin_next/main/_submit_buttons.html.erb +26 -0
  41. data/app/views/rails_admin_next/main/bulk_delete.html.erb +19 -0
  42. data/app/views/rails_admin_next/main/dashboard.html.erb +64 -0
  43. data/app/views/rails_admin_next/main/delete.html.erb +21 -0
  44. data/app/views/rails_admin_next/main/edit.html.erb +3 -0
  45. data/app/views/rails_admin_next/main/export.html.erb +149 -0
  46. data/app/views/rails_admin_next/main/history.html.erb +80 -0
  47. data/app/views/rails_admin_next/main/index.html.erb +186 -0
  48. data/app/views/rails_admin_next/main/new.html.erb +3 -0
  49. data/app/views/rails_admin_next/main/show.html.erb +30 -0
  50. data/config/brakeman.ignore +145 -0
  51. data/config/importmap.rails_admin.rb +29 -0
  52. data/config/initializers/active_record_extensions.rb +21 -0
  53. data/config/locales/rails_admin_next.en.yml +158 -0
  54. data/config/routes.rb +14 -0
  55. data/lib/generators/rails_admin_next/install_generator.rb +39 -0
  56. data/lib/generators/rails_admin_next/templates/initializer.erb +40 -0
  57. data/lib/generators/rails_admin_next/utils.rb +21 -0
  58. data/lib/rails_admin_next/abstract_model.rb +266 -0
  59. data/lib/rails_admin_next/adapters/active_record/association.rb +130 -0
  60. data/lib/rails_admin_next/adapters/active_record/object_extension.rb +13 -0
  61. data/lib/rails_admin_next/adapters/active_record/property.rb +58 -0
  62. data/lib/rails_admin_next/adapters/active_record.rb +371 -0
  63. data/lib/rails_admin_next/config/actions/base.rb +160 -0
  64. data/lib/rails_admin_next/config/actions/bulk_delete.rb +67 -0
  65. data/lib/rails_admin_next/config/actions/dashboard.rb +62 -0
  66. data/lib/rails_admin_next/config/actions/delete.rb +58 -0
  67. data/lib/rails_admin_next/config/actions/edit.rb +56 -0
  68. data/lib/rails_admin_next/config/actions/export.rb +47 -0
  69. data/lib/rails_admin_next/config/actions/history_index.rb +40 -0
  70. data/lib/rails_admin_next/config/actions/history_show.rb +40 -0
  71. data/lib/rails_admin_next/config/actions/index.rb +108 -0
  72. data/lib/rails_admin_next/config/actions/new.rb +69 -0
  73. data/lib/rails_admin_next/config/actions/show.rb +36 -0
  74. data/lib/rails_admin_next/config/actions/show_in_app.rb +37 -0
  75. data/lib/rails_admin_next/config/actions.rb +122 -0
  76. data/lib/rails_admin_next/config/configurable.rb +107 -0
  77. data/lib/rails_admin_next/config/const_load_suppressor.rb +78 -0
  78. data/lib/rails_admin_next/config/fields/association.rb +169 -0
  79. data/lib/rails_admin_next/config/fields/base.rb +391 -0
  80. data/lib/rails_admin_next/config/fields/collection_association.rb +90 -0
  81. data/lib/rails_admin_next/config/fields/factories/action_text.rb +14 -0
  82. data/lib/rails_admin_next/config/fields/factories/active_storage.rb +34 -0
  83. data/lib/rails_admin_next/config/fields/factories/association.rb +32 -0
  84. data/lib/rails_admin_next/config/fields/factories/encrypted_password.rb +28 -0
  85. data/lib/rails_admin_next/config/fields/factories/enum.rb +23 -0
  86. data/lib/rails_admin_next/config/fields/factories/password.rb +19 -0
  87. data/lib/rails_admin_next/config/fields/group.rb +77 -0
  88. data/lib/rails_admin_next/config/fields/singular_association.rb +62 -0
  89. data/lib/rails_admin_next/config/fields/types/action_text.rb +20 -0
  90. data/lib/rails_admin_next/config/fields/types/active_record_enum.rb +62 -0
  91. data/lib/rails_admin_next/config/fields/types/active_storage.rb +70 -0
  92. data/lib/rails_admin_next/config/fields/types/all.rb +32 -0
  93. data/lib/rails_admin_next/config/fields/types/belongs_to_association.rb +48 -0
  94. data/lib/rails_admin_next/config/fields/types/boolean.rb +70 -0
  95. data/lib/rails_admin_next/config/fields/types/citext.rb +15 -0
  96. data/lib/rails_admin_next/config/fields/types/color.rb +43 -0
  97. data/lib/rails_admin_next/config/fields/types/date.rb +38 -0
  98. data/lib/rails_admin_next/config/fields/types/datetime.rb +79 -0
  99. data/lib/rails_admin_next/config/fields/types/decimal.rb +23 -0
  100. data/lib/rails_admin_next/config/fields/types/enum.rb +52 -0
  101. data/lib/rails_admin_next/config/fields/types/file_upload.rb +87 -0
  102. data/lib/rails_admin_next/config/fields/types/float.rb +23 -0
  103. data/lib/rails_admin_next/config/fields/types/has_and_belongs_to_many_association.rb +16 -0
  104. data/lib/rails_admin_next/config/fields/types/has_many_association.rb +16 -0
  105. data/lib/rails_admin_next/config/fields/types/has_one_association.rb +35 -0
  106. data/lib/rails_admin_next/config/fields/types/hidden.rb +31 -0
  107. data/lib/rails_admin_next/config/fields/types/inet.rb +15 -0
  108. data/lib/rails_admin_next/config/fields/types/integer.rb +20 -0
  109. data/lib/rails_admin_next/config/fields/types/json.rb +37 -0
  110. data/lib/rails_admin_next/config/fields/types/multiple_active_storage.rb +82 -0
  111. data/lib/rails_admin_next/config/fields/types/multiple_file_upload.rb +134 -0
  112. data/lib/rails_admin_next/config/fields/types/numeric.rb +24 -0
  113. data/lib/rails_admin_next/config/fields/types/password.rb +45 -0
  114. data/lib/rails_admin_next/config/fields/types/polymorphic_association.rb +118 -0
  115. data/lib/rails_admin_next/config/fields/types/serialized.rb +28 -0
  116. data/lib/rails_admin_next/config/fields/types/string.rb +50 -0
  117. data/lib/rails_admin_next/config/fields/types/string_like.rb +25 -0
  118. data/lib/rails_admin_next/config/fields/types/text.rb +28 -0
  119. data/lib/rails_admin_next/config/fields/types/time.rb +39 -0
  120. data/lib/rails_admin_next/config/fields/types/timestamp.rb +16 -0
  121. data/lib/rails_admin_next/config/fields/types/uuid.rb +15 -0
  122. data/lib/rails_admin_next/config/fields/types.rb +29 -0
  123. data/lib/rails_admin_next/config/fields.rb +88 -0
  124. data/lib/rails_admin_next/config/groupable.rb +20 -0
  125. data/lib/rails_admin_next/config/has_description.rb +14 -0
  126. data/lib/rails_admin_next/config/has_fields.rb +144 -0
  127. data/lib/rails_admin_next/config/has_groups.rb +27 -0
  128. data/lib/rails_admin_next/config/hideable.rb +30 -0
  129. data/lib/rails_admin_next/config/inspectable.rb +39 -0
  130. data/lib/rails_admin_next/config/lazy_model.rb +74 -0
  131. data/lib/rails_admin_next/config/model.rb +126 -0
  132. data/lib/rails_admin_next/config/proxyable/proxy.rb +42 -0
  133. data/lib/rails_admin_next/config/proxyable.rb +26 -0
  134. data/lib/rails_admin_next/config/sections/base.rb +36 -0
  135. data/lib/rails_admin_next/config/sections/create.rb +13 -0
  136. data/lib/rails_admin_next/config/sections/edit.rb +13 -0
  137. data/lib/rails_admin_next/config/sections/export.rb +13 -0
  138. data/lib/rails_admin_next/config/sections/list.rb +63 -0
  139. data/lib/rails_admin_next/config/sections/modal.rb +12 -0
  140. data/lib/rails_admin_next/config/sections/nested.rb +12 -0
  141. data/lib/rails_admin_next/config/sections/show.rb +12 -0
  142. data/lib/rails_admin_next/config/sections/update.rb +12 -0
  143. data/lib/rails_admin_next/config/sections.rb +39 -0
  144. data/lib/rails_admin_next/config.rb +436 -0
  145. data/lib/rails_admin_next/engine.rb +91 -0
  146. data/lib/rails_admin_next/extension.rb +35 -0
  147. data/lib/rails_admin_next/extensions/cancancan/authorization_adapter.rb +90 -0
  148. data/lib/rails_admin_next/extensions/cancancan.rb +5 -0
  149. data/lib/rails_admin_next/extensions/controller_extension.rb +8 -0
  150. data/lib/rails_admin_next/extensions/paper_trail/auditing_adapter.rb +192 -0
  151. data/lib/rails_admin_next/extensions/paper_trail.rb +5 -0
  152. data/lib/rails_admin_next/extensions/pundit/authorization_adapter.rb +72 -0
  153. data/lib/rails_admin_next/extensions/pundit.rb +5 -0
  154. data/lib/rails_admin_next/extensions/url_for_extension.rb +15 -0
  155. data/lib/rails_admin_next/icons.rb +143 -0
  156. data/lib/rails_admin_next/support/composite_keys_serializer.rb +15 -0
  157. data/lib/rails_admin_next/support/csv_converter.rb +100 -0
  158. data/lib/rails_admin_next/support/hash_helper.rb +31 -0
  159. data/lib/rails_admin_next/support/paginated_collection.rb +79 -0
  160. data/lib/rails_admin_next/version.rb +17 -0
  161. data/lib/rails_admin_next.rb +72 -0
  162. data/lib/tasks/rails_admin.rake +40 -0
  163. data/src/rails_admin/application.js +15 -0
  164. data/src/rails_admin/base.js +12 -0
  165. data/src/rails_admin/controllers/alert_controller.js +28 -0
  166. data/src/rails_admin/controllers/collapse_controller.js +16 -0
  167. data/src/rails_admin/controllers/dropdown_controller.js +110 -0
  168. data/src/rails_admin/controllers/feedback_controller.js +73 -0
  169. data/src/rails_admin/controllers/filter_box_controller.js +346 -0
  170. data/src/rails_admin/controllers/filtering_multiselect_controller.js +386 -0
  171. data/src/rails_admin/controllers/filtering_select_controller.js +509 -0
  172. data/src/rails_admin/controllers/index.js +31 -0
  173. data/src/rails_admin/controllers/modal_controller.js +142 -0
  174. data/src/rails_admin/controllers/nested_form_controller.js +117 -0
  175. data/src/rails_admin/controllers/polymorphic_controller.js +26 -0
  176. data/src/rails_admin/controllers/remote_form_controller.js +173 -0
  177. data/src/rails_admin/controllers/sidescroll_controller.js +23 -0
  178. data/src/rails_admin/dom-ready.js +20 -0
  179. data/src/rails_admin/dom.js +19 -0
  180. data/src/rails_admin/i18n.js +20 -0
  181. data/src/rails_admin/icons.js +64 -0
  182. data/src/rails_admin/interactions.js +221 -0
  183. data/src/rails_admin/select_query.js +65 -0
  184. metadata +346 -0
@@ -0,0 +1,436 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_admin_next/config/lazy_model"
4
+ require "rails_admin_next/config/sections/list"
5
+ require "rails_admin_next/support/composite_keys_serializer"
6
+ require "active_support/core_ext/module/attribute_accessors"
7
+
8
+ module RailsAdminNext
9
+ module Config
10
+ # RailsAdminNext authentication is fully pluggable — set authenticate_with to any proc.
11
+ # Works with Devise, Rodauth, authentication-zero, has_secure_password, or any Warden setup.
12
+ #
13
+ # @see RailsAdminNext::Config.authenticate_with
14
+ # @see RailsAdminNext::Config.authorize_with
15
+ DEFAULT_AUTHENTICATION = proc {}
16
+
17
+ DEFAULT_AUTHORIZE = proc {}
18
+
19
+ DEFAULT_AUDIT = proc {}
20
+
21
+ DEFAULT_CURRENT_USER = proc {}
22
+
23
+ class << self
24
+ # Configuration option to specify which models you want to exclude.
25
+ attr_accessor :excluded_models
26
+
27
+ # Configuration option to specify a allowlist of models you want to RailsAdminNext to work with.
28
+ # The excluded_models list applies against the allowlist as well and further reduces the models
29
+ # RailsAdminNext will use.
30
+ # If included_models is left empty ([]), then RailsAdminNext will automatically use all the models
31
+ # in your application (less any excluded_models you may have specified).
32
+ attr_accessor :included_models
33
+
34
+ # Fields to be hidden in show, create and update views
35
+ attr_reader :default_hidden_fields
36
+
37
+ # Default items per page value used if a model level option has not
38
+ # been configured
39
+ attr_accessor :default_items_per_page
40
+
41
+ # Default association limit
42
+ attr_accessor :default_associated_collection_limit
43
+
44
+ attr_reader :default_search_operator
45
+
46
+ # Configuration option to specify which method names will be searched for
47
+ # to be used as a label for object records. This defaults to [:name, :title]
48
+ attr_accessor :label_methods
49
+
50
+ # hide blank fields in show view if true
51
+ attr_accessor :compact_show_view
52
+
53
+ # Tell browsers whether to use the native HTML5 validations (novalidate form option).
54
+ attr_accessor :browser_validations
55
+
56
+ # Color scheme of the admin UI: :auto (default) follows the OS preference,
57
+ # :light or :dark pins it. Rendered as the layout's <meta name="color-scheme">,
58
+ # which every light-dark() token pair in the engine stylesheets resolves against.
59
+ attr_reader :color_scheme
60
+
61
+ # set parent controller
62
+ attr_reader :parent_controller
63
+
64
+ # Stores model configuration objects in a hash identified by model's class
65
+ # name.
66
+ #
67
+ # @see RailsAdminNext.config
68
+ attr_reader :registry
69
+
70
+ # Bootstrap CSS classes used for Navigation bar
71
+ attr_accessor :navbar_css_classes
72
+
73
+ # show Gravatar in Navigation bar
74
+ attr_accessor :show_gravatar
75
+
76
+ # accepts a hash of static links to be shown below the main navigation
77
+ attr_accessor :navigation_static_links
78
+ attr_accessor :navigation_static_label
79
+
80
+ # For customization of composite keys representation
81
+ attr_accessor :composite_keys_serializer
82
+
83
+ # Setup authentication to be run as a before filter
84
+ # This is run inside the controller instance so you can setup any authentication you need to
85
+ #
86
+ # By default, the authentication will run via warden if available
87
+ # and will run the default.
88
+ #
89
+ # Works with any auth: Devise, Rodauth, authentication-zero, has_secure_password.
90
+ #
91
+ # @example Warden (any setup)
92
+ # RailsAdminNext.config do |config|
93
+ # config.authenticate_with do
94
+ # warden.authenticate! scope: :user
95
+ # end
96
+ # end
97
+ #
98
+ # @example Custom scope
99
+ # RailsAdminNext.config do |config|
100
+ # config.authenticate_with do
101
+ # warden.authenticate! scope: :paranoid
102
+ # end
103
+ # end
104
+ #
105
+ # @see RailsAdminNext::Config::DEFAULT_AUTHENTICATION
106
+ def authenticate_with(&blk)
107
+ @authenticate = blk if blk
108
+ @authenticate || DEFAULT_AUTHENTICATION
109
+ end
110
+
111
+ # Content Security Policy for the admin. Opt-in: the engine enforces no policy by default.
112
+ #
113
+ # Pass a block receiving an `ActionDispatch::ContentSecurityPolicy`; it is applied per-request
114
+ # to admin responses only. The engine threads a per-request nonce onto every inline tag it
115
+ # emits (the importmap JSON, the import entry point, the index column-width style), so a
116
+ # policy may use `:self` + nonces without breaking the admin's own modules.
117
+ # Pass `report_only: true` to send `Content-Security-Policy-Report-Only` instead of enforcing.
118
+ #
119
+ # NOTE: the admin still renders a few inline `style="…"` attributes (enumerated in
120
+ # docs/security.md), so a `style-src` directive currently needs `:unsafe_inline`.
121
+ #
122
+ # @example Enforce a locked-down policy for the admin
123
+ # RailsAdminNext.config do |config|
124
+ # config.content_security_policy do |policy|
125
+ # policy.default_src :self
126
+ # policy.script_src :self
127
+ # policy.style_src :self, :unsafe_inline
128
+ # policy.img_src :self, :data
129
+ # end
130
+ # end
131
+ #
132
+ # @example Report-only while tuning it
133
+ # RailsAdminNext.config do |config|
134
+ # config.content_security_policy(report_only: true) { |policy| policy.default_src :self }
135
+ # end
136
+ def content_security_policy(report_only: false, &block)
137
+ if block
138
+ @content_security_policy = block
139
+ @content_security_policy_report_only = report_only
140
+ end
141
+ @content_security_policy
142
+ end
143
+
144
+ # Whether the opt-in CSP is sent as report-only (set via #content_security_policy).
145
+ attr_reader :content_security_policy_report_only
146
+
147
+ # Setup auditing/versioning provider that observe objects lifecycle
148
+ def audit_with(*args, &block)
149
+ extension = args.shift
150
+ if extension
151
+ klass = RailsAdminNext::AUDITING_ADAPTERS[extension]
152
+ klass.setup if klass.respond_to? :setup
153
+ @audit = proc do
154
+ @auditing_adapter = klass.new(*([self] + args).compact, &block)
155
+ end
156
+ elsif block
157
+ @audit = block
158
+ end
159
+ @audit || DEFAULT_AUDIT
160
+ end
161
+
162
+ # Setup authorization to be run as a before filter
163
+ # This is run inside the controller instance so you can setup any authorization you need to.
164
+ #
165
+ # By default, there is no authorization.
166
+ #
167
+ # @example Custom
168
+ # RailsAdminNext.config do |config|
169
+ # config.authorize_with do
170
+ # redirect_to root_path unless warden.user.is_admin?
171
+ # end
172
+ # end
173
+ #
174
+ # To use an authorization adapter, pass the name of the adapter. For example,
175
+ # to use with CanCanCan[https://github.com/CanCanCommunity/cancancan/], pass it like this.
176
+ #
177
+ # @example CanCanCan
178
+ # RailsAdminNext.config do |config|
179
+ # config.authorize_with :cancancan
180
+ # end
181
+ #
182
+ # See the wiki[https://github.com/railsadminteam/rails_admin/wiki] for more on authorization.
183
+ #
184
+ # @see RailsAdminNext::Config::DEFAULT_AUTHORIZE
185
+ def authorize_with(*args, &block)
186
+ extension = args.shift
187
+ if extension
188
+ klass = RailsAdminNext::AUTHORIZATION_ADAPTERS[extension]
189
+ klass.setup if klass.respond_to? :setup
190
+ @authorize = proc do
191
+ @authorization_adapter = klass.new(*([self] + args).compact, &block)
192
+ end
193
+ elsif block
194
+ @authorize = block
195
+ end
196
+ @authorize || DEFAULT_AUTHORIZE
197
+ end
198
+
199
+ # Setup configuration using an extension-provided ConfigurationAdapter
200
+ #
201
+ # @example Custom configuration for role-based setup.
202
+ # RailsAdminNext.config do |config|
203
+ # config.configure_with(:custom) do |config|
204
+ # config.models = ['User', 'Comment']
205
+ # config.roles = {
206
+ # 'Admin' => :all,
207
+ # 'User' => ['User']
208
+ # }
209
+ # end
210
+ # end
211
+ def configure_with(extension)
212
+ configuration = RailsAdminNext::CONFIGURATION_ADAPTERS[extension].new
213
+ yield(configuration) if block_given?
214
+ end
215
+
216
+ # Setup a different method to determine the current user or admin logged in.
217
+ # This is run inside the controller instance and made available as a helper.
218
+ #
219
+ # By default, _request.env["warden"].user_ or _current_user_ will be used (auth-library agnostic).
220
+ #
221
+ # @example Custom
222
+ # RailsAdminNext.config do |config|
223
+ # config.current_user_method do
224
+ # current_admin
225
+ # end
226
+ # end
227
+ #
228
+ # @see RailsAdminNext::Config::DEFAULT_CURRENT_USER
229
+ def current_user_method(&block)
230
+ @current_user = block if block
231
+ @current_user || DEFAULT_CURRENT_USER
232
+ end
233
+
234
+ # Validate at configuration time so a typo fails with an actionable
235
+ # message instead of a bare KeyError on the first admin page render.
236
+ def color_scheme=(scheme)
237
+ raise ArgumentError.new("color_scheme must be :auto, :light or :dark (got #{scheme.inspect})") unless %i[auto light dark].include?(scheme)
238
+
239
+ @color_scheme = scheme
240
+ end
241
+
242
+ def default_search_operator=(operator)
243
+ if %w[default like not_like starts_with ends_with is =].include? operator
244
+ @default_search_operator = operator
245
+ else
246
+ raise ArgumentError.new("Search operator '#{operator}' not supported")
247
+ end
248
+ end
249
+
250
+ # pool of all found model names from the whole application
251
+ def models_pool
252
+ (viable_models - excluded_models.collect(&:to_s)).uniq.sort
253
+ end
254
+
255
+ # Loads a model configuration instance from the registry or registers
256
+ # a new one if one is yet to be added.
257
+ #
258
+ # First argument can be an instance of requested model, its class object,
259
+ # its class name as a string or symbol or a RailsAdminNext::AbstractModel
260
+ # instance.
261
+ #
262
+ # If a block is given it is evaluated in the context of configuration instance.
263
+ #
264
+ # Returns given model's configuration
265
+ #
266
+ # @see RailsAdminNext::Config.registry
267
+ def model(entity, &block)
268
+ key =
269
+ case entity
270
+ when RailsAdminNext::AbstractModel
271
+ entity.model.try(:name).try :to_sym
272
+ when Class, ConstLoadSuppressor::ConstProxy
273
+ entity.name.to_sym
274
+ when String, Symbol
275
+ entity.to_sym
276
+ else
277
+ entity.class.name.to_sym
278
+ end
279
+
280
+ @registry[key] ||= RailsAdminNext::Config::LazyModel.new(key.to_s)
281
+ @registry[key].add_deferred_block(&block) if block
282
+ @registry[key]
283
+ end
284
+
285
+ # The engine ships a single zero-build asset path: browser-native ESM + CSS served by Propshaft
286
+ # and pinned via an engine-owned importmap. The option is vestigial — assignment is deprecated
287
+ # and ignored, kept only so a host's legacy `config.asset_source = :sprockets` doesn't raise.
288
+ def asset_source
289
+ :importmap
290
+ end
291
+
292
+ def asset_source=(_)
293
+ RailsAdminNext.deprecator.warn("The asset_source configuration option was removed, RailsAdminNext serves a single importmap + Propshaft asset pipeline now.")
294
+ end
295
+
296
+ def default_hidden_fields=(fields)
297
+ if fields.is_a?(Array)
298
+ @default_hidden_fields = {}
299
+ @default_hidden_fields[:edit] = fields
300
+ @default_hidden_fields[:show] = fields
301
+ else
302
+ @default_hidden_fields = fields
303
+ end
304
+ end
305
+
306
+ def parent_controller=(name)
307
+ @parent_controller = name
308
+
309
+ if defined?(RailsAdminNext::ApplicationController) || defined?(RailsAdminNext::MainController)
310
+ RailsAdminNext::Config::ConstLoadSuppressor.allowing do
311
+ RailsAdminNext.send(:remove_const, :ApplicationController)
312
+ RailsAdminNext.send(:remove_const, :MainController)
313
+ load RailsAdminNext::Engine.root.join("app/controllers/rails_admin_next/application_controller.rb")
314
+ load RailsAdminNext::Engine.root.join("app/controllers/rails_admin_next/main_controller.rb")
315
+ end
316
+ end
317
+ end
318
+
319
+ def total_columns_width=(_)
320
+ RailsAdminNext.deprecator.warn("The total_columns_width configuration option is deprecated and has no effect.")
321
+ end
322
+
323
+ def sidescroll=(_)
324
+ RailsAdminNext.deprecator.warn("The sidescroll configuration option was removed, it is always enabled now.")
325
+ end
326
+
327
+ # Setup actions to be used.
328
+ def actions(&block)
329
+ return unless block
330
+
331
+ RailsAdminNext::Config::Actions.reset
332
+ RailsAdminNext::Config::Actions.instance_eval(&block)
333
+ end
334
+
335
+ # Returns all model configurations
336
+ #
337
+ # @see RailsAdminNext::Config.registry
338
+ def models
339
+ RailsAdminNext::AbstractModel.all.collect { |m| model(m) }
340
+ end
341
+
342
+ # Reset all configurations to defaults.
343
+ #
344
+ # @see RailsAdminNext::Config.registry
345
+ def reset
346
+ @compact_show_view = true
347
+ @browser_validations = true
348
+ @color_scheme = :auto
349
+ @authenticate = nil
350
+ @authorize = nil
351
+ @audit = nil
352
+ @current_user = nil
353
+ @default_hidden_fields = {}
354
+ @default_hidden_fields[:base] = [:_type]
355
+ @default_hidden_fields[:edit] = %i[id _id created_at created_on deleted_at updated_at updated_on deleted_on]
356
+ @default_hidden_fields[:show] = %i[id _id created_at created_on deleted_at updated_at updated_on deleted_on]
357
+ @default_items_per_page = 20
358
+ @default_associated_collection_limit = 100
359
+ @default_search_operator = "default"
360
+ @excluded_models = []
361
+ @included_models = []
362
+ @label_methods = %i[name title]
363
+ @registry = {}
364
+ @navbar_css_classes = %w[navbar-dark bg-primary border-bottom]
365
+ @show_gravatar = true
366
+ @navigation_static_links = {}
367
+ @navigation_static_label = nil
368
+ @composite_keys_serializer = RailsAdminNext::Support::CompositeKeysSerializer
369
+ @parent_controller = "::ActionController::Base"
370
+ @content_security_policy = nil
371
+ @content_security_policy_report_only = false
372
+ RailsAdminNext::Config::Actions.reset
373
+ RailsAdminNext::AbstractModel.reset
374
+ end
375
+
376
+ # Reset a provided model's configuration.
377
+ #
378
+ # @see RailsAdminNext::Config.registry
379
+ def reset_model(model)
380
+ key = model.is_a?(Class) ? model.name.to_sym : model.to_sym
381
+ @registry.delete(key)
382
+ end
383
+
384
+ # Perform reset, then load RailsAdminNext initializer again
385
+ def reload!
386
+ reset
387
+ load RailsAdminNext::Engine.config.initializer_path
388
+ end
389
+
390
+ # Get all models that are configured as visible sorted by their weight and label.
391
+ #
392
+ # @see RailsAdminNext::Config::Hideable
393
+ def visible_models(bindings)
394
+ visible_models_with_bindings(bindings).sort do |a, b|
395
+ if (weight_order = a.weight <=> b.weight) == 0
396
+ a.label.casecmp(b.label)
397
+ else
398
+ weight_order
399
+ end
400
+ end
401
+ end
402
+
403
+ private
404
+
405
+ def viable_models
406
+ included_models.collect(&:to_s).presence || begin
407
+ @@system_models ||= # memoization for tests
408
+ ([Rails.application] + Rails::Engine.subclasses.collect(&:instance)).flat_map do |app|
409
+ (app.paths["app/models"].to_a + app.config.eager_load_paths).collect do |load_path|
410
+ Dir.glob(app.root.join(load_path)).collect do |load_dir|
411
+ path_prefix = "#{app.root.join(load_dir)}/"
412
+ Dir.glob("#{load_dir}/**/*.rb").collect do |filename|
413
+ # app/models/module/class.rb => module/class.rb => module/class => Module::Class
414
+ filename.delete_prefix(path_prefix).chomp(".rb").camelize
415
+ end
416
+ end
417
+ end
418
+ end.flatten.reject { |m| m.starts_with?("Concerns::") } # rubocop:disable Style/MultilineBlockChain
419
+
420
+ @@system_models + @registry.keys.collect(&:to_s)
421
+ end
422
+ end
423
+
424
+ def visible_models_with_bindings(bindings)
425
+ models.collect { |m| m.with(bindings) }.select do |m|
426
+ m.visible? &&
427
+ RailsAdminNext::Config::Actions.find(:index, bindings.merge(abstract_model: m.abstract_model)).try(:authorized?) &&
428
+ (!m.abstract_model.embedded? || m.abstract_model.cyclic?)
429
+ end
430
+ end
431
+ end
432
+
433
+ # Set default values for configuration options on load
434
+ reset
435
+ end
436
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_cable/engine"
4
+ require "geared_pagination"
5
+ require "rails"
6
+ require "rails_admin_next"
7
+ require "rails_admin_next/extensions/url_for_extension"
8
+ require "rails_admin_next/version"
9
+ require "stimulus-rails"
10
+ require "turbo-rails"
11
+
12
+ module RailsAdminNext
13
+ class Engine < Rails::Engine
14
+ isolate_namespace RailsAdminNext
15
+
16
+ attr_accessor :importmap
17
+
18
+ config.action_dispatch.rescue_responses["RailsAdminNext::ActionNotAllowed"] = :forbidden
19
+
20
+ initializer "RailsAdminNext load UrlForExtension" do
21
+ RailsAdminNext::Engine.routes.singleton_class.prepend(RailsAdminNext::Extensions::UrlForExtension)
22
+ end
23
+
24
+ initializer "RailsAdminNext reload config in development" do |app|
25
+ config.initializer_path = app.root.join("config/initializers/rails_admin_next.rb")
26
+
27
+ unless Rails.application.config.cache_classes
28
+ ActiveSupport::Reloader.before_class_unload do
29
+ RailsAdminNext::Config.reload!
30
+ end
31
+
32
+ reloader = app.config.file_watcher.new([config.initializer_path], []) do
33
+ # Do nothing, ActiveSupport::Reloader will trigger class_unload! anyway
34
+ end
35
+
36
+ app.reloaders << reloader
37
+ app.reloader.to_run do
38
+ reloader.execute_if_updated { require_unload_lock! }
39
+ end
40
+ reloader.execute
41
+ end
42
+ end
43
+
44
+ initializer "RailsAdminNext assets", group: :all do |app|
45
+ # Propshaft auto-registers the engine's app/assets (stylesheets + fonts); the browser-native
46
+ # ESM modules live under src/ — a non-conventional asset dir — so push it explicitly.
47
+ app.config.assets.paths << RailsAdminNext::Engine.root.join("src")
48
+
49
+ # The engine draws its OWN importmap (distinct from the host's config/importmap.rb), rendered
50
+ # inline — with the request CSP nonce — by app/views/layouts/rails_admin_next/_head.html.erb.
51
+ # A host that ships config/importmap.rails_admin.rb has it appended last, so it can override
52
+ # the `rails_admin_next` entrypoint pin (to add its own ActiveStorage/locales/custom UI) or add pins.
53
+ self.importmap = Importmap::Map.new.draw(RailsAdminNext::Engine.root.join("config/importmap.rails_admin.rb"))
54
+ host_importmap = app.root.join("config/importmap.rails_admin.rb")
55
+ importmap.draw(host_importmap) if host_importmap.exist?
56
+
57
+ # Invalidate the cached importmap JSON in development when an engine ESM file changes.
58
+ importmap.cache_sweeper(watches: RailsAdminNext::Engine.root.join("src"))
59
+ app.reloader.to_run { RailsAdminNext::Engine.importmap.cache_sweeper.execute_if_updated } unless app.config.cache_classes
60
+
61
+ # SRI/integrity is optional hardening: its value is cross-origin/CDN assets, which
62
+ # self-hosting same-origin already removes. Enable it only when the host opts in via
63
+ # config.assets.integrity_hash_algorithm (requires propshaft >= 1.2).
64
+ importmap.enable_integrity! if app.config.assets.try(:integrity_hash_algorithm).present?
65
+ end
66
+
67
+ # Check for required middlewares, users may forget to use them in Rails API mode
68
+ config.after_initialize do |app|
69
+ has_session_store = app.config.middleware.to_a.any? do |m|
70
+ m.klass.try(:<=, ActionDispatch::Session::AbstractStore) ||
71
+ m.klass.try(:<=, ActionDispatch::Session::AbstractSecureStore) ||
72
+ m.klass.name =~ /^ActionDispatch::Session::/
73
+ end
74
+ loaded = app.config.middleware.to_a.map(&:name)
75
+ required = %w[ActionDispatch::Cookies ActionDispatch::Flash Rack::MethodOverride]
76
+ missing = required - loaded
77
+ unless missing.empty? && has_session_store
78
+ configs = missing.map { |m| "config.middleware.use #{m}" }
79
+ configs << "config.middleware.use #{app.config.session_store.try(:name) || "ActionDispatch::Session::CookieStore"}, #{app.config.session_options}" unless has_session_store
80
+ raise <<~ERROR
81
+ Required middlewares for RailsAdminNext are not added
82
+ To fix this, add
83
+
84
+ #{configs.join("\n ")}
85
+
86
+ to config/application.rb.
87
+ ERROR
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_admin_next/extensions/controller_extension"
4
+
5
+ module RailsAdminNext
6
+ EXTENSIONS = [] # rubocop:disable Style/MutableConstant
7
+ AUTHORIZATION_ADAPTERS = {} # rubocop:disable Style/MutableConstant
8
+ AUDITING_ADAPTERS = {} # rubocop:disable Style/MutableConstant
9
+ CONFIGURATION_ADAPTERS = {} # rubocop:disable Style/MutableConstant
10
+
11
+ # Extend RailsAdminNext
12
+ #
13
+ # The extension may define various adapters (e.g., for authorization) and
14
+ # register those via the options hash.
15
+ def self.add_extension(extension_key, extension_definition, options = {})
16
+ options.assert_valid_keys(:authorization, :configuration, :auditing)
17
+
18
+ EXTENSIONS << extension_key
19
+
20
+ AUTHORIZATION_ADAPTERS[extension_key] = extension_definition::AuthorizationAdapter if options[:authorization]
21
+
22
+ CONFIGURATION_ADAPTERS[extension_key] = extension_definition::ConfigurationAdapter if options[:configuration]
23
+
24
+ AUDITING_ADAPTERS[extension_key] = extension_definition::AuditingAdapter if options[:auditing]
25
+ end
26
+
27
+ # Setup all extensions for testing
28
+ def self.setup_all_extensions
29
+ (AUTHORIZATION_ADAPTERS.values + AUDITING_ADAPTERS.values).each do |klass|
30
+ klass.setup if klass.respond_to? :setup
31
+ rescue # rubocop:disable Style/RescueStandardError
32
+ # ignore errors
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAdminNext
4
+ module Extensions
5
+ module CanCanCan
6
+ # This adapter is for the CanCanCan[https://github.com/CanCanCommunity/cancancan] authorization library.
7
+ class AuthorizationAdapter
8
+ module ControllerExtension
9
+ def current_ability
10
+ # use _current_user instead of default current_user so it works with
11
+ # whatever current user method is defined with RailsAdminNext
12
+ @current_ability ||= ability_class.new(_current_user)
13
+ end
14
+ end
15
+
16
+ include RailsAdminNext::Config::Configurable
17
+
18
+ def self.setup
19
+ RailsAdminNext::Extensions::ControllerExtension.include ControllerExtension
20
+ end
21
+
22
+ # See the +authorize_with+ config method for where the initialization happens.
23
+ def initialize(controller, ability = nil, &block)
24
+ @controller = controller
25
+ ability_class { ability } if ability
26
+ instance_eval(&block) if block
27
+
28
+ adapter = self
29
+ ControllerExtension.define_method(:ability_class) do
30
+ adapter.ability_class
31
+ end
32
+ @controller.current_ability.authorize! :access, :rails_admin
33
+ end
34
+
35
+ register_instance_option :ability_class do
36
+ Ability
37
+ end
38
+
39
+ # This method is called in every controller action and should raise an exception
40
+ # when the authorization fails. The first argument is the name of the controller
41
+ # action as a symbol (:create, :bulk_delete, etc.). The second argument is the
42
+ # AbstractModel instance that applies. The third argument is the actual model
43
+ # instance if it is available.
44
+ def authorize(action, abstract_model = nil, model_object = nil)
45
+ return unless action
46
+
47
+ action, subject = resolve_action_and_subject(action, abstract_model, model_object)
48
+ @controller.current_ability.authorize!(action, subject)
49
+ end
50
+
51
+ # This method is called primarily from the view to determine whether the given user
52
+ # has access to perform the action on a given model. It should return true when authorized.
53
+ # This takes the same arguments as +authorize+. The difference is that this will
54
+ # return a boolean whereas +authorize+ will raise an exception when not authorized.
55
+ def authorized?(action, abstract_model = nil, model_object = nil)
56
+ return unless action
57
+
58
+ action, subject = resolve_action_and_subject(action, abstract_model, model_object)
59
+ @controller.current_ability.can?(action, subject)
60
+ end
61
+
62
+ # This is called when needing to scope a database query. It is called within the list
63
+ # and bulk_delete/destroy actions and should return a scope which limits the records
64
+ # to those which the user can perform the given action on.
65
+ def query(action, abstract_model)
66
+ abstract_model.model.accessible_by(@controller.current_ability, action)
67
+ end
68
+
69
+ # This is called in the new/create actions to determine the initial attributes for new
70
+ # records. It should return a hash of attributes which match what the user
71
+ # is authorized to create.
72
+ def attributes_for(action, abstract_model)
73
+ @controller.current_ability.attributes_for(action, abstract_model&.model)
74
+ end
75
+
76
+ private
77
+
78
+ def resolve_action_and_subject(action, abstract_model, model_object)
79
+ subject = model_object || abstract_model&.model
80
+ if subject
81
+ [action, subject]
82
+ else
83
+ # For :dashboard compatibility
84
+ [:read, action]
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_admin_next/extensions/cancancan/authorization_adapter"
4
+
5
+ RailsAdminNext.add_extension(:cancancan, RailsAdminNext::Extensions::CanCanCan, authorization: true)
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAdminNext
4
+ module Extensions
5
+ module ControllerExtension
6
+ end
7
+ end
8
+ end