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.
data/docs/fields.md ADDED
@@ -0,0 +1,188 @@
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
+
@@ -0,0 +1,80 @@
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 ADDED
@@ -0,0 +1,98 @@
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
+
data/docs/releasing.md ADDED
@@ -0,0 +1,38 @@
1
+ # Releasing
2
+
3
+ This page is intended for maintainers publishing `admin_suite` to RubyGems.
4
+
5
+ ## Checklist
6
+
7
+ 1. Bump the version
8
+
9
+ - Update `lib/admin_suite/version.rb`
10
+
11
+ 2. Update changelog
12
+
13
+ - Add an entry to `CHANGELOG.md`
14
+
15
+ 3. Run tests and build the gem
16
+
17
+ ```bash
18
+ bundle exec rake test
19
+ gem build admin_suite.gemspec
20
+ ```
21
+
22
+ 4. (Recommended) Tag the release
23
+
24
+ ```bash
25
+ git tag -a "vX.Y.Z" -m "AdminSuite vX.Y.Z"
26
+ git push --tags
27
+ ```
28
+
29
+ 5. Publish to RubyGems
30
+
31
+ ```bash
32
+ gem push "admin_suite-X.Y.Z.gem"
33
+ ```
34
+
35
+ Notes:
36
+
37
+ - RubyGems commonly requires MFA/OTP for pushes (this gem is configured with `rubygems_mfa_required`).
38
+
data/docs/resources.md ADDED
@@ -0,0 +1,237 @@
1
+ # Resources
2
+
3
+ Resources are defined using `Admin::Base::Resource`.
4
+
5
+ AdminSuite loads resource definition files from `AdminSuite.config.resource_globs`
6
+ (defaults include `config/admin_suite/resources/*.rb` and `app/admin/resources/*.rb`).
7
+
8
+ ## Create a resource
9
+
10
+ A resource is a Ruby class under `Admin::Resources` ending in `Resource`.
11
+
12
+ Example:
13
+
14
+ ```ruby
15
+ # config/admin_suite/resources/user.rb
16
+ module Admin
17
+ module Resources
18
+ class UserResource < Admin::Base::Resource
19
+ model ::User
20
+ portal :ops
21
+ section :accounts
22
+
23
+ nav label: "Users", icon: "users", order: 10
24
+
25
+ index do
26
+ searchable :email, :name
27
+ sortable :created_at, :email, default: :created_at, direction: :desc
28
+ paginate 50
29
+
30
+ columns do
31
+ column :id
32
+ column :email
33
+ column :created_at
34
+ column :admin, type: :toggle, toggle_field: :admin, header: "Admin?"
35
+ column :status, type: :label, label_color: ->(u) { u.active? ? :emerald : :slate }, label_size: :sm
36
+ end
37
+
38
+ filters do
39
+ filter :search, type: :text, placeholder: "Search users..."
40
+ filter :status, type: :select, options: [["Active", "active"], ["Inactive", "inactive"]]
41
+ end
42
+
43
+ stats do
44
+ stat :total, -> { User.count }, color: :slate
45
+ stat :new_24h, -> { User.where("created_at > ?", 24.hours.ago).count }, color: :emerald
46
+ end
47
+ end
48
+
49
+ form do
50
+ section "Basics", description: "Core account fields" do
51
+ field :email, type: :email, required: true
52
+ field :name, required: true
53
+ end
54
+
55
+ row cols: 2 do
56
+ field :admin, type: :toggle, help: "Grants access to internal tools."
57
+ field :status, type: :select, collection: [["Active", "active"], ["Inactive", "inactive"]]
58
+ end
59
+ end
60
+
61
+ show do
62
+ main do
63
+ panel :details, title: "User details", fields: %i[email name status created_at]
64
+ panel :activity, title: "Activity", render: :custom_activity_timeline
65
+ end
66
+
67
+ sidebar do
68
+ panel :summary, title: "Summary", fields: %i[id admin]
69
+ end
70
+ end
71
+
72
+ actions do
73
+ action :reset_password, label: "Reset password", icon: "key", confirm: "Send reset email?"
74
+ end
75
+ end
76
+ end
77
+ end
78
+ ```
79
+
80
+ ## Core DSL
81
+
82
+ ### Model
83
+
84
+ ```ruby
85
+ model ::User
86
+ ```
87
+
88
+ ### Navigation placement
89
+
90
+ ```ruby
91
+ portal :ops
92
+ section :accounts
93
+ ```
94
+
95
+ These determine:
96
+
97
+ - URL: `/:portal/:resource_name`
98
+ - Sidebar placement: portal group → section group → resource link
99
+
100
+ ### Navigation metadata
101
+
102
+ ```ruby
103
+ nav label: "Users", icon: "users", order: 10
104
+ ```
105
+
106
+ Also available as convenience setters:
107
+
108
+ ```ruby
109
+ label "Users"
110
+ icon "users"
111
+ order 10
112
+ ```
113
+
114
+ ## Index DSL (`index do ... end`)
115
+
116
+ ### Search
117
+
118
+ ```ruby
119
+ searchable :name, :email
120
+ ```
121
+
122
+ Search uses `ILIKE` across the configured fields.
123
+
124
+ ### Sort
125
+
126
+ ```ruby
127
+ sortable :created_at, :email, default: :created_at, direction: :desc
128
+ ```
129
+
130
+ ### Pagination
131
+
132
+ ```ruby
133
+ paginate 25
134
+ ```
135
+
136
+ ### Columns
137
+
138
+ ```ruby
139
+ columns do
140
+ column :email
141
+ column :job_listings, ->(u) { u.job_listings.count }
142
+ end
143
+ ```
144
+
145
+ `column` options:
146
+
147
+ - `header:` string (defaults to a humanized name)
148
+ - `class:` css class for the cell
149
+ - `render:` custom render key (advanced)
150
+ - `type:` `:toggle` or `:label` (special rendering)
151
+ - `toggle_field:` field to flip when `type: :toggle`
152
+ - `label_color:` color for `type: :label` (Symbol or Proc)
153
+ - `label_size:` `:sm`/`:md` (or Proc)
154
+ - `sortable:` boolean (reserved for future per-column sorting UI)
155
+
156
+ ### Filters
157
+
158
+ ```ruby
159
+ filters do
160
+ filter :status, type: :select, options: [["Active", "active"], ["Inactive", "inactive"]]
161
+ end
162
+ ```
163
+
164
+ Filter options:
165
+
166
+ - `type:` (default `:text`)
167
+ - `label:`
168
+ - `placeholder:`
169
+ - `options:` / `collection:` (for select-like UI)
170
+ - `field:` which model field to filter on (defaults to the filter name)
171
+ - `apply:` Proc that receives the scope (advanced)
172
+
173
+ ### Stats
174
+
175
+ ```ruby
176
+ stats do
177
+ stat :total, -> { User.count }, color: :slate
178
+ end
179
+ ```
180
+
181
+ ## Form DSL (`form do ... end`)
182
+
183
+ ```ruby
184
+ form do
185
+ field :name, required: true
186
+ field :website, type: :url
187
+ end
188
+ ```
189
+
190
+ See [Fields](fields.md) for supported types and options.
191
+
192
+ AdminSuite also supports basic layout helpers in forms:
193
+
194
+ - `section "Title" do ... end`
195
+ - `row cols: 2 do ... end`
196
+
197
+ ## Show DSL (`show do ... end`)
198
+
199
+ Show is section-based. Sections can live in the main column or sidebar.
200
+
201
+ ```ruby
202
+ show do
203
+ main do
204
+ panel :details, title: "Details", fields: %i[name email]
205
+ panel :related, title: "Projects", association: :projects, display: :table, columns: %i[id name status], paginate: true
206
+ end
207
+
208
+ sidebar do
209
+ panel :meta, title: "Meta", fields: %i[id created_at updated_at]
210
+ end
211
+ end
212
+ ```
213
+
214
+ Panel options:
215
+
216
+ - `title:` (defaults to a humanized name)
217
+ - `fields:` array of field names to display
218
+ - `render:` custom renderer key (see `custom_renderers` in [Configuration](configuration.md))
219
+ - `association:` association name to render (`has_many`, `belongs_to`, etc.)
220
+ - `display:` `:list` (default), `:table`, or `:cards` for associations
221
+ - `columns:` columns for association `:table` display
222
+ - `link_to:` helper method name to build links for association items (optional)
223
+ - `paginate:` boolean, enables pagination within the association section
224
+ - `per_page:` items per page for association pagination
225
+ - `limit:` max items (if not paginating)
226
+ - `collapsible:` / `collapsed:` (reserved for future UI toggles)
227
+
228
+ ## Actions DSL (`actions do ... end`)
229
+
230
+ ```ruby
231
+ actions do
232
+ action :reindex, label: "Reindex", method: :post, confirm: "Reindex this record?"
233
+ end
234
+ ```
235
+
236
+ See [Actions](actions.md) for how actions execute and how to define handlers.
237
+
data/docs/theming.md ADDED
@@ -0,0 +1,63 @@
1
+ # Theming & assets
2
+
3
+ AdminSuite is designed to work in two modes:
4
+
5
+ 1. **Engine-build mode (default)**: AdminSuite builds and ships its own Tailwind CSS into your host app at `assets:precompile` time.
6
+ 2. **Host-themed mode (optional)**: your host app includes a stylesheet after AdminSuite for branding/overrides.
7
+
8
+ ## Theme colors (`config.theme`)
9
+
10
+ AdminSuite uses CSS variables scoped to `body.admin-suite`.
11
+
12
+ ```ruby
13
+ AdminSuite.configure do |config|
14
+ config.theme = { primary: :emerald, secondary: :cyan }
15
+ end
16
+ ```
17
+
18
+ ### Allowed values
19
+
20
+ - **Named colors**: symbols/strings like `:indigo`, `:emerald`, `:cyan`, `:amber`, `:violet`, `:slate`, etc.
21
+ - **Hex**: `"#4f46e5"` (uses that exact color as primary/secondary in key places)
22
+
23
+ The theme primarily drives:
24
+
25
+ - Primary links/buttons (`--admin-suite-primary`, `--admin-suite-primary-hover`)
26
+ - Sidebar gradient (`--admin-suite-sidebar-from/via/to`)
27
+
28
+ ## Host stylesheet (`config.host_stylesheet`)
29
+
30
+ If your host app already has Tailwind (or you want to override the engine UI), you can include an additional stylesheet after AdminSuite in the engine layout:
31
+
32
+ ```ruby
33
+ AdminSuite.configure do |config|
34
+ config.host_stylesheet = :app
35
+ end
36
+ ```
37
+
38
+ This calls `stylesheet_link_tag :app` after `admin_suite.css` and `admin_suite_tailwind.css`.
39
+
40
+ ## Tailwind build
41
+
42
+ AdminSuite writes an engine stylesheet into your host app during `assets:precompile`:
43
+
44
+ - Input: `AdminSuite::Engine.root/app/assets/tailwind/admin_suite.css`
45
+ - Output: `Rails.root/app/assets/builds/admin_suite_tailwind.css`
46
+
47
+ In development, the engine also makes a best-effort to create the output file if it’s missing, so the UI stays usable.
48
+
49
+ ## Icons
50
+
51
+ AdminSuite uses lucide icons by default via `lucide-rails`.
52
+
53
+ If you need a different icon provider:
54
+
55
+ ```ruby
56
+ AdminSuite.configure do |config|
57
+ config.icon_renderer = ->(name, view, **opts) do
58
+ # return HTML-safe SVG (string or ActiveSupport::SafeBuffer)
59
+ view.content_tag(:span, name, class: opts[:class])
60
+ end
61
+ end
62
+ ```
63
+