admin_resources 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +208 -0
- data/Rakefile +4 -0
- data/app/controllers/admin_resources/application_controller.rb +26 -0
- data/app/controllers/admin_resources/dashboard_controller.rb +10 -0
- data/app/controllers/admin_resources/resources_controller.rb +127 -0
- data/app/models/admin_resources/admin_user.rb +7 -0
- data/app/views/admin_resources/dashboard/index.html.erb +13 -0
- data/app/views/admin_resources/resources/_form.html.erb +54 -0
- data/app/views/admin_resources/resources/edit.html.erb +5 -0
- data/app/views/admin_resources/resources/index.html.erb +38 -0
- data/app/views/admin_resources/resources/new.html.erb +5 -0
- data/app/views/admin_resources/resources/show.html.erb +133 -0
- data/app/views/layouts/admin_resources/admin.html.erb +75 -0
- data/config/routes.rb +16 -0
- data/db/migrate/20240101000000_create_admin_resources_admin_users.rb +16 -0
- data/lib/admin_resources/configuration.rb +23 -0
- data/lib/admin_resources/engine.rb +21 -0
- data/lib/admin_resources/version.rb +5 -0
- data/lib/admin_resources.rb +26 -0
- data/sig/admin_resources.rbs +4 -0
- metadata +92 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3be67639f99785b3922b74d53915eb671d2bd18e9f9f29e2f4b176229b8ec37e
|
|
4
|
+
data.tar.gz: 581deeb6b0c2202bf2db8bddb021606aa804ed9eb44f386958480c5d56c84e1d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b27b897928284a64a6d8f5e4bf733feea84508d72f41235187e45fa048004d6ea1ac4b0f2732cbf64fc86a179ba6776b785cda9db3b2d7cf600a1f9255a9360a
|
|
7
|
+
data.tar.gz: a469f701fde5346d425bea3fa7a8fa4dff3a656006abdb4a822cc0747488f617655c8bf46d7b7143e1eb00acd39383c3b007cbb96781dbcd4866695e1a1f4d1c
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mark Rosenberg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# AdminResources
|
|
2
|
+
|
|
3
|
+
A mountable Rails engine that automatically generates a full admin dashboard for your models. Register the models you want to manage, mount the engine, and you get a complete CRUD interface with zero boilerplate controllers or views in your app.
|
|
4
|
+
|
|
5
|
+
**Features:**
|
|
6
|
+
- Auto-generated index, show, new, edit, and delete for every registered model
|
|
7
|
+
- Dashboard with record counts for all registered models
|
|
8
|
+
- Built-in admin authentication via Devise (own `AdminUser` model, own table)
|
|
9
|
+
- Smart field rendering: JSON/JSONB pretty-printed, foreign keys linked to related admin pages, booleans as Yes/No, datetimes formatted
|
|
10
|
+
- Smart form generation: checkboxes for booleans, dropdowns for foreign keys, textareas for JSON, etc.
|
|
11
|
+
- `has_one` and `has_many` associations shown inline on show pages
|
|
12
|
+
- Your own styling — dark sidebar, clean table layout, no external CSS dependencies
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- Rails 7.0+
|
|
19
|
+
- Ruby 3.0+
|
|
20
|
+
- Devise 4.0+
|
|
21
|
+
- PostgreSQL (for array column support) — other databases work but array columns won't be handled
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Add to your `Gemfile`:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
gem "admin_resources", github: "doeswork/admin_resources"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or for local development:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
gem "admin_resources", path: "../admin_resources"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then run:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
bundle install
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Setup
|
|
48
|
+
|
|
49
|
+
### 1. Run the migration
|
|
50
|
+
|
|
51
|
+
Copy and run the engine's migration to create the `admin_resources_admin_users` table:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
rails admin_resources:install:migrations
|
|
55
|
+
rails db:migrate
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 2. Mount the engine
|
|
59
|
+
|
|
60
|
+
In `config/routes.rb`:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
Rails.application.routes.draw do
|
|
64
|
+
mount AdminResources::Engine, at: "/admin"
|
|
65
|
+
|
|
66
|
+
# ... rest of your routes
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 3. Configure your models
|
|
71
|
+
|
|
72
|
+
Create `config/initializers/admin_resources.rb`:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
AdminResources.configure do |config|
|
|
76
|
+
# Register with specific index columns:
|
|
77
|
+
config.register "User", columns: %w[id email created_at]
|
|
78
|
+
config.register "Post", columns: %w[id title user_id published_at]
|
|
79
|
+
|
|
80
|
+
# Register without columns — defaults to first 6 columns:
|
|
81
|
+
config.register "Comment"
|
|
82
|
+
config.register "Tag"
|
|
83
|
+
end
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 4. Create your first admin user
|
|
87
|
+
|
|
88
|
+
Open a Rails console and create an `AdminResources::AdminUser`:
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
AdminResources::AdminUser.create!(
|
|
92
|
+
email: "admin@example.com",
|
|
93
|
+
password: "yourpassword",
|
|
94
|
+
password_confirmation: "yourpassword"
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Then visit `/admin` and sign in.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Configuration reference
|
|
103
|
+
|
|
104
|
+
`config.register` accepts:
|
|
105
|
+
|
|
106
|
+
| Option | Type | Default | Description |
|
|
107
|
+
|--------|------|---------|-------------|
|
|
108
|
+
| `columns` | `Array<String>` | first 6 columns | Column names to display in the index table |
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
AdminResources.configure do |config|
|
|
112
|
+
config.register "ModelName", columns: %w[col1 col2 col3]
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Model names must be strings matching the exact class name (e.g. `"WorkflowStep"`, not `"workflow_step"`).
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## What gets generated automatically
|
|
121
|
+
|
|
122
|
+
For each registered model, the engine provides:
|
|
123
|
+
|
|
124
|
+
| Route | Description |
|
|
125
|
+
|-------|-------------|
|
|
126
|
+
| `GET /admin/users` | Index — paginated table of all records |
|
|
127
|
+
| `GET /admin/users/new` | New form |
|
|
128
|
+
| `POST /admin/users` | Create |
|
|
129
|
+
| `GET /admin/users/:id` | Show — all columns + associations |
|
|
130
|
+
| `GET /admin/users/:id/edit` | Edit form |
|
|
131
|
+
| `PATCH /admin/users/:id` | Update |
|
|
132
|
+
| `DELETE /admin/users/:id` | Destroy |
|
|
133
|
+
|
|
134
|
+
Route helpers follow the pattern `admin_resources_<plural>_path` and `admin_resources_<singular>_path`.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## How field rendering works
|
|
139
|
+
|
|
140
|
+
### Index + show pages
|
|
141
|
+
|
|
142
|
+
| Value type | Rendered as |
|
|
143
|
+
|------------|-------------|
|
|
144
|
+
| `nil` | *nil* (italic, grey) |
|
|
145
|
+
| JSON / JSONB column | `<pre>` with pretty-printed JSON |
|
|
146
|
+
| Column named `params` or `data` | Same as JSON |
|
|
147
|
+
| Foreign key (`*_id`) pointing to a registered model | Clickable link to that record's admin show page |
|
|
148
|
+
| `Boolean` | Yes / No |
|
|
149
|
+
| `Time` / `DateTime` | `YYYY-MM-DD HH:MM:SS` |
|
|
150
|
+
| Everything else | Plain text (truncated to 50 chars on index) |
|
|
151
|
+
|
|
152
|
+
### Forms
|
|
153
|
+
|
|
154
|
+
| Column type | Field rendered |
|
|
155
|
+
|-------------|----------------|
|
|
156
|
+
| `:boolean` | Checkbox |
|
|
157
|
+
| `:text` | Textarea (4 rows) |
|
|
158
|
+
| `:integer`, `:decimal`, `:float` | Number input |
|
|
159
|
+
| `:date` | Date picker |
|
|
160
|
+
| `:datetime` | Datetime-local picker |
|
|
161
|
+
| `:json`, `:jsonb` | Textarea (serialized to JSON) |
|
|
162
|
+
| `*_id` foreign key | Dropdown (`collection_select`) populated with all records of the associated model |
|
|
163
|
+
| Column name contains `password` | Password input |
|
|
164
|
+
| Column name contains `email` | Email input |
|
|
165
|
+
| Everything else | Text input |
|
|
166
|
+
|
|
167
|
+
### Show page associations
|
|
168
|
+
|
|
169
|
+
The show page automatically renders:
|
|
170
|
+
|
|
171
|
+
- **`has_one`** associations: shown as a detail card below the main record, with View/Edit links if the associated model is also registered
|
|
172
|
+
- **`has_many`** associations: shown as a table (limited to 20 rows) with a count and "New" link if the associated model is registered
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Authentication
|
|
177
|
+
|
|
178
|
+
The engine bundles its own `AdminResources::AdminUser` model with Devise. It lives in a separate table (`admin_resources_admin_users`) and is completely independent from any `User` model in your app.
|
|
179
|
+
|
|
180
|
+
Devise modules included: `database_authenticatable`, `recoverable`, `rememberable`, `validatable`.
|
|
181
|
+
|
|
182
|
+
All admin routes require a signed-in `AdminResources::AdminUser`. Unauthenticated requests are redirected to the admin login page.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Development
|
|
187
|
+
|
|
188
|
+
Clone the repo and install dependencies:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
git clone https://github.com/doeswork/admin_resources
|
|
192
|
+
cd admin_resources
|
|
193
|
+
bundle install
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
To install onto a local Rails app for testing, add `gem "admin_resources", path: "/path/to/admin_resources"` to that app's Gemfile.
|
|
197
|
+
|
|
198
|
+
To release a new version, update `lib/admin_resources/version.rb` and run:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
bundle exec rake release
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Contributing
|
|
207
|
+
|
|
208
|
+
Bug reports and pull requests are welcome at https://github.com/doeswork/admin_resources.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module AdminResources
|
|
2
|
+
class ApplicationController < ActionController::Base
|
|
3
|
+
before_action :authenticate_admin_resources_admin_user!
|
|
4
|
+
layout "admin_resources/admin"
|
|
5
|
+
|
|
6
|
+
helper_method :admin_models, :admin_path_for
|
|
7
|
+
|
|
8
|
+
def admin_models
|
|
9
|
+
AdminResources.model_names
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def admin_path_for(model_name, action = :index, resource = nil)
|
|
13
|
+
route_name = model_name.underscore.pluralize
|
|
14
|
+
case action
|
|
15
|
+
when :index
|
|
16
|
+
send("admin_resources_#{route_name}_path")
|
|
17
|
+
when :new
|
|
18
|
+
send("new_admin_resources_#{route_name.singularize}_path")
|
|
19
|
+
when :show
|
|
20
|
+
send("admin_resources_#{route_name.singularize}_path", resource)
|
|
21
|
+
when :edit
|
|
22
|
+
send("edit_admin_resources_#{route_name.singularize}_path", resource)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module AdminResources
|
|
2
|
+
class DashboardController < ApplicationController
|
|
3
|
+
def index
|
|
4
|
+
puts "[AdminResources::DashboardController] index - showing dashboard"
|
|
5
|
+
@model_counts = AdminResources.model_names.each_with_object({}) do |model_name, hash|
|
|
6
|
+
hash[model_name] = model_name.constantize.count
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
module AdminResources
|
|
2
|
+
class ResourcesController < ApplicationController
|
|
3
|
+
before_action :set_model_class
|
|
4
|
+
before_action :set_resource, only: %i[show edit update destroy]
|
|
5
|
+
|
|
6
|
+
helper_method :model_class, :model_name, :index_columns, :form_columns, :admin_value_display
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
puts "[AdminResources::ResourcesController] index for #{model_name}"
|
|
10
|
+
@resources = model_class.all.order(id: :desc)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def show
|
|
14
|
+
puts "[AdminResources::ResourcesController] show #{model_name}##{@resource.id}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def new
|
|
18
|
+
puts "[AdminResources::ResourcesController] new #{model_name}"
|
|
19
|
+
@resource = model_class.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def create
|
|
23
|
+
puts "[AdminResources::ResourcesController] create #{model_name}"
|
|
24
|
+
@resource = model_class.new(resource_params)
|
|
25
|
+
if @resource.save
|
|
26
|
+
redirect_to admin_path_for(model_name, :show, @resource), notice: "#{model_name} was successfully created."
|
|
27
|
+
else
|
|
28
|
+
render :new, status: :unprocessable_entity
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def edit
|
|
33
|
+
puts "[AdminResources::ResourcesController] edit #{model_name}##{@resource.id}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def update
|
|
37
|
+
puts "[AdminResources::ResourcesController] update #{model_name}##{@resource.id}"
|
|
38
|
+
if @resource.update(resource_params)
|
|
39
|
+
redirect_to admin_path_for(model_name, :show, @resource), notice: "#{model_name} was successfully updated."
|
|
40
|
+
else
|
|
41
|
+
render :edit, status: :unprocessable_entity
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def destroy
|
|
46
|
+
puts "[AdminResources::ResourcesController] destroy #{model_name}##{@resource.id}"
|
|
47
|
+
@resource.destroy
|
|
48
|
+
redirect_to admin_path_for(model_name, :index), notice: "#{model_name} was successfully deleted."
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def set_model_class
|
|
54
|
+
model_param = params[:model]
|
|
55
|
+
unless AdminResources.model_names.include?(model_param)
|
|
56
|
+
raise ActiveRecord::RecordNotFound, "Model '#{model_param}' not registered in AdminResources"
|
|
57
|
+
end
|
|
58
|
+
@model_class = model_param.constantize
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def set_resource
|
|
62
|
+
@resource = model_class.find(params[:id])
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def model_class
|
|
66
|
+
@model_class
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def model_name
|
|
70
|
+
model_class.name
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def index_columns
|
|
74
|
+
config = AdminResources.models[model_name]
|
|
75
|
+
config&.dig(:columns) || model_class.column_names.first(6)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def form_columns
|
|
79
|
+
model_class.column_names - %w[id created_at updated_at]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns [display_text, link_path_or_nil]
|
|
83
|
+
def admin_value_display(resource, column)
|
|
84
|
+
unless resource.respond_to?(column)
|
|
85
|
+
return ["[invalid column: #{column}]", nil]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
value = resource.send(column)
|
|
89
|
+
return [nil, nil] if value.nil?
|
|
90
|
+
|
|
91
|
+
if column.end_with?("_id") && value.present?
|
|
92
|
+
assoc_name = column.sub(/_id$/, "")
|
|
93
|
+
association = resource.class.reflect_on_association(assoc_name.to_sym)
|
|
94
|
+
|
|
95
|
+
if association && association.macro == :belongs_to
|
|
96
|
+
assoc_class = association.klass
|
|
97
|
+
assoc_model = assoc_class.name
|
|
98
|
+
|
|
99
|
+
if AdminResources.model_names.include?(assoc_model)
|
|
100
|
+
associated_record = assoc_class.find_by(id: value)
|
|
101
|
+
if associated_record
|
|
102
|
+
display = "#{assoc_model} ##{value}"
|
|
103
|
+
display = associated_record.name if associated_record.respond_to?(:name) && associated_record.name.present?
|
|
104
|
+
display = associated_record.version if associated_record.respond_to?(:version) && associated_record.version.present?
|
|
105
|
+
display = associated_record.email if associated_record.respond_to?(:email) && associated_record.email.present?
|
|
106
|
+
return [display, admin_path_for(assoc_model, :show, associated_record)]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
[value, nil]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def resource_params
|
|
116
|
+
permitted = form_columns.map do |col|
|
|
117
|
+
column = model_class.columns_hash[col]
|
|
118
|
+
if column&.array?
|
|
119
|
+
{ col.to_sym => [] }
|
|
120
|
+
else
|
|
121
|
+
col.to_sym
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
params.require(model_class.model_name.param_key).permit(*permitted)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<div class="admin-header">
|
|
2
|
+
<h1>Dashboard</h1>
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
<div class="admin-grid">
|
|
6
|
+
<% @model_counts.each do |model_name, count| %>
|
|
7
|
+
<div class="admin-card admin-stat">
|
|
8
|
+
<div class="admin-stat-value"><%= count %></div>
|
|
9
|
+
<div class="admin-stat-label"><%= model_name.pluralize %></div>
|
|
10
|
+
<%= link_to "View all", admin_path_for(model_name), class: "admin-btn admin-btn-secondary" %>
|
|
11
|
+
</div>
|
|
12
|
+
<% end %>
|
|
13
|
+
</div>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<%= form_with model: @resource, url: (@resource.new_record? ? admin_path_for(model_name, :index) : admin_path_for(model_name, :show, @resource)), class: "admin-form" do |form| %>
|
|
2
|
+
<% if @resource.errors.any? %>
|
|
3
|
+
<div class="admin-flash alert">
|
|
4
|
+
<strong><%= pluralize(@resource.errors.count, "error") %> prohibited this <%= model_name.downcase %> from being saved:</strong>
|
|
5
|
+
<ul>
|
|
6
|
+
<% @resource.errors.full_messages.each do |message| %>
|
|
7
|
+
<li><%= message %></li>
|
|
8
|
+
<% end %>
|
|
9
|
+
</ul>
|
|
10
|
+
</div>
|
|
11
|
+
<% end %>
|
|
12
|
+
|
|
13
|
+
<% form_columns.each do |col| %>
|
|
14
|
+
<% column = model_class.columns_hash[col] %>
|
|
15
|
+
<div class="field">
|
|
16
|
+
<%= form.label col %>
|
|
17
|
+
<% case column&.type %>
|
|
18
|
+
<% when :boolean %>
|
|
19
|
+
<%= form.check_box col %>
|
|
20
|
+
<% when :text %>
|
|
21
|
+
<%= form.text_area col, rows: 4 %>
|
|
22
|
+
<% when :integer, :decimal, :float %>
|
|
23
|
+
<%= form.number_field col, step: (column.type == :integer ? 1 : 'any') %>
|
|
24
|
+
<% when :date %>
|
|
25
|
+
<%= form.date_field col %>
|
|
26
|
+
<% when :datetime %>
|
|
27
|
+
<%= form.datetime_local_field col %>
|
|
28
|
+
<% when :json, :jsonb %>
|
|
29
|
+
<%= form.text_area col, rows: 4, value: @resource.send(col)&.to_json %>
|
|
30
|
+
<% else %>
|
|
31
|
+
<% if col.end_with?('_id') %>
|
|
32
|
+
<% assoc_name = col.sub(/_id$/, '').classify %>
|
|
33
|
+
<% assoc_class = assoc_name.safe_constantize %>
|
|
34
|
+
<% if assoc_class && assoc_class < ActiveRecord::Base %>
|
|
35
|
+
<%= form.collection_select col, assoc_class.all, :id, :to_s, include_blank: true %>
|
|
36
|
+
<% else %>
|
|
37
|
+
<%= form.number_field col %>
|
|
38
|
+
<% end %>
|
|
39
|
+
<% elsif col.include?('password') %>
|
|
40
|
+
<%= form.password_field col %>
|
|
41
|
+
<% elsif col.include?('email') %>
|
|
42
|
+
<%= form.email_field col %>
|
|
43
|
+
<% else %>
|
|
44
|
+
<%= form.text_field col %>
|
|
45
|
+
<% end %>
|
|
46
|
+
<% end %>
|
|
47
|
+
</div>
|
|
48
|
+
<% end %>
|
|
49
|
+
|
|
50
|
+
<div class="field">
|
|
51
|
+
<%= form.submit class: "admin-btn admin-btn-primary" %>
|
|
52
|
+
<%= link_to "Cancel", (@resource.new_record? ? admin_path_for(model_name) : admin_path_for(model_name, :show, @resource)), class: "admin-btn admin-btn-secondary" %>
|
|
53
|
+
</div>
|
|
54
|
+
<% end %>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<div class="admin-header">
|
|
2
|
+
<h1><%= model_name.pluralize %></h1>
|
|
3
|
+
<%= link_to "New #{model_name}", admin_path_for(model_name, :new), class: "admin-btn admin-btn-primary" %>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<table class="admin-table">
|
|
7
|
+
<thead>
|
|
8
|
+
<tr>
|
|
9
|
+
<% index_columns.each do |col| %>
|
|
10
|
+
<th><%= col.titleize %></th>
|
|
11
|
+
<% end %>
|
|
12
|
+
<th>Actions</th>
|
|
13
|
+
</tr>
|
|
14
|
+
</thead>
|
|
15
|
+
<tbody>
|
|
16
|
+
<% @resources.each do |resource| %>
|
|
17
|
+
<tr>
|
|
18
|
+
<% index_columns.each do |col| %>
|
|
19
|
+
<td>
|
|
20
|
+
<% display, link = admin_value_display(resource, col) %>
|
|
21
|
+
<% column_type = model_class.columns_hash[col]&.type %>
|
|
22
|
+
<% if %w[params data].include?(col) || column_type == :json || column_type == :jsonb %>
|
|
23
|
+
<pre style="margin: 0; font-size: 0.75rem; max-width: 300px; overflow-x: auto; white-space: pre-wrap;"><%= JSON.pretty_generate(display.is_a?(String) ? JSON.parse(display) : display) rescue display %></pre>
|
|
24
|
+
<% elsif link %>
|
|
25
|
+
<%= link_to truncate(display.to_s, length: 50), link %>
|
|
26
|
+
<% else %>
|
|
27
|
+
<%= truncate(display.to_s, length: 50) %>
|
|
28
|
+
<% end %>
|
|
29
|
+
</td>
|
|
30
|
+
<% end %>
|
|
31
|
+
<td class="admin-actions">
|
|
32
|
+
<%= link_to "View", admin_path_for(model_name, :show, resource), class: "admin-btn admin-btn-secondary" %>
|
|
33
|
+
<%= link_to "Edit", admin_path_for(model_name, :edit, resource), class: "admin-btn admin-btn-secondary" %>
|
|
34
|
+
</td>
|
|
35
|
+
</tr>
|
|
36
|
+
<% end %>
|
|
37
|
+
</tbody>
|
|
38
|
+
</table>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<div class="admin-header">
|
|
2
|
+
<h1><%= model_name %> #<%= @resource.id %></h1>
|
|
3
|
+
<div class="admin-actions">
|
|
4
|
+
<%= link_to "Edit", admin_path_for(model_name, :edit, @resource), class: "admin-btn admin-btn-primary" %>
|
|
5
|
+
<%= link_to "Back to list", admin_path_for(model_name), class: "admin-btn admin-btn-secondary" %>
|
|
6
|
+
<%= button_to "Delete", admin_path_for(model_name, :show, @resource),
|
|
7
|
+
method: :delete,
|
|
8
|
+
class: "admin-btn admin-btn-danger",
|
|
9
|
+
data: { turbo_confirm: "Are you sure you want to delete this #{model_name.downcase}?" } %>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="admin-card">
|
|
14
|
+
<% model_class.column_names.each do |col| %>
|
|
15
|
+
<div class="admin-detail-row">
|
|
16
|
+
<div class="admin-detail-label"><%= col.titleize %></div>
|
|
17
|
+
<div class="admin-detail-value">
|
|
18
|
+
<% display, link = admin_value_display(@resource, col) %>
|
|
19
|
+
<% column_type = model_class.columns_hash[col]&.type %>
|
|
20
|
+
<% if display.nil? %>
|
|
21
|
+
<em style="color: #999;">nil</em>
|
|
22
|
+
<% elsif %w[params data].include?(col) || column_type == :json || column_type == :jsonb %>
|
|
23
|
+
<pre style="margin: 0; font-size: 0.85rem; background: #f5f5f5; padding: 0.5rem; border-radius: 4px; overflow-x: auto;"><%= JSON.pretty_generate(display) rescue display %></pre>
|
|
24
|
+
<% elsif display.is_a?(Time) || display.is_a?(DateTime) %>
|
|
25
|
+
<%= display.strftime("%Y-%m-%d %H:%M:%S") %>
|
|
26
|
+
<% elsif display.is_a?(TrueClass) || display.is_a?(FalseClass) %>
|
|
27
|
+
<%= display ? "Yes" : "No" %>
|
|
28
|
+
<% elsif link %>
|
|
29
|
+
<%= link_to display, link %>
|
|
30
|
+
<% else %>
|
|
31
|
+
<%= display %>
|
|
32
|
+
<% end %>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<%# Display has_one associations %>
|
|
39
|
+
<% model_class.reflect_on_all_associations(:has_one).each do |assoc| %>
|
|
40
|
+
<% record = @resource.send(assoc.name) %>
|
|
41
|
+
<% next if record.nil? %>
|
|
42
|
+
|
|
43
|
+
<div style="margin-top: 2rem;">
|
|
44
|
+
<div class="admin-header">
|
|
45
|
+
<h2><%= assoc.name.to_s.titleize %></h2>
|
|
46
|
+
<% if AdminResources.model_names.include?(assoc.klass.name) %>
|
|
47
|
+
<div class="admin-actions">
|
|
48
|
+
<%= link_to "View", admin_path_for(assoc.klass.name, :show, record), class: "admin-btn admin-btn-secondary" %>
|
|
49
|
+
<%= link_to "Edit", admin_path_for(assoc.klass.name, :edit, record), class: "admin-btn admin-btn-secondary" %>
|
|
50
|
+
</div>
|
|
51
|
+
<% end %>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div class="admin-card">
|
|
55
|
+
<% assoc.klass.column_names.each do |col| %>
|
|
56
|
+
<div class="admin-detail-row">
|
|
57
|
+
<div class="admin-detail-label"><%= col.titleize %></div>
|
|
58
|
+
<div class="admin-detail-value">
|
|
59
|
+
<% display, link = admin_value_display(record, col) %>
|
|
60
|
+
<% if display.nil? %>
|
|
61
|
+
<em style="color: #999;">nil</em>
|
|
62
|
+
<% elsif display.is_a?(Time) || display.is_a?(DateTime) %>
|
|
63
|
+
<%= display.strftime("%Y-%m-%d %H:%M:%S") %>
|
|
64
|
+
<% elsif display.is_a?(TrueClass) || display.is_a?(FalseClass) %>
|
|
65
|
+
<%= display ? "Yes" : "No" %>
|
|
66
|
+
<% elsif link %>
|
|
67
|
+
<%= link_to display, link %>
|
|
68
|
+
<% else %>
|
|
69
|
+
<%= truncate(display.to_s, length: 100) %>
|
|
70
|
+
<% end %>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
<% end %>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<% end %>
|
|
77
|
+
|
|
78
|
+
<%# Display has_many associations %>
|
|
79
|
+
<% model_class.reflect_on_all_associations(:has_many).each do |assoc| %>
|
|
80
|
+
<% associated_records = @resource.send(assoc.name) %>
|
|
81
|
+
<% next if associated_records.empty? %>
|
|
82
|
+
|
|
83
|
+
<div style="margin-top: 2rem;">
|
|
84
|
+
<div class="admin-header">
|
|
85
|
+
<h2><%= assoc.name.to_s.titleize %> (<%= associated_records.count %>)</h2>
|
|
86
|
+
<% assoc_model = assoc.klass.name %>
|
|
87
|
+
<% if AdminResources.model_names.include?(assoc_model) %>
|
|
88
|
+
<%= link_to "New #{assoc_model}", admin_path_for(assoc_model, :new), class: "admin-btn admin-btn-secondary" %>
|
|
89
|
+
<% end %>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<table class="admin-table">
|
|
93
|
+
<thead>
|
|
94
|
+
<tr>
|
|
95
|
+
<% assoc.klass.column_names.first(6).each do |col| %>
|
|
96
|
+
<th><%= col.titleize %></th>
|
|
97
|
+
<% end %>
|
|
98
|
+
<th>Actions</th>
|
|
99
|
+
</tr>
|
|
100
|
+
</thead>
|
|
101
|
+
<tbody>
|
|
102
|
+
<% associated_records.limit(20).each do |record| %>
|
|
103
|
+
<tr>
|
|
104
|
+
<% assoc.klass.column_names.first(6).each do |col| %>
|
|
105
|
+
<td>
|
|
106
|
+
<% display, link = admin_value_display(record, col) %>
|
|
107
|
+
<% column_type = assoc.klass.columns_hash[col]&.type %>
|
|
108
|
+
<% if col == "id" && AdminResources.model_names.include?(assoc.klass.name) %>
|
|
109
|
+
<%= link_to record.id, admin_path_for(assoc.klass.name, :show, record) %>
|
|
110
|
+
<% elsif %w[params data].include?(col) || column_type == :json || column_type == :jsonb %>
|
|
111
|
+
<pre style="margin: 0; font-size: 0.75rem; max-width: 300px; overflow-x: auto; white-space: pre-wrap;"><%= JSON.pretty_generate(display) rescue display %></pre>
|
|
112
|
+
<% elsif link %>
|
|
113
|
+
<%= link_to truncate(display.to_s, length: 50), link %>
|
|
114
|
+
<% else %>
|
|
115
|
+
<%= truncate(display.to_s, length: 50) %>
|
|
116
|
+
<% end %>
|
|
117
|
+
</td>
|
|
118
|
+
<% end %>
|
|
119
|
+
<td class="admin-actions">
|
|
120
|
+
<% if AdminResources.model_names.include?(assoc.klass.name) %>
|
|
121
|
+
<%= link_to "View", admin_path_for(assoc.klass.name, :show, record), class: "admin-btn admin-btn-secondary" %>
|
|
122
|
+
<%= link_to "Edit", admin_path_for(assoc.klass.name, :edit, record), class: "admin-btn admin-btn-secondary" %>
|
|
123
|
+
<% end %>
|
|
124
|
+
</td>
|
|
125
|
+
</tr>
|
|
126
|
+
<% end %>
|
|
127
|
+
</tbody>
|
|
128
|
+
</table>
|
|
129
|
+
<% if associated_records.count > 20 %>
|
|
130
|
+
<p style="margin-top: 0.5rem; color: #666;">Showing 20 of <%= associated_records.count %> records</p>
|
|
131
|
+
<% end %>
|
|
132
|
+
</div>
|
|
133
|
+
<% end %>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Admin - <%= content_for(:title) || "Admin" %></title>
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<%= csrf_meta_tags %>
|
|
7
|
+
<%= csp_meta_tag %>
|
|
8
|
+
<style>
|
|
9
|
+
.admin-layout { display: flex; min-height: 100vh; }
|
|
10
|
+
.admin-sidebar { width: 220px; background: #1a1a2e; padding: 1rem; }
|
|
11
|
+
.admin-sidebar a { color: #eee; text-decoration: none; display: block; padding: 0.5rem; border-radius: 4px; }
|
|
12
|
+
.admin-sidebar a:hover { background: #16213e; }
|
|
13
|
+
.admin-sidebar a.active { background: #0f3460; }
|
|
14
|
+
.admin-sidebar h2 { color: #fff; font-size: 1.25rem; margin-bottom: 1rem; }
|
|
15
|
+
.admin-sidebar h3 { color: #888; font-size: 0.75rem; text-transform: uppercase; margin: 1rem 0 0.5rem; }
|
|
16
|
+
.admin-main { flex: 1; padding: 2rem; background: #f5f5f5; }
|
|
17
|
+
.admin-header { margin-bottom: 2rem; display: flex; justify-content: space-between; align-items: center; }
|
|
18
|
+
.admin-header h1 { margin: 0; }
|
|
19
|
+
.admin-table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
20
|
+
.admin-table th, .admin-table td { padding: 0.75rem 1rem; text-align: left; border-bottom: 1px solid #eee; }
|
|
21
|
+
.admin-table th { background: #fafafa; font-weight: 600; }
|
|
22
|
+
.admin-table tr:hover { background: #fafafa; }
|
|
23
|
+
.admin-btn { display: inline-block; padding: 0.5rem 1rem; border-radius: 4px; text-decoration: none; font-size: 0.875rem; cursor: pointer; border: none; }
|
|
24
|
+
.admin-btn-primary { background: #0f3460; color: #fff; }
|
|
25
|
+
.admin-btn-secondary { background: #eee; color: #333; }
|
|
26
|
+
.admin-btn-danger { background: #dc3545; color: #fff; }
|
|
27
|
+
.admin-btn:hover { opacity: 0.9; }
|
|
28
|
+
.admin-form { background: #fff; padding: 1.5rem; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); max-width: 600px; }
|
|
29
|
+
.admin-form .field { margin-bottom: 1rem; }
|
|
30
|
+
.admin-form label { display: block; margin-bottom: 0.25rem; font-weight: 500; }
|
|
31
|
+
.admin-form input[type="text"], .admin-form input[type="email"], .admin-form input[type="number"], .admin-form input[type="password"], .admin-form textarea, .admin-form select { width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem; }
|
|
32
|
+
.admin-form input[type="checkbox"] { width: auto; }
|
|
33
|
+
.admin-flash { padding: 1rem; border-radius: 4px; margin-bottom: 1rem; }
|
|
34
|
+
.admin-flash.notice { background: #d4edda; color: #155724; }
|
|
35
|
+
.admin-flash.alert { background: #f8d7da; color: #721c24; }
|
|
36
|
+
.admin-card { background: #fff; padding: 1.5rem; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
37
|
+
.admin-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; }
|
|
38
|
+
.admin-stat { text-align: center; }
|
|
39
|
+
.admin-stat-value { font-size: 2rem; font-weight: bold; color: #0f3460; }
|
|
40
|
+
.admin-stat-label { color: #666; }
|
|
41
|
+
.admin-detail-row { display: flex; border-bottom: 1px solid #eee; padding: 0.75rem 0; }
|
|
42
|
+
.admin-detail-label { width: 200px; font-weight: 500; color: #666; }
|
|
43
|
+
.admin-detail-value { flex: 1; }
|
|
44
|
+
.admin-actions { display: flex; gap: 0.5rem; }
|
|
45
|
+
</style>
|
|
46
|
+
</head>
|
|
47
|
+
|
|
48
|
+
<body>
|
|
49
|
+
<div class="admin-layout">
|
|
50
|
+
<nav class="admin-sidebar">
|
|
51
|
+
<h2><%= link_to "Admin", admin_resources.root_path %></h2>
|
|
52
|
+
|
|
53
|
+
<h3>Resources</h3>
|
|
54
|
+
<% admin_models.each do |model_name| %>
|
|
55
|
+
<%= link_to model_name.pluralize, admin_path_for(model_name),
|
|
56
|
+
class: ('active' if params[:model] == model_name) %>
|
|
57
|
+
<% end %>
|
|
58
|
+
|
|
59
|
+
<h3>Account</h3>
|
|
60
|
+
<%= link_to "Sign Out", admin_resources.destroy_admin_user_session_path, data: { turbo_method: :delete } %>
|
|
61
|
+
</nav>
|
|
62
|
+
|
|
63
|
+
<main class="admin-main">
|
|
64
|
+
<% if notice %>
|
|
65
|
+
<div class="admin-flash notice"><%= notice %></div>
|
|
66
|
+
<% end %>
|
|
67
|
+
<% if alert %>
|
|
68
|
+
<div class="admin-flash alert"><%= alert %></div>
|
|
69
|
+
<% end %>
|
|
70
|
+
|
|
71
|
+
<%= yield %>
|
|
72
|
+
</main>
|
|
73
|
+
</div>
|
|
74
|
+
</body>
|
|
75
|
+
</html>
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
AdminResources::Engine.routes.draw do
|
|
2
|
+
devise_for :admin_users,
|
|
3
|
+
class_name: "AdminResources::AdminUser",
|
|
4
|
+
module: :devise,
|
|
5
|
+
path: "",
|
|
6
|
+
path_names: { sign_in: "login", sign_out: "logout" }
|
|
7
|
+
|
|
8
|
+
# Dynamically generate routes for every registered model
|
|
9
|
+
AdminResources.model_names.each do |model_name|
|
|
10
|
+
resources model_name.underscore.pluralize.to_sym,
|
|
11
|
+
controller: "resources",
|
|
12
|
+
defaults: { model: model_name }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
root to: "dashboard#index"
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class CreateAdminResourcesAdminUsers < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :admin_resources_admin_users do |t|
|
|
4
|
+
t.string :email, null: false, default: ""
|
|
5
|
+
t.string :encrypted_password, null: false, default: ""
|
|
6
|
+
t.string :reset_password_token
|
|
7
|
+
t.datetime :reset_password_sent_at
|
|
8
|
+
t.datetime :remember_created_at
|
|
9
|
+
|
|
10
|
+
t.timestamps null: false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
add_index :admin_resources_admin_users, :email, unique: true
|
|
14
|
+
add_index :admin_resources_admin_users, :reset_password_token, unique: true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AdminResources
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_reader :models
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@models = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Register a model for admin management
|
|
12
|
+
# Usage: config.register "User", columns: %w[id email created_at]
|
|
13
|
+
# Usage: config.register "User" (defaults to first 6 columns)
|
|
14
|
+
def register(model_name, columns: nil)
|
|
15
|
+
name = model_name.to_s.classify
|
|
16
|
+
@models[name] = { columns: columns&.map(&:to_s) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def model_names
|
|
20
|
+
@models.keys
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/engine"
|
|
4
|
+
require "devise"
|
|
5
|
+
|
|
6
|
+
module AdminResources
|
|
7
|
+
class Engine < ::Rails::Engine
|
|
8
|
+
isolate_namespace AdminResources
|
|
9
|
+
|
|
10
|
+
initializer "admin_resources.load_admin_user_migration" do |app|
|
|
11
|
+
# Add our migrations to the host app's migration path
|
|
12
|
+
config.paths["db/migrate"].expanded.each do |expanded_path|
|
|
13
|
+
app.config.paths["db/migrate"] << expanded_path
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
config.generators do |g|
|
|
18
|
+
g.test_framework nil
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "admin_resources/version"
|
|
4
|
+
require_relative "admin_resources/configuration"
|
|
5
|
+
require_relative "admin_resources/engine"
|
|
6
|
+
|
|
7
|
+
module AdminResources
|
|
8
|
+
class << self
|
|
9
|
+
def configuration
|
|
10
|
+
@configuration ||= Configuration.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def configure
|
|
14
|
+
yield configuration
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Shorthand accessors used throughout the engine
|
|
18
|
+
def models
|
|
19
|
+
configuration.models
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def model_names
|
|
23
|
+
configuration.model_names
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: admin_resources
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- mark rosenberg
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-21 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '7.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: devise
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '4.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '4.0'
|
|
41
|
+
description: Mount AdminResources::Engine in your Rails app, configure which models
|
|
42
|
+
to expose, and get a full admin dashboard with zero boilerplate.
|
|
43
|
+
email:
|
|
44
|
+
- mark@does.work
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- LICENSE
|
|
50
|
+
- README.md
|
|
51
|
+
- Rakefile
|
|
52
|
+
- app/controllers/admin_resources/application_controller.rb
|
|
53
|
+
- app/controllers/admin_resources/dashboard_controller.rb
|
|
54
|
+
- app/controllers/admin_resources/resources_controller.rb
|
|
55
|
+
- app/models/admin_resources/admin_user.rb
|
|
56
|
+
- app/views/admin_resources/dashboard/index.html.erb
|
|
57
|
+
- app/views/admin_resources/resources/_form.html.erb
|
|
58
|
+
- app/views/admin_resources/resources/edit.html.erb
|
|
59
|
+
- app/views/admin_resources/resources/index.html.erb
|
|
60
|
+
- app/views/admin_resources/resources/new.html.erb
|
|
61
|
+
- app/views/admin_resources/resources/show.html.erb
|
|
62
|
+
- app/views/layouts/admin_resources/admin.html.erb
|
|
63
|
+
- config/routes.rb
|
|
64
|
+
- db/migrate/20240101000000_create_admin_resources_admin_users.rb
|
|
65
|
+
- lib/admin_resources.rb
|
|
66
|
+
- lib/admin_resources/configuration.rb
|
|
67
|
+
- lib/admin_resources/engine.rb
|
|
68
|
+
- lib/admin_resources/version.rb
|
|
69
|
+
- sig/admin_resources.rbs
|
|
70
|
+
homepage: https://github.com/doeswork/admin_resources
|
|
71
|
+
licenses: []
|
|
72
|
+
metadata: {}
|
|
73
|
+
post_install_message:
|
|
74
|
+
rdoc_options: []
|
|
75
|
+
require_paths:
|
|
76
|
+
- lib
|
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: 3.0.0
|
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
requirements: []
|
|
88
|
+
rubygems_version: 3.5.16
|
|
89
|
+
signing_key:
|
|
90
|
+
specification_version: 4
|
|
91
|
+
summary: Mountable Rails engine that auto-generates admin CRUD UI for registered models.
|
|
92
|
+
test_files: []
|