admin_suite 0.2.7 → 0.2.9
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -1
- data/CONTRIBUTING.md +2 -2
- data/README.md +10 -24
- data/app/controllers/admin_suite/resources_controller.rb +14 -4
- data/app/helpers/admin_suite/base_helper.rb +56 -0
- data/app/javascript/admin_suite_application.js +44 -0
- data/app/javascript/controllers/admin_suite/dependent_searchable_select_controller.js +228 -0
- data/app/javascript/controllers/admin_suite/searchable_select_controller.js +1 -0
- data/app/views/admin_suite/resources/index.html.erb +3 -1
- data/app/views/admin_suite/resources/show.html.erb +2 -2
- data/app/views/layouts/admin_suite/application.html.erb +1 -0
- data/config/importmap.rb +1 -0
- data/lib/admin/base/filter_builder.rb +2 -1
- data/lib/admin/base/resource.rb +17 -4
- data/lib/admin_suite/ui/field_renderer_registry.rb +4 -0
- data/lib/admin_suite/version.rb +1 -1
- data/test/controllers/resources_controller_test.rb +80 -0
- data/test/integration/read_only_resource_test.rb +143 -0
- data/test/lib/resource_observability_extensions_test.rb +53 -0
- metadata +7 -14
- data/docs/README.md +0 -26
- data/docs/actions.md +0 -98
- data/docs/configuration.md +0 -284
- data/docs/development.md +0 -64
- data/docs/docs_viewer.md +0 -79
- data/docs/fields.md +0 -188
- data/docs/installation.md +0 -80
- data/docs/portals.md +0 -140
- data/docs/releasing.md +0 -67
- data/docs/resources.md +0 -237
- data/docs/theming.md +0 -63
- data/docs/troubleshooting.md +0 -50
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
module AdminSuite
|
|
6
|
+
class ResourcesControllerTest < ActiveSupport::TestCase
|
|
7
|
+
class TestController < ResourcesController
|
|
8
|
+
attr_writer :test_resource_config
|
|
9
|
+
attr_reader :filter_calls, :paginated_scope
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
super
|
|
13
|
+
@filter_calls = 0
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def resource_config
|
|
19
|
+
@test_resource_config
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def filtered_collection
|
|
23
|
+
@filter_calls += 1
|
|
24
|
+
{ total: 37 }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def paginate_collection(scope)
|
|
28
|
+
@paginated_scope = scope
|
|
29
|
+
[ Object.new, :paginated ]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class StatsResource < Admin::Base::Resource
|
|
34
|
+
index do
|
|
35
|
+
stats do
|
|
36
|
+
stat :legacy, -> { 11 }
|
|
37
|
+
stat :filtered, ->(scope) { scope.fetch(:total) }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class BrokenStatsResource < Admin::Base::Resource
|
|
43
|
+
index do
|
|
44
|
+
stats do
|
|
45
|
+
stat :broken, ->(_scope) { raise "boom" }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
test "stats preserve zero arity calculators and pass the filtered scope to one arity calculators" do
|
|
51
|
+
controller = TestController.new
|
|
52
|
+
controller.test_resource_config = StatsResource
|
|
53
|
+
scope = { total: 37 }
|
|
54
|
+
|
|
55
|
+
stats = controller.send(:calculate_stats, scope)
|
|
56
|
+
|
|
57
|
+
assert_equal 11, stats.first[:value]
|
|
58
|
+
assert_equal 37, stats.second[:value]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
test "stats preserve the existing calculator rescue behavior" do
|
|
62
|
+
controller = TestController.new
|
|
63
|
+
controller.test_resource_config = BrokenStatsResource
|
|
64
|
+
|
|
65
|
+
assert_equal "N/A", controller.send(:calculate_stats, Object.new).first[:value]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
test "index reuses one filtered unpaginated scope for stats and pagination" do
|
|
69
|
+
controller = TestController.new
|
|
70
|
+
controller.test_resource_config = StatsResource
|
|
71
|
+
|
|
72
|
+
controller.index
|
|
73
|
+
|
|
74
|
+
assert_equal 1, controller.filter_calls
|
|
75
|
+
assert_equal({ total: 37 }, controller.paginated_scope)
|
|
76
|
+
assert_equal 37, controller.instance_variable_get(:@stats).second[:value]
|
|
77
|
+
assert_equal :paginated, controller.instance_variable_get(:@collection)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
# The dummy app is intentionally database-free, while the generic controller
|
|
6
|
+
# supports Active Record hosts. Supply only the exception type its lookup path
|
|
7
|
+
# rescues so show-page behavior can be exercised with an in-memory fixture.
|
|
8
|
+
unless defined?(ActiveRecord::RecordNotFound)
|
|
9
|
+
module ActiveRecord
|
|
10
|
+
class RecordNotFound < StandardError; end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module TurboFrameTestHelper
|
|
15
|
+
def turbo_frame_tag(name, **options, &block)
|
|
16
|
+
content_tag(:turbo_frame, capture(&block), id: name, **options)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
ActionView::Base.include(TurboFrameTestHelper)
|
|
21
|
+
|
|
22
|
+
module ReadOnlyResourceFixtures
|
|
23
|
+
class Relation
|
|
24
|
+
include Enumerable
|
|
25
|
+
|
|
26
|
+
def initialize(records)
|
|
27
|
+
@records = records
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def each(&block)
|
|
31
|
+
@records.each(&block)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def count(*)
|
|
35
|
+
@records.count
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def offset(*)
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def limit(*)
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class Widget
|
|
48
|
+
extend ActiveModel::Naming
|
|
49
|
+
|
|
50
|
+
attr_reader :id, :name
|
|
51
|
+
|
|
52
|
+
def initialize(id: 1, name: "Observed widget")
|
|
53
|
+
@id = id
|
|
54
|
+
@name = name
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.all
|
|
58
|
+
Relation.new([ new ])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.column_names
|
|
62
|
+
%w[id name]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.primary_key
|
|
66
|
+
"id"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.columns_hash
|
|
70
|
+
{ "id" => Struct.new(:type).new(:integer) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.find(id)
|
|
74
|
+
raise ActiveRecord::RecordNotFound unless id.to_s == "1"
|
|
75
|
+
|
|
76
|
+
new
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def to_param
|
|
80
|
+
id.to_s
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def attributes
|
|
84
|
+
{ "id" => id, "name" => name }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
module Admin
|
|
90
|
+
module Resources
|
|
91
|
+
class ReadOnlyWidgetResource < Admin::Base::Resource
|
|
92
|
+
model ReadOnlyResourceFixtures::Widget
|
|
93
|
+
portal :ops
|
|
94
|
+
section :observability
|
|
95
|
+
read_only
|
|
96
|
+
|
|
97
|
+
index do
|
|
98
|
+
columns do
|
|
99
|
+
column :name
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
module AdminSuite
|
|
107
|
+
class ReadOnlyResourceTest < ActionDispatch::IntegrationTest
|
|
108
|
+
BASE_PATH = "/internal/admin_suite/ops/read_only_widgets"
|
|
109
|
+
|
|
110
|
+
test "direct built in mutation endpoints are rejected" do
|
|
111
|
+
get "#{BASE_PATH}/new"
|
|
112
|
+
assert_response :not_found
|
|
113
|
+
|
|
114
|
+
post BASE_PATH, params: { read_only_resource_fixtures_widget: { name: "changed" } }
|
|
115
|
+
assert_response :not_found
|
|
116
|
+
|
|
117
|
+
get "#{BASE_PATH}/1/edit"
|
|
118
|
+
assert_response :not_found
|
|
119
|
+
|
|
120
|
+
patch "#{BASE_PATH}/1", params: { read_only_resource_fixtures_widget: { name: "changed" } }
|
|
121
|
+
assert_response :not_found
|
|
122
|
+
|
|
123
|
+
delete "#{BASE_PATH}/1"
|
|
124
|
+
assert_response :not_found
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
test "index hides create and edit controls" do
|
|
128
|
+
get BASE_PATH
|
|
129
|
+
|
|
130
|
+
assert_response :success
|
|
131
|
+
assert_includes response.body, "Observed widget"
|
|
132
|
+
refute_includes response.body, "New Widget"
|
|
133
|
+
refute_match(/>\s*Edit\s*</, response.body)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
test "show mutation controls are conditional on write access" do
|
|
137
|
+
template = AdminSuite::Engine.root.join("app/views/admin_suite/resources/show.html.erb").read
|
|
138
|
+
|
|
139
|
+
assert_includes template, "has_edit_route = !resource_config.read_only?"
|
|
140
|
+
assert_includes template, "has_destroy_route = !resource_config.read_only?"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
module AdminSuite
|
|
6
|
+
class ResourceObservabilityExtensionsTest < ActiveSupport::TestCase
|
|
7
|
+
FakeScope = Struct.new(:filters) do
|
|
8
|
+
def where(*arguments)
|
|
9
|
+
self.class.new(filters + [ arguments ])
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class FilteredResource < Admin::Base::Resource
|
|
14
|
+
index do
|
|
15
|
+
filters do
|
|
16
|
+
filter :window, type: :select, default: "24h",
|
|
17
|
+
apply: ->(scope, value) { scope.where(window: value) }
|
|
18
|
+
filter :status, type: :select
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class ReadOnlyResource < Admin::Base::Resource
|
|
24
|
+
read_only
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
test "resources are writable by default and may be declared read only" do
|
|
28
|
+
refute Admin::Base::Resource.read_only?
|
|
29
|
+
assert ReadOnlyResource.read_only?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test "filter defaults apply when the parameter is absent" do
|
|
33
|
+
scope = Admin::Base::FilterBuilder.new(FilteredResource, ActionController::Parameters.new)
|
|
34
|
+
.apply(FakeScope.new([]))
|
|
35
|
+
|
|
36
|
+
assert_equal [ [ { window: "24h" } ] ], scope.filters
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
test "filter defaults apply when the parameter is blank and compose with explicit filters" do
|
|
40
|
+
params = ActionController::Parameters.new(window: "", status: "failed")
|
|
41
|
+
scope = Admin::Base::FilterBuilder.new(FilteredResource, params).apply(FakeScope.new([]))
|
|
42
|
+
|
|
43
|
+
assert_equal [ [ { window: "24h" } ], [ { status: "failed" } ] ], scope.filters
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
test "an explicit filter overrides its default" do
|
|
47
|
+
params = ActionController::Parameters.new(window: "7d")
|
|
48
|
+
scope = Admin::Base::FilterBuilder.new(FilteredResource, params).apply(FakeScope.new([]))
|
|
49
|
+
|
|
50
|
+
assert_equal [ [ { window: "7d" } ] ], scope.filters
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: admin_suite
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- TechWright Labs
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -136,9 +136,11 @@ files:
|
|
|
136
136
|
- app/helpers/admin_suite/panels_helper.rb
|
|
137
137
|
- app/helpers/admin_suite/resources_helper.rb
|
|
138
138
|
- app/helpers/admin_suite/theme_helper.rb
|
|
139
|
+
- app/javascript/admin_suite_application.js
|
|
139
140
|
- app/javascript/controllers/admin_suite/click_actions_controller.js
|
|
140
141
|
- app/javascript/controllers/admin_suite/clipboard_controller.js
|
|
141
142
|
- app/javascript/controllers/admin_suite/code_editor_controller.js
|
|
143
|
+
- app/javascript/controllers/admin_suite/dependent_searchable_select_controller.js
|
|
142
144
|
- app/javascript/controllers/admin_suite/file_upload_controller.js
|
|
143
145
|
- app/javascript/controllers/admin_suite/flash_controller.js
|
|
144
146
|
- app/javascript/controllers/admin_suite/json_editor_controller.js
|
|
@@ -171,18 +173,6 @@ files:
|
|
|
171
173
|
- app/views/layouts/admin_suite/application.html.erb
|
|
172
174
|
- config/importmap.rb
|
|
173
175
|
- config/routes.rb
|
|
174
|
-
- docs/README.md
|
|
175
|
-
- docs/actions.md
|
|
176
|
-
- docs/configuration.md
|
|
177
|
-
- docs/development.md
|
|
178
|
-
- docs/docs_viewer.md
|
|
179
|
-
- docs/fields.md
|
|
180
|
-
- docs/installation.md
|
|
181
|
-
- docs/portals.md
|
|
182
|
-
- docs/releasing.md
|
|
183
|
-
- docs/resources.md
|
|
184
|
-
- docs/theming.md
|
|
185
|
-
- docs/troubleshooting.md
|
|
186
176
|
- lib/admin/base/action_executor.rb
|
|
187
177
|
- lib/admin/base/action_handler.rb
|
|
188
178
|
- lib/admin/base/filter_builder.rb
|
|
@@ -207,6 +197,7 @@ files:
|
|
|
207
197
|
- lib/generators/admin_suite/scaffold/scaffold_generator.rb
|
|
208
198
|
- lib/tasks/admin_suite_tailwind.rake
|
|
209
199
|
- lib/tasks/admin_suite_test.rake
|
|
200
|
+
- test/controllers/resources_controller_test.rb
|
|
210
201
|
- test/dummy/Gemfile
|
|
211
202
|
- test/dummy/README.md
|
|
212
203
|
- test/dummy/Rakefile
|
|
@@ -251,9 +242,11 @@ files:
|
|
|
251
242
|
- test/fixtures/docs/progress/PROGRESS_REPORT.md
|
|
252
243
|
- test/integration/dashboard_test.rb
|
|
253
244
|
- test/integration/docs_test.rb
|
|
245
|
+
- test/integration/read_only_resource_test.rb
|
|
254
246
|
- test/integration/theme_test.rb
|
|
255
247
|
- test/lib/action_executor_test.rb
|
|
256
248
|
- test/lib/markdown_renderer_test.rb
|
|
249
|
+
- test/lib/resource_observability_extensions_test.rb
|
|
257
250
|
- test/lib/theme_palette_test.rb
|
|
258
251
|
- test/lib/zeitwerk_integration_test.rb
|
|
259
252
|
- test/test_helper.rb
|
data/docs/README.md
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# AdminSuite Documentation
|
|
2
|
-
|
|
3
|
-
AdminSuite is a mountable Rails engine that provides:
|
|
4
|
-
|
|
5
|
-
- A **resource DSL** for CRUD + search/sort/filter + show/form configuration
|
|
6
|
-
- A **portal system** (navigation + optional portal dashboards)
|
|
7
|
-
- A built-in **docs viewer** (renders Markdown from your host app filesystem)
|
|
8
|
-
- A small baseline **UI layer** (Tailwind optional)
|
|
9
|
-
|
|
10
|
-
## Getting started
|
|
11
|
-
|
|
12
|
-
- [Installation](installation.md)
|
|
13
|
-
- [Configuration](configuration.md)
|
|
14
|
-
- [Portals & dashboards](portals.md)
|
|
15
|
-
- [Resources](resources.md)
|
|
16
|
-
- [Fields](fields.md)
|
|
17
|
-
- [Actions](actions.md)
|
|
18
|
-
- [Theming & assets](theming.md)
|
|
19
|
-
- [Docs viewer](docs_viewer.md)
|
|
20
|
-
- [Troubleshooting](troubleshooting.md)
|
|
21
|
-
|
|
22
|
-
## Contributing / maintainers
|
|
23
|
-
|
|
24
|
-
- [Development](development.md)
|
|
25
|
-
- [Releasing](releasing.md)
|
|
26
|
-
|
data/docs/actions.md
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# Actions
|
|
2
|
-
|
|
3
|
-
AdminSuite supports three action “shapes” in the resource DSL:
|
|
4
|
-
|
|
5
|
-
- `action` (member action on a single record)
|
|
6
|
-
- `bulk_action` (runs across selected records)
|
|
7
|
-
- `collection_action` (runs on a scope / collection)
|
|
8
|
-
|
|
9
|
-
## Defining actions
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
actions do
|
|
13
|
-
action :reindex, label: "Reindex", method: :post, confirm: "Reindex this record?"
|
|
14
|
-
bulk_action :archive, label: "Archive", confirm: "Archive selected records?"
|
|
15
|
-
end
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Supported action options:
|
|
19
|
-
|
|
20
|
-
- `method:` HTTP method (default `:post`)
|
|
21
|
-
- `label:` button label (default is humanized action name)
|
|
22
|
-
- `icon:` lucide icon name (optional)
|
|
23
|
-
- `color:` (optional)
|
|
24
|
-
- `confirm:` string confirmation (optional)
|
|
25
|
-
- `type:` reserved (default `:button`)
|
|
26
|
-
- `if:` Proc condition (member actions only)
|
|
27
|
-
- `unless:` Proc condition (member actions only)
|
|
28
|
-
|
|
29
|
-
## How actions execute
|
|
30
|
-
|
|
31
|
-
When you trigger an action, AdminSuite resolves behavior in this order:
|
|
32
|
-
|
|
33
|
-
1. **Model method**: if the target responds to `action_name`, it calls that method.
|
|
34
|
-
2. **Bang model method**: else if it responds to `action_name!`, it calls that.
|
|
35
|
-
3. **Action handler class**: else it tries to find a handler class.
|
|
36
|
-
|
|
37
|
-
### Handler class naming convention
|
|
38
|
-
|
|
39
|
-
By default, AdminSuite looks for:
|
|
40
|
-
|
|
41
|
-
```ruby
|
|
42
|
-
Admin::Actions::<ResourceName><ActionName>Action
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Example for `UserResource` + `:reset_password`:
|
|
46
|
-
|
|
47
|
-
```ruby
|
|
48
|
-
Admin::Actions::UserResetPasswordAction
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Handlers should inherit from `Admin::Base::ActionHandler`:
|
|
52
|
-
|
|
53
|
-
```ruby
|
|
54
|
-
# app/admin/actions/user_reset_password_action.rb
|
|
55
|
-
module Admin
|
|
56
|
-
module Actions
|
|
57
|
-
class UserResetPasswordAction < Admin::Base::ActionHandler
|
|
58
|
-
def call
|
|
59
|
-
# record is available as `record`, actor as `actor`, request params as `params`
|
|
60
|
-
record.send_reset_password_instructions!
|
|
61
|
-
success "Reset email sent."
|
|
62
|
-
rescue StandardError => e
|
|
63
|
-
failure "Failed to send reset: #{e.message}"
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Overriding handler resolution (`config.resolve_action_handler`)
|
|
71
|
-
|
|
72
|
-
If your app doesn’t want to follow the default naming convention, you can provide a resolver:
|
|
73
|
-
|
|
74
|
-
```ruby
|
|
75
|
-
AdminSuite.configure do |config|
|
|
76
|
-
config.resolve_action_handler = ->(resource_class, action_name) do
|
|
77
|
-
# return a Class or nil
|
|
78
|
-
if resource_class.name == "Admin::Resources::UserResource" && action_name.to_sym == :reset_password
|
|
79
|
-
Admin::Actions::UserResetPasswordAction
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## Auditing hook (`config.on_action_executed`)
|
|
86
|
-
|
|
87
|
-
You can record or log all actions after they run:
|
|
88
|
-
|
|
89
|
-
```ruby
|
|
90
|
-
AdminSuite.configure do |config|
|
|
91
|
-
config.on_action_executed = ->(actor:, action_name:, resource_class:, subject:, params:, result:) do
|
|
92
|
-
Rails.logger.info(
|
|
93
|
-
"[admin_suite] actor=#{actor&.id} action=#{resource_class.name}##{action_name} success=#{result.success?}"
|
|
94
|
-
)
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
```
|
|
98
|
-
|