admin_suite 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f7c1be0b2abc25afb2d0e162c35694d27c03acb1c0db9aa5f7c75fdf9f5a0de
4
- data.tar.gz: 5f969a9e650ccc2e135334658c00bda4fe59918d8b4f8402a43c7207ffd5b0cb
3
+ metadata.gz: fa02eb4d89f039fb322c1d967a8a8183d7a8d08073a1ca5efa3162722a44d4ad
4
+ data.tar.gz: e266e08457f21f178029a2321b0bef62826ae2ff17ff4ddee5163a856aabc807
5
5
  SHA512:
6
- metadata.gz: afadac82d6a9e20a8b6416c1299e3cba56fc4f0cab22ca33987498c7809e44167743636dc991acf08d2d6d142b5eac4132a8f8fafb3f79cd7c1dd697b12ba861
7
- data.tar.gz: d2dac896f3b213c9058ec702a9673201d4561a793105c2f552afa6a79c7d4896b716372737a08db4eb0c5230b5a7a1d719f9d98f20c994143f9393c8b52005dc
6
+ metadata.gz: 5180c720a4df074f35dfe87133beb75cdaca7e0b37d104d98bfd362366fd52c79f531510e8f6af1c149fce616687d9515727d5238c29881564658c2a4f51c831
7
+ data.tar.gz: 92a32f372ae2a098421b322da249fd8139710d8ebd1c9b598c105305ae95c8b0340c2d0976190dba529687e089877c8d4f703aa3ea66d8524837ca915d71aa29
data/.gitignore CHANGED
@@ -8,3 +8,4 @@
8
8
  !/test/dummy/log/.keep
9
9
  /test/dummy/tmp/
10
10
  /test/dummy/storage/
11
+ admin_suite-*.gem
data/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-02-04
11
+
12
+ ### Added
13
+
14
+ - Initial extraction of the AdminSuite Rails engine.
15
+ - Resource/portal DSL, docs viewer, and theming primitives.
16
+ - Isolated gem test suite with a dummy Rails app.
17
+
data/README.md CHANGED
@@ -1,7 +1,143 @@
1
1
  # AdminSuite
2
2
 
3
- A mountable Rails engine that provides a resource-based admin UI and generators.
3
+ A mountable Rails engine that provides a **resource-based admin UI** (CRUD + search/filter/sort),
4
+ a **portal/dashboard system**, and a built-in **Markdown docs viewer**.
4
5
 
5
6
  This engine is currently extracted from the Gleania app and is intended to be reused
6
7
  across other products.
7
8
 
9
+ ## Features
10
+
11
+ - **Portals**: group resources by portal + section, optional per-portal dashboards
12
+ - **Resources DSL**: index (columns/filters/stats), form fields, show panels/associations, actions
13
+ - **Docs viewer**: renders `*.md` from your host app filesystem at `/docs`
14
+ - **UI**: baseline CSS + engine Tailwind build; host overrides optional
15
+
16
+ ## Documentation
17
+
18
+ Start here:
19
+
20
+ - `docs/README.md` (index)
21
+
22
+ Read more:
23
+
24
+ - `docs/installation.md`
25
+ - `docs/configuration.md`
26
+ - `docs/portals.md`
27
+ - `docs/resources.md`
28
+ - `docs/fields.md`
29
+ - `docs/actions.md`
30
+ - `docs/theming.md`
31
+ - `docs/docs_viewer.md`
32
+ - `docs/troubleshooting.md`
33
+
34
+ ## Quickstart
35
+
36
+ Add the gem:
37
+
38
+ ```ruby
39
+ # Gemfile
40
+ gem "admin_suite"
41
+ ```
42
+
43
+ Install and generate the initializer + mount:
44
+
45
+ ```bash
46
+ bundle install
47
+ bin/rails g admin_suite:install
48
+ ```
49
+
50
+ By default, the engine mounts at `/internal/admin`. You can customize it:
51
+
52
+ ```bash
53
+ bin/rails g admin_suite:install --mount-path=/internal/admin
54
+ ```
55
+
56
+ ### Secure it (recommended)
57
+
58
+ Set `config.authenticate` so only authorized users can access AdminSuite:
59
+
60
+ ```ruby
61
+ # config/initializers/admin_suite.rb
62
+ AdminSuite.configure do |config|
63
+ config.authenticate = ->(controller) do
64
+ user = controller.respond_to?(:current_user) ? controller.current_user : nil
65
+ controller.head(:forbidden) unless user&.admin?
66
+ end
67
+ end
68
+ ```
69
+
70
+ Read more: `docs/configuration.md`
71
+
72
+ ### Add portals (navigation metadata)
73
+
74
+ ```ruby
75
+ AdminSuite.configure do |config|
76
+ config.portals = {
77
+ ops: { label: "Ops", icon: "settings", color: :amber, order: 10 },
78
+ ai: { label: "AI", icon: "cpu", color: :cyan, order: 20 }
79
+ }
80
+ end
81
+ ```
82
+
83
+ Read more: `docs/portals.md`
84
+
85
+ ### Add a resource
86
+
87
+ Place resource definitions under one of the default globs (recommended):
88
+
89
+ - `config/admin_suite/resources/*.rb`
90
+
91
+ Example:
92
+
93
+ ```ruby
94
+ # config/admin_suite/resources/user.rb
95
+ module Admin
96
+ module Resources
97
+ class UserResource < Admin::Base::Resource
98
+ model ::User
99
+ portal :ops
100
+ section :accounts
101
+
102
+ index do
103
+ searchable :email, :name
104
+ sortable :created_at, default: :created_at, direction: :desc
105
+
106
+ columns do
107
+ column :id
108
+ column :email
109
+ column :created_at
110
+ end
111
+ end
112
+
113
+ form do
114
+ field :email, type: :email, required: true
115
+ field :name, required: true
116
+ end
117
+ end
118
+ end
119
+ end
120
+ ```
121
+
122
+ Read more: `docs/resources.md` and `docs/fields.md`
123
+
124
+ ### Add docs (optional)
125
+
126
+ Create markdown files in your host app:
127
+
128
+ - `docs/*.md` (or set `config.docs_path`)
129
+
130
+ Then visit:
131
+
132
+ - `/internal/admin/docs`
133
+
134
+ Read more: `docs/docs_viewer.md`
135
+
136
+ ## Contributing
137
+
138
+ See:
139
+
140
+ - `CONTRIBUTING.md`
141
+ - `docs/development.md`
142
+ - `docs/releasing.md`
143
+
data/docs/README.md ADDED
@@ -0,0 +1,26 @@
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 ADDED
@@ -0,0 +1,98 @@
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
+
@@ -0,0 +1,198 @@
1
+ # Configuration
2
+
3
+ AdminSuite is configured via an initializer:
4
+
5
+ - `config/initializers/admin_suite.rb` (generated by `bin/rails g admin_suite:install`)
6
+
7
+ All configuration lives on `AdminSuite.config` (an `AdminSuite::Configuration` instance).
8
+
9
+ ## Minimal secure configuration
10
+
11
+ ```ruby
12
+ # config/initializers/admin_suite.rb
13
+ AdminSuite.configure do |config|
14
+ config.authenticate = ->(controller) do
15
+ # Example: require an admin user
16
+ controller.redirect_to(controller.main_app.root_path) unless controller.respond_to?(:current_user) && controller.current_user&.admin?
17
+ end
18
+
19
+ config.current_actor = ->(controller) do
20
+ controller.respond_to?(:current_user) ? controller.current_user : nil
21
+ end
22
+ end
23
+ ```
24
+
25
+ ## Defaults
26
+
27
+ These are the defaults in `AdminSuite::Configuration` / `AdminSuite::Engine`:
28
+
29
+ - `authenticate`: `nil`
30
+ - `current_actor`: `nil`
31
+ - `authorize`: `nil`
32
+ - `resource_globs`: defaults to:
33
+ - `Rails.root/config/admin_suite/resources/*.rb`
34
+ - `Rails.root/app/admin/resources/*.rb`
35
+ - `portal_globs`: defaults to:
36
+ - `Rails.root/config/admin_suite/portals/*.rb`
37
+ - `Rails.root/app/admin/portals/*.rb`
38
+ - `Rails.root/app/admin_suite/portals/*.rb`
39
+ - `portals`: default portal metadata for `:ops`, `:email`, `:ai`, `:assistant`
40
+ - `custom_renderers`: `{}`
41
+ - `icon_renderer`: `nil` (uses lucide-rails by default when available)
42
+ - `docs_url`: `nil`
43
+ - `docs_path`: `Rails.root.join("docs")`
44
+ - `partials`: `{}`
45
+ - `theme`: `{ primary: :indigo, secondary: :purple }`
46
+ - `host_stylesheet`: `nil`
47
+ - `tailwind_cdn`: `true`
48
+ - `on_action_executed`: `nil`
49
+ - `resolve_action_handler`: `nil`
50
+
51
+ ## Options
52
+
53
+ ### `authenticate`
54
+
55
+ Called as a `before_action` inside the engine.
56
+
57
+ - **Type**: `Proc` or `nil`
58
+ - **Signature**: `->(controller) { ... }`
59
+
60
+ If you don’t set it, AdminSuite will be accessible to any user that can reach the mounted route.
61
+
62
+ ### `current_actor`
63
+
64
+ Used by actions/auditing hooks to identify “who initiated this”.
65
+
66
+ - **Type**: `Proc` or `nil`
67
+ - **Signature**: `->(controller) { current_user }`
68
+
69
+ ### `authorize`
70
+
71
+ Optional authorization hook (you can wire Pundit/CanCan/ActionPolicy/etc).
72
+
73
+ - **Type**: `Proc` or `nil`
74
+ - **Signature**: `->(actor, action:, subject:, resource:, controller:) { true/false }`
75
+
76
+ Note: this hook is available, but your app must call it from resource definitions / custom actions as needed (AdminSuite will not guess your authorization policy).
77
+
78
+ ### `resource_globs`
79
+
80
+ Where AdminSuite should load resource definition files from.
81
+
82
+ - **Type**: `Array<String>`
83
+
84
+ Example:
85
+
86
+ ```ruby
87
+ config.resource_globs = [
88
+ Rails.root.join("app/admin/resources/**/*.rb").to_s
89
+ ]
90
+ ```
91
+
92
+ ### `portal_globs`
93
+
94
+ Where AdminSuite should load portal definition files from (files typically call `AdminSuite.portal(:key) { ... }`).
95
+
96
+ - **Type**: `Array<String>`
97
+
98
+ ### `portals`
99
+
100
+ Portal metadata used for navigation (label/icon/color/order). This is separate from the portal DSL and can be used alone.
101
+
102
+ - **Type**: `Hash{Symbol => Hash}`
103
+
104
+ Example:
105
+
106
+ ```ruby
107
+ config.portals = {
108
+ ops: { label: "Ops", icon: "settings", color: :amber, order: 10 },
109
+ billing: { label: "Billing", icon: "credit-card", color: :emerald, order: 20 }
110
+ }
111
+ ```
112
+
113
+ ### `theme`
114
+
115
+ Two-color theme used to set CSS variables scoped to AdminSuite.
116
+
117
+ - **Type**: `Hash` with `:primary` and `:secondary`
118
+ - **Values**: Tailwind-ish color names (`:indigo`, `:emerald`, …) or a hex string (`"#4f46e5"`)
119
+
120
+ See [Theming & assets](theming.md).
121
+
122
+ ### `host_stylesheet`
123
+
124
+ If set, AdminSuite will include your host app stylesheet **after** its own styles in the engine layout.
125
+
126
+ - **Type**: `Symbol` or `String` (passed to `stylesheet_link_tag`)
127
+ - **Example**: `config.host_stylesheet = :app`
128
+
129
+ ### `tailwind_cdn`
130
+
131
+ Reserved for host setups that want a CDN fallback. (AdminSuite already builds its own Tailwind CSS into your host app during `assets:precompile`.)
132
+
133
+ - **Type**: `true/false`
134
+
135
+ ### `docs_url`
136
+
137
+ If set, shows a “Docs” link in the AdminSuite sidebar.
138
+
139
+ - **Type**: `String` or `nil`
140
+
141
+ ### `docs_path`
142
+
143
+ Filesystem path where the docs viewer reads markdown from.
144
+
145
+ - **Type**: `Pathname`, `String`, or `Proc`
146
+ - **Proc signature**: `->(controller) { Rails.root.join("docs") }`
147
+
148
+ ### `partials`
149
+
150
+ Override specific engine partials.
151
+
152
+ - **Type**: `Hash`
153
+
154
+ Example:
155
+
156
+ ```ruby
157
+ config.partials[:flash] = "shared/flash"
158
+ config.partials[:panel_stat] = "admin/panels/stat"
159
+ ```
160
+
161
+ ### `custom_renderers`
162
+
163
+ Register custom show-section renderers (used when a show panel uses `render: :your_key`).
164
+
165
+ - **Type**: `Hash{Symbol => Proc}`
166
+ - **Proc signature**: `->(record, view_context) { ... }`
167
+
168
+ Example:
169
+
170
+ ```ruby
171
+ config.custom_renderers[:billing_snapshot] = ->(record, view) do
172
+ view.render(partial: "admin/billing_snapshot", locals: { record: record })
173
+ end
174
+ ```
175
+
176
+ ### `icon_renderer`
177
+
178
+ Replace the default icon provider (lucide-rails).
179
+
180
+ - **Type**: `Proc` or `nil`
181
+ - **Proc signature**: `->(name, view_context, **opts) { ... }`
182
+
183
+ ### `resolve_action_handler`
184
+
185
+ Override how AdminSuite finds action handler classes.
186
+
187
+ - **Type**: `Proc` or `nil`
188
+ - **Proc signature**: `->(resource_class, action_name) { handler_class_or_nil }`
189
+
190
+ See [Actions](actions.md).
191
+
192
+ ### `on_action_executed`
193
+
194
+ Hook called after action execution (success or failure).
195
+
196
+ - **Type**: `Proc` or `nil`
197
+ - **Proc signature**: `->(actor:, action_name:, resource_class:, subject:, params:, result:) { ... }`
198
+
@@ -0,0 +1,64 @@
1
+ # Development
2
+
3
+ This page is intended for contributors/maintainers working on the engine itself.
4
+
5
+ ## Setup
6
+
7
+ From the gem root:
8
+
9
+ ```bash
10
+ bundle install
11
+ ```
12
+
13
+ ## Run tests
14
+
15
+ ```bash
16
+ bundle exec rake test
17
+ ```
18
+
19
+ ## Dummy app
20
+
21
+ AdminSuite uses a Rails “dummy” app under `test/dummy` for integration tests and to
22
+ exercise routing/assets in a host-like environment.
23
+
24
+ Useful commands (from the gem root):
25
+
26
+ ```bash
27
+ cd test/dummy
28
+ bin/rails s
29
+ ```
30
+
31
+ ## Assets / Tailwind
32
+
33
+ AdminSuite ships:
34
+
35
+ - `app/assets/admin_suite.css` (baseline CSS)
36
+ - `app/assets/tailwind/admin_suite.css` (Tailwind input)
37
+
38
+ The engine Tailwind build task writes the compiled CSS into the **host app** builds folder:
39
+
40
+ - Output: `Rails.root/app/assets/builds/admin_suite_tailwind.css`
41
+
42
+ In a host app, this is run automatically during `assets:precompile`:
43
+
44
+ ```bash
45
+ bin/rails admin_suite:tailwind:build
46
+ ```
47
+
48
+ When developing inside the engine repo itself, you can run it from the dummy app:
49
+
50
+ ```bash
51
+ cd test/dummy
52
+ bin/rails admin_suite:tailwind:build
53
+ ```
54
+
55
+ ## Docs
56
+
57
+ Engine docs live under:
58
+
59
+ - `docs/`
60
+
61
+ The docs viewer feature in the engine reads from the host app by default:
62
+
63
+ - `Rails.root/docs` (configurable via `AdminSuite.config.docs_path`)
64
+
@@ -0,0 +1,79 @@
1
+ # Docs viewer
2
+
3
+ AdminSuite includes a built-in docs viewer at:
4
+
5
+ - `/docs` (relative to the mount path)
6
+
7
+ It renders Markdown (`.md`) files from a folder on your host app filesystem.
8
+
9
+ ## Quick start
10
+
11
+ 1. Create `docs/` in your host app:
12
+
13
+ ```bash
14
+ mkdir -p docs
15
+ ```
16
+
17
+ 2. Add a markdown file:
18
+
19
+ ```md
20
+ <!-- docs/getting_started.md -->
21
+ # Getting started
22
+
23
+ Hello from AdminSuite docs.
24
+ ```
25
+
26
+ 3. Visit:
27
+
28
+ - `/internal/admin/docs`
29
+
30
+ ## Configuring the docs root (`config.docs_path`)
31
+
32
+ By default:
33
+
34
+ - `AdminSuite.config.docs_path = Rails.root.join("docs")`
35
+
36
+ You can point it somewhere else:
37
+
38
+ ```ruby
39
+ AdminSuite.configure do |config|
40
+ config.docs_path = Rails.root.join("admin_docs")
41
+ end
42
+ ```
43
+
44
+ Or compute per-request:
45
+
46
+ ```ruby
47
+ AdminSuite.configure do |config|
48
+ config.docs_path = ->(_controller) { Rails.root.join("docs") }
49
+ end
50
+ ```
51
+
52
+ ## Sidebar “Docs” link (`config.docs_url`)
53
+
54
+ If you want a persistent docs link in the AdminSuite sidebar, set:
55
+
56
+ ```ruby
57
+ AdminSuite.configure do |config|
58
+ config.docs_url = "/internal/admin/docs"
59
+ end
60
+ ```
61
+
62
+ This can also point to external docs.
63
+
64
+ ## Organization
65
+
66
+ Docs are grouped by their first folder name. For example:
67
+
68
+ - `docs/ops/runbooks.md` → group “Ops”
69
+ - `docs/api/authentication.md` → group “API”
70
+ - `docs/getting_started.md` → group “Docs”
71
+
72
+ ## Security notes
73
+
74
+ The docs viewer defends against path traversal:
75
+
76
+ - Rejects any path containing `..`
77
+ - Requires a `.md` extension
78
+ - Resolves realpaths and ensures the requested file stays under the docs root
79
+