madmin 1.0.0.beta2 → 1.2.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 +4 -4
- data/README.md +48 -1
- data/app/controllers/madmin/resource_controller.rb +4 -2
- data/app/helpers/madmin/sort_helper.rb +31 -0
- data/app/views/madmin/application/_javascript.html.erb +40 -5
- data/app/views/madmin/application/index.html.erb +1 -1
- data/app/views/madmin/fields/belongs_to/_index.html.erb +3 -2
- data/app/views/madmin/fields/belongs_to/_show.html.erb +3 -2
- data/app/views/madmin/fields/enum/_form.html.erb +1 -2
- data/app/views/madmin/fields/nested_has_many/_fields.html.erb +18 -0
- data/app/views/madmin/fields/nested_has_many/_form.html.erb +30 -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 +5 -0
- data/app/views/madmin/fields/password/_form.html.erb +2 -0
- data/app/views/madmin/fields/password/_index.html.erb +1 -0
- data/app/views/madmin/fields/password/_show.html.erb +1 -0
- data/lib/generators/madmin/resource/resource_generator.rb +14 -3
- data/lib/generators/madmin/resource/templates/resource.rb.tt +14 -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.rb +11 -9
- data/lib/madmin/fields/belongs_to.rb +3 -2
- data/lib/madmin/fields/enum.rb +3 -0
- data/lib/madmin/fields/has_many.rb +3 -2
- data/lib/madmin/fields/nested_has_many.rb +40 -0
- data/lib/madmin/fields/password.rb +6 -0
- data/lib/madmin/namespace.rb +35 -0
- data/lib/madmin/resource.rb +42 -9
- data/lib/madmin/version.rb +1 -1
- data/lib/madmin/view_generator.rb +42 -0
- metadata +25 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e868d17abd508729232e7ebeebd4b79a8e69545b853276a700c60e61e5d6348c
|
4
|
+
data.tar.gz: b19043a0a2e7dd0ad6392f995b76812b434e54f73682eb163ca28e1aaeb4ad34
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '09b7ec920a1c8ae8a1429db77cff71ca45574e379563db5a06648d5cd5434950597b99eed5b836f7834cdc5c71e9d68411f064cc78d363da848efde9a0ef1b1a'
|
7
|
+
data.tar.gz: 51b2b25ddabe18e8777c9eab9e34dd1b8afd74a05aae5671f2f94c85cb404043bcde8423d996033a9dd4fa07ee672ad36240e65e195166586afabc02d756237a
|
data/README.md
CHANGED
@@ -7,9 +7,12 @@
|
|
7
7
|
Why another Ruby on Rails admin? We wanted an admin that was:
|
8
8
|
|
9
9
|
* Familiar and customizable like Rails scaffolds (less DSL)
|
10
|
-
* Supports all the Rails features out of the box (ActionText, ActionMailbox, etc)
|
10
|
+
* Supports all the Rails features out of the box (ActionText, ActionMailbox, has_secure_password, etc)
|
11
11
|
* Stimulus / Turbolinks / Hotwire ready
|
12
12
|
|
13
|
+

|
14
|
+
_We're still working on the design!_
|
15
|
+
|
13
16
|
## Installation
|
14
17
|
Add `madmin` to your application's Gemfile:
|
15
18
|
|
@@ -37,6 +40,50 @@ To generate a resource for a model, you can run:
|
|
37
40
|
rails g madmin:resource ActionText::RichText
|
38
41
|
```
|
39
42
|
|
43
|
+
## Configuring Views
|
44
|
+
|
45
|
+
The views packaged within the gem are a great starting point, but inevitably people will need to be able to customize those views.
|
46
|
+
|
47
|
+
You can use the included generator to create the appropriate view files, which can then be customized.
|
48
|
+
|
49
|
+
For example, running the following will copy over all of the views into your application that will be used for every resource:
|
50
|
+
```bash
|
51
|
+
rails generate madmin:views
|
52
|
+
```
|
53
|
+
|
54
|
+
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:
|
55
|
+
* `application.html.erb` (layout file)
|
56
|
+
* `_javascript.html.erb` (default JavaScript setup)
|
57
|
+
* `_navigation.html.erb` (renders the navigation/sidebar menu)
|
58
|
+
|
59
|
+
As with the other views, you can specifically run the views generator for only the navigation or application layout views:
|
60
|
+
```bash
|
61
|
+
rails g madmin:views:navigation
|
62
|
+
# -> app/views/madmin/_navigation.html.erb
|
63
|
+
|
64
|
+
rails g madmin:views:layout # Note the layout generator includes the layout, javascript, and navigation files.
|
65
|
+
# -> app/views/madmin/application.html.erb
|
66
|
+
# -> app/views/madmin/_javascript.html.erb
|
67
|
+
# -> app/views/madmin/_navigation.html.erb
|
68
|
+
```
|
69
|
+
|
70
|
+
If you only need to customize specific views, you can restrict which views are copied by the generator:
|
71
|
+
```bash
|
72
|
+
rails g madmin:views:index
|
73
|
+
# -> app/views/madmin/application/index.html.erb
|
74
|
+
```
|
75
|
+
|
76
|
+
You can also scope the copied view(s) to a specific Resource/Model:
|
77
|
+
```bash
|
78
|
+
rails generate madmin:views:index Book
|
79
|
+
# -> app/views/madmin/books/index.html.erb
|
80
|
+
```
|
81
|
+
|
82
|
+
## Authentication
|
83
|
+
|
84
|
+
You can use a couple of strategies to authenticate users who are trying to
|
85
|
+
access your madmin panel: [Authentication Docs](docs/authentication.md)
|
86
|
+
|
40
87
|
## 🙏 Contributing
|
41
88
|
|
42
89
|
This project uses Standard for formatting Ruby code. Please make sure to run standardrb before submitting pull requests.
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Madmin
|
2
2
|
class ResourceController < ApplicationController
|
3
|
+
include SortHelper
|
4
|
+
|
3
5
|
before_action :set_record, except: [:index, :new, :create]
|
4
6
|
|
5
7
|
def index
|
@@ -41,7 +43,7 @@ module Madmin
|
|
41
43
|
private
|
42
44
|
|
43
45
|
def set_record
|
44
|
-
@record = resource.
|
46
|
+
@record = resource.model_find(params[:id])
|
45
47
|
end
|
46
48
|
|
47
49
|
def resource
|
@@ -54,7 +56,7 @@ module Madmin
|
|
54
56
|
end
|
55
57
|
|
56
58
|
def scoped_resources
|
57
|
-
resource.model.send(valid_scope)
|
59
|
+
resource.model.send(valid_scope).order(sort_column => sort_direction)
|
58
60
|
end
|
59
61
|
|
60
62
|
def valid_scope
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Madmin
|
2
|
+
module SortHelper
|
3
|
+
def sortable(column, title, options = {})
|
4
|
+
matching_column = (column.to_s == sort_column)
|
5
|
+
|
6
|
+
link_to request.params.merge(sort: column, direction: sort_direction), options do
|
7
|
+
concat title
|
8
|
+
if matching_column
|
9
|
+
concat " "
|
10
|
+
concat tag.i(sort_direction == "asc" ? "▲" : "▼")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def sort_column
|
16
|
+
resource.sortable_columns.include?(params[:sort]) ? params[:sort] : default_sort_column
|
17
|
+
end
|
18
|
+
|
19
|
+
def sort_direction
|
20
|
+
["asc", "desc"].include?(params[:direction]) ? params[:direction] : default_sort_direction
|
21
|
+
end
|
22
|
+
|
23
|
+
def default_sort_column
|
24
|
+
resource.try(:default_sort_column) || "created_at"
|
25
|
+
end
|
26
|
+
|
27
|
+
def default_sort_direction
|
28
|
+
resource.try(:default_sort_direction) || "desc"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
|
-
<%= stylesheet_link_tag "https://
|
2
|
-
<%= stylesheet_link_tag "https://
|
3
|
-
<%= stylesheet_link_tag "https://
|
1
|
+
<%= stylesheet_link_tag "https://unpkg.com/flatpickr/dist/flatpickr.min.css", "data-turbo-track": "reload" %>
|
2
|
+
<%= stylesheet_link_tag "https://unpkg.com/trix/dist/trix.css", "data-turbo-track": "reload" %>
|
3
|
+
<%= stylesheet_link_tag "https://unpkg.com/slim-select@1.27.0/dist/slimselect.min.css", "data-turbo-track": "reload" %>
|
4
4
|
|
5
5
|
<script type="module">
|
6
|
-
import { Application } from 'https://cdn.skypack.dev/stimulus'
|
6
|
+
import { Application, Controller } from 'https://cdn.skypack.dev/stimulus'
|
7
7
|
const application = Application.start()
|
8
8
|
|
9
9
|
import stimulusFlatpickr from 'https://cdn.skypack.dev/stimulus-flatpickr'
|
@@ -17,8 +17,43 @@
|
|
17
17
|
import 'https://cdn.skypack.dev/trix'
|
18
18
|
import 'https://cdn.skypack.dev/@rails/actiontext@<%= npm_rails_version %>'
|
19
19
|
|
20
|
-
Rails.start()
|
20
|
+
if (!window._rails_loaded) { Rails.start() }
|
21
21
|
ActiveStorage.start()
|
22
22
|
|
23
23
|
import * as Turbo from "https://cdn.skypack.dev/@hotwired/turbo"
|
24
|
+
|
25
|
+
(() => {
|
26
|
+
application.register('nested-form', class extends Controller {
|
27
|
+
static get targets() {
|
28
|
+
return [ "links", "template" ]
|
29
|
+
}
|
30
|
+
|
31
|
+
connect() {
|
32
|
+
this.wrapperClass = this.data.get("wrapperClass") || "nested-fields"
|
33
|
+
}
|
34
|
+
|
35
|
+
add_association(event) {
|
36
|
+
event.preventDefault()
|
37
|
+
|
38
|
+
var content = this.templateTarget.innerHTML.replace(/NEW_RECORD/g, new Date().getTime())
|
39
|
+
this.linksTarget.insertAdjacentHTML('beforebegin', content)
|
40
|
+
}
|
41
|
+
|
42
|
+
remove_association(event) {
|
43
|
+
event.preventDefault()
|
44
|
+
|
45
|
+
let wrapper = event.target.closest("." + this.wrapperClass)
|
46
|
+
|
47
|
+
// New records are simply removed from the page
|
48
|
+
if (wrapper.dataset.newRecord == "true") {
|
49
|
+
wrapper.remove()
|
50
|
+
|
51
|
+
// Existing records are hidden and flagged for deletion
|
52
|
+
} else {
|
53
|
+
wrapper.querySelector("input[name*='_destroy']").value = 1
|
54
|
+
wrapper.style.display = 'none'
|
55
|
+
}
|
56
|
+
}
|
57
|
+
})
|
58
|
+
})()
|
24
59
|
</script>
|
@@ -20,7 +20,7 @@
|
|
20
20
|
<% next if attribute[:field].nil? %>
|
21
21
|
<% next unless attribute[:field].visible?(action_name) %>
|
22
22
|
|
23
|
-
<th><%= attribute[:name].to_s.titleize %></th>
|
23
|
+
<th><%= sortable attribute[:name], attribute[:name].to_s.titleize %></th>
|
24
24
|
<% end %>
|
25
25
|
<th>Actions</th>
|
26
26
|
</tr>
|
@@ -1,2 +1,3 @@
|
|
1
|
-
<% object = field.value(record) %>
|
2
|
-
<%= link_to Madmin.resource_for(object).display_name(object), Madmin.resource_for(object).show_path(object) %>
|
1
|
+
<% if (object = field.value(record)) %>
|
2
|
+
<%= link_to Madmin.resource_for(object).display_name(object), Madmin.resource_for(object).show_path(object) %>
|
3
|
+
<% end %>
|
@@ -1,2 +1,3 @@
|
|
1
|
-
<% object = field.value(record) %>
|
2
|
-
<%= link_to Madmin.resource_for(object).display_name(object), Madmin.resource_for(object).show_path(object) %>
|
1
|
+
<% if (object = field.value(record)) %>
|
2
|
+
<%= link_to Madmin.resource_for(object).display_name(object), Madmin.resource_for(object).show_path(object) %>
|
3
|
+
<% end %>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<%= content_tag :div, class: "nested-fields bg-gray-100 rounded-t-xl p-5", data: { new_record: f.object.new_record? } do %>
|
2
|
+
<% field.nested_attributes.each do |nested_attribute| %>
|
3
|
+
<% next if nested_attribute[:field].nil? %>
|
4
|
+
<% next unless nested_attribute[:field].visible?(action_name) %>
|
5
|
+
<% next unless nested_attribute[:field].visible?(:form) %>
|
6
|
+
|
7
|
+
<% nested_field = nested_attribute[:field] %>
|
8
|
+
|
9
|
+
<div class="mb-4 flex">
|
10
|
+
<%= render partial: nested_field.to_partial_path("form"), locals: { field: nested_field, record: f.object, form: f, resource: field.resource } %>
|
11
|
+
</div>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<small><%= link_to "Remove", "#", data: { action: "click->nested-form#remove_association" } %></small>
|
15
|
+
|
16
|
+
<%= f.hidden_field :_destroy %>
|
17
|
+
|
18
|
+
<% end %>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<%= form.label field.attribute_name, class: "inline-block w-32 flex-shrink-0" %>
|
2
|
+
|
3
|
+
<div class="container space-y-8" data-controller="nested-form">
|
4
|
+
<template data-target="nested-form.template">
|
5
|
+
|
6
|
+
<%= form.fields_for field.attribute_name, field.to_model.new, child_index: 'NEW_RECORD' do |nested_form| %>
|
7
|
+
<%= render(
|
8
|
+
partial: field.to_partial_path('fields'),
|
9
|
+
locals: {
|
10
|
+
f: nested_form,
|
11
|
+
field: field
|
12
|
+
}
|
13
|
+
) %>
|
14
|
+
<% end %>
|
15
|
+
</template>
|
16
|
+
|
17
|
+
<%= form.fields_for field.attribute_name do |nested_form| %>
|
18
|
+
<%= render(
|
19
|
+
partial: field.to_partial_path('fields'),
|
20
|
+
locals: {
|
21
|
+
f: nested_form,
|
22
|
+
field: field
|
23
|
+
}
|
24
|
+
) %>
|
25
|
+
<% end %>
|
26
|
+
|
27
|
+
<%= content_tag :div, class: '', data: { target:"nested-form.links" } do %>
|
28
|
+
<%= link_to "+ Add new", "#", data: { action: "click->nested-form#add_association" } %>
|
29
|
+
<% end %>
|
30
|
+
</div>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= pluralize field.value(record).count, field.attribute_name.to_s %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= field.value(record) %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= field.value(record) %>
|
@@ -46,6 +46,10 @@ module Madmin
|
|
46
46
|
def virtual_attributes
|
47
47
|
virtual = []
|
48
48
|
|
49
|
+
# has_secure_password columns
|
50
|
+
password_attributes = model.attribute_types.keys.select { |k| k.ends_with?("_digest") }.map { |k| k.delete_suffix("_digest") }
|
51
|
+
virtual += password_attributes.map { |attr| [attr, "#{attr}_confirmation"] }.flatten
|
52
|
+
|
49
53
|
# Add virtual attributes for ActionText and ActiveStorage
|
50
54
|
model.reflections.each do |name, association|
|
51
55
|
if name.starts_with?("rich_text")
|
@@ -63,6 +67,9 @@ module Madmin
|
|
63
67
|
def redundant_attributes
|
64
68
|
redundant = []
|
65
69
|
|
70
|
+
# has_secure_password columns
|
71
|
+
redundant += model.attribute_types.keys.select { |k| k.ends_with?("_digest") }
|
72
|
+
|
66
73
|
model.reflections.each do |name, association|
|
67
74
|
if association.has_one?
|
68
75
|
next
|
@@ -98,13 +105,17 @@ module Madmin
|
|
98
105
|
if %w[id created_at updated_at].include?(name)
|
99
106
|
{form: false}
|
100
107
|
|
101
|
-
#
|
102
|
-
elsif
|
103
|
-
{index: false}
|
108
|
+
# has_secure_passwords should only show on forms
|
109
|
+
elsif name.ends_with?("_confirmation") || virtual_attributes.include?("#{name}_confirmation")
|
110
|
+
{index: false, show: false}
|
104
111
|
|
105
112
|
# Counter cache columns are typically not editable
|
106
113
|
elsif name.ends_with?("_count")
|
107
114
|
{form: false}
|
115
|
+
|
116
|
+
# Attributes without a database column
|
117
|
+
elsif !model.column_names.include?(name)
|
118
|
+
{index: false}
|
108
119
|
end
|
109
120
|
end
|
110
121
|
end
|
@@ -8,4 +8,18 @@ class <%= class_name %>Resource < Madmin::Resource
|
|
8
8
|
<% associations.each do |association_name| -%>
|
9
9
|
attribute :<%= association_name %>
|
10
10
|
<% end -%>
|
11
|
+
|
12
|
+
# Uncomment this to customize the display name of records in the admin area.
|
13
|
+
# def self.display_name(record)
|
14
|
+
# record.name
|
15
|
+
# end
|
16
|
+
|
17
|
+
# Uncomment this to customize the default sort column and direction.
|
18
|
+
# def self.default_sort_column
|
19
|
+
# "created_at"
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# def self.default_sort_direction
|
23
|
+
# "desc"
|
24
|
+
# end
|
11
25
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "madmin/view_generator"
|
2
|
+
|
3
|
+
module Madmin
|
4
|
+
module Generators
|
5
|
+
module Views
|
6
|
+
class EditGenerator < Madmin::ViewGenerator
|
7
|
+
source_root template_source_path
|
8
|
+
|
9
|
+
def copy_edit
|
10
|
+
copy_resource_template("edit")
|
11
|
+
copy_resource_template("_form")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "madmin/view_generator"
|
2
|
+
|
3
|
+
module Madmin
|
4
|
+
module Generators
|
5
|
+
module Views
|
6
|
+
class FormGenerator < Madmin::ViewGenerator
|
7
|
+
source_root template_source_path
|
8
|
+
|
9
|
+
def copy_form
|
10
|
+
copy_resource_template("_form")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "madmin/view_generator"
|
2
|
+
|
3
|
+
module Madmin
|
4
|
+
module Generators
|
5
|
+
module Views
|
6
|
+
class IndexGenerator < Madmin::ViewGenerator
|
7
|
+
source_root template_source_path
|
8
|
+
|
9
|
+
def copy_template
|
10
|
+
copy_resource_template("index")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "madmin/view_generator"
|
2
|
+
|
3
|
+
module Madmin
|
4
|
+
module Generators
|
5
|
+
module Views
|
6
|
+
class JavascriptGenerator < Madmin::ViewGenerator
|
7
|
+
source_root template_source_path
|
8
|
+
|
9
|
+
def copy_navigation
|
10
|
+
copy_resource_template("_javascript")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "madmin/view_generator"
|
2
|
+
|
3
|
+
module Madmin
|
4
|
+
module Generators
|
5
|
+
module Views
|
6
|
+
class LayoutGenerator < Madmin::ViewGenerator
|
7
|
+
source_root template_source_path
|
8
|
+
|
9
|
+
def copy_template
|
10
|
+
copy_file(
|
11
|
+
"../../layouts/madmin/application.html.erb",
|
12
|
+
"app/views/layouts/madmin/application.html.erb"
|
13
|
+
)
|
14
|
+
|
15
|
+
call_generator("madmin:views:navigation")
|
16
|
+
copy_resource_template("_javascript")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "madmin/view_generator"
|
2
|
+
|
3
|
+
module Madmin
|
4
|
+
module Generators
|
5
|
+
module Views
|
6
|
+
class NavigationGenerator < Madmin::ViewGenerator
|
7
|
+
source_root template_source_path
|
8
|
+
|
9
|
+
def copy_navigation
|
10
|
+
copy_resource_template("_navigation")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "madmin/view_generator"
|
2
|
+
|
3
|
+
module Madmin
|
4
|
+
module Generators
|
5
|
+
module Views
|
6
|
+
class NewGenerator < Madmin::ViewGenerator
|
7
|
+
source_root template_source_path
|
8
|
+
|
9
|
+
def copy_new
|
10
|
+
copy_resource_template("new")
|
11
|
+
copy_resource_template("_form")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "madmin/view_generator"
|
2
|
+
|
3
|
+
module Madmin
|
4
|
+
module Generators
|
5
|
+
module Views
|
6
|
+
class ShowGenerator < Madmin::ViewGenerator
|
7
|
+
source_root template_source_path
|
8
|
+
|
9
|
+
def copy_template
|
10
|
+
copy_resource_template("show")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "madmin/view_generator"
|
2
|
+
|
3
|
+
module Madmin
|
4
|
+
module Generators
|
5
|
+
class ViewsGenerator < Madmin::ViewGenerator
|
6
|
+
def copy_templates
|
7
|
+
# Some generators duplicate templates, so not everything is present here
|
8
|
+
call_generator("madmin:views:edit", resource_path, "--namespace", namespace)
|
9
|
+
call_generator("madmin:views:index", resource_path, "--namespace", namespace)
|
10
|
+
call_generator("madmin:views:layout", resource_path, "--namespace", namespace)
|
11
|
+
call_generator("madmin:views:new", resource_path, "--namespace", namespace)
|
12
|
+
call_generator("madmin:views:show", resource_path, "--namespace", namespace)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/madmin.rb
CHANGED
@@ -8,24 +8,26 @@ module Madmin
|
|
8
8
|
autoload :Resource, "madmin/resource"
|
9
9
|
|
10
10
|
module Fields
|
11
|
+
autoload :Attachment, "madmin/fields/attachment"
|
12
|
+
autoload :Attachments, "madmin/fields/attachments"
|
13
|
+
autoload :BelongsTo, "madmin/fields/belongs_to"
|
11
14
|
autoload :Boolean, "madmin/fields/boolean"
|
12
|
-
autoload :Integer, "madmin/fields/integer"
|
13
|
-
autoload :String, "madmin/fields/string"
|
14
|
-
autoload :Text, "madmin/fields/text"
|
15
15
|
autoload :Date, "madmin/fields/date"
|
16
16
|
autoload :DateTime, "madmin/fields/date_time"
|
17
17
|
autoload :Decimal, "madmin/fields/decimal"
|
18
|
-
autoload :Json, "madmin/fields/json"
|
19
18
|
autoload :Enum, "madmin/fields/enum"
|
20
19
|
autoload :Float, "madmin/fields/float"
|
21
|
-
autoload :Time, "madmin/fields/time"
|
22
|
-
autoload :BelongsTo, "madmin/fields/belongs_to"
|
23
|
-
autoload :Polymorphic, "madmin/fields/polymorphic"
|
24
20
|
autoload :HasMany, "madmin/fields/has_many"
|
25
21
|
autoload :HasOne, "madmin/fields/has_one"
|
22
|
+
autoload :Integer, "madmin/fields/integer"
|
23
|
+
autoload :Json, "madmin/fields/json"
|
24
|
+
autoload :NestedHasMany, "madmin/fields/nested_has_many"
|
25
|
+
autoload :Password, "madmin/fields/password"
|
26
|
+
autoload :Polymorphic, "madmin/fields/polymorphic"
|
26
27
|
autoload :RichText, "madmin/fields/rich_text"
|
27
|
-
autoload :
|
28
|
-
autoload :
|
28
|
+
autoload :String, "madmin/fields/string"
|
29
|
+
autoload :Text, "madmin/fields/text"
|
30
|
+
autoload :Time, "madmin/fields/time"
|
29
31
|
end
|
30
32
|
|
31
33
|
mattr_accessor :resources, default: []
|
@@ -3,10 +3,11 @@ module Madmin
|
|
3
3
|
class BelongsTo < Field
|
4
4
|
def options_for_select(record)
|
5
5
|
association = record.class.reflect_on_association(attribute_name)
|
6
|
-
|
7
6
|
klass = association.klass
|
7
|
+
resource = nil
|
8
8
|
klass.all.map do |r|
|
9
|
-
|
9
|
+
resource ||= Madmin.resource_for(r)
|
10
|
+
[resource.display_name(r), r.id]
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
data/lib/madmin/fields/enum.rb
CHANGED
@@ -3,10 +3,11 @@ module Madmin
|
|
3
3
|
class HasMany < Field
|
4
4
|
def options_for_select(record)
|
5
5
|
association = record.class.reflect_on_association(attribute_name)
|
6
|
-
|
7
6
|
klass = association.klass
|
7
|
+
resource = nil
|
8
8
|
klass.all.map do |r|
|
9
|
-
|
9
|
+
resource ||= Madmin.resource_for(r)
|
10
|
+
[resource.display_name(r), r.id]
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Madmin
|
2
|
+
module Fields
|
3
|
+
class NestedHasMany < Field
|
4
|
+
DEFAULT_ATTRIBUTES = %w[_destroy id].freeze
|
5
|
+
def nested_attributes
|
6
|
+
resource.attributes.reject { |i| skipped_fields.include?(i[:name]) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def resource
|
10
|
+
"#{to_model.name}Resource".constantize
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_param
|
14
|
+
{"#{attribute_name}_attributes": permitted_fields}
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_partial_path(name)
|
18
|
+
unless %w[index show form fields].include? name
|
19
|
+
raise ArgumentError, "`partial` must be 'index', 'show', 'form' or 'fields'"
|
20
|
+
end
|
21
|
+
|
22
|
+
"/madmin/fields/#{self.class.field_type}/#{name}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_model
|
26
|
+
attribute_name.to_s.singularize.classify.constantize
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def permitted_fields
|
32
|
+
(resource.permitted_params - skipped_fields + DEFAULT_ATTRIBUTES).uniq
|
33
|
+
end
|
34
|
+
|
35
|
+
def skipped_fields
|
36
|
+
options[:skip] || []
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Madmin
|
2
|
+
class Namespace
|
3
|
+
def initialize(namespace)
|
4
|
+
@namespace = namespace
|
5
|
+
end
|
6
|
+
|
7
|
+
def resources
|
8
|
+
@resources ||= routes.map(&:first).uniq.map { |path|
|
9
|
+
Resource.new(namespace, path)
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def routes
|
14
|
+
@routes ||= all_routes.select { |controller, _action|
|
15
|
+
controller.starts_with?("#{namespace}/")
|
16
|
+
}.map { |controller, action|
|
17
|
+
[controller.gsub(/^#{namespace}\//, ""), action]
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def resources_with_index_route
|
22
|
+
routes.select { |_resource, route| route == "index" }.map(&:first).uniq
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :namespace
|
28
|
+
|
29
|
+
def all_routes
|
30
|
+
Rails.application.routes.routes.map do |route|
|
31
|
+
route.defaults.values_at(:controller, :action).map(&:to_s)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/madmin/resource.rb
CHANGED
@@ -18,6 +18,10 @@ module Madmin
|
|
18
18
|
model_name.constantize
|
19
19
|
end
|
20
20
|
|
21
|
+
def model_find(id)
|
22
|
+
friendly_model? ? model.friendly.find(id) : model.find(id)
|
23
|
+
end
|
24
|
+
|
21
25
|
def model_name
|
22
26
|
to_s.chomp("Resource").classify
|
23
27
|
end
|
@@ -38,21 +42,27 @@ module Madmin
|
|
38
42
|
end
|
39
43
|
|
40
44
|
def index_path(options = {})
|
41
|
-
|
42
|
-
|
43
|
-
|
45
|
+
route_name = "madmin_#{model.table_name}_path"
|
46
|
+
|
47
|
+
url_helpers.send(route_name, options)
|
44
48
|
end
|
45
49
|
|
46
50
|
def new_path
|
47
|
-
"
|
51
|
+
route_name = "new_madmin_#{model.model_name.singular}_path"
|
52
|
+
|
53
|
+
url_helpers.send(route_name)
|
48
54
|
end
|
49
55
|
|
50
56
|
def show_path(record)
|
51
|
-
"
|
57
|
+
route_name = "madmin_#{model.model_name.singular}_path"
|
58
|
+
|
59
|
+
url_helpers.send(route_name, record.to_param)
|
52
60
|
end
|
53
61
|
|
54
62
|
def edit_path(record)
|
55
|
-
"
|
63
|
+
route_name = "edit_madmin_#{model.model_name.singular}_path"
|
64
|
+
|
65
|
+
url_helpers.send(route_name, record.to_param)
|
56
66
|
end
|
57
67
|
|
58
68
|
def param_key
|
@@ -60,13 +70,21 @@ module Madmin
|
|
60
70
|
end
|
61
71
|
|
62
72
|
def permitted_params
|
63
|
-
attributes.
|
73
|
+
attributes.filter_map { |a| a[:field].to_param if a[:field].visible?(:form) }
|
64
74
|
end
|
65
75
|
|
66
76
|
def display_name(record)
|
67
77
|
"#{record.class} ##{record.id}"
|
68
78
|
end
|
69
79
|
|
80
|
+
def friendly_model?
|
81
|
+
model.respond_to? :friendly
|
82
|
+
end
|
83
|
+
|
84
|
+
def sortable_columns
|
85
|
+
model.column_names
|
86
|
+
end
|
87
|
+
|
70
88
|
private
|
71
89
|
|
72
90
|
def field_for_type(name, type)
|
@@ -90,6 +108,7 @@ module Madmin
|
|
90
108
|
text: Fields::Text,
|
91
109
|
time: Fields::Time,
|
92
110
|
timestamp: Fields::Time,
|
111
|
+
password: Fields::Password,
|
93
112
|
|
94
113
|
# Postgres specific types
|
95
114
|
bit: Fields::String,
|
@@ -126,7 +145,8 @@ module Madmin
|
|
126
145
|
polymorphic: Fields::Polymorphic,
|
127
146
|
has_many: Fields::HasMany,
|
128
147
|
has_one: Fields::HasOne,
|
129
|
-
rich_text: Fields::RichText
|
148
|
+
rich_text: Fields::RichText,
|
149
|
+
nested_has_many: Fields::NestedHasMany
|
130
150
|
}.fetch(type)
|
131
151
|
rescue
|
132
152
|
raise ArgumentError, <<~MESSAGE
|
@@ -143,7 +163,12 @@ module Madmin
|
|
143
163
|
name_string = name.to_s
|
144
164
|
|
145
165
|
if model.attribute_types.include?(name_string)
|
146
|
-
model.attribute_types[name_string]
|
166
|
+
column_type = model.attribute_types[name_string]
|
167
|
+
if column_type.is_a? ActiveRecord::Enum::EnumType
|
168
|
+
:enum
|
169
|
+
else
|
170
|
+
column_type.type || :string
|
171
|
+
end
|
147
172
|
elsif (association = model.reflect_on_association(name))
|
148
173
|
type_for_association(association)
|
149
174
|
elsif model.reflect_on_association(:"rich_text_#{name_string}")
|
@@ -152,6 +177,10 @@ module Madmin
|
|
152
177
|
:attachment
|
153
178
|
elsif model.reflect_on_association(:"#{name_string}_attachments")
|
154
179
|
:attachments
|
180
|
+
|
181
|
+
# has_secure_password
|
182
|
+
elsif model.attribute_types.include?("#{name_string}_digest") || name_string.ends_with?("_confirmation")
|
183
|
+
:password
|
155
184
|
end
|
156
185
|
end
|
157
186
|
|
@@ -166,6 +195,10 @@ module Madmin
|
|
166
195
|
:belongs_to
|
167
196
|
end
|
168
197
|
end
|
198
|
+
|
199
|
+
def url_helpers
|
200
|
+
@url_helpers ||= Rails.application.routes.url_helpers
|
201
|
+
end
|
169
202
|
end
|
170
203
|
end
|
171
204
|
end
|
data/lib/madmin/version.rb
CHANGED
@@ -0,0 +1,42 @@
|
|
1
|
+
require "rails/generators/base"
|
2
|
+
require "madmin/generator_helpers"
|
3
|
+
require "madmin/namespace"
|
4
|
+
|
5
|
+
module Madmin
|
6
|
+
class ViewGenerator < Rails::Generators::Base
|
7
|
+
include Madmin::GeneratorHelpers
|
8
|
+
class_option :namespace, type: :string, default: "madmin"
|
9
|
+
|
10
|
+
def self.template_source_path
|
11
|
+
File.expand_path(
|
12
|
+
"../../../app/views/madmin/application",
|
13
|
+
__FILE__
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def namespace
|
20
|
+
options[:namespace]
|
21
|
+
end
|
22
|
+
|
23
|
+
def copy_resource_template(template_name)
|
24
|
+
template_file = "#{template_name}.html.erb"
|
25
|
+
|
26
|
+
copy_file(
|
27
|
+
template_file,
|
28
|
+
"app/views/#{namespace}/#{resource_path}/#{template_file}"
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def resource_path
|
33
|
+
args.first.try(:underscore).try(:pluralize) || BaseResourcePath.new
|
34
|
+
end
|
35
|
+
|
36
|
+
class BaseResourcePath
|
37
|
+
def to_s
|
38
|
+
"application"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: madmin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Oliver
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2021-
|
12
|
+
date: 2021-05-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -63,6 +63,7 @@ files:
|
|
63
63
|
- app/controllers/madmin/dashboard_controller.rb
|
64
64
|
- app/controllers/madmin/resource_controller.rb
|
65
65
|
- app/helpers/madmin/application_helper.rb
|
66
|
+
- app/helpers/madmin/sort_helper.rb
|
66
67
|
- app/views/layouts/madmin/application.html.erb
|
67
68
|
- app/views/madmin/application/_form.html.erb
|
68
69
|
- app/views/madmin/application/_javascript.html.erb
|
@@ -111,6 +112,13 @@ files:
|
|
111
112
|
- app/views/madmin/fields/json/_form.html.erb
|
112
113
|
- app/views/madmin/fields/json/_index.html.erb
|
113
114
|
- app/views/madmin/fields/json/_show.html.erb
|
115
|
+
- app/views/madmin/fields/nested_has_many/_fields.html.erb
|
116
|
+
- app/views/madmin/fields/nested_has_many/_form.html.erb
|
117
|
+
- app/views/madmin/fields/nested_has_many/_index.html.erb
|
118
|
+
- app/views/madmin/fields/nested_has_many/_show.html.erb
|
119
|
+
- app/views/madmin/fields/password/_form.html.erb
|
120
|
+
- app/views/madmin/fields/password/_index.html.erb
|
121
|
+
- app/views/madmin/fields/password/_show.html.erb
|
114
122
|
- app/views/madmin/fields/polymorphic/_form.html.erb
|
115
123
|
- app/views/madmin/fields/polymorphic/_index.html.erb
|
116
124
|
- app/views/madmin/fields/polymorphic/_show.html.erb
|
@@ -131,6 +139,15 @@ files:
|
|
131
139
|
- lib/generators/madmin/resource/resource_generator.rb
|
132
140
|
- lib/generators/madmin/resource/templates/controller.rb.tt
|
133
141
|
- lib/generators/madmin/resource/templates/resource.rb.tt
|
142
|
+
- lib/generators/madmin/views/edit_generator.rb
|
143
|
+
- lib/generators/madmin/views/form_generator.rb
|
144
|
+
- lib/generators/madmin/views/index_generator.rb
|
145
|
+
- lib/generators/madmin/views/javascript_generator.rb
|
146
|
+
- lib/generators/madmin/views/layout_generator.rb
|
147
|
+
- lib/generators/madmin/views/navigation_generator.rb
|
148
|
+
- lib/generators/madmin/views/new_generator.rb
|
149
|
+
- lib/generators/madmin/views/show_generator.rb
|
150
|
+
- lib/generators/madmin/views/views_generator.rb
|
134
151
|
- lib/madmin.rb
|
135
152
|
- lib/madmin/engine.rb
|
136
153
|
- lib/madmin/field.rb
|
@@ -147,14 +164,18 @@ files:
|
|
147
164
|
- lib/madmin/fields/has_one.rb
|
148
165
|
- lib/madmin/fields/integer.rb
|
149
166
|
- lib/madmin/fields/json.rb
|
167
|
+
- lib/madmin/fields/nested_has_many.rb
|
168
|
+
- lib/madmin/fields/password.rb
|
150
169
|
- lib/madmin/fields/polymorphic.rb
|
151
170
|
- lib/madmin/fields/rich_text.rb
|
152
171
|
- lib/madmin/fields/string.rb
|
153
172
|
- lib/madmin/fields/text.rb
|
154
173
|
- lib/madmin/fields/time.rb
|
155
174
|
- lib/madmin/generator_helpers.rb
|
175
|
+
- lib/madmin/namespace.rb
|
156
176
|
- lib/madmin/resource.rb
|
157
177
|
- lib/madmin/version.rb
|
178
|
+
- lib/madmin/view_generator.rb
|
158
179
|
- lib/tasks/madmin_tasks.rake
|
159
180
|
homepage: https://github.com/excid3/madmin
|
160
181
|
licenses:
|
@@ -171,9 +192,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
171
192
|
version: 2.5.0
|
172
193
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
194
|
requirements:
|
174
|
-
- - "
|
195
|
+
- - ">="
|
175
196
|
- !ruby/object:Gem::Version
|
176
|
-
version:
|
197
|
+
version: '0'
|
177
198
|
requirements: []
|
178
199
|
rubygems_version: 3.2.3
|
179
200
|
signing_key:
|