madmin 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +102 -0
  4. data/Rakefile +32 -0
  5. data/app/assets/config/madmin_manifest.js +2 -0
  6. data/app/assets/javascripts/madmin/application.js +15 -0
  7. data/app/assets/javascripts/madmin/dashboard.js +2 -0
  8. data/app/assets/javascripts/madmin/resources.js +36 -0
  9. data/app/assets/stylesheets/madmin/application.css +22 -0
  10. data/app/assets/stylesheets/madmin/dashboard.css +4 -0
  11. data/app/assets/stylesheets/madmin/resources.css +4 -0
  12. data/app/controllers/madmin/application_controller.rb +16 -0
  13. data/app/controllers/madmin/base_controller.rb +16 -0
  14. data/app/controllers/madmin/dashboard_controller.rb +8 -0
  15. data/app/controllers/madmin/resources_controller.rb +97 -0
  16. data/app/decorators/madmin/resource_decorator.rb +16 -0
  17. data/app/helpers/madmin/application_helper.rb +15 -0
  18. data/app/helpers/madmin/fields/polymorphic_helper.rb +25 -0
  19. data/app/jobs/madmin/application_job.rb +4 -0
  20. data/app/mailers/madmin/application_mailer.rb +6 -0
  21. data/app/models/madmin/application_record.rb +5 -0
  22. data/app/views/application/_navigation.html.erb +17 -0
  23. data/app/views/layouts/madmin/application.html.erb +30 -0
  24. data/app/views/madmin/dashboard/index.html.erb +6 -0
  25. data/app/views/madmin/fields/belongs_to/_form.html.erb +14 -0
  26. data/app/views/madmin/fields/belongs_to/_index.html.erb +7 -0
  27. data/app/views/madmin/fields/belongs_to/_show.html.erb +8 -0
  28. data/app/views/madmin/fields/check_box/_form.html.erb +4 -0
  29. data/app/views/madmin/fields/check_box/_index.html.erb +1 -0
  30. data/app/views/madmin/fields/check_box/_show.html.erb +8 -0
  31. data/app/views/madmin/fields/email/_form.html.erb +4 -0
  32. data/app/views/madmin/fields/email/_index.html.erb +1 -0
  33. data/app/views/madmin/fields/email/_show.html.erb +8 -0
  34. data/app/views/madmin/fields/has_many/_form.html.erb +0 -0
  35. data/app/views/madmin/fields/has_many/_show.html.erb +15 -0
  36. data/app/views/madmin/fields/has_one/_form.html.erb +0 -0
  37. data/app/views/madmin/fields/has_one/_show.html.erb +12 -0
  38. data/app/views/madmin/fields/number/_form.html.erb +4 -0
  39. data/app/views/madmin/fields/number/_index.html.erb +1 -0
  40. data/app/views/madmin/fields/number/_show.html.erb +8 -0
  41. data/app/views/madmin/fields/password/_form.html.erb +4 -0
  42. data/app/views/madmin/fields/password/_index.html.erb +1 -0
  43. data/app/views/madmin/fields/password/_show.html.erb +8 -0
  44. data/app/views/madmin/fields/polymorphic/_form.html.erb +32 -0
  45. data/app/views/madmin/fields/polymorphic/_index.html.erb +1 -0
  46. data/app/views/madmin/fields/polymorphic/_show.html.erb +14 -0
  47. data/app/views/madmin/fields/select/_form.html.erb +4 -0
  48. data/app/views/madmin/fields/select/_index.html.erb +1 -0
  49. data/app/views/madmin/fields/select/_show.html.erb +8 -0
  50. data/app/views/madmin/fields/text/_form.html.erb +4 -0
  51. data/app/views/madmin/fields/text/_index.html.erb +1 -0
  52. data/app/views/madmin/fields/text/_show.html.erb +8 -0
  53. data/app/views/madmin/fields/text_area/_form.html.erb +4 -0
  54. data/app/views/madmin/fields/text_area/_index.html.erb +1 -0
  55. data/app/views/madmin/fields/text_area/_show.html.erb +8 -0
  56. data/app/views/madmin/resources/_form.html.erb +15 -0
  57. data/app/views/madmin/resources/_scopes.html.erb +10 -0
  58. data/app/views/madmin/resources/edit.html.erb +2 -0
  59. data/app/views/madmin/resources/index.html.erb +13 -0
  60. data/app/views/madmin/resources/index/_content.html.erb +33 -0
  61. data/app/views/madmin/resources/new.html.erb +2 -0
  62. data/app/views/madmin/resources/show.html.erb +10 -0
  63. data/config/routes.rb +11 -0
  64. data/lib/generators/madmin/controller/USAGE +8 -0
  65. data/lib/generators/madmin/controller/controller_generator.rb +10 -0
  66. data/lib/generators/madmin/install/install_generator.rb +31 -0
  67. data/lib/generators/madmin/page/USAGE +8 -0
  68. data/lib/generators/madmin/page/page_generator.rb +20 -0
  69. data/lib/generators/madmin/page/templates/template.html.erb +2 -0
  70. data/lib/generators/madmin/page/templates/template.rb.erb +10 -0
  71. data/lib/generators/madmin/resource/resource_generator.rb +76 -0
  72. data/lib/generators/madmin/resource/templates/resource.rb.erb +11 -0
  73. data/lib/generators/madmin/views/views_generator.rb +15 -0
  74. data/lib/madmin.rb +30 -0
  75. data/lib/madmin/engine.rb +9 -0
  76. data/lib/madmin/field.rb +64 -0
  77. data/lib/madmin/field/associatable.rb +58 -0
  78. data/lib/madmin/field/belongs_to.rb +9 -0
  79. data/lib/madmin/field/check_box.rb +8 -0
  80. data/lib/madmin/field/date_time.rb +8 -0
  81. data/lib/madmin/field/email.rb +8 -0
  82. data/lib/madmin/field/has_many.rb +9 -0
  83. data/lib/madmin/field/has_one.rb +9 -0
  84. data/lib/madmin/field/number.rb +8 -0
  85. data/lib/madmin/field/password.rb +8 -0
  86. data/lib/madmin/field/polymorphic.rb +57 -0
  87. data/lib/madmin/field/select.rb +13 -0
  88. data/lib/madmin/field/text.rb +8 -0
  89. data/lib/madmin/field/text_area.rb +8 -0
  90. data/lib/madmin/generator_helpers.rb +13 -0
  91. data/lib/madmin/resourceable.rb +72 -0
  92. data/lib/madmin/resourceable/class_methods.rb +152 -0
  93. data/lib/madmin/resources.rb +13 -0
  94. data/lib/madmin/version.rb +3 -0
  95. data/lib/tasks/madmin_tasks.rake +4 -0
  96. metadata +197 -0
@@ -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
@@ -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.
@@ -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).
@@ -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,2 @@
1
+ //= link_directory ../javascripts/madmin .js
2
+ //= link_directory ../stylesheets/madmin .css
@@ -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,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -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,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -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,8 @@
1
+ require_dependency "madmin/application_controller"
2
+
3
+ module Madmin
4
+ class DashboardController < ApplicationController
5
+ def index
6
+ end
7
+ end
8
+ 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,16 @@
1
+ module Madmin
2
+ class ResourceDecorator < SimpleDelegator
3
+ def initialize(resource)
4
+ @resource = resource
5
+ super
6
+ end
7
+
8
+ def name
9
+ resource.class.name
10
+ end
11
+
12
+ private
13
+
14
+ attr_reader :resource
15
+ end
16
+ 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