simple_admin_rails 0.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.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +22 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/javascripts/application.js +10 -0
  6. data/app/assets/javascripts/bootstrap.js +4 -0
  7. data/app/assets/javascripts/moment.js +3043 -0
  8. data/app/assets/javascripts/simple_admin/admin.js +8 -0
  9. data/app/assets/javascripts/simple_admin/application.js +13 -0
  10. data/app/assets/stylesheets/application.css +7 -0
  11. data/app/assets/stylesheets/bootstrap_and_overrides.css.less +29 -0
  12. data/app/assets/stylesheets/simple_admin/application.css +29 -0
  13. data/app/controllers/simple_admin/admin_controller.rb +118 -0
  14. data/app/controllers/simple_admin/application_controller.rb +4 -0
  15. data/app/helpers/simple_admin/application_helper.rb +4 -0
  16. data/app/models/admin.rb +165 -0
  17. data/app/views/layouts/simple_admin/application.html.erb +70 -0
  18. data/app/views/simple_admin/admin/_dashboard.html.erb +19 -0
  19. data/app/views/simple_admin/admin/index.html.erb +50 -0
  20. data/app/views/simple_admin/admin/model_edit.html.erb +13 -0
  21. data/app/views/simple_admin/admin/model_new.html.erb +10 -0
  22. data/config/initializers/kaminari_config.rb +10 -0
  23. data/config/initializers/simple_form.rb +166 -0
  24. data/config/initializers/simple_form_bootstrap.rb +136 -0
  25. data/config/locales/en.bootstrap.yml +23 -0
  26. data/config/locales/simple_form.en.yml +31 -0
  27. data/config/routes.rb +12 -0
  28. data/lib/simple_admin.rb +4 -0
  29. data/lib/simple_admin/engine.rb +40 -0
  30. data/lib/simple_admin/version.rb +3 -0
  31. data/lib/tasks/simple_admin_tasks.rake +4 -0
  32. data/lib/templates/erb/scaffold/_form.html.erb +13 -0
  33. data/test/dummy/README.rdoc +28 -0
  34. data/test/dummy/Rakefile +6 -0
  35. data/test/dummy/app/assets/javascripts/application.js +13 -0
  36. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  37. data/test/dummy/app/controllers/application_controller.rb +5 -0
  38. data/test/dummy/app/helpers/application_helper.rb +2 -0
  39. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  40. data/test/dummy/bin/bundle +3 -0
  41. data/test/dummy/bin/rails +4 -0
  42. data/test/dummy/bin/rake +4 -0
  43. data/test/dummy/bin/setup +29 -0
  44. data/test/dummy/config.ru +4 -0
  45. data/test/dummy/config/application.rb +26 -0
  46. data/test/dummy/config/boot.rb +5 -0
  47. data/test/dummy/config/database.yml +25 -0
  48. data/test/dummy/config/environment.rb +5 -0
  49. data/test/dummy/config/environments/development.rb +41 -0
  50. data/test/dummy/config/environments/production.rb +79 -0
  51. data/test/dummy/config/environments/test.rb +42 -0
  52. data/test/dummy/config/initializers/assets.rb +11 -0
  53. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  54. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  55. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  56. data/test/dummy/config/initializers/inflections.rb +16 -0
  57. data/test/dummy/config/initializers/mime_types.rb +4 -0
  58. data/test/dummy/config/initializers/session_store.rb +3 -0
  59. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  60. data/test/dummy/config/locales/en.yml +23 -0
  61. data/test/dummy/config/routes.rb +4 -0
  62. data/test/dummy/config/secrets.yml +22 -0
  63. data/test/dummy/db/development.sqlite3 +0 -0
  64. data/test/dummy/log/development.log +6 -0
  65. data/test/dummy/public/404.html +67 -0
  66. data/test/dummy/public/422.html +67 -0
  67. data/test/dummy/public/500.html +66 -0
  68. data/test/dummy/public/favicon.ico +0 -0
  69. data/test/integration/navigation_test.rb +10 -0
  70. data/test/simple_admin_test.rb +7 -0
  71. data/test/test_helper.rb +19 -0
  72. metadata +244 -0
@@ -0,0 +1,8 @@
1
+ $(document).ready(function() {
2
+ $formatDateEls = $(".format-date");
3
+ $.each($formatDateEls, function(key, value){
4
+ var $el = value;
5
+ var newDate = moment($el.innerText).format('L');
6
+ $el.innerText = newDate;
7
+ });
8
+ });
@@ -0,0 +1,13 @@
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.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,7 @@
1
+ /*
2
+ * This is a manifest file that'll automatically include all the stylesheets available in this directory
3
+ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
4
+ * the top of the compiled file, but it's generally better to create a new file per style scope.
5
+ *= require_self
6
+ *= require_tree .
7
+ */
@@ -0,0 +1,29 @@
1
+ @import "twitter/bootstrap/bootstrap";
2
+
3
+ // Set the correct sprite paths
4
+ @iconSpritePath: image-url("twitter/bootstrap/glyphicons-halflings.png");
5
+ @iconWhiteSpritePath: image-url("twitter/bootstrap/glyphicons-halflings-white.png");
6
+
7
+ // Set the Font Awesome (Font Awesome is default. You can disable by commenting below lines)
8
+ @fontAwesomeEotPath: font-url("fontawesome-webfont.eot");
9
+ @fontAwesomeEotPath_iefix: font-url("fontawesome-webfont.eot?#iefix");
10
+ @fontAwesomeWoffPath: font-url("fontawesome-webfont.woff");
11
+ @fontAwesomeTtfPath: font-url("fontawesome-webfont.ttf");
12
+ @fontAwesomeSvgPath: font-url("fontawesome-webfont.svg#fontawesomeregular");
13
+
14
+ // Font Awesome
15
+ @import "fontawesome/font-awesome";
16
+
17
+ // Glyphicons
18
+ //@import "twitter/bootstrap/glyphicons.less";
19
+
20
+ // Your custom LESS stylesheets goes here
21
+ //
22
+ // Since bootstrap was imported above you have access to its mixins which
23
+ // you may use and inherit here
24
+ //
25
+ // If you'd like to override bootstrap's own variables, you can do so here as well
26
+ // See http://twitter.github.com/bootstrap/customize.html#variables for their names and documentation
27
+ //
28
+ // Example:
29
+ // @link-color: #ff0000;
@@ -0,0 +1,29 @@
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 styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
16
+
17
+
18
+ h2.admin-header a {
19
+ text-decoration: none;
20
+ }
21
+
22
+ .pagination {
23
+ display:inline-block;
24
+ }
25
+
26
+ td.action {
27
+ white-space: nowrap;
28
+ width: 1px;
29
+ }
@@ -0,0 +1,118 @@
1
+ ActionController::Parameters.permit_all_parameters = true
2
+
3
+ class SimpleAdmin::AdminController < ApplicationController
4
+ http_basic_authenticate_with name: SimpleAdmin.config_username, password: SimpleAdmin.config_password
5
+
6
+ before_action :admin
7
+ before_action :setup
8
+ before_action :restrict
9
+
10
+ layout "simple_admin/application"
11
+
12
+ def index
13
+ end
14
+
15
+ def model_index
16
+ @resources = @model_name.constantize.order('updated_at DESC').page(params[:page]) || []
17
+ render :index
18
+ end
19
+
20
+ def model_edit
21
+ @resource = @admin.get_resource(params[:model_name], params[:id])
22
+ end
23
+
24
+ def model_new
25
+ @resource = @admin.new_resource(params[:model_name])
26
+ end
27
+
28
+ def update
29
+ form_params = params[@model_name.downcase]
30
+ @resource = @admin.get_resource(@model_name, params[:id])
31
+ @resource.assign_attributes form_params.to_h.symbolize_keys!
32
+
33
+ if @resource.save
34
+ redirect_to admin_model_index_path @model_name
35
+ else
36
+ flash[:error] = "Could not update #{@model_name}! – #{@resource.errors.messages.to_a.join(", ")}"
37
+ redirect_to admin_model_edit_path(@model_name, params[:id])
38
+ end
39
+ end
40
+
41
+ def create
42
+ form_params = params[@model_name.downcase]
43
+ @resource = @admin.new_resource(@model_name)
44
+ @resource.assign_attributes form_params.to_h.symbolize_keys!
45
+
46
+ if @resource.save
47
+ redirect_to admin_model_index_path @model_name
48
+ else
49
+ flash[:error] = "Could not create #{@model_name}! – #{@resource.errors.messages.to_a.join(", ")}"
50
+ redirect_to admin_model_new_path @model_name
51
+ end
52
+ end
53
+
54
+ def destroy
55
+ @resource = @admin.get_resource(@model_name, params[:id])
56
+ @resource.destroy!
57
+ redirect_to admin_model_index_path @model_name
58
+ end
59
+
60
+ def extension
61
+ @extension = @admin.extensions.find{ |e| e.constant.name == params[:name].camelize }
62
+ @extension_instance = @extension.instantiate
63
+ render path_to_extension_view
64
+ end
65
+
66
+ def extension_post
67
+ @extension = @admin.extensions.find{ |e| e.constant.name == params[:name].camelize }
68
+ @extension_instance = @extension.instantiate
69
+
70
+ if params[:extension].present?
71
+ @extension_instance.form_params = params[:extension]
72
+
73
+ if @extension_instance.process!
74
+ # success
75
+ flash[:notice] = @extension_instance.flash_notice
76
+ redirect_to admin_extension_path(params[:name])
77
+ else
78
+ # failure
79
+ flash[:alert] = @extension_instance.flash_alert
80
+ render path_to_extension_view
81
+ end
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def admin
88
+ @admin = Admin.new admin_config
89
+ end
90
+
91
+ def setup
92
+ @table_models = @admin.table_models
93
+ @creatable_resources = @admin.creatable_models_whitelist
94
+ end
95
+
96
+ def restrict
97
+ # make sure we only try to access what we can
98
+ return unless params[:model_name]
99
+ @model_name = params[:model_name]
100
+ render(text: "Could not access that Model", status: 401) unless @table_models.map(&:name).include?(@model_name)
101
+ end
102
+
103
+ def admin_config
104
+ {
105
+ model_blacklist: SimpleAdmin.config_model_blacklist,
106
+ visible_columns_blacklist: SimpleAdmin.config_visible_columns_blacklist,
107
+ editable_columns_blacklist: SimpleAdmin.config_editable_columns_blacklist,
108
+ creatable_models_whitelist: SimpleAdmin.config_creatable_models_whitelist,
109
+ editable_associations_whitelist: SimpleAdmin.config_editable_associations_whitelist,
110
+ extensions: SimpleAdmin.config_extensions
111
+ }
112
+ end
113
+
114
+ def path_to_extension_view
115
+ # TODO: make hardcoded path to view configurable
116
+ "/simple_admin/extensions/#{params[:name]}"
117
+ end
118
+ end
@@ -0,0 +1,4 @@
1
+ module SimpleAdmin
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module SimpleAdmin
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,165 @@
1
+ # there is no Boolean
2
+ module Boolean; end
3
+ class TrueClass; include Boolean; end
4
+ class FalseClass; include Boolean; end
5
+
6
+ class Admin
7
+ attr_accessor :model_blacklist, :visible_columns_blacklist, :editable_columns_blacklist,
8
+ :creatable_models_whitelist, :editable_associations_whitelist, :extensions
9
+
10
+ def initialize(params)
11
+ @model_blacklist = []
12
+ @visible_columns_blacklist = []
13
+ @editable_columns_blacklist = []
14
+ @creatable_models_whitelist = []
15
+ @editable_associations_whitelist = {}
16
+ @extensions = []
17
+
18
+ self.model_blacklist = params[:model_blacklist]
19
+ self.visible_columns_blacklist = params[:visible_columns_blacklist]
20
+ self.editable_columns_blacklist = params[:editable_columns_blacklist]
21
+ self.creatable_models_whitelist = params[:creatable_models_whitelist]
22
+ self.editable_associations_whitelist = params[:editable_associations_whitelist]
23
+ self.extensions = params[:extensions]
24
+ end
25
+
26
+ def model_blacklist=(items)
27
+ raise Exception.new("params must be of type Array") unless items.is_a? Array
28
+ @model_blacklist << items
29
+ @model_blacklist.flatten!.uniq!
30
+ end
31
+
32
+ def visible_columns_blacklist=(items)
33
+ raise Exception.new("params must be of type Array") unless items.is_a? Array
34
+ @visible_columns_blacklist << items
35
+ @visible_columns_blacklist.flatten!.uniq!
36
+ end
37
+
38
+ def editable_columns_blacklist=(items)
39
+ raise Exception.new("params must be of type Array") unless items.is_a? Array
40
+ @editable_columns_blacklist << items
41
+ @editable_columns_blacklist.flatten!.uniq!
42
+ end
43
+
44
+ def creatable_models_whitelist=(items)
45
+ raise Exception.new("params must be of type Array") unless items.is_a? Array
46
+ @creatable_models_whitelist << items
47
+ @creatable_models_whitelist.flatten!.uniq!
48
+ @creatable_models_whitelist = @creatable_models_whitelist.map(&:constantize)
49
+ end
50
+
51
+ def editable_associations_whitelist=(items_hash)
52
+ # key should be model name, values should be array of permitted editable associations
53
+ raise Exception.new("param must be of type Hash") unless items_hash.is_a? Hash
54
+ @editable_associations_whitelist = @editable_associations_whitelist.merge items_hash
55
+ end
56
+
57
+ def table_models
58
+ @table_models = []
59
+ ActiveRecord::Base.connection.tables.collect do |table|
60
+ unless model_blacklist && model_blacklist.include?(table.classify)
61
+ begin
62
+ @table_models << table.classify.constantize
63
+ rescue
64
+ nil
65
+ end
66
+ end
67
+ end
68
+ @table_models.compact! || @table_models
69
+ end
70
+
71
+ def decorate_column_class(col_name)
72
+ case col_name
73
+ when 'created_at'
74
+ 'format-date'
75
+ when 'updated_at'
76
+ 'format-date'
77
+ end
78
+ end
79
+
80
+ def visible_attributes_for_model(my_model)
81
+ my_model = my_model.constantize if my_model.is_a? String
82
+ my_model.new.attribute_names - visible_columns_blacklist
83
+ end
84
+
85
+ def editable_attributes_for_model(my_model)
86
+ my_model = my_model.constantize if my_model.is_a? String
87
+ my_model.new.attribute_names - editable_columns_blacklist
88
+ end
89
+
90
+ def get_resource(model_name, id)
91
+ model_name.constantize.find(id)
92
+ end
93
+
94
+ def new_resource(model_name)
95
+ model_name.constantize.new
96
+ end
97
+
98
+ def simple_form_field(simple_form_obj, my_model, alternate_input_hash={})
99
+ editable_attributes = self.editable_attributes_for_model my_model
100
+ collection_validators = my_model.constantize.validators.select{|v| v.options.keys.include? :in }
101
+ html = ""
102
+
103
+ # table columns
104
+ editable_attributes.each do |ea|
105
+ if !alternate_input_hash.blank? && ea =~ Regexp.new(alternate_input_hash.keys.first.to_s)
106
+ # non simple_form inputs
107
+ html += "<div class='form-group'>"
108
+ html += simple_form_obj.send(alternate_input_hash.values.first.to_s, ea.to_sym, {}, { class: "form-control" })
109
+ html += "</div>"
110
+ else
111
+ # simple_form inputs
112
+ collection_validator = collection_validators.select{|cv| cv.attributes.include? ea.to_sym}.first
113
+ if collection_validator
114
+ # treat as a collection
115
+ html += simple_form_obj.input(ea.to_sym, collection: collection_validator.send(:delimiter), input_html: {class: 'form-control'}, wrapper_html: {class: 'form-group'})
116
+ else
117
+ if simple_form_obj.object.send(ea.to_sym).is_a? Boolean
118
+ class_for_group = nil
119
+ class_for_control = nil
120
+ else
121
+ class_for_group = "form-group"
122
+ class_for_control = "form-control"
123
+ end
124
+ # is not a collection
125
+ html += simple_form_obj.input(ea.to_sym, input_html: {class: class_for_control}, wrapper_html: {class: class_for_group})
126
+ end
127
+ end
128
+ end
129
+
130
+ # model associations
131
+ my_model.constantize.reflections.keys.each do |associated|
132
+ if editable_associations_whitelist[my_model] &&
133
+ editable_associations_whitelist[my_model].include?(associated)
134
+ # TODO: check association type, one to many one to one,
135
+ # and switch appropriate form control
136
+ html += simple_form_obj.association(associated, as: :check_boxes)
137
+ end
138
+ end
139
+
140
+ html.html_safe
141
+ end
142
+
143
+ def extensions=(ext_array)
144
+ raise Exception.new("Extension requires an array of constants: [ ExtensionConstant, AnotherExtension ]") unless ext_array.is_a?(Array)
145
+ @extensions = ext_array.map{ |ext| Extension.new(ext) }
146
+ end
147
+ end
148
+
149
+ Extension = Struct.new(:ext) do
150
+ def view_name
151
+ self.ext.name.underscore
152
+ end
153
+
154
+ def title
155
+ self.ext.name.titleize
156
+ end
157
+
158
+ def instantiate
159
+ self.ext.new
160
+ end
161
+
162
+ def constant
163
+ self.ext
164
+ end
165
+ end
@@ -0,0 +1,70 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= SimpleAdmin.config_site_name %></title>
5
+
6
+ <!-- Bootstrap -->
7
+ <!-- Latest compiled and minified CSS -->
8
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
9
+ <!-- Optional theme -->
10
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
11
+ <!-- Latest compiled and minified JavaScript -->
12
+ <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
13
+
14
+ <%= stylesheet_link_tag "simple_admin/application", media: "all" %>
15
+ <%= javascript_include_tag "simple_admin/application" %>
16
+ <%= csrf_meta_tags %>
17
+ </head>
18
+ <body>
19
+
20
+ <div class="container">
21
+
22
+ <h2 class="page-header admin-header"><%= link_to SimpleAdmin.config_site_name, admin_path %></h2>
23
+
24
+ <% if flash[:notice] %>
25
+ <p class="alert alert-info"><%= flash[:notice] %></p>
26
+ <% end %>
27
+
28
+ <% if flash[:alert] %>
29
+ <p class="alert alert-warning"><%= flash[:alert] %></p>
30
+ <% end %>
31
+
32
+ <div class="row">
33
+ <div class="col-sm-4">
34
+
35
+ <div class="row">
36
+ <div class="col-sm-12">
37
+ <% @table_models.each do |t_model| %>
38
+ <a href="<%= url_for admin_model_index_path(t_model) %>" class="list-group-item <%= 'active' if t_model.name == @model_name %> ">
39
+ <span class="badge pull-right"><%= t_model.count %></span>
40
+ <h4 class="list-group-item-heading"><%= t_model.name %></h4>
41
+ </a>
42
+ <% end %>
43
+ </div>
44
+ </div>
45
+
46
+ <% if @admin.extensions.any? %>
47
+ <hr>
48
+ <div class="row">
49
+ <div class="col-sm-12">
50
+ <% @admin.extensions.each do |ext| %>
51
+ <a href="<%= url_for admin_extension_path(ext.view_name) %>" class="list-group-item <%= 'active' if ext.title == @extension.try(:title) %> ">
52
+ <h4 class="list-group-item-heading"><%= ext.title %></h4>
53
+ </a>
54
+ <% end %>
55
+ </div>
56
+ </div>
57
+ <% end %>
58
+
59
+ </div>
60
+
61
+ <div class="col-sm-8">
62
+ <%= yield %>
63
+ </div>
64
+
65
+ </div>
66
+
67
+ </div>
68
+
69
+ </body>
70
+ </html>