madmin 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb075b59c9ecf34a7f4b939bb87a3e564fd984bb0d6866b13926d9873a335d3b
4
- data.tar.gz: d6ec7fa589c2784e9c4a07d43b8cb5344e8637c7f7a3f53871cd17ecf1241061
3
+ metadata.gz: 531eb134ff2fc5b4f8d16f24167561fcd7836f1f5e8886d39f103a156ccffc6b
4
+ data.tar.gz: 28cec8a6ae99f9a36e6ecdc65383fe10306f2880ec7ae499e08a50093b38b7e1
5
5
  SHA512:
6
- metadata.gz: 4d98c36cdfa429788088b18bdfc2bb7d9b5263575941156612c40efe4bf96ba7e3abc0560550eec7b3707aab12ec6485d5ea80182b36e8e3f9b04d3698d1c1ca
7
- data.tar.gz: 24a57436a187fce39ace57a461989fb996395a6967c0513bba022c96197f054414754bd6b2a770cf54040daabfbd71880fb768fb12672917ba2a08bf94d577d3
6
+ metadata.gz: 122ff7eda3d0bc65945b4e34b9b23d6ddc700d711b9fa7381e8c3fd9bdfcd73113a083f2dcaded130978b6716bf79e3233bcc0e132c80704436b42d5065d407b
7
+ data.tar.gz: 92e06ee0cfcba0279947e46972efc5f2463b35b020b97bc5d830f7554cf5b004cc242f4203d3cc124919df49d8ecf1a4b16ac1e94b5e70715961b8e2afa8a1aa
data/README.md CHANGED
@@ -6,14 +6,15 @@
6
6
 
7
7
  Why another Ruby on Rails admin? We wanted an admin that was:
8
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
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
12
 
13
13
  ![Madmin Screenshot](docs/images/screenshot.png)
14
14
  _We're still working on the design!_
15
15
 
16
16
  ## Installation
17
+
17
18
  Add `madmin` to your application's Gemfile:
18
19
 
19
20
  ```bash
@@ -40,6 +41,23 @@ To generate a resource for a model, you can run:
40
41
  rails g madmin:resource ActionText::RichText
41
42
  ```
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
+
43
61
  ## Configuring Views
44
62
 
45
63
  The views packaged within the gem are a great starting point, but inevitably people will need to be able to customize those views.
@@ -47,16 +65,19 @@ The views packaged within the gem are a great starting point, but inevitably peo
47
65
  You can use the included generator to create the appropriate view files, which can then be customized.
48
66
 
49
67
  For example, running the following will copy over all of the views into your application that will be used for every resource:
68
+
50
69
  ```bash
51
70
  rails generate madmin:views
52
71
  ```
53
72
 
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)
73
+ 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:
74
+
75
+ - `application.html.erb` (layout file)
76
+ - `_javascript.html.erb` (default JavaScript setup)
77
+ - `_navigation.html.erb` (renders the navigation/sidebar menu)
58
78
 
59
79
  As with the other views, you can specifically run the views generator for only the navigation or application layout views:
80
+
60
81
  ```bash
61
82
  rails g madmin:views:navigation
62
83
  # -> app/views/madmin/_navigation.html.erb
@@ -68,6 +89,7 @@ rails g madmin:views:layout # Note the layout generator includes the layout, ja
68
89
  ```
69
90
 
70
91
  If you only need to customize specific views, you can restrict which views are copied by the generator:
92
+
71
93
  ```bash
72
94
  rails g madmin:views:index
73
95
  # -> app/views/madmin/application/index.html.erb
@@ -79,6 +101,7 @@ The `attribute` method in model_resource.rb gives you that flexibility.
79
101
  ```bash
80
102
  # -> app/madmin/resources/book_resource.rb
81
103
  ```
104
+
82
105
  ```ruby
83
106
  class UserResource < Madmin::Resource
84
107
  attribute :id, form: false
@@ -91,6 +114,7 @@ end
91
114
  ```
92
115
 
93
116
  You can also scope the copied view(s) to a specific Resource/Model:
117
+
94
118
  ```bash
95
119
  rails generate madmin:views:index Book
96
120
  # -> app/views/madmin/books/index.html.erb
@@ -126,9 +150,14 @@ end
126
150
  You can use a couple of strategies to authenticate users who are trying to
127
151
  access your madmin panel: [Authentication Docs](docs/authentication.md)
128
152
 
153
+ ## Assets
154
+ You can customize the JavaScript and CSS assets used by Madmin for your application. To learn how
155
+ see the [Assets Doc](docs/assets.md)
156
+
129
157
  ## 🙏 Contributing
130
158
 
131
159
  This project uses Standard for formatting Ruby code. Please make sure to run standardrb before submitting pull requests.
132
160
 
133
161
  ## 📝 License
162
+
134
163
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,11 @@
1
+ /*
2
+ *= require trix
3
+ *= require madmin/actiontext
4
+ *= require madmin/reset
5
+ *= require madmin/base
6
+ *= require madmin/sidebar
7
+ *= require madmin/buttons
8
+ *= require madmin/forms
9
+ *= require madmin/tables
10
+ *= require madmin/pagination
11
+ */
@@ -1,88 +1,10 @@
1
- @import url("../trix.css");
2
- @import url("./actiontext.css");
3
- @import url("./reset.css");
4
- @import url("./sidebar.css");
5
- @import url("./buttons.css");
6
- @import url("./forms.css");
7
- @import url("./tables.css");
8
- @import url("./pagination.css");
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");
9
10
 
10
- :root {
11
- --primary-color: rgb(37 99 235);
12
- --border-color: rgb(229 231 235);
13
- --background-color: rgb(249 250 251);
14
- --text-color: rgb(2 6 23);
15
- --light-text-color: rgb(71 85 105);
16
-
17
- --sidebar-width: 16rem;
18
- }
19
-
20
- body {
21
- color: var(--text-color);
22
- font-size: 14px;
23
- }
24
-
25
- a {
26
- color: var(--primary-color);
27
- }
28
-
29
- .search {
30
- display: flex;
31
- align-items: center;
32
- }
33
-
34
- .header {
35
- border-bottom: 1px solid rgb(229 231 235);
36
- display: flex;
37
- justify-content: space-between;
38
- align-items: center;
39
- padding-bottom: 1rem;
40
- margin-bottom: 1rem;
41
-
42
- h1 {
43
- margin: 0;
44
- }
45
-
46
- a {
47
- text-decoration: none;
48
-
49
- &:hover {
50
- text-decoration: underline;
51
- }
52
- }
53
-
54
- .actions {
55
- align-items: center;
56
- display: flex;
57
- gap: 0.5rem;
58
- }
59
- }
60
-
61
- .metrics {
62
- display: flex;
63
-
64
- .metric {
65
- border: 1px solid rgb(229 231 235);
66
- border-radius: 0.25rem;
67
- padding: 1rem;
68
- margin: 1rem;
69
- flex: 1 1 0%;
70
-
71
- h4 {
72
- color: rgb(75 85 99);
73
- font-weight: 600;
74
- margin-top: 0;
75
- margin-bottom: 0.5rem;
76
- }
77
-
78
- p {
79
- font-size: 2rem;
80
- font-weight: 600;
81
- margin: 0;
82
- }
83
- }
84
- }
85
-
86
- .scopes {
87
- margin-bottom: 1rem;
88
- }
@@ -0,0 +1,117 @@
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
+ }
14
+
15
+ a {
16
+ color: var(--primary-color);
17
+ }
18
+
19
+ .alert {
20
+ border-radius: 0.5rem;
21
+ font-weight: 500;
22
+ padding: 1rem;
23
+ margin-bottom: 1rem;
24
+
25
+ ul {
26
+ margin-top: 0.5rem;
27
+ margin-bottom: 0;
28
+ padding-left: 2rem;
29
+ }
30
+
31
+ svg {
32
+ display: inline-block;
33
+ height: 1rem;
34
+ margin-right: 0.25rem;
35
+ width: 1rem;
36
+ vertical-align: text-bottom;
37
+ }
38
+
39
+ &.alert-danger {
40
+ background-color: oklch(.936 .032 17.717);
41
+ color: oklch(.444 .177 26.899);
42
+
43
+ svg {
44
+ color: oklch(.637 .237 25.331);
45
+ }
46
+ }
47
+
48
+ &.alert-notice {
49
+ background-color: oklch(.962 .044 156.743);
50
+ color: oklch(.448 .119 151.328);
51
+
52
+ svg {
53
+ color: oklch(.723 .219 149.579);
54
+ }
55
+ }
56
+ }
57
+
58
+ .search {
59
+ display: flex;
60
+ align-items: center;
61
+ }
62
+
63
+ .header {
64
+ border-bottom: 1px solid rgb(229 231 235);
65
+ display: flex;
66
+ justify-content: space-between;
67
+ align-items: center;
68
+ padding-bottom: 1rem;
69
+ margin-bottom: 1rem;
70
+
71
+ h1 {
72
+ margin: 0;
73
+ }
74
+
75
+ a {
76
+ text-decoration: none;
77
+
78
+ &:hover {
79
+ text-decoration: underline;
80
+ }
81
+ }
82
+
83
+ .actions {
84
+ align-items: center;
85
+ display: flex;
86
+ gap: 0.5rem;
87
+ }
88
+ }
89
+
90
+ .metrics {
91
+ display: flex;
92
+
93
+ .metric {
94
+ border: 1px solid rgb(229 231 235);
95
+ border-radius: 0.25rem;
96
+ padding: 1rem;
97
+ margin: 1rem;
98
+ flex: 1 1 0%;
99
+
100
+ h4 {
101
+ color: rgb(75 85 99);
102
+ font-weight: 600;
103
+ margin-top: 0;
104
+ margin-bottom: 0.5rem;
105
+ }
106
+
107
+ p {
108
+ font-size: 2rem;
109
+ font-weight: 600;
110
+ margin: 0;
111
+ }
112
+ }
113
+ }
114
+
115
+ .scopes {
116
+ margin-bottom: 1rem;
117
+ }
@@ -1,21 +1,3 @@
1
- .alert {
2
- border-radius: 0.25rem;
3
- font-weight: 500;
4
- padding: 1rem;
5
- margin-bottom: 1rem;
6
-
7
- ul {
8
- margin-top: 0.5rem;
9
- margin-bottom: 0;
10
- padding-left: 2rem;
11
- }
12
-
13
- &.alert-danger {
14
- background-color: rgb(254 226 226);
15
- color: rgb(153 27 27);
16
- }
17
- }
18
-
19
1
  .form-hint {
20
2
  font-size: 0.875rem;
21
3
  margin-top: 0.5rem;
@@ -25,7 +25,7 @@ module Madmin
25
25
  end
26
26
 
27
27
  def new
28
- @record = resource.model.new
28
+ @record = resource.model.new(new_resource_params)
29
29
  end
30
30
 
31
31
  def create
@@ -85,6 +85,12 @@ module Madmin
85
85
  .transform_values { |v| change_polymorphic(v) }
86
86
  end
87
87
 
88
+ def new_resource_params
89
+ params.fetch(resource.param_key, {}).permit!
90
+ .permit(*resource.permitted_params)
91
+ .transform_values { |v| change_polymorphic(v) }
92
+ end
93
+
88
94
  def change_polymorphic(data)
89
95
  return data unless data.is_a?(ActionController::Parameters) && data[:type]
90
96
 
@@ -18,6 +18,7 @@
18
18
  <%= render "navigation" %>
19
19
  </aside>
20
20
  <main>
21
+ <%= render "flash" %>
21
22
  <%= yield %>
22
23
  </main>
23
24
  </body>
@@ -0,0 +1,13 @@
1
+ <% if alert %>
2
+ <div class="alert alert-danger">
3
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor"><path fill-rule="evenodd" d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14ZM8 4a.75.75 0 0 1 .75.75v3a.75.75 0 0 1-1.5 0v-3A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd"></path></svg>
4
+ <%= alert %>
5
+ </div>
6
+ <% end %>
7
+
8
+ <% if notice %>
9
+ <div class="alert alert-notice">
10
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor"><path fill-rule="evenodd" d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14Zm3.844-8.791a.75.75 0 0 0-1.188-.918l-3.7 4.79-1.649-1.833a.75.75 0 1 0-1.114 1.004l2.25 2.5a.75.75 0 0 0 1.15-.043l4.25-5.5Z" clip-rule="evenodd"></path></svg>
11
+ <%= notice %>
12
+ </div>
13
+ <% end %>
@@ -1,5 +1,5 @@
1
1
  <%= javascript_importmap_tags "application", importmap: Madmin.importmap %>
2
2
 
3
- <%= stylesheet_link_tag "madmin/application", "data-turbo-track": "reload" %>
3
+ <%= stylesheet_link_tag *Madmin.stylesheets, "data-turbo-track": "reload" %>
4
4
  <%= stylesheet_link_tag "https://unpkg.com/flatpickr/dist/flatpickr.min.css", "data-turbo-track": "reload" %>
5
5
  <%= stylesheet_link_tag "https://unpkg.com/tom-select/dist/css/tom-select.min.css", "data-turbo-track": "reload" %>
@@ -1,5 +1,11 @@
1
- <% field.value(record).each do |object| %>
1
+ <% pagy, records = field.paginated_value(record, params) %>
2
+ <% records.each do |object| %>
2
3
  <div>
3
4
  <%= link_to Madmin.resource_for(object).display_name(object), Madmin.resource_for(object).show_path(object), class: "text-blue-500 underline" %>
4
5
  </div>
5
6
  <% end %>
7
+
8
+ <div class="pagination">
9
+ <%== pagy_nav pagy if pagy.pages > 1 %>
10
+ <span>Showing <%= tag.strong pagy.in %> of <%= tag.strong pagy.count %></span>
11
+ </div>
@@ -1,5 +1,11 @@
1
- <% field.value(record).each do |object| %>
1
+ <% pagy, records = field.paginated_value(record, params) %>
2
+ <% records.each do |object| %>
2
3
  <div>
3
4
  <%= link_to Madmin.resource_for(object).display_name(object), Madmin.resource_for(object).show_path(object), class: "text-blue-500 underline" %>
4
5
  </div>
5
6
  <% end %>
7
+
8
+ <div class="pagination">
9
+ <%== pagy_nav pagy if pagy.pages > 1 %>
10
+ <span>Showing <%= tag.strong pagy.in %> of <%= tag.strong pagy.count %></span>
11
+ </div>
@@ -20,7 +20,7 @@ module Madmin
20
20
 
21
21
  def generate_route
22
22
  if route_namespace_exists?
23
- route "resources :#{plural_name}", namespace: class_path, indentation: separated_routes_file? ? 2 : 4, sentinel: /namespace :madmin do\s*\n/m
23
+ route "resources :#{plural_name}", namespace: class_path, indentation: separated_routes_file? ? 2 : 4, sentinel: /namespace :madmin[^\n]*do\s*\n/m
24
24
  else
25
25
  route "resources :#{plural_name}", namespace: [:madmin] + class_path
26
26
  end
data/lib/madmin/engine.rb CHANGED
@@ -19,6 +19,12 @@ module Madmin
19
19
  app.config.assets.paths << root.join("app/assets/stylesheets")
20
20
  app.config.assets.paths << root.join("app/javascript")
21
21
  app.config.assets.precompile += %w[madmin_manifest]
22
+
23
+ Madmin.stylesheets << if defined?(::Sprockets)
24
+ "madmin/application-sprockets"
25
+ else
26
+ "madmin/application"
27
+ end
22
28
  end
23
29
  end
24
30
 
data/lib/madmin/field.rb CHANGED
@@ -53,5 +53,9 @@ module Madmin
53
53
  def searchable?
54
54
  false
55
55
  end
56
+
57
+ def paginateable?
58
+ false
59
+ end
56
60
  end
57
61
  end
@@ -1,6 +1,8 @@
1
1
  module Madmin
2
2
  module Fields
3
3
  class HasMany < Field
4
+ include Pagy::Backend
5
+
4
6
  def options_for_select(record)
5
7
  if (records = record.send(attribute_name))
6
8
  return [] unless records.first
@@ -18,6 +20,21 @@ module Madmin
18
20
  def index_path
19
21
  Madmin.resource_by_name(model.reflect_on_association(attribute_name).klass).index_path(format: :json)
20
22
  end
23
+
24
+ def paginateable?
25
+ true
26
+ end
27
+
28
+ def paginated_value(record, params)
29
+ pagy value(record), params: params, page_param: "#{attribute_name}_page"
30
+ end
31
+
32
+ # Override to access params from vars since we're not in a controller/view
33
+ def pagy_get_page(vars, force_integer: true)
34
+ params = vars[:params]
35
+ page = params[vars[:page_param] || DEFAULT[:page_param]]
36
+ force_integer ? (page || 1).to_i : page
37
+ end
21
38
  end
22
39
  end
23
40
  end
@@ -1,6 +1,6 @@
1
1
  module Madmin
2
2
  module Fields
3
- class NestedHasMany < Field
3
+ class NestedHasMany < HasMany
4
4
  DEFAULT_ATTRIBUTES = %w[_destroy id].freeze
5
5
  def nested_attributes
6
6
  resource.attributes.except(*skipped_fields)
@@ -26,6 +26,10 @@ module Madmin
26
26
  attribute_name.to_s.singularize.classify.constantize
27
27
  end
28
28
 
29
+ def paginateable?
30
+ true
31
+ end
32
+
29
33
  private
30
34
 
31
35
  def permitted_fields
@@ -41,11 +41,15 @@ module Madmin
41
41
  end
42
42
 
43
43
  def default_sentinel(file)
44
- file.eql?(ROUTES_FILE[:default]) ? /\.routes\.draw do\s*\n/m : /namespace :madmin do\s*\n/m
44
+ file.eql?(ROUTES_FILE[:default]) ? /\.routes\.draw do\s*\n/m : /namespace :madmin[^\n]*do\s*\n/m
45
45
  end
46
46
 
47
47
  def default_routes_file
48
- rails6_1_and_up? ? ROUTES_FILE[:separated] : ROUTES_FILE[:default]
48
+ if rails6_1_and_up? && File.exist?(ROUTES_FILE[:separated])
49
+ ROUTES_FILE[:separated]
50
+ else
51
+ ROUTES_FILE[:default]
52
+ end
49
53
  end
50
54
 
51
55
  def generator_options
@@ -1,3 +1,3 @@
1
1
  module Madmin
2
- VERSION = "2.0.0"
2
+ VERSION = "2.0.1"
3
3
  end
data/lib/madmin.rb CHANGED
@@ -39,6 +39,7 @@ module Madmin
39
39
  mattr_accessor :importmap, default: Importmap::Map.new
40
40
  mattr_accessor :menu, default: Menu.new
41
41
  mattr_accessor :site_name
42
+ mattr_accessor :stylesheets, default: []
42
43
 
43
44
  class << self
44
45
  def resource_for(object)
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: madmin
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Oliver
8
8
  - Andrea Fomera
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2024-11-21 00:00:00.000000000 Z
11
+ date: 2025-02-25 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rails
@@ -80,7 +79,9 @@ files:
80
79
  - Rakefile
81
80
  - app/assets/config/madmin_manifest.js
82
81
  - app/assets/stylesheets/madmin/actiontext.css
82
+ - app/assets/stylesheets/madmin/application-sprockets.css
83
83
  - app/assets/stylesheets/madmin/application.css
84
+ - app/assets/stylesheets/madmin/base.css
84
85
  - app/assets/stylesheets/madmin/buttons.css
85
86
  - app/assets/stylesheets/madmin/forms.css
86
87
  - app/assets/stylesheets/madmin/pagination.css
@@ -100,6 +101,7 @@ files:
100
101
  - app/javascript/madmin/controllers/nested_form_controller.js
101
102
  - app/javascript/madmin/controllers/select_controller.js
102
103
  - app/views/layouts/madmin/application.html.erb
104
+ - app/views/madmin/application/_flash.html.erb
103
105
  - app/views/madmin/application/_form.html.erb
104
106
  - app/views/madmin/application/_javascript.html.erb
105
107
  - app/views/madmin/application/_navigation.html.erb
@@ -239,7 +241,6 @@ homepage: https://github.com/excid3/madmin
239
241
  licenses:
240
242
  - MIT
241
243
  metadata: {}
242
- post_install_message:
243
244
  rdoc_options: []
244
245
  require_paths:
245
246
  - lib
@@ -254,8 +255,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
254
255
  - !ruby/object:Gem::Version
255
256
  version: '0'
256
257
  requirements: []
257
- rubygems_version: 3.5.23
258
- signing_key:
258
+ rubygems_version: 3.6.5
259
259
  specification_version: 4
260
260
  summary: A modern admin for Ruby on Rails apps
261
261
  test_files: []