madmin 2.0.4 → 2.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 +4 -4
- data/app/assets/stylesheets/madmin/application.css +0 -1
- data/app/assets/stylesheets/madmin/base.css +32 -5
- data/app/assets/stylesheets/madmin/forms.css +32 -6
- data/app/assets/stylesheets/madmin/pagination.css +1 -1
- data/app/assets/stylesheets/madmin/sidebar.css +99 -2
- data/app/assets/stylesheets/madmin/tables.css +14 -9
- data/app/controllers/madmin/base_controller.rb +6 -1
- data/app/controllers/madmin/resource_controller.rb +0 -3
- data/app/helpers/madmin/application_helper.rb +1 -1
- data/app/javascript/madmin/controllers/mobile_nav_controller.js +50 -0
- data/app/javascript/madmin/controllers/select_controller.js +8 -3
- data/app/views/layouts/madmin/application.html.erb +22 -6
- data/app/views/madmin/application/_missing_resource.html.erb +8 -0
- data/app/views/madmin/application/_navigation.html.erb +0 -2
- data/app/views/madmin/application/edit.html.erb +1 -1
- data/app/views/madmin/application/index.html.erb +10 -3
- data/app/views/madmin/dashboard/show.html.erb +2 -4
- data/app/views/madmin/fields/belongs_to/_show.html.erb +5 -1
- data/app/views/madmin/fields/has_many/_show.html.erb +12 -5
- data/app/views/madmin/fields/has_one/_show.html.erb +5 -1
- data/app/views/madmin/fields/nested_has_many/_fields.html.erb +3 -2
- data/app/views/madmin/fields/nested_has_many/_show.html.erb +12 -5
- data/app/views/madmin/fields/polymorphic/_show.html.erb +5 -1
- data/app/views/madmin/fields/time/_form.html.erb +4 -3
- data/lib/madmin/engine.rb +1 -1
- data/lib/madmin/fields/belongs_to.rb +8 -2
- data/lib/madmin/fields/has_many.rb +35 -9
- data/lib/madmin/fields/has_one.rb +4 -0
- data/lib/madmin/fields/polymorphic.rb +5 -0
- data/lib/madmin/resource.rb +1 -1
- data/lib/madmin/version.rb +1 -1
- data/lib/madmin.rb +24 -5
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de0ee3515b8f8689bbd5c7b6b99675c2724f0be4cf7774e97d71da99076f3b68
|
4
|
+
data.tar.gz: 4224940c11670cfbc2a481cc0001ad64d846595929993f5971490c1f53e0343c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eaf9f28c5e3dac557dceaff92f823b3372d9c927a2c429d296da83bee28c3f14cd78b31bf198bde7d43bfc8025aaa01d96e1586edc42ebef5c49e3f827929cfc
|
7
|
+
data.tar.gz: e0b561bb2c7d0367238ba4c9af7794161480396ccf2bc625ffad2a3adfbeb1b6919490f59a8fc24c1eac89eec4f0162e202186dc47913f41351b893972c2fb13
|
@@ -10,17 +10,26 @@
|
|
10
10
|
body {
|
11
11
|
color: var(--text-color);
|
12
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;
|
13
19
|
}
|
14
20
|
|
15
21
|
a {
|
16
22
|
color: var(--primary-color);
|
17
23
|
}
|
18
24
|
|
25
|
+
.size-5 {
|
26
|
+
width: 1.25rem;
|
27
|
+
height: 1.25rem;
|
28
|
+
}
|
29
|
+
|
19
30
|
.alert {
|
20
|
-
border-radius: 0.5rem;
|
21
31
|
font-weight: 500;
|
22
32
|
padding: 1rem;
|
23
|
-
margin-bottom: 1rem;
|
24
33
|
|
25
34
|
ul {
|
26
35
|
margin-top: 0.5rem;
|
@@ -61,12 +70,13 @@ a {
|
|
61
70
|
}
|
62
71
|
|
63
72
|
.header {
|
64
|
-
border-bottom: 1px solid rgb(229 231 235);
|
65
73
|
display: flex;
|
74
|
+
flex-wrap: wrap;
|
66
75
|
justify-content: space-between;
|
67
76
|
align-items: center;
|
77
|
+
padding-top: 1rem;
|
68
78
|
padding-bottom: 1rem;
|
69
|
-
|
79
|
+
gap: 0.5rem;
|
70
80
|
|
71
81
|
h1 {
|
72
82
|
margin: 0;
|
@@ -83,12 +93,15 @@ a {
|
|
83
93
|
.actions {
|
84
94
|
align-items: center;
|
85
95
|
display: flex;
|
96
|
+
flex-wrap: wrap;
|
86
97
|
gap: 0.5rem;
|
87
98
|
}
|
88
99
|
}
|
89
100
|
|
90
101
|
.metrics {
|
91
|
-
display:
|
102
|
+
display: grid;
|
103
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
104
|
+
|
92
105
|
|
93
106
|
.metric {
|
94
107
|
border: 1px solid rgb(229 231 235);
|
@@ -115,3 +128,17 @@ a {
|
|
115
128
|
.scopes {
|
116
129
|
margin-bottom: 1rem;
|
117
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
|
+
}
|
@@ -1,10 +1,10 @@
|
|
1
|
+
.error {
|
2
|
+
color: rgb(239 68 68);
|
3
|
+
}
|
4
|
+
|
1
5
|
.form-hint {
|
2
6
|
font-size: 0.875rem;
|
3
7
|
margin-top: 0.5rem;
|
4
|
-
|
5
|
-
&.error {
|
6
|
-
color: rgb(239 68 68);
|
7
|
-
}
|
8
8
|
}
|
9
9
|
|
10
10
|
.form-group {
|
@@ -26,7 +26,11 @@ label {
|
|
26
26
|
font-size: 0.875rem;
|
27
27
|
}
|
28
28
|
|
29
|
-
button,
|
29
|
+
button,
|
30
|
+
input,
|
31
|
+
optgroup,
|
32
|
+
select,
|
33
|
+
textarea {
|
30
34
|
font-family: inherit;
|
31
35
|
font-feature-settings: inherit;
|
32
36
|
font-variation-settings: inherit;
|
@@ -39,7 +43,22 @@ button, input, optgroup, select, textarea {
|
|
39
43
|
padding: 0;
|
40
44
|
}
|
41
45
|
|
42
|
-
[type='text'],
|
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 {
|
43
62
|
-webkit-appearance: none;
|
44
63
|
-moz-appearance: none;
|
45
64
|
appearance: none;
|
@@ -62,3 +81,10 @@ button, input, optgroup, select, textarea {
|
|
62
81
|
color: rgb(239 68 68);
|
63
82
|
font-weight: 600;
|
64
83
|
}
|
84
|
+
|
85
|
+
.nested-fields {
|
86
|
+
border: 1px solid #e5e7eb;
|
87
|
+
border-radius: 0.5rem;
|
88
|
+
padding: 1.25rem;
|
89
|
+
margin-bottom: 1rem;
|
90
|
+
}
|
@@ -1,17 +1,60 @@
|
|
1
|
+
#navbar {
|
2
|
+
border-bottom: 1px solid var(--border-color);
|
3
|
+
|
4
|
+
padding-top: 0.5rem;
|
5
|
+
padding-bottom: 0.5rem;
|
6
|
+
padding-left: 0.5rem;
|
7
|
+
padding-right: 0.5rem;
|
8
|
+
|
9
|
+
display: flex;
|
10
|
+
align-items: center;
|
11
|
+
justify-content: space-between;
|
12
|
+
|
13
|
+
.nav-group {
|
14
|
+
display: flex;
|
15
|
+
gap: 0.5em;
|
16
|
+
align-items: center;
|
17
|
+
}
|
18
|
+
|
19
|
+
a {
|
20
|
+
border-radius: .375rem;
|
21
|
+
color: var(--text-color);
|
22
|
+
display: block;
|
23
|
+
font-weight: 500;
|
24
|
+
padding: 0.5rem;
|
25
|
+
text-decoration: none;
|
26
|
+
|
27
|
+
margin-top: 0.1rem;
|
28
|
+
margin-bottom: 0.1rem;
|
29
|
+
|
30
|
+
&:hover {
|
31
|
+
background-color: rgb(243 244 246);
|
32
|
+
}
|
33
|
+
|
34
|
+
&.active {
|
35
|
+
background-color: rgb(243 244 246);
|
36
|
+
font-weight: 600;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
1
41
|
main {
|
2
|
-
padding-top: 1rem;
|
3
42
|
padding-right: 1rem;
|
4
43
|
padding-bottom: 1rem;
|
5
44
|
padding-left: calc(1rem + var(--sidebar-width));
|
45
|
+
position: relative;
|
6
46
|
}
|
7
47
|
|
8
48
|
#sidebar {
|
49
|
+
position: absolute;
|
50
|
+
inset: 0;
|
51
|
+
z-index: 10; /* Ensure sidebar is above main content */
|
52
|
+
|
9
53
|
border-right: 1px solid var(--border-color);
|
10
54
|
height: 100%;
|
11
55
|
margin: 0;
|
12
56
|
overflow: auto;
|
13
57
|
padding: 1rem;
|
14
|
-
position: fixed;
|
15
58
|
width: var(--sidebar-width);
|
16
59
|
|
17
60
|
h1 {
|
@@ -78,3 +121,57 @@ main {
|
|
78
121
|
}
|
79
122
|
}
|
80
123
|
}
|
124
|
+
|
125
|
+
/* Mobile Navigation Styles */
|
126
|
+
#hamburger-menu {
|
127
|
+
display: none;
|
128
|
+
background: none;
|
129
|
+
border: none;
|
130
|
+
padding: 0.5rem;
|
131
|
+
cursor: pointer;
|
132
|
+
color: var(--text-color);
|
133
|
+
}
|
134
|
+
|
135
|
+
.mobile-nav-overlay {
|
136
|
+
opacity: 0;
|
137
|
+
position: fixed;
|
138
|
+
top: 0;
|
139
|
+
left: 0;
|
140
|
+
width: 100%;
|
141
|
+
height: 100%;
|
142
|
+
background: rgba(0, 0, 0, 0.15);
|
143
|
+
z-index: 40;
|
144
|
+
transition: 0.2s ease-in-out;
|
145
|
+
pointer-events: none;
|
146
|
+
}
|
147
|
+
|
148
|
+
.mobile-nav-overlay.is-active {
|
149
|
+
opacity: 1;
|
150
|
+
pointer-events: auto;
|
151
|
+
}
|
152
|
+
|
153
|
+
@media (max-width: 768px) {
|
154
|
+
#hamburger-menu {
|
155
|
+
display: block;
|
156
|
+
}
|
157
|
+
|
158
|
+
main {
|
159
|
+
/* reset padding */
|
160
|
+
padding-left: 1rem;
|
161
|
+
}
|
162
|
+
|
163
|
+
#sidebar {
|
164
|
+
position: fixed;
|
165
|
+
top: 0;
|
166
|
+
left: 0;
|
167
|
+
height: 100vh;
|
168
|
+
transform: translateX(-100%);
|
169
|
+
transition: transform 0.2s ease-in-out;
|
170
|
+
background-color: var(--background-color, #fff);
|
171
|
+
z-index: 50;
|
172
|
+
}
|
173
|
+
|
174
|
+
#sidebar.open {
|
175
|
+
transform: translateX(0);
|
176
|
+
}
|
177
|
+
}
|
@@ -3,6 +3,8 @@
|
|
3
3
|
border: 1px solid var(--border-color);
|
4
4
|
overflow-x: auto;
|
5
5
|
position: relative;
|
6
|
+
min-width: 0;
|
7
|
+
width: 100%;
|
6
8
|
}
|
7
9
|
|
8
10
|
table {
|
@@ -13,10 +15,6 @@ table {
|
|
13
15
|
background-color: var(--background-color);
|
14
16
|
border-bottom: 1px solid var(--border-color);
|
15
17
|
text-align: left;
|
16
|
-
padding-bottom: 0.75rem;
|
17
|
-
padding-top: 0.75rem;
|
18
|
-
padding-right: 0.875rem;
|
19
|
-
padding-left: 0.875rem;
|
20
18
|
|
21
19
|
a {
|
22
20
|
color: rgb(17 24 39);
|
@@ -36,11 +34,6 @@ table {
|
|
36
34
|
}
|
37
35
|
|
38
36
|
td {
|
39
|
-
padding-bottom: 0.75rem;
|
40
|
-
padding-top: 0.75rem;
|
41
|
-
padding-right: 0.875rem;
|
42
|
-
padding-left: 0.875rem;
|
43
|
-
|
44
37
|
a {
|
45
38
|
font-weight: 500;
|
46
39
|
}
|
@@ -53,4 +46,16 @@ table {
|
|
53
46
|
border-bottom: none;
|
54
47
|
}
|
55
48
|
}
|
49
|
+
|
50
|
+
th, td {
|
51
|
+
/* force scroll */
|
52
|
+
min-width: 60px;
|
53
|
+
white-space: nowrap;
|
54
|
+
|
55
|
+
/* responsive padding */
|
56
|
+
padding-top: clamp(0.5rem, 0.25rem + 1.25vw, 0.75rem);
|
57
|
+
padding-bottom: clamp(0.5rem, 0.25rem + 1.25vw, 0.75rem);
|
58
|
+
padding-left: clamp(0.5rem, 0.125rem + 1.875vw, 0.875rem);
|
59
|
+
padding-right: clamp(0.5rem, 0.125rem + 1.875vw, 0.875rem);
|
60
|
+
}
|
56
61
|
}
|
@@ -1,7 +1,12 @@
|
|
1
1
|
module Madmin
|
2
2
|
class BaseController < ActionController::Base
|
3
3
|
include ::ActiveStorage::SetCurrent if defined?(::ActiveStorage)
|
4
|
-
|
4
|
+
|
5
|
+
if Gem::Version.new(Pagy::VERSION) >= Gem::Version.new("43.0.0.rc")
|
6
|
+
include Pagy::Method
|
7
|
+
else
|
8
|
+
include Pagy::Backend
|
9
|
+
end
|
5
10
|
|
6
11
|
protect_from_forgery with: :exception
|
7
12
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = ["menu", "overlay"]
|
5
|
+
|
6
|
+
connect() {
|
7
|
+
this.handleEscape = this.handleEscape.bind(this)
|
8
|
+
}
|
9
|
+
|
10
|
+
toggle() {
|
11
|
+
const open = this.menuTarget.classList.toggle("open")
|
12
|
+
this.element.setAttribute("aria-expanded", open)
|
13
|
+
this.toggleOverlay(open)
|
14
|
+
this.toggleBodyScroll(open)
|
15
|
+
this.toggleEscapeListener(open)
|
16
|
+
}
|
17
|
+
|
18
|
+
close() {
|
19
|
+
if (this.menuTarget.classList.contains("open")) {
|
20
|
+
this.toggle()
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
handleEscape(event) {
|
25
|
+
if (event.key === "Escape") {
|
26
|
+
this.close()
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
disconnect() {
|
31
|
+
this.toggleEscapeListener(false)
|
32
|
+
this.toggleBodyScroll(false)
|
33
|
+
this.close()
|
34
|
+
}
|
35
|
+
|
36
|
+
toggleOverlay(open) {
|
37
|
+
if (this.hasOverlayTarget) {
|
38
|
+
this.overlayTarget.classList.toggle("is-active", open)
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
toggleBodyScroll(open) {
|
43
|
+
document.body.style.overflow = open ? 'hidden' : ''
|
44
|
+
}
|
45
|
+
|
46
|
+
toggleEscapeListener(open) {
|
47
|
+
const method = open ? 'addEventListener' : 'removeEventListener'
|
48
|
+
document[method]("keydown", this.handleEscape)
|
49
|
+
}
|
50
|
+
}
|
@@ -8,12 +8,15 @@ export default class extends Controller {
|
|
8
8
|
}
|
9
9
|
|
10
10
|
connect() {
|
11
|
-
|
11
|
+
let options = {
|
12
12
|
plugins: ['remove_button'],
|
13
13
|
valueField: 'id',
|
14
14
|
labelField: 'name',
|
15
15
|
searchField: 'name',
|
16
|
-
|
16
|
+
}
|
17
|
+
|
18
|
+
if (this.hasUrlValue) {
|
19
|
+
options["load"] = (search, callback) => {
|
17
20
|
let url = search ? `${this.urlValue}?q=${search}` : this.urlValue;
|
18
21
|
fetch(url)
|
19
22
|
.then(response => response.json())
|
@@ -23,7 +26,9 @@ export default class extends Controller {
|
|
23
26
|
callback();
|
24
27
|
});
|
25
28
|
}
|
26
|
-
}
|
29
|
+
}
|
30
|
+
|
31
|
+
this.select = new TomSelect(this.element, options)
|
27
32
|
}
|
28
33
|
|
29
34
|
disconnect() {
|
@@ -13,13 +13,29 @@
|
|
13
13
|
<%= csrf_meta_tags %>
|
14
14
|
<%= render "javascript" %>
|
15
15
|
</head>
|
16
|
-
<body>
|
17
|
-
<
|
18
|
-
<%= render "navigation" %>
|
19
|
-
</aside>
|
20
|
-
<main>
|
16
|
+
<body class="<%= controller_name %> <%= action_name %>" data-controller="mobile-nav">
|
17
|
+
<header>
|
21
18
|
<%= render "flash" %>
|
22
|
-
|
19
|
+
</header>
|
20
|
+
<header id="navbar">
|
21
|
+
<div class="nav-group">
|
22
|
+
<button id="hamburger-menu" type="button" data-action="click->mobile-nav#toggle" aria-label="Open menu" aria-expanded="false" aria-controls="sidebar">
|
23
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5"> <path fill-rule="evenodd" d="M2 4.75A.75.75 0 0 1 2.75 4h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 4.75ZM2 10a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 10Zm0 5.25a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1-.75-.75Z" clip-rule="evenodd" /> </svg>
|
24
|
+
</button>
|
25
|
+
<%= link_to_if respond_to?(:root_url), Madmin.site_name, root_url, data: {turbo: false} %>
|
26
|
+
</div>
|
27
|
+
</header>
|
28
|
+
|
29
|
+
<main>
|
30
|
+
<aside id="sidebar" data-mobile-nav-target="menu">
|
31
|
+
<%= render "navigation" %>
|
32
|
+
</aside>
|
33
|
+
|
34
|
+
<div>
|
35
|
+
<%= yield %>
|
36
|
+
</div.>
|
23
37
|
</main>
|
38
|
+
|
39
|
+
<div data-mobile-nav-target="overlay" data-action="click->mobile-nav#close" class="mobile-nav-overlay"></div>
|
24
40
|
</body>
|
25
41
|
</html>
|
@@ -15,7 +15,9 @@
|
|
15
15
|
</svg>
|
16
16
|
<% end if params[:q].present? %>
|
17
17
|
|
18
|
-
<%= link_to
|
18
|
+
<%= link_to resource.new_path, class: "btn btn-secondary" do %>
|
19
|
+
New <%= tag.span resource.friendly_name, class: "hidden md:inline-block" %>
|
20
|
+
<% end %>
|
19
21
|
</div>
|
20
22
|
</header>
|
21
23
|
|
@@ -63,6 +65,11 @@
|
|
63
65
|
</div>
|
64
66
|
|
65
67
|
<div class="pagination">
|
66
|
-
|
67
|
-
|
68
|
+
<% if @pagy.respond_to?(:series_nav) %>
|
69
|
+
<%== @pagy.series_nav if @pagy.last > 1 %>
|
70
|
+
<%== @pagy.info_tag %>
|
71
|
+
<% else %>
|
72
|
+
<%== pagy_nav @pagy if @pagy.last > 1 %>
|
73
|
+
<%== pagy_info @pagy %>
|
74
|
+
<% end %>
|
68
75
|
</div>
|
@@ -1,4 +1,2 @@
|
|
1
|
-
<
|
2
|
-
|
3
|
-
<p>Create <code>app/views/madmin/dashboard/show.html.erb</code> to customize your dashboard.</p>
|
4
|
-
</div>
|
1
|
+
<h1>Madmin Dashboard</h1>
|
2
|
+
<p>Create <code>app/views/madmin/dashboard/show.html.erb</code> to customize your dashboard.</p>
|
@@ -1,3 +1,7 @@
|
|
1
1
|
<% if (object = field.value(record)) %>
|
2
|
-
|
2
|
+
<% if (associated_resource = field.associated_resource_for(object)) %>
|
3
|
+
<%= link_to associated_resource.display_name(object), associated_resource.show_path(object) %>
|
4
|
+
<% else %>
|
5
|
+
<%= render partial: "missing_resource", locals: { resource_name: Madmin.resource_name_for(object) } %>
|
6
|
+
<% end %>
|
3
7
|
<% end %>
|
@@ -1,11 +1,18 @@
|
|
1
1
|
<% pagy, records = field.paginated_value(record, params) %>
|
2
2
|
<% records.each do |object| %>
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
<% if (associated_resource = field.associated_resource_for(object)) %>
|
4
|
+
<div><%= link_to associated_resource.display_name(object), associated_resource.show_path(object) %></div>
|
5
|
+
<% else %>
|
6
|
+
<%= render partial: "missing_resource", locals: { resource_name: Madmin.resource_name_for(object) } %>
|
7
|
+
<% end %>
|
6
8
|
<% end %>
|
7
9
|
|
8
10
|
<div class="pagination">
|
9
|
-
|
10
|
-
|
11
|
+
<% if pagy.respond_to?(:series_nav) %>
|
12
|
+
<%== pagy.series_nav if pagy.last > 1 %>
|
13
|
+
<%== pagy.info_tag %>
|
14
|
+
<% else %>
|
15
|
+
<%== pagy_nav pagy if pagy.last > 1 %>
|
16
|
+
<%== pagy_info pagy %>
|
17
|
+
<% end %>
|
11
18
|
</div>
|
@@ -1,3 +1,7 @@
|
|
1
1
|
<% if (object = field.value(record)) %>
|
2
|
-
|
2
|
+
<% if (associated_resource = field.associated_resource_for(object)) %>
|
3
|
+
<%= link_to associated_resource.display_name(object), associated_resource.show_path(object) %>
|
4
|
+
<% else %>
|
5
|
+
<%= render partial: "missing_resource", locals: { resource_name: Madmin.resource_name_for(object) } %>
|
6
|
+
<% end %>
|
3
7
|
<% end %>
|
@@ -1,11 +1,12 @@
|
|
1
|
-
<%= content_tag :div, class: "nested-fields
|
1
|
+
<%= content_tag :div, class: "nested-fields", data: { new_record: f.object.new_record? } do %>
|
2
2
|
<% field.nested_attributes.each do |name, nested_attribute| %>
|
3
3
|
<% nested_field = nested_attribute.field %>
|
4
4
|
<% next if nested_field.nil? %>
|
5
5
|
<% next unless nested_field.visible?(action_name) %>
|
6
6
|
<% next unless nested_field.visible?(:form) %>
|
7
7
|
|
8
|
-
<div class="
|
8
|
+
<div class="form-group">
|
9
|
+
<%= render "madmin/shared/label", form: f, field: nested_field %>
|
9
10
|
<%= render partial: nested_field.to_partial_path("form"), locals: { field: nested_field, record: f.object, form: f, resource: field.resource } %>
|
10
11
|
</div>
|
11
12
|
<% end %>
|
@@ -1,11 +1,18 @@
|
|
1
1
|
<% pagy, records = field.paginated_value(record, params) %>
|
2
2
|
<% records.each do |object| %>
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
<% if (associated_resource = field.associated_resource_for(object)) %>
|
4
|
+
<div><%= link_to associated_resource.display_name(object), associated_resource.show_path(object) %></div>
|
5
|
+
<% else %>
|
6
|
+
<%= render partial: "missing_resource", locals: { resource_name: Madmin.resource_name_for(object) } %>
|
7
|
+
<% end %>
|
6
8
|
<% end %>
|
7
9
|
|
8
10
|
<div class="pagination">
|
9
|
-
|
10
|
-
|
11
|
+
<% if pagy.respond_to?(:series_nav) %>
|
12
|
+
<%== pagy.series_nav if pagy.last > 1 %>
|
13
|
+
<%== pagy.info_tag %>
|
14
|
+
<% else %>
|
15
|
+
<%== pagy_nav pagy if pagy.last > 1 %>
|
16
|
+
<%== pagy_info pagy %>
|
17
|
+
<% end %>
|
11
18
|
</div>
|
@@ -1,3 +1,7 @@
|
|
1
1
|
<% if (object = field.value(record)) %>
|
2
|
-
|
2
|
+
<% if (associated_resource = field.associated_resource_for(object)) %>
|
3
|
+
<%= link_to associated_resource.display_name(object), associated_resource.show_path(object), class: "text-blue-500 underline" %>
|
4
|
+
<% else %>
|
5
|
+
<%= render partial: "missing_resource", locals: { resource_name: Madmin.resource_name_for(object) } %>
|
6
|
+
<% end %>
|
3
7
|
<% end %>
|
@@ -1,4 +1,5 @@
|
|
1
|
-
<div
|
2
|
-
<%=
|
1
|
+
<div>
|
2
|
+
<%= form.time_select field.attribute_name,
|
3
|
+
{},
|
4
|
+
class: "form-select inline-block" %>
|
3
5
|
</div>
|
4
|
-
<%= form.time_select field.attribute_name, {}, { class: "form-select" } %>
|
data/lib/madmin/engine.rb
CHANGED
@@ -11,7 +11,7 @@ module Madmin
|
|
11
11
|
|
12
12
|
config.to_prepare do
|
13
13
|
Madmin.reset_resources!
|
14
|
-
Madmin.site_name ||= Rails.application.class.module_parent_name
|
14
|
+
Madmin.site_name ||= Rails.application.class.module_parent_name.titleize
|
15
15
|
end
|
16
16
|
|
17
17
|
initializer "madmin.assets" do |app|
|
@@ -15,12 +15,18 @@ module Madmin
|
|
15
15
|
"#{attribute_name}_id"
|
16
16
|
end
|
17
17
|
|
18
|
-
def index_path
|
19
|
-
associated_resource
|
18
|
+
def index_path(format: :json)
|
19
|
+
associated_resource&.index_path(format: format)
|
20
20
|
end
|
21
21
|
|
22
22
|
def associated_resource
|
23
23
|
Madmin.resource_by_name(model.reflect_on_association(attribute_name).klass)
|
24
|
+
rescue MissingResource
|
25
|
+
end
|
26
|
+
|
27
|
+
def associated_resource_for(object)
|
28
|
+
Madmin.resource_for(object)
|
29
|
+
rescue MissingResource
|
24
30
|
end
|
25
31
|
end
|
26
32
|
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
module Madmin
|
2
2
|
module Fields
|
3
3
|
class HasMany < Field
|
4
|
-
include Pagy::Backend
|
5
|
-
|
6
4
|
def options_for_select(record)
|
7
5
|
if (records = record.send(attribute_name))
|
8
6
|
return [] unless records.first
|
@@ -17,19 +15,47 @@ module Madmin
|
|
17
15
|
{"#{attribute_name.to_s.singularize}_ids": []}
|
18
16
|
end
|
19
17
|
|
20
|
-
def index_path
|
21
|
-
|
18
|
+
def index_path(format: :json)
|
19
|
+
associated_resource&.index_path(format: format)
|
20
|
+
end
|
21
|
+
|
22
|
+
def associated_resource
|
23
|
+
Madmin.resource_by_name(model.reflect_on_association(attribute_name).klass)
|
24
|
+
rescue MissingResource
|
25
|
+
end
|
26
|
+
|
27
|
+
def associated_resource_for(object)
|
28
|
+
Madmin.resource_for(object)
|
29
|
+
rescue MissingResource
|
22
30
|
end
|
23
31
|
|
24
32
|
def paginateable?
|
25
33
|
true
|
26
34
|
end
|
27
35
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
36
|
+
if Gem::Version.new(Pagy::VERSION) >= Gem::Version.new("43.0.0.rc")
|
37
|
+
include Pagy::Method
|
38
|
+
|
39
|
+
def paginated_value(record, params)
|
40
|
+
page_key = "#{attribute_name}_page"
|
41
|
+
request = {
|
42
|
+
query: {
|
43
|
+
"#{attribute_name}_page" => [params[page_key].to_i, 1].max
|
44
|
+
}
|
45
|
+
}
|
46
|
+
pagy value(record), page_key: page_key, request: request
|
47
|
+
rescue Pagy::OptionError
|
48
|
+
end
|
49
|
+
else
|
50
|
+
include Pagy::Backend
|
51
|
+
|
52
|
+
def paginated_value(record, params)
|
53
|
+
page_key = "#{attribute_name}_page"
|
54
|
+
page = [params[page_key].to_i, 1].max
|
55
|
+
pagy value(record), page: page, page_param: page_key
|
56
|
+
rescue Pagy::OverflowError, Pagy::VariableError
|
57
|
+
pagy value, page: 1, page_param: page_key
|
58
|
+
end
|
33
59
|
end
|
34
60
|
end
|
35
61
|
end
|
data/lib/madmin/resource.rb
CHANGED
data/lib/madmin/version.rb
CHANGED
data/lib/madmin.rb
CHANGED
@@ -41,19 +41,32 @@ module Madmin
|
|
41
41
|
mattr_accessor :site_name
|
42
42
|
mattr_accessor :stylesheets, default: []
|
43
43
|
|
44
|
+
class MissingResource < StandardError
|
45
|
+
end
|
46
|
+
|
44
47
|
class << self
|
45
48
|
def resource_for(object)
|
49
|
+
if (resource_name = resource_name_for(object))
|
50
|
+
resource_name.constantize
|
51
|
+
end
|
52
|
+
rescue NameError
|
53
|
+
raise MissingResource, <<~MESSAGE
|
54
|
+
#{resource_name} is missing. Create it by running:
|
55
|
+
|
56
|
+
bin/rails generate madmin:resource #{resource_name.split("Resource").first}
|
57
|
+
MESSAGE
|
58
|
+
end
|
59
|
+
|
60
|
+
def resource_name_for(object)
|
46
61
|
if object.is_a? ::ActiveStorage::Attached
|
47
|
-
"ActiveStorage::AttachmentResource"
|
62
|
+
"ActiveStorage::AttachmentResource"
|
48
63
|
else
|
49
64
|
begin
|
50
|
-
"#{object.class.name}Resource"
|
65
|
+
"#{object.class.name}Resource"
|
51
66
|
rescue
|
52
67
|
# For STI models, see if there's a superclass resource available
|
53
68
|
if (column = object.class.inheritance_column) && object.class.column_names.include?(column)
|
54
|
-
"#{object.class.superclass.base_class.name}Resource"
|
55
|
-
else
|
56
|
-
raise
|
69
|
+
"#{object.class.superclass.base_class.name}Resource"
|
57
70
|
end
|
58
71
|
end
|
59
72
|
end
|
@@ -61,6 +74,12 @@ module Madmin
|
|
61
74
|
|
62
75
|
def resource_by_name(name)
|
63
76
|
"#{name}Resource".constantize
|
77
|
+
rescue NameError
|
78
|
+
raise MissingResource, <<~MESSAGE
|
79
|
+
#{name}Resource is missing. Create it by running:
|
80
|
+
|
81
|
+
bin/rails generate madmin:resource #{resource_name.split("Resource").first}
|
82
|
+
MESSAGE
|
64
83
|
end
|
65
84
|
|
66
85
|
def resources
|
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: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Oliver
|
@@ -98,12 +98,14 @@ files:
|
|
98
98
|
- app/javascript/madmin/application.js
|
99
99
|
- app/javascript/madmin/controllers/application.js
|
100
100
|
- app/javascript/madmin/controllers/index.js
|
101
|
+
- app/javascript/madmin/controllers/mobile_nav_controller.js
|
101
102
|
- app/javascript/madmin/controllers/nested_form_controller.js
|
102
103
|
- app/javascript/madmin/controllers/select_controller.js
|
103
104
|
- app/views/layouts/madmin/application.html.erb
|
104
105
|
- app/views/madmin/application/_flash.html.erb
|
105
106
|
- app/views/madmin/application/_form.html.erb
|
106
107
|
- app/views/madmin/application/_javascript.html.erb
|
108
|
+
- app/views/madmin/application/_missing_resource.html.erb
|
107
109
|
- app/views/madmin/application/_navigation.html.erb
|
108
110
|
- app/views/madmin/application/edit.html.erb
|
109
111
|
- app/views/madmin/application/index.html.erb
|
@@ -255,7 +257,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
255
257
|
- !ruby/object:Gem::Version
|
256
258
|
version: '0'
|
257
259
|
requirements: []
|
258
|
-
rubygems_version: 3.
|
260
|
+
rubygems_version: 3.7.1
|
259
261
|
specification_version: 4
|
260
262
|
summary: A modern admin for Ruby on Rails apps
|
261
263
|
test_files: []
|