madmin 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +102 -0
- data/Rakefile +32 -0
- data/app/assets/config/madmin_manifest.js +2 -0
- data/app/assets/javascripts/madmin/application.js +15 -0
- data/app/assets/javascripts/madmin/dashboard.js +2 -0
- data/app/assets/javascripts/madmin/resources.js +36 -0
- data/app/assets/stylesheets/madmin/application.css +22 -0
- data/app/assets/stylesheets/madmin/dashboard.css +4 -0
- data/app/assets/stylesheets/madmin/resources.css +4 -0
- data/app/controllers/madmin/application_controller.rb +16 -0
- data/app/controllers/madmin/base_controller.rb +16 -0
- data/app/controllers/madmin/dashboard_controller.rb +8 -0
- data/app/controllers/madmin/resources_controller.rb +97 -0
- data/app/decorators/madmin/resource_decorator.rb +16 -0
- data/app/helpers/madmin/application_helper.rb +15 -0
- data/app/helpers/madmin/fields/polymorphic_helper.rb +25 -0
- data/app/jobs/madmin/application_job.rb +4 -0
- data/app/mailers/madmin/application_mailer.rb +6 -0
- data/app/models/madmin/application_record.rb +5 -0
- data/app/views/application/_navigation.html.erb +17 -0
- data/app/views/layouts/madmin/application.html.erb +30 -0
- data/app/views/madmin/dashboard/index.html.erb +6 -0
- data/app/views/madmin/fields/belongs_to/_form.html.erb +14 -0
- data/app/views/madmin/fields/belongs_to/_index.html.erb +7 -0
- data/app/views/madmin/fields/belongs_to/_show.html.erb +8 -0
- data/app/views/madmin/fields/check_box/_form.html.erb +4 -0
- data/app/views/madmin/fields/check_box/_index.html.erb +1 -0
- data/app/views/madmin/fields/check_box/_show.html.erb +8 -0
- data/app/views/madmin/fields/email/_form.html.erb +4 -0
- data/app/views/madmin/fields/email/_index.html.erb +1 -0
- data/app/views/madmin/fields/email/_show.html.erb +8 -0
- data/app/views/madmin/fields/has_many/_form.html.erb +0 -0
- data/app/views/madmin/fields/has_many/_show.html.erb +15 -0
- data/app/views/madmin/fields/has_one/_form.html.erb +0 -0
- data/app/views/madmin/fields/has_one/_show.html.erb +12 -0
- data/app/views/madmin/fields/number/_form.html.erb +4 -0
- data/app/views/madmin/fields/number/_index.html.erb +1 -0
- data/app/views/madmin/fields/number/_show.html.erb +8 -0
- data/app/views/madmin/fields/password/_form.html.erb +4 -0
- data/app/views/madmin/fields/password/_index.html.erb +1 -0
- data/app/views/madmin/fields/password/_show.html.erb +8 -0
- data/app/views/madmin/fields/polymorphic/_form.html.erb +32 -0
- data/app/views/madmin/fields/polymorphic/_index.html.erb +1 -0
- data/app/views/madmin/fields/polymorphic/_show.html.erb +14 -0
- data/app/views/madmin/fields/select/_form.html.erb +4 -0
- data/app/views/madmin/fields/select/_index.html.erb +1 -0
- data/app/views/madmin/fields/select/_show.html.erb +8 -0
- data/app/views/madmin/fields/text/_form.html.erb +4 -0
- data/app/views/madmin/fields/text/_index.html.erb +1 -0
- data/app/views/madmin/fields/text/_show.html.erb +8 -0
- data/app/views/madmin/fields/text_area/_form.html.erb +4 -0
- data/app/views/madmin/fields/text_area/_index.html.erb +1 -0
- data/app/views/madmin/fields/text_area/_show.html.erb +8 -0
- data/app/views/madmin/resources/_form.html.erb +15 -0
- data/app/views/madmin/resources/_scopes.html.erb +10 -0
- data/app/views/madmin/resources/edit.html.erb +2 -0
- data/app/views/madmin/resources/index.html.erb +13 -0
- data/app/views/madmin/resources/index/_content.html.erb +33 -0
- data/app/views/madmin/resources/new.html.erb +2 -0
- data/app/views/madmin/resources/show.html.erb +10 -0
- data/config/routes.rb +11 -0
- data/lib/generators/madmin/controller/USAGE +8 -0
- data/lib/generators/madmin/controller/controller_generator.rb +10 -0
- data/lib/generators/madmin/install/install_generator.rb +31 -0
- data/lib/generators/madmin/page/USAGE +8 -0
- data/lib/generators/madmin/page/page_generator.rb +20 -0
- data/lib/generators/madmin/page/templates/template.html.erb +2 -0
- data/lib/generators/madmin/page/templates/template.rb.erb +10 -0
- data/lib/generators/madmin/resource/resource_generator.rb +76 -0
- data/lib/generators/madmin/resource/templates/resource.rb.erb +11 -0
- data/lib/generators/madmin/views/views_generator.rb +15 -0
- data/lib/madmin.rb +30 -0
- data/lib/madmin/engine.rb +9 -0
- data/lib/madmin/field.rb +64 -0
- data/lib/madmin/field/associatable.rb +58 -0
- data/lib/madmin/field/belongs_to.rb +9 -0
- data/lib/madmin/field/check_box.rb +8 -0
- data/lib/madmin/field/date_time.rb +8 -0
- data/lib/madmin/field/email.rb +8 -0
- data/lib/madmin/field/has_many.rb +9 -0
- data/lib/madmin/field/has_one.rb +9 -0
- data/lib/madmin/field/number.rb +8 -0
- data/lib/madmin/field/password.rb +8 -0
- data/lib/madmin/field/polymorphic.rb +57 -0
- data/lib/madmin/field/select.rb +13 -0
- data/lib/madmin/field/text.rb +8 -0
- data/lib/madmin/field/text_area.rb +8 -0
- data/lib/madmin/generator_helpers.rb +13 -0
- data/lib/madmin/resourceable.rb +72 -0
- data/lib/madmin/resourceable/class_methods.rb +152 -0
- data/lib/madmin/resources.rb +13 -0
- data/lib/madmin/version.rb +3 -0
- data/lib/tasks/madmin_tasks.rake +4 -0
- metadata +197 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: be4b01320deccbc46e6184ae804f74e278c909ec7b11f74274f09dc4442e78d4
|
4
|
+
data.tar.gz: cdecb4f3ca23924befe03dc43ec4dda63626adabc24c70cbc892e3e21dd0ca62
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7f8691c029a1f37cf1b515c1ec399481d12ee2a4320c1e564f586e38b2d7e12610796df1c3b391356496dfca4c6b1d156b5459850873dd6bda54940e69a2f951
|
7
|
+
data.tar.gz: 36e1b5138b977c5a7bb46b7e1a96078c645c204865de2df919fdef9909c4ac9ba6930b18ad905ca6489ff6e8b11656776561aa1c424b38d79ba3e9db3beee204
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2019 Jason Charnes
|
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,102 @@
|
|
1
|
+
# Madmin
|
2
|
+
|
3
|
+
Short description and motivation.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
How to use my plugin.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'madmin'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
```bash
|
20
|
+
$ bundle
|
21
|
+
```
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
$ gem install madmin
|
27
|
+
```
|
28
|
+
|
29
|
+
Then you can run the installer to generate resources for all models in
|
30
|
+
your app that inherit from `ActiveRecord::Base` by running:
|
31
|
+
|
32
|
+
```bash
|
33
|
+
rails generate madmin:install
|
34
|
+
```
|
35
|
+
|
36
|
+
## Generating Resources
|
37
|
+
|
38
|
+
To generate (or re-generate) a Madmin dashboard for a resource, you
|
39
|
+
can run the following command and pass in the model name
|
40
|
+
|
41
|
+
```bash
|
42
|
+
rails generate madmin:install User
|
43
|
+
```
|
44
|
+
|
45
|
+
## Implementing Authentication
|
46
|
+
|
47
|
+
To implement user authentication for your admin dashboard, you can override `authenticate!` in `app/controllers/madmin/application_controller.rb`.
|
48
|
+
|
49
|
+
To access this controller run the following command
|
50
|
+
|
51
|
+
```bash
|
52
|
+
rails generate madmin:controller Application
|
53
|
+
```
|
54
|
+
|
55
|
+
If you're using Devise, simply have `authenticate!` authenticate your resource
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
class Madmin::ApplicationController < Madmin::BaseController
|
59
|
+
...
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def authenticate!
|
64
|
+
authenticate_user!
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
If you're wanting to use simple HTTP Basic authentication, have `authenticate!` use `authenticate_with_http_basic` like the following
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
class Madmin::ApplicationController < Madmin::BaseController
|
73
|
+
...
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def authenticate!
|
78
|
+
authenticated = authenticate_with_http_basic { |user, password|
|
79
|
+
user == "user" && password == "password"
|
80
|
+
}
|
81
|
+
|
82
|
+
request_http_basic_authentication unless authenticated
|
83
|
+
end
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
## Autoloading Lib
|
88
|
+
|
89
|
+
If you want to avoid having to restart your Rails application everytime you make an adjustment to a `lib/madmin/resources.rb`, add the following to `config/application.rb`:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
# Autoload Madmin
|
93
|
+
config.autoload_paths += Dir["#{config.root}/lib/madmin/**/"]
|
94
|
+
```
|
95
|
+
|
96
|
+
## Contributing
|
97
|
+
|
98
|
+
Contribution directions go here.
|
99
|
+
|
100
|
+
## License
|
101
|
+
|
102
|
+
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,32 @@
|
|
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
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
18
|
+
load "rails/tasks/engine.rake"
|
19
|
+
|
20
|
+
load "rails/tasks/statistics.rake"
|
21
|
+
|
22
|
+
require "bundler/gem_tasks"
|
23
|
+
|
24
|
+
require "rake/testtask"
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << "test"
|
28
|
+
t.pattern = "test/**/*_test.rb"
|
29
|
+
t.verbose = false
|
30
|
+
end
|
31
|
+
|
32
|
+
task default: :test
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file. JavaScript code in this file should be added after the last require_* statement.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require rails-ujs
|
14
|
+
//= require activestorage
|
15
|
+
//= require_tree .
|
@@ -0,0 +1,36 @@
|
|
1
|
+
// Place all the behaviors and hooks related to the matching controller here.
|
2
|
+
// All this logic will automatically be available in application.js.
|
3
|
+
|
4
|
+
var updateResourceOptions = function(polymorphicField) {
|
5
|
+
var idField = document.getElementById("commentable_id");
|
6
|
+
var slug =
|
7
|
+
polymorphicField.options[polymorphicField.selectedIndex].dataset.slug;
|
8
|
+
|
9
|
+
idField.options.length = 0;
|
10
|
+
|
11
|
+
if (!slug) {
|
12
|
+
idField.classList.add("d-none");
|
13
|
+
return;
|
14
|
+
}
|
15
|
+
|
16
|
+
axios.get("/madmin/" + slug + ".json").then(function(response) {
|
17
|
+
response.data.forEach(function(resource) {
|
18
|
+
var option = document.createElement("option");
|
19
|
+
option.text = resource.display_value;
|
20
|
+
option.value = resource.id;
|
21
|
+
idField.add(option);
|
22
|
+
});
|
23
|
+
|
24
|
+
idField.classList.remove("d-none");
|
25
|
+
});
|
26
|
+
};
|
27
|
+
|
28
|
+
if (window.polymorphicFields) {
|
29
|
+
window.polymorphicFields.forEach(function(field) {
|
30
|
+
var polymorphicField = document.getElementById(field + "_type");
|
31
|
+
|
32
|
+
polymorphicField.addEventListener("change", function() {
|
33
|
+
updateResourceOptions(polymorphicField);
|
34
|
+
});
|
35
|
+
});
|
36
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
+
* It is generally better to create a new file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
16
|
+
|
17
|
+
.sidebar {
|
18
|
+
height: 100%;
|
19
|
+
left: 0;
|
20
|
+
position: fixed;
|
21
|
+
top: 0;
|
22
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Madmin
|
2
|
+
class ApplicationController < BaseController
|
3
|
+
protect_from_forgery with: :exception
|
4
|
+
|
5
|
+
before_action :authenticate!
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def authenticate!
|
10
|
+
# redirect_to x_path unless current_user
|
11
|
+
#
|
12
|
+
# If using Devise, set this method to call:
|
13
|
+
# authenticate_user!
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_dependency "madmin/resources"
|
2
|
+
|
3
|
+
module Madmin
|
4
|
+
class BaseController < ActionController::Base
|
5
|
+
before_action :validate_resources
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
# Taking a peek at all the resources will raise an error if one isn't found.
|
10
|
+
# Let's inform the user if we can't find a resource no matter what page
|
11
|
+
# they're on. This should fail to prevent surprises at run time.
|
12
|
+
def validate_resources
|
13
|
+
Madmin::Resources.all
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require_dependency "madmin/application_controller"
|
2
|
+
|
3
|
+
module Madmin
|
4
|
+
class ResourcesController < ApplicationController
|
5
|
+
include ActiveSupport::Inflector
|
6
|
+
|
7
|
+
before_action :find_resource, only: [:show, :edit, :update, :destroy]
|
8
|
+
|
9
|
+
helper_method :madmin_resource
|
10
|
+
|
11
|
+
def index
|
12
|
+
@scopes = madmin_resource.scopes
|
13
|
+
@headers = madmin_resource.index_headers
|
14
|
+
|
15
|
+
if params[:scope]&.to_sym&.in?(@scopes)
|
16
|
+
begin
|
17
|
+
@collection = resource.send(params[:scope])
|
18
|
+
rescue ArgumentError
|
19
|
+
raise ScopeWithArgumentsError, "The scope #{params[:scope.to_sym]} on #{resource.name} takes arguments, which are currently unsupported."
|
20
|
+
end
|
21
|
+
else
|
22
|
+
@collection = resource.all
|
23
|
+
end
|
24
|
+
|
25
|
+
respond_to do |format|
|
26
|
+
format.html
|
27
|
+
format.json { render json: @collection.map { |c| {id: c.id, display_value: c.title} } }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def show
|
32
|
+
end
|
33
|
+
|
34
|
+
def new
|
35
|
+
@resource = ResourceDecorator.new(resource.new(resource_params))
|
36
|
+
end
|
37
|
+
|
38
|
+
def create
|
39
|
+
@resource = ResourceDecorator.new(resource.new(resource_params))
|
40
|
+
|
41
|
+
if @resource.save
|
42
|
+
redirect_to resource_path(id: @resource.id)
|
43
|
+
else
|
44
|
+
render :new
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def edit
|
49
|
+
end
|
50
|
+
|
51
|
+
def update
|
52
|
+
if @resource.update(resource_params)
|
53
|
+
redirect_to resource_path(id: @resource.id)
|
54
|
+
else
|
55
|
+
render :edit
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def destroy
|
60
|
+
if @resource.destroy
|
61
|
+
redirect_to resources_path(params[:resource])
|
62
|
+
else
|
63
|
+
flash[:error] = "There was an issue deleting the record."
|
64
|
+
redirect_to :back
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def find_resource
|
71
|
+
@resource ||= Madmin::ResourceDecorator.new(resource.find(params[:id]))
|
72
|
+
end
|
73
|
+
|
74
|
+
def form_keys
|
75
|
+
madmin_resource.form_fields.map { |field| field.strong_params_keys }.flatten
|
76
|
+
end
|
77
|
+
|
78
|
+
def madmin_resource
|
79
|
+
Object.const_get("::Madmin::Resources::#{resource_name}").new
|
80
|
+
end
|
81
|
+
|
82
|
+
def resource
|
83
|
+
Object.const_get(resource_name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def resource_name
|
87
|
+
camelize(params[:resource].singularize)
|
88
|
+
end
|
89
|
+
|
90
|
+
def resource_params
|
91
|
+
param_key = resource_name.downcase.to_sym
|
92
|
+
return unless params.dig(param_key)
|
93
|
+
|
94
|
+
params.require(param_key).permit(form_keys)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Madmin
|
2
|
+
module ApplicationHelper
|
3
|
+
def available_resources
|
4
|
+
@available_resources ||= Madmin::Resources.gather.map { |model| madmin_resource_for(model: model) }
|
5
|
+
end
|
6
|
+
|
7
|
+
def madmin_resource_for(model:)
|
8
|
+
Object.const_get("::Madmin::Resources::#{model}").new
|
9
|
+
end
|
10
|
+
|
11
|
+
def pages
|
12
|
+
Madmin::Pages.all
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Madmin
|
2
|
+
module Fields
|
3
|
+
module PolymorphicHelper
|
4
|
+
def polymorphic_models(type)
|
5
|
+
all_resources = Madmin::Resources.all.map { |r| Madmin::ResourceDecorator.new(r) }
|
6
|
+
|
7
|
+
polymorphic_resources = all_resources.select { |resource|
|
8
|
+
associations = resource.model.reflect_on_all_associations
|
9
|
+
associations.select { |a| a.options.dig(:as) === type }.any?
|
10
|
+
}
|
11
|
+
|
12
|
+
polymorphic_resources.map(&:model)
|
13
|
+
end
|
14
|
+
|
15
|
+
def polymorphic_options_for_selected_type(form:, field:)
|
16
|
+
options_from_collection_for_select(
|
17
|
+
form.object.send(field.polymorphic_type_param).constantize.send(field.polymorphic_scope),
|
18
|
+
:id,
|
19
|
+
field.polymorphic_display_value,
|
20
|
+
form.object.send(field.polymorphic_id_param)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|