admin_suite 0.2.8 → 0.3.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -1
  3. data/CONTRIBUTING.md +2 -2
  4. data/Gemfile +3 -0
  5. data/README.md +10 -24
  6. data/app/controllers/admin_suite/application_controller.rb +53 -11
  7. data/app/controllers/admin_suite/resources_controller.rb +63 -9
  8. data/app/views/admin_suite/resources/index.html.erb +3 -1
  9. data/app/views/admin_suite/resources/show.html.erb +2 -2
  10. data/lib/admin/base/filter_builder.rb +2 -1
  11. data/lib/admin/base/resource.rb +14 -2
  12. data/lib/admin_suite/auth/host_hook.rb +25 -0
  13. data/lib/admin_suite/auth/http_basic.rb +46 -0
  14. data/lib/admin_suite/auth/strategy.rb +25 -0
  15. data/lib/admin_suite/auth.rb +33 -0
  16. data/lib/admin_suite/configuration.rb +8 -0
  17. data/lib/admin_suite/ui/show_value_formatter.rb +2 -2
  18. data/lib/admin_suite/version.rb +1 -1
  19. data/lib/admin_suite.rb +16 -0
  20. data/lib/generators/admin_suite/install/templates/admin_suite.rb +24 -7
  21. data/test/controllers/resources_controller_test.rb +80 -0
  22. data/test/dummy/config/initializers/admin_suite_auth.rb +8 -0
  23. data/test/integration/authentication_test.rb +99 -0
  24. data/test/integration/authorization_test.rb +131 -0
  25. data/test/integration/read_only_resource_test.rb +130 -0
  26. data/test/lib/auth_http_basic_test.rb +62 -0
  27. data/test/lib/auth_resolution_test.rb +75 -0
  28. data/test/lib/auth_test.rb +29 -0
  29. data/test/lib/resource_observability_extensions_test.rb +53 -0
  30. data/test/test_helper.rb +43 -0
  31. metadata +15 -14
  32. data/docs/README.md +0 -26
  33. data/docs/actions.md +0 -98
  34. data/docs/configuration.md +0 -284
  35. data/docs/development.md +0 -64
  36. data/docs/docs_viewer.md +0 -79
  37. data/docs/fields.md +0 -188
  38. data/docs/installation.md +0 -80
  39. data/docs/portals.md +0 -140
  40. data/docs/releasing.md +0 -67
  41. data/docs/resources.md +0 -237
  42. data/docs/theming.md +0 -63
  43. data/docs/troubleshooting.md +0 -50
data/docs/docs_viewer.md DELETED
@@ -1,79 +0,0 @@
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
-
data/docs/fields.md DELETED
@@ -1,188 +0,0 @@
1
- # Fields
2
-
3
- Fields are defined in the resource `form do ... end` block:
4
-
5
- ```ruby
6
- form do
7
- field :name
8
- field :status, type: :select, collection: [["Active", "active"], ["Inactive", "inactive"]]
9
- end
10
- ```
11
-
12
- ## Common options
13
-
14
- All fields support:
15
-
16
- - `type:` (defaults to `:text`)
17
- - `required:` (`true/false`)
18
- - `label:` (String)
19
- - `help:` (String)
20
- - `placeholder:` (String)
21
- - `readonly:` (`true/false`)
22
- - `if:` Proc (render only if truthy)
23
- - `unless:` Proc (render only if falsy)
24
-
25
- Example conditional field:
26
-
27
- ```ruby
28
- field :admin_notes, type: :textarea, if: ->(record) { record.admin? }
29
- ```
30
-
31
- ## Supported field types
32
-
33
- ### Text-like
34
-
35
- - `:text` (default)
36
- - `:textarea` (`rows:` supported)
37
- - `:email`
38
- - `:url`
39
- - `:number`
40
- - `:date`
41
- - `:time`
42
- - `:datetime`
43
-
44
- ### Toggle
45
-
46
- - `:toggle` (renders a switch)
47
-
48
- ```ruby
49
- field :enabled, type: :toggle
50
- ```
51
-
52
- ### Select
53
-
54
- - `:select` (uses Rails `select`)
55
-
56
- Options:
57
-
58
- - `collection:` Array of `[label, value]` or simple values
59
-
60
- ```ruby
61
- field :status, type: :select, collection: [["Active", "active"], ["Inactive", "inactive"]]
62
- ```
63
-
64
- ### Searchable select
65
-
66
- - `:searchable_select` (Stimulus-powered searchable dropdown)
67
-
68
- Options:
69
-
70
- - `collection:` either:
71
- - an Array (static options), or
72
- - a String URL (advanced; used by the JS controller as a “search URL”)
73
- - `create_url:` (String) enables “creatable” behavior in the UI
74
-
75
- ```ruby
76
- field :company_id,
77
- type: :searchable_select,
78
- collection: Company.order(:name).pluck(:name, :id),
79
- placeholder: "Search companies..."
80
- ```
81
-
82
- ### Multi-select & tags
83
-
84
- - `:multi_select`
85
- - `:tags`
86
-
87
- Options:
88
-
89
- - `collection:` Array of options (used for suggestions)
90
- - `create_url:` enables “creatable” behavior
91
- - `multiple:` boolean (reserved; arrays are permitted automatically)
92
-
93
- Notes:
94
-
95
- - These submit arrays and are permitted automatically by AdminSuite.
96
- - For `:tags`, AdminSuite uses a `tag_list` parameter by default (or `#{field_name}_list` if your model exposes it).
97
-
98
- ```ruby
99
- field :tag_list, type: :tags, placeholder: "Add tags..."
100
- field :roles, type: :multi_select, collection: %w[admin editor viewer]
101
- ```
102
-
103
- ### File uploads / attachments
104
-
105
- - `:file`
106
- - `:attachment`
107
- - `:image`
108
-
109
- Options:
110
-
111
- - `accept:` MIME accept string (e.g. `"image/*"`, `"application/pdf"`)
112
-
113
- These assume your host app uses **Active Storage**.
114
-
115
- ```ruby
116
- field :avatar, type: :image, accept: "image/*"
117
- field :resume, type: :file, accept: "application/pdf"
118
- ```
119
-
120
- ### Rich text
121
-
122
- - `:trix`
123
- - `:rich_text`
124
-
125
- These assume your host app uses **Action Text**.
126
-
127
- ```ruby
128
- field :bio, type: :rich_text
129
- ```
130
-
131
- ### Markdown
132
-
133
- - `:markdown` (textarea enhanced by EasyMDE via CDN in the engine layout)
134
-
135
- ```ruby
136
- field :prompt_template, type: :markdown, rows: 16
137
- ```
138
-
139
- ### JSON editor
140
-
141
- - `:json` (renders the engine’s JSON editor partial)
142
-
143
- ```ruby
144
- field :settings, type: :json
145
- ```
146
-
147
- ### Code editor
148
-
149
- - `:code` (monospace editor container; enhanced by engine JS)
150
-
151
- ```ruby
152
- field :ruby_code, type: :code, rows: 20
153
- ```
154
-
155
- ### Label (read-only)
156
-
157
- - `:label` renders a badge-like value (useful for status fields)
158
-
159
- Options:
160
-
161
- - `label_color:` Symbol or Proc
162
- - `label_size:` `:sm`/`:md` or Proc
163
-
164
- ```ruby
165
- field :status, type: :label, label_color: ->(r) { r.active? ? :emerald : :slate }, label_size: :sm
166
- ```
167
-
168
- ## Layout helpers
169
-
170
- Inside `form do ... end` you can group fields:
171
-
172
- ### `section`
173
-
174
- ```ruby
175
- section "Billing", description: "Payment settings", collapsible: true do
176
- field :stripe_customer_id, readonly: true
177
- end
178
- ```
179
-
180
- ### `row`
181
-
182
- ```ruby
183
- row cols: 2 do
184
- field :first_name
185
- field :last_name
186
- end
187
- ```
188
-
data/docs/installation.md DELETED
@@ -1,80 +0,0 @@
1
- # Installation
2
-
3
- ## Requirements
4
-
5
- - Ruby **3.2+**
6
- - Rails **8.0+**
7
-
8
- AdminSuite is a mountable engine. You mount it under a path in your host app’s `config/routes.rb`.
9
-
10
- ## Add the gem
11
-
12
- In your host app `Gemfile`:
13
-
14
- ```ruby
15
- gem "admin_suite"
16
- ```
17
-
18
- Then:
19
-
20
- ```bash
21
- bundle install
22
- ```
23
-
24
- ## Install into your app
25
-
26
- Run the install generator (creates an initializer and mounts the engine):
27
-
28
- ```bash
29
- bin/rails g admin_suite:install
30
- ```
31
-
32
- To mount at a custom path:
33
-
34
- ```bash
35
- bin/rails g admin_suite:install --mount-path=/internal/admin
36
- ```
37
-
38
- This will:
39
-
40
- - Create `config/initializers/admin_suite.rb`
41
- - Add a route like `mount AdminSuite::Engine => "/internal/admin"`
42
-
43
- ## First run checklist
44
-
45
- - **Auth**: set `config.authenticate` so only permitted users can access AdminSuite.
46
- - **Actor**: set `config.current_actor` if you want actions/auditing to know “who did it”.
47
- - **Resources**: add at least one resource definition file (see [Resources](resources.md)).
48
- - **Portals**: set up portal metadata and dashboards (see [Portals](portals.md)).
49
-
50
- ## Assets (CSS/JS)
51
-
52
- AdminSuite ships with:
53
-
54
- - A small baseline stylesheet (`admin_suite.css`)
55
- - A compiled Tailwind stylesheet (`admin_suite_tailwind.css`) that is built into the host app at asset precompile time
56
- - Engine-provided Stimulus controllers via importmap
57
-
58
- ### CSS build behavior
59
-
60
- When your app runs `assets:precompile`, AdminSuite automatically runs:
61
-
62
- - `admin_suite:tailwind:build` → writes `app/assets/builds/admin_suite_tailwind.css` in your **host app**
63
-
64
- So in production you typically just need to ensure your deployment runs `assets:precompile` as usual.
65
-
66
- ### Host stylesheet overrides (optional)
67
-
68
- If your app already uses Tailwind (or you want custom branding), you can include your app stylesheet after AdminSuite:
69
-
70
- - Set `config.host_stylesheet` (see [Theming & assets](theming.md))
71
-
72
- ## Routes and URLs
73
-
74
- Assuming you mounted at `/internal/admin`:
75
-
76
- - `/internal/admin` → AdminSuite dashboard
77
- - `/internal/admin/docs` → docs viewer (optional)
78
- - `/internal/admin/:portal` → portal dashboard (optional)
79
- - `/internal/admin/:portal/:resource_name` → resource index
80
-
data/docs/portals.md DELETED
@@ -1,140 +0,0 @@
1
- # Portals & dashboards
2
-
3
- AdminSuite navigation is organized by **portal** and **section**:
4
-
5
- - A **portal** is the top-level grouping (e.g. `:ops`, `:ai`)
6
- - A **section** is a grouping within a portal (e.g. `:billing`, `:users`)
7
- - A **resource** belongs to exactly one portal + section via the resource DSL
8
-
9
- You can configure portals in two complementary ways:
10
-
11
- 1. **Portal metadata** via `AdminSuite.config.portals` (label/icon/color/order)
12
- 2. **Portal DSL** via `AdminSuite.portal(:key) { ... }` (metadata + dashboard layout)
13
-
14
- ## Portal metadata (`config.portals`)
15
-
16
- ```ruby
17
- AdminSuite.configure do |config|
18
- config.portals = {
19
- ops: { label: "Ops", icon: "settings", color: :amber, order: 10 },
20
- ai: { label: "AI", icon: "cpu", color: :cyan, order: 20 }
21
- }
22
- end
23
- ```
24
-
25
- ### Portal fields
26
-
27
- - `label` (String): display label
28
- - `icon` (String/Symbol): lucide icon name (e.g. `"settings"`)
29
- - `color` (Symbol/String): used for accents (`:amber`, `:emerald`, `:cyan`, `:violet`, `:slate`)
30
- - `order` (Integer): sort order in the root dashboard and sidebar
31
- - `description` (String, optional): shown on the root dashboard cards when present
32
-
33
- ## Portal DSL (`AdminSuite.portal`)
34
-
35
- Portal DSL files are loaded from `AdminSuite.config.portal_globs` (defaults include
36
- `config/admin_suite/portals/*.rb`, `app/admin/portals/*.rb`, `app/admin_suite/portals/*.rb`).
37
-
38
- Example file:
39
-
40
- ```ruby
41
- # config/admin_suite/portals/ops.rb
42
- AdminSuite.portal :ops do
43
- label "Ops Portal"
44
- icon "settings"
45
- color :amber
46
- order 10
47
- description "Operational tools and internal resources."
48
-
49
- dashboard do
50
- row do
51
- stat_panel "New users (24h)", -> { User.where("created_at > ?", 24.hours.ago).count }, color: :emerald, span: 3
52
- stat_panel "Failed jobs", -> { SolidQueue::FailedExecution.count }, color: :red, span: 3
53
- end
54
-
55
- row do
56
- recent_panel "Recent signups", scope: -> { User.order(created_at: :desc).limit(5) }, span: 6
57
- table_panel "Queue summary",
58
- rows: -> { SolidQueue::Job.order(created_at: :desc).limit(10) },
59
- columns: %i[id class_name created_at],
60
- span: 6
61
- end
62
- end
63
- end
64
- ```
65
-
66
- ## Dashboard DSL
67
-
68
- The dashboard DSL is available inside `portal.dashboard do ... end`.
69
-
70
- - `dashboard` contains `row { ... }`
71
- - each `row` contains one or more `panel(...)` calls
72
-
73
- ### Panel helpers
74
-
75
- These are convenience helpers that all create a `panel` under the hood:
76
-
77
- - `stat_panel(title, value=nil, span: nil, **options, &block)`
78
- - `health_panel(title, status: nil, metrics: nil, span: nil, **options, &block)`
79
- - `chart_panel(title, data: nil, span: nil, **options, &block)`
80
- - `cards_panel(title, resources: nil, span: nil, **options, &block)`
81
- - `recent_panel(title, scope: nil, link: nil, span: nil, **options, &block)`
82
- - `table_panel(title, rows: nil, columns: nil, span: nil, **options, &block)`
83
-
84
- ### `span`
85
-
86
- `span` controls width in a 12-column grid. Typical values: `3`, `4`, `6`, `12`.
87
-
88
- ## Portal pages
89
-
90
- Once mounted, portal pages are served at:
91
-
92
- - `/:portal` (relative to the mount path)
93
-
94
- Example:
95
-
96
- - `/internal/admin/ops`
97
- - `/internal/admin/ai`
98
-
99
- ## Root dashboard (`AdminSuite.root_dashboard`)
100
-
101
- The engine root (`/`, relative to the mount path) renders a default dashboard (portal cards + basic stats).
102
-
103
- To customize it, create a dashboard definition file (loaded from `AdminSuite.config.dashboard_globs`, which defaults to paths like `config/admin_suite/dashboard.rb` and `app/admin_suite/dashboard.rb`):
104
-
105
- ```ruby
106
- # config/admin_suite/dashboard.rb
107
- AdminSuite.configure do |config|
108
- config.root_dashboard_title = "Developer Portal"
109
- config.root_dashboard_description = "Internal tools for managing application resources."
110
- end
111
-
112
- AdminSuite.root_dashboard do
113
- row do
114
- cards_panel "Portals",
115
- span: 12,
116
- variant: :portals,
117
- resources: ->(view) do
118
- view.navigation_items
119
- .sort_by { |(_k, meta)| (meta[:order] || 100).to_i }
120
- .map do |portal_key, portal|
121
- {
122
- key: portal_key,
123
- label: portal[:label] || portal_key.to_s.humanize,
124
- description: portal[:description],
125
- color: view.portal_color(portal_key),
126
- icon: portal[:icon],
127
- path: view.portal_path(portal: portal_key),
128
- count: (portal[:sections] || {}).values.sum { |s| Array(s[:items]).size }
129
- }
130
- end
131
- end
132
- end
133
-
134
- row do
135
- stat_panel "Portals", ->(view) { view.navigation_items.keys.count }, span: 6, variant: :mini, color: :slate
136
- stat_panel "Resources", -> { Admin::Base::Resource.registered_resources.count }, span: 6, variant: :mini, color: :slate
137
- end
138
- end
139
- ```
140
-
data/docs/releasing.md DELETED
@@ -1,67 +0,0 @@
1
- # Releasing
2
-
3
- This page is intended for maintainers publishing `admin_suite` to RubyGems.
4
-
5
- ## Automated Release Process (Recommended)
6
-
7
- The gem is automatically published to RubyGems when changes are merged to `main`, provided the version has been bumped.
8
-
9
- ### Steps
10
-
11
- 1. **Bump the version**
12
- - Update `lib/admin_suite/version.rb`
13
-
14
- 2. **Update changelog**
15
- - Add an entry to `CHANGELOG.md`
16
-
17
- 3. **Create a PR and get it merged**
18
- - The CI workflow will run tests automatically on the PR
19
- - Once merged to `main`, after CI passes, the publish workflow will:
20
- - Check if the version already exists on RubyGems
21
- - Build and publish the gem (if it's a new version)
22
- - Create a Git tag for the release (if it doesn't already exist)
23
- - Create a GitHub Release with notes extracted from `CHANGELOG.md`, or auto-generated from commits since the previous tag if no CHANGELOG entry exists
24
-
25
- ### Requirements
26
-
27
- - The `RUBYGEMS_API_KEY` secret must be configured in the repository settings
28
- - The version in `lib/admin_suite/version.rb` must be unique (not already published)
29
-
30
- ## Manual Release Process
31
-
32
- If you need to publish manually:
33
-
34
- 1. Bump the version
35
- - Update `lib/admin_suite/version.rb`
36
-
37
- 2. Update changelog
38
- - Add an entry to `CHANGELOG.md`
39
-
40
- 3. Run tests and build the gem
41
-
42
- ```bash
43
- bundle exec rake test
44
- gem build admin_suite.gemspec
45
- ```
46
-
47
- 4. Tag the release
48
-
49
- ```bash
50
- git tag -a "vX.Y.Z" -m "AdminSuite vX.Y.Z"
51
- git push --tags
52
- ```
53
-
54
- 5. Publish to RubyGems
55
-
56
- ```bash
57
- gem push "admin_suite-X.Y.Z.gem"
58
- ```
59
-
60
- ## Notes
61
-
62
- - RubyGems commonly requires MFA/OTP for pushes (this gem is configured with `rubygems_mfa_required`)
63
- - The automated workflow uses a GitHub Actions bot to push tags and create GitHub Releases
64
- - The publish workflow only runs after the CI workflow completes successfully
65
- - GitHub Release notes are sourced from the matching version section in `CHANGELOG.md`; if no entry exists, they are auto-generated from commits since the previous tag; a plain "Release vX.Y.Z" string is used only as a final fallback when no previous tag or commit-generated notes are available
66
- - You can manually trigger the publish workflow from the GitHub Actions tab if needed
67
-