jav_madmin 2.6.2
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/MIT-LICENSE +20 -0
- data/README.md +294 -0
- data/Rakefile +30 -0
- data/app/assets/config/madmin_manifest.js +3 -0
- data/app/assets/stylesheets/madmin/actiontext.css +31 -0
- data/app/assets/stylesheets/madmin/application-sprockets.css +11 -0
- data/app/assets/stylesheets/madmin/application.css +9 -0
- data/app/assets/stylesheets/madmin/base.css +144 -0
- data/app/assets/stylesheets/madmin/buttons.css +53 -0
- data/app/assets/stylesheets/madmin/forms.css +90 -0
- data/app/assets/stylesheets/madmin/pagination.css +59 -0
- data/app/assets/stylesheets/madmin/reset.css +242 -0
- data/app/assets/stylesheets/madmin/sidebar.css +179 -0
- data/app/assets/stylesheets/madmin/tables.css +71 -0
- data/app/controllers/madmin/application_controller.rb +17 -0
- data/app/controllers/madmin/base_controller.rb +15 -0
- data/app/controllers/madmin/dashboard_controller.rb +6 -0
- data/app/controllers/madmin/resource_controller.rb +119 -0
- data/app/helpers/madmin/application_helper.rb +10 -0
- data/app/helpers/madmin/nav_helper.rb +34 -0
- data/app/helpers/madmin/sort_helper.rb +48 -0
- data/app/javascript/madmin/application.js +3 -0
- data/app/javascript/madmin/controllers/application.js +12 -0
- data/app/javascript/madmin/controllers/index.js +5 -0
- data/app/javascript/madmin/controllers/mobile_nav_controller.js +50 -0
- data/app/javascript/madmin/controllers/nested_form_controller.js +34 -0
- data/app/javascript/madmin/controllers/select_controller.js +37 -0
- data/app/views/layouts/madmin/application.html.erb +41 -0
- data/app/views/madmin/application/_flash.html.erb +13 -0
- data/app/views/madmin/application/_form.html.erb +24 -0
- data/app/views/madmin/application/_javascript.html.erb +8 -0
- data/app/views/madmin/application/_missing_resource.html.erb +8 -0
- data/app/views/madmin/application/_navigation.html.erb +22 -0
- data/app/views/madmin/application/edit.html.erb +11 -0
- data/app/views/madmin/application/index.html.erb +85 -0
- data/app/views/madmin/application/new.html.erb +11 -0
- data/app/views/madmin/application/show.html.erb +40 -0
- data/app/views/madmin/dashboard/show.html.erb +2 -0
- data/app/views/madmin/fields/attachment/_form.html.erb +11 -0
- data/app/views/madmin/fields/attachment/_index.html.erb +7 -0
- data/app/views/madmin/fields/attachment/_show.html.erb +9 -0
- data/app/views/madmin/fields/attachments/_form.html.erb +1 -0
- data/app/views/madmin/fields/attachments/_index.html.erb +1 -0
- data/app/views/madmin/fields/attachments/_show.html.erb +11 -0
- data/app/views/madmin/fields/belongs_to/_form.html.erb +1 -0
- data/app/views/madmin/fields/belongs_to/_index.html.erb +4 -0
- data/app/views/madmin/fields/belongs_to/_show.html.erb +7 -0
- data/app/views/madmin/fields/boolean/_form.html.erb +3 -0
- data/app/views/madmin/fields/boolean/_index.html.erb +1 -0
- data/app/views/madmin/fields/boolean/_show.html.erb +1 -0
- data/app/views/madmin/fields/currency/_form.html.erb +1 -0
- data/app/views/madmin/fields/currency/_index.html.erb +1 -0
- data/app/views/madmin/fields/currency/_show.html.erb +1 -0
- data/app/views/madmin/fields/date/_form.html.erb +1 -0
- data/app/views/madmin/fields/date/_index.html.erb +1 -0
- data/app/views/madmin/fields/date/_show.html.erb +1 -0
- data/app/views/madmin/fields/date_time/_form.html.erb +1 -0
- data/app/views/madmin/fields/date_time/_index.html.erb +1 -0
- data/app/views/madmin/fields/date_time/_show.html.erb +1 -0
- data/app/views/madmin/fields/decimal/_form.html.erb +1 -0
- data/app/views/madmin/fields/decimal/_index.html.erb +1 -0
- data/app/views/madmin/fields/decimal/_show.html.erb +1 -0
- data/app/views/madmin/fields/enum/_form.html.erb +1 -0
- data/app/views/madmin/fields/enum/_index.html.erb +1 -0
- data/app/views/madmin/fields/enum/_show.html.erb +1 -0
- data/app/views/madmin/fields/file/_form.html.erb +1 -0
- data/app/views/madmin/fields/file/_index.html.erb +1 -0
- data/app/views/madmin/fields/file/_show.html.erb +6 -0
- data/app/views/madmin/fields/float/_form.html.erb +1 -0
- data/app/views/madmin/fields/float/_index.html.erb +1 -0
- data/app/views/madmin/fields/float/_show.html.erb +1 -0
- data/app/views/madmin/fields/has_many/_form.html.erb +1 -0
- data/app/views/madmin/fields/has_many/_index.html.erb +1 -0
- data/app/views/madmin/fields/has_many/_show.html.erb +18 -0
- data/app/views/madmin/fields/has_one/_form.html.erb +1 -0
- data/app/views/madmin/fields/has_one/_index.html.erb +3 -0
- data/app/views/madmin/fields/has_one/_show.html.erb +7 -0
- data/app/views/madmin/fields/integer/_form.html.erb +1 -0
- data/app/views/madmin/fields/integer/_index.html.erb +1 -0
- data/app/views/madmin/fields/integer/_show.html.erb +1 -0
- data/app/views/madmin/fields/json/_form.html.erb +1 -0
- data/app/views/madmin/fields/json/_index.html.erb +1 -0
- data/app/views/madmin/fields/json/_show.html.erb +1 -0
- data/app/views/madmin/fields/nested_has_many/_fields.html.erb +17 -0
- data/app/views/madmin/fields/nested_has_many/_form.html.erb +28 -0
- data/app/views/madmin/fields/nested_has_many/_index.html.erb +1 -0
- data/app/views/madmin/fields/nested_has_many/_show.html.erb +18 -0
- data/app/views/madmin/fields/password/_form.html.erb +1 -0
- data/app/views/madmin/fields/password/_index.html.erb +1 -0
- data/app/views/madmin/fields/password/_show.html.erb +1 -0
- data/app/views/madmin/fields/polymorphic/_form.html.erb +4 -0
- data/app/views/madmin/fields/polymorphic/_index.html.erb +3 -0
- data/app/views/madmin/fields/polymorphic/_show.html.erb +7 -0
- data/app/views/madmin/fields/rich_text/_form.html.erb +1 -0
- data/app/views/madmin/fields/rich_text/_index.html.erb +1 -0
- data/app/views/madmin/fields/rich_text/_show.html.erb +3 -0
- data/app/views/madmin/fields/select/_form.html.erb +1 -0
- data/app/views/madmin/fields/select/_index.html.erb +1 -0
- data/app/views/madmin/fields/select/_show.html.erb +1 -0
- data/app/views/madmin/fields/string/_form.html.erb +1 -0
- data/app/views/madmin/fields/string/_index.html.erb +1 -0
- data/app/views/madmin/fields/string/_show.html.erb +1 -0
- data/app/views/madmin/fields/text/_form.html.erb +1 -0
- data/app/views/madmin/fields/text/_index.html.erb +1 -0
- data/app/views/madmin/fields/text/_show.html.erb +1 -0
- data/app/views/madmin/fields/time/_form.html.erb +5 -0
- data/app/views/madmin/fields/time/_index.html.erb +1 -0
- data/app/views/madmin/fields/time/_show.html.erb +1 -0
- data/app/views/madmin/shared/_label.html.erb +4 -0
- data/config/importmap.rb +15 -0
- data/config/locales/el.yml +42 -0
- data/config/locales/en.yml +42 -0
- data/config/locales/fr.yml +42 -0
- data/config/locales/ko.yml +40 -0
- data/lib/generators/madmin/field/field_generator.rb +31 -0
- data/lib/generators/madmin/field/templates/_form.html.erb +1 -0
- data/lib/generators/madmin/field/templates/_index.html.erb +1 -0
- data/lib/generators/madmin/field/templates/_show.html.erb +1 -0
- data/lib/generators/madmin/field/templates/field.rb.tt +26 -0
- data/lib/generators/madmin/install/install_generator.rb +55 -0
- data/lib/generators/madmin/install/templates/controller.rb.tt +15 -0
- data/lib/generators/madmin/install/templates/routes.rb.tt +3 -0
- data/lib/generators/madmin/resource/resource_generator.rb +73 -0
- data/lib/generators/madmin/resource/templates/controller.rb.tt +10 -0
- data/lib/generators/madmin/resource/templates/resource.rb.tt +33 -0
- data/lib/generators/madmin/views/edit_generator.rb +16 -0
- data/lib/generators/madmin/views/form_generator.rb +15 -0
- data/lib/generators/madmin/views/index_generator.rb +15 -0
- data/lib/generators/madmin/views/javascript_generator.rb +15 -0
- data/lib/generators/madmin/views/layout_generator.rb +21 -0
- data/lib/generators/madmin/views/navigation_generator.rb +15 -0
- data/lib/generators/madmin/views/new_generator.rb +16 -0
- data/lib/generators/madmin/views/show_generator.rb +15 -0
- data/lib/generators/madmin/views/views_generator.rb +16 -0
- data/lib/madmin/engine.rb +47 -0
- data/lib/madmin/field.rb +67 -0
- data/lib/madmin/fields/attachment.rb +6 -0
- data/lib/madmin/fields/attachments.rb +9 -0
- data/lib/madmin/fields/belongs_to.rb +29 -0
- data/lib/madmin/fields/boolean.rb +6 -0
- data/lib/madmin/fields/currency.rb +15 -0
- data/lib/madmin/fields/date.rb +6 -0
- data/lib/madmin/fields/date_time.rb +6 -0
- data/lib/madmin/fields/decimal.rb +6 -0
- data/lib/madmin/fields/enum.rb +9 -0
- data/lib/madmin/fields/file.rb +9 -0
- data/lib/madmin/fields/float.rb +6 -0
- data/lib/madmin/fields/has_many.rb +62 -0
- data/lib/madmin/fields/has_one.rb +10 -0
- data/lib/madmin/fields/integer.rb +6 -0
- data/lib/madmin/fields/json.rb +6 -0
- data/lib/madmin/fields/nested_has_many.rb +44 -0
- data/lib/madmin/fields/password.rb +6 -0
- data/lib/madmin/fields/polymorphic.rb +22 -0
- data/lib/madmin/fields/rich_text.rb +6 -0
- data/lib/madmin/fields/select.rb +9 -0
- data/lib/madmin/fields/string.rb +9 -0
- data/lib/madmin/fields/text.rb +9 -0
- data/lib/madmin/fields/time.rb +6 -0
- data/lib/madmin/generator_helpers.rb +59 -0
- data/lib/madmin/member_action.rb +19 -0
- data/lib/madmin/menu.rb +70 -0
- data/lib/madmin/namespace.rb +35 -0
- data/lib/madmin/resource.rb +295 -0
- data/lib/madmin/resource_builder.rb +88 -0
- data/lib/madmin/search.rb +62 -0
- data/lib/madmin/version.rb +3 -0
- data/lib/madmin/view_generator.rb +43 -0
- data/lib/madmin.rb +114 -0
- data/lib/tasks/madmin_tasks.rake +11 -0
- metadata +284 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 5d92b6e04ea290f8ea0692b83ed2a324fc76d51cb1c63a35435a34552cdeecba
|
|
4
|
+
data.tar.gz: 24f2b178256dc83dd6c5dd9ed6d276be5b174b9ea34bc2fb4c99549d618488f9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 384f3b0526579449b6dc3e58f3e633ca7d5bb193070a17b052d6d38863f671fc9a84b054b1349912f71d479743b5b7f69f2518e67b693ea7941deb95a55d4b6e
|
|
7
|
+
data.tar.gz: '0293d9d07f98036e8ff5895753aa87549b81345e5356fa8b171fe9bd5fbd3578842669a5c6682fa92efd84683b009eeb997a13dcc095b0e2f71ccab000468f81'
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2020 Chris Oliver
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# Madmin
|
|
2
|
+
|
|
3
|
+
### 🛠 A robust Admin Interface for Ruby on Rails apps
|
|
4
|
+
|
|
5
|
+
[](https://github.com/excid3/madmin/actions) [](https://badge.fury.io/rb/madmin)
|
|
6
|
+
|
|
7
|
+
Why another Ruby on Rails admin? We wanted an admin that was:
|
|
8
|
+
|
|
9
|
+
- Familiar and customizable like Rails scaffolds (less DSL)
|
|
10
|
+
- Supports all the Rails features out of the box (ActionText, ActionMailbox, has_secure_password, etc)
|
|
11
|
+
- Stimulus / Turbolinks / Hotwire ready
|
|
12
|
+
- Works with Import maps and Sprockets
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
Add `madmin` to your application's Gemfile:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bundle add madmin
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Then run the madmin generator:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
rails g madmin:install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This will install Madmin and generate resources for each of the models it finds.
|
|
31
|
+
|
|
32
|
+
## Resources
|
|
33
|
+
|
|
34
|
+
Madmin uses `Resource` classes to add models to the admin area.
|
|
35
|
+
|
|
36
|
+
### Generate a Resource
|
|
37
|
+
|
|
38
|
+
To generate a resource for a model, you can run:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
rails g madmin:resource ActionText::RichText
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Avoid N+1 queries
|
|
45
|
+
|
|
46
|
+
In case of N+1 queries, you can preload the association by overriding the `scoped_resource` method in the controller:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
module Madmin
|
|
50
|
+
class PostsController < Madmin::ResourceController
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def scoped_resources
|
|
54
|
+
super.includes(:user)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Read-only Resources
|
|
62
|
+
|
|
63
|
+
To expose a resource in the admin without allowing writes, override `readonly?` in the resource:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
class AuditLogResource < Madmin::Resource
|
|
67
|
+
def self.readonly?
|
|
68
|
+
true
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The new, edit, and delete links are hidden for read-only resources, and any write actions redirect back to the index.
|
|
74
|
+
|
|
75
|
+
### Scopes
|
|
76
|
+
|
|
77
|
+
Declare scopes on a resource to render filter buttons on the index page. Each scope must be a scope or class method on the model:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
class PostResource < Madmin::Resource
|
|
81
|
+
scope :published
|
|
82
|
+
scope :draft
|
|
83
|
+
end
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Button labels default to the humanized scope name. To customize or translate them, define an I18n key — `madmin.scopes.<param_key>.<scope>` for one resource, or `madmin.scopes.<scope>` shared across resources:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
en:
|
|
90
|
+
madmin:
|
|
91
|
+
scopes:
|
|
92
|
+
post:
|
|
93
|
+
published: Live
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
For labels that need logic (e.g. looking up a record name), override `scope_label`:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
class PostResource < Madmin::Resource
|
|
100
|
+
def self.scope_label(name)
|
|
101
|
+
name.to_s.titleize
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Configuring Views
|
|
107
|
+
|
|
108
|
+
The views packaged within the gem are a great starting point, but inevitably people will need to be able to customize those views.
|
|
109
|
+
|
|
110
|
+
You can use the included generator to create the appropriate view files, which can then be customized.
|
|
111
|
+
|
|
112
|
+
For example, running the following will copy over all of the views into your application that will be used for every resource:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
rails generate madmin:views
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The view files that are copied over in this case includes all of the standard Rails action views (index, new, edit, show, and \_form), as well as:
|
|
119
|
+
|
|
120
|
+
- `application.html.erb` (layout file)
|
|
121
|
+
- `_javascript.html.erb` (default JavaScript setup)
|
|
122
|
+
- `_navigation.html.erb` (renders the navigation/sidebar menu)
|
|
123
|
+
|
|
124
|
+
As with the other views, you can specifically run the views generator for only the navigation or application layout views:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
rails g madmin:views:navigation
|
|
128
|
+
# -> app/views/madmin/_navigation.html.erb
|
|
129
|
+
|
|
130
|
+
rails g madmin:views:layout # Note the layout generator includes the layout, javascript, and navigation files.
|
|
131
|
+
# -> app/views/madmin/application.html.erb
|
|
132
|
+
# -> app/views/madmin/_javascript.html.erb
|
|
133
|
+
# -> app/views/madmin/_navigation.html.erb
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
If you only need to customize specific views, you can restrict which views are copied by the generator:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
rails g madmin:views:index
|
|
140
|
+
# -> app/views/madmin/application/index.html.erb
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
You might want to make some of your model's attributes visible in some views but invisible in others.
|
|
144
|
+
The `attribute` method in model_resource.rb gives you that flexibility.
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# -> app/madmin/resources/book_resource.rb
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
class BookResource < Madmin::Resource
|
|
152
|
+
attribute :id, form: false
|
|
153
|
+
attribute :title
|
|
154
|
+
attribute :subtitle, index: false
|
|
155
|
+
attribute :author
|
|
156
|
+
attribute :genre
|
|
157
|
+
attribute :pages, show: false
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
You can also scope the copied view(s) to a specific Resource/Model:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
rails generate madmin:views:index Book
|
|
165
|
+
# -> app/views/madmin/books/index.html.erb
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Specifying Field Types
|
|
169
|
+
|
|
170
|
+
You can set a field type as the second argument. Field types may have additional options to render the field UI.
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
For example, we can use a select for the genre attribute and specify the collection of options to choose from.
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
class BookResource < Madmin::Resource
|
|
177
|
+
attribute :genre, :select, collection: ["Fiction", "Mystery", "Thriller"]
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Custom Fields
|
|
182
|
+
|
|
183
|
+
You can generate a custom field with:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
rails g madmin:field Custom
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
This will create a `CustomField` class in `app/madmin/fields/custom_field.rb`
|
|
190
|
+
And the related views:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# -> app/views/madmin/fields/custom_field/_form.html.erb
|
|
194
|
+
# -> app/views/madmin/fields/custom_field/_index.html.erb
|
|
195
|
+
# -> app/views/madmin/fields/custom_field/_show.html.erb
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
You can then use this field on our resource:
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
class PostResource < Madmin::Resource
|
|
202
|
+
attribute :title, field: CustomField
|
|
203
|
+
end
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Actions
|
|
207
|
+
|
|
208
|
+
### Member Actions
|
|
209
|
+
|
|
210
|
+
`member_action` lets you add custom buttons to a resource's **show** page. Each block receives the current `record` and is rendered in the header's `.actions` div:
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
class PostResource < Madmin::Resource
|
|
214
|
+
member_action do |record|
|
|
215
|
+
link_to "Publish", publish_admin_post_path(record), class: "btn btn-secondary"
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Pass `collection: true` to also render the action on the **index** page, in each row next to the built-in "View" and "Edit" links:
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
class PostResource < Madmin::Resource
|
|
224
|
+
member_action collection: true do |record|
|
|
225
|
+
link_to "Publish", publish_admin_post_path(record), class: "btn btn-secondary"
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Collection Actions
|
|
231
|
+
|
|
232
|
+
`collection_action` is the index-page counterpart to `member_action`. Blocks are rendered in the index header's `.actions` div, immediately before the built-in "New \<Resource\>" link.
|
|
233
|
+
|
|
234
|
+
Unlike `member_action`, blocks receive **no** arguments. Each block is `instance_exec`-ed on the index view context, so `link_to`, route helpers, `policy`, `current_user`, `params`, `resource`, and `@records` are all available. Multiple blocks render in registration order.
|
|
235
|
+
|
|
236
|
+
Each block is responsible for its own authorization gating — if a button should be hidden for some users, guard it inside the block.
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
class PostResource < Madmin::Resource
|
|
240
|
+
collection_action do
|
|
241
|
+
link_to "Bulk Import", bulk_import_admin_posts_path, class: "btn btn-secondary"
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
collection_action do
|
|
245
|
+
link_to "Export CSV", export_admin_posts_path(format: :csv), class: "btn btn-secondary"
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Extending Madmin
|
|
251
|
+
|
|
252
|
+
Madmin runs `ActiveSupport` load hooks on its core classes so you can extend them from an initializer without reopening or monkey patching them. This is the recommended way for engines and gems to add behavior to Madmin.
|
|
253
|
+
|
|
254
|
+
| Hook | Base | Use it for |
|
|
255
|
+
| --- | --- | --- |
|
|
256
|
+
| `:madmin_resource` | `Madmin::Resource` | Adding to the resource DSL |
|
|
257
|
+
| `:madmin_field` | `Madmin::Field` | Shared behavior across every field type |
|
|
258
|
+
| `:madmin_search` | `Madmin::Search` | Customizing how the search query is built |
|
|
259
|
+
| `:madmin_base_controller` | `Madmin::BaseController` | Layouts, helpers, `rescue_from`, authentication |
|
|
260
|
+
| `:madmin_resource_controller` | `Madmin::ResourceController` | CRUD callbacks, scoping records, audit logging |
|
|
261
|
+
|
|
262
|
+
Blocks are `class_eval`-ed on the base class, so `self` is the class itself:
|
|
263
|
+
|
|
264
|
+
```ruby
|
|
265
|
+
# config/initializers/madmin.rb
|
|
266
|
+
ActiveSupport.on_load(:madmin_resource) do
|
|
267
|
+
# self == Madmin::Resource, so `extend` adds class-level DSL to every resource
|
|
268
|
+
extend MyAdmin::ResourceExtensions
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
ActiveSupport.on_load(:madmin_base_controller) do
|
|
272
|
+
# self == Madmin::BaseController
|
|
273
|
+
rescue_from Pundit::NotAuthorizedError, with: :admin_not_authorized
|
|
274
|
+
end
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Hooks registered after a class has already loaded run immediately, so ordering in your initializers doesn't matter. The controllers live in the engine's `app/` directory and are reloadable, which means their hooks re-run on every reload in development — keep the blocks idempotent.
|
|
278
|
+
|
|
279
|
+
## Authentication
|
|
280
|
+
|
|
281
|
+
You can use a couple of strategies to authenticate users who are trying to
|
|
282
|
+
access your madmin panel: [Authentication Docs](docs/authentication.md)
|
|
283
|
+
|
|
284
|
+
## Assets
|
|
285
|
+
You can customize the JavaScript and CSS assets used by Madmin for your application. To learn how
|
|
286
|
+
see the [Assets Doc](docs/assets.md)
|
|
287
|
+
|
|
288
|
+
## 🙏 Contributing
|
|
289
|
+
|
|
290
|
+
This project uses Standard for formatting Ruby code. Please make sure to run standardrb before submitting pull requests.
|
|
291
|
+
|
|
292
|
+
## 📝 License
|
|
293
|
+
|
|
294
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require "bundler/setup"
|
|
3
|
+
rescue LoadError
|
|
4
|
+
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
require "rdoc/task"
|
|
8
|
+
|
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
|
10
|
+
rdoc.rdoc_dir = "rdoc"
|
|
11
|
+
rdoc.title = "Madmin"
|
|
12
|
+
rdoc.options << "--line-numbers"
|
|
13
|
+
rdoc.rdoc_files.include("README.md")
|
|
14
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
require "bundler/gem_tasks"
|
|
18
|
+
|
|
19
|
+
require "rake/testtask"
|
|
20
|
+
|
|
21
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
|
22
|
+
load "rails/tasks/engine.rake"
|
|
23
|
+
|
|
24
|
+
Rake::TestTask.new(:test) do |t|
|
|
25
|
+
t.libs << "test"
|
|
26
|
+
t.pattern = "test/**/*_test.rb"
|
|
27
|
+
t.verbose = false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
task default: :test
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
|
|
3
|
+
* the trix-editor content (whether displayed or under editing). Feel free to incorporate this
|
|
4
|
+
* inclusion directly in any other asset bundle and remove this file.
|
|
5
|
+
*
|
|
6
|
+
*= require trix
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* We need to override trix.css’s image gallery styles to accommodate the
|
|
11
|
+
* <action-text-attachment> element we wrap around attachments. Otherwise,
|
|
12
|
+
* images in galleries will be squished by the max-width: 33%; rule.
|
|
13
|
+
*/
|
|
14
|
+
.trix-content .attachment-gallery > action-text-attachment,
|
|
15
|
+
.trix-content .attachment-gallery > .attachment {
|
|
16
|
+
flex: 1 0 33%;
|
|
17
|
+
padding: 0 0.5em;
|
|
18
|
+
max-width: 33%;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment,
|
|
22
|
+
.trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment,
|
|
23
|
+
.trix-content .attachment-gallery.attachment-gallery--4 > .attachment {
|
|
24
|
+
flex-basis: 50%;
|
|
25
|
+
max-width: 50%;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.trix-content action-text-attachment .attachment {
|
|
29
|
+
padding: 0 !important;
|
|
30
|
+
max-width: 100% !important;
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
@import url("/trix.css");
|
|
2
|
+
@import url("/madmin/actiontext.css");
|
|
3
|
+
@import url("/madmin/reset.css");
|
|
4
|
+
@import url("/madmin/base.css");
|
|
5
|
+
@import url("/madmin/sidebar.css");
|
|
6
|
+
@import url("/madmin/buttons.css");
|
|
7
|
+
@import url("/madmin/forms.css");
|
|
8
|
+
@import url("/madmin/tables.css");
|
|
9
|
+
@import url("/madmin/pagination.css");
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--primary-color: rgb(37 99 235);
|
|
3
|
+
--border-color: rgb(229 231 235);
|
|
4
|
+
--background-color: rgb(249 250 251);
|
|
5
|
+
--text-color: rgb(2 6 23);
|
|
6
|
+
--light-text-color: rgb(71 85 105);
|
|
7
|
+
--sidebar-width: 16rem;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
color: var(--text-color);
|
|
12
|
+
font-size: 14px;
|
|
13
|
+
-webkit-font-smoothing: antialiased;
|
|
14
|
+
-moz-osx-font-smoothing: grayscale;
|
|
15
|
+
|
|
16
|
+
display: grid;
|
|
17
|
+
min-height: 100dvh;
|
|
18
|
+
grid-template-rows: auto auto 1fr auto;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
a {
|
|
22
|
+
color: var(--primary-color);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.size-5 {
|
|
26
|
+
width: 1.25rem;
|
|
27
|
+
height: 1.25rem;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.alert {
|
|
31
|
+
font-weight: 500;
|
|
32
|
+
padding: 1rem;
|
|
33
|
+
|
|
34
|
+
ul {
|
|
35
|
+
margin-top: 0.5rem;
|
|
36
|
+
margin-bottom: 0;
|
|
37
|
+
padding-left: 2rem;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
svg {
|
|
41
|
+
display: inline-block;
|
|
42
|
+
height: 1rem;
|
|
43
|
+
margin-right: 0.25rem;
|
|
44
|
+
width: 1rem;
|
|
45
|
+
vertical-align: text-bottom;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&.alert-danger {
|
|
49
|
+
background-color: oklch(.936 .032 17.717);
|
|
50
|
+
color: oklch(.444 .177 26.899);
|
|
51
|
+
|
|
52
|
+
svg {
|
|
53
|
+
color: oklch(.637 .237 25.331);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
&.alert-notice {
|
|
58
|
+
background-color: oklch(.962 .044 156.743);
|
|
59
|
+
color: oklch(.448 .119 151.328);
|
|
60
|
+
|
|
61
|
+
svg {
|
|
62
|
+
color: oklch(.723 .219 149.579);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.search {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.header {
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-wrap: wrap;
|
|
75
|
+
justify-content: space-between;
|
|
76
|
+
align-items: center;
|
|
77
|
+
padding-top: 1rem;
|
|
78
|
+
padding-bottom: 1rem;
|
|
79
|
+
gap: 0.5rem;
|
|
80
|
+
|
|
81
|
+
h1 {
|
|
82
|
+
margin: 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
a {
|
|
86
|
+
text-decoration: none;
|
|
87
|
+
|
|
88
|
+
&:hover {
|
|
89
|
+
text-decoration: underline;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.actions {
|
|
94
|
+
align-items: center;
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-wrap: wrap;
|
|
97
|
+
gap: 0.5rem;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.metrics {
|
|
102
|
+
display: grid;
|
|
103
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
.metric {
|
|
107
|
+
border: 1px solid rgb(229 231 235);
|
|
108
|
+
border-radius: 0.25rem;
|
|
109
|
+
padding: 1rem;
|
|
110
|
+
margin: 1rem;
|
|
111
|
+
flex: 1 1 0%;
|
|
112
|
+
|
|
113
|
+
h4 {
|
|
114
|
+
color: rgb(75 85 99);
|
|
115
|
+
font-weight: 600;
|
|
116
|
+
margin-top: 0;
|
|
117
|
+
margin-bottom: 0.5rem;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
p {
|
|
121
|
+
font-size: 2rem;
|
|
122
|
+
font-weight: 600;
|
|
123
|
+
margin: 0;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.scopes {
|
|
129
|
+
margin-bottom: 1rem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.hidden {
|
|
133
|
+
display: none;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@media (min-width: 768px) {
|
|
137
|
+
.md\:inline-block {
|
|
138
|
+
display: inline-block;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.md\:hidden {
|
|
142
|
+
display: none;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
.btn {
|
|
2
|
+
border-radius: 0.375rem;
|
|
3
|
+
display: inline-block;
|
|
4
|
+
font-weight: 600;
|
|
5
|
+
text-decoration: none;
|
|
6
|
+
|
|
7
|
+
font-size: 0.875rem;
|
|
8
|
+
line-height: 1.25rem;
|
|
9
|
+
padding-bottom: 0.5rem;
|
|
10
|
+
padding-top: 0.5rem;
|
|
11
|
+
padding-left: 0.75rem;
|
|
12
|
+
padding-right: 0.75rem;
|
|
13
|
+
|
|
14
|
+
&:hover,
|
|
15
|
+
&:focus {
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
text-decoration: none !important;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&:disabled {
|
|
21
|
+
opacity: 60%;
|
|
22
|
+
cursor: not-allowed;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&.btn-primary {
|
|
26
|
+
background-color: var(--primary-color);
|
|
27
|
+
color: white;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&.btn-secondary {
|
|
31
|
+
background-color: white;
|
|
32
|
+
color: rgb(31 41 55);
|
|
33
|
+
box-shadow: inset 0 0 0 1px rgb(156 163 175);
|
|
34
|
+
|
|
35
|
+
&:hover {
|
|
36
|
+
background-color: rgb(243 244 246);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&.active {
|
|
40
|
+
background-color: rgb(243 244 246);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&.btn-danger {
|
|
45
|
+
background-color: white;
|
|
46
|
+
color: rgb(239 68 68);
|
|
47
|
+
box-shadow: inset 0 0 0 1px rgb(239 68 68);
|
|
48
|
+
|
|
49
|
+
&:hover {
|
|
50
|
+
background-color: rgb(243 244 246);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
.error {
|
|
2
|
+
color: rgb(239 68 68);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.form-hint {
|
|
6
|
+
font-size: 0.875rem;
|
|
7
|
+
margin-top: 0.5rem;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.form-group {
|
|
11
|
+
margin-bottom: 1rem;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.form-input {
|
|
15
|
+
width: 100%;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.form-description {
|
|
19
|
+
color: var(--light-text-color);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
label {
|
|
23
|
+
color: rgb(17 24 39);
|
|
24
|
+
line-height: 1.5rem;
|
|
25
|
+
font-weight: 500;
|
|
26
|
+
font-size: 0.875rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
button,
|
|
30
|
+
input,
|
|
31
|
+
optgroup,
|
|
32
|
+
select,
|
|
33
|
+
textarea {
|
|
34
|
+
font-family: inherit;
|
|
35
|
+
font-feature-settings: inherit;
|
|
36
|
+
font-variation-settings: inherit;
|
|
37
|
+
font-size: 100%;
|
|
38
|
+
font-weight: inherit;
|
|
39
|
+
line-height: inherit;
|
|
40
|
+
letter-spacing: inherit;
|
|
41
|
+
color: inherit;
|
|
42
|
+
margin: 0;
|
|
43
|
+
padding: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
[type='text'],
|
|
47
|
+
input:where(:not([type])),
|
|
48
|
+
[type='email'],
|
|
49
|
+
[type='url'],
|
|
50
|
+
[type='password'],
|
|
51
|
+
[type='number'],
|
|
52
|
+
[type='date'],
|
|
53
|
+
[type='datetime-local'],
|
|
54
|
+
[type='month'],
|
|
55
|
+
[type='search'],
|
|
56
|
+
[type='tel'],
|
|
57
|
+
[type='time'],
|
|
58
|
+
[type='week'],
|
|
59
|
+
[multiple],
|
|
60
|
+
textarea,
|
|
61
|
+
select {
|
|
62
|
+
-webkit-appearance: none;
|
|
63
|
+
-moz-appearance: none;
|
|
64
|
+
appearance: none;
|
|
65
|
+
background-color: #fff;
|
|
66
|
+
|
|
67
|
+
border-radius: 0.375rem;
|
|
68
|
+
box-shadow: inset 0 0 0 1px rgb(156 163 175);
|
|
69
|
+
line-height: 1.5rem;
|
|
70
|
+
padding-bottom: 0.375rem;
|
|
71
|
+
padding-left: 0.75rem;
|
|
72
|
+
padding-right: 0.75rem;
|
|
73
|
+
padding-top: 0.375rem;
|
|
74
|
+
|
|
75
|
+
&:focus {
|
|
76
|
+
box-shadow: inset 0 0 0 1px rgb(var(--primary-color));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.required {
|
|
81
|
+
color: rgb(239 68 68);
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.nested-fields {
|
|
86
|
+
border: 1px solid #e5e7eb;
|
|
87
|
+
border-radius: 0.5rem;
|
|
88
|
+
padding: 1.25rem;
|
|
89
|
+
margin-bottom: 1rem;
|
|
90
|
+
}
|