simple_admin_rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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>