goodmin 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 (147) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +1359 -0
  4. data/Rakefile +56 -0
  5. data/app/assets/stylesheets/goodmin/application.css +75 -0
  6. data/app/controllers/goodmin/application_controller.rb +43 -0
  7. data/app/controllers/goodmin/resource_controller.rb +235 -0
  8. data/app/helpers/goodmin/application_helper.rb +45 -0
  9. data/app/javascript/goodmin/application.js +8 -0
  10. data/app/javascript/goodmin/controllers/batch_actions_controller.js +101 -0
  11. data/app/javascript/goodmin/controllers/datetimepicker_controller.js +24 -0
  12. data/app/javascript/goodmin/controllers/navigation_controller.js +30 -0
  13. data/app/jobs/goodmin/application_job.rb +4 -0
  14. data/app/mailers/goodmin/application_mailer.rb +6 -0
  15. data/app/models/goodmin/application_record.rb +5 -0
  16. data/app/views/goodmin/application/welcome.html.erb +17 -0
  17. data/app/views/goodmin/fields/association/_form.html.erb +19 -0
  18. data/app/views/goodmin/fields/association/_index.html.erb +6 -0
  19. data/app/views/goodmin/fields/association/_show.html.erb +6 -0
  20. data/app/views/goodmin/fields/boolean/_form.html.erb +7 -0
  21. data/app/views/goodmin/fields/boolean/_index.html.erb +1 -0
  22. data/app/views/goodmin/fields/boolean/_show.html.erb +1 -0
  23. data/app/views/goodmin/fields/date/_form.html.erb +6 -0
  24. data/app/views/goodmin/fields/date/_index.html.erb +1 -0
  25. data/app/views/goodmin/fields/date/_show.html.erb +1 -0
  26. data/app/views/goodmin/fields/date_time/_form.html.erb +6 -0
  27. data/app/views/goodmin/fields/date_time/_index.html.erb +1 -0
  28. data/app/views/goodmin/fields/date_time/_show.html.erb +1 -0
  29. data/app/views/goodmin/fields/enum/_index.html.erb +1 -0
  30. data/app/views/goodmin/fields/enum/_show.html.erb +1 -0
  31. data/app/views/goodmin/fields/nested_has_one/_form.html.erb +6 -0
  32. data/app/views/goodmin/fields/nested_has_one/_index.html.erb +1 -0
  33. data/app/views/goodmin/fields/nested_has_one/_show.html.erb +1 -0
  34. data/app/views/goodmin/fields/number/_form.html.erb +5 -0
  35. data/app/views/goodmin/fields/number/_index.html.erb +1 -0
  36. data/app/views/goodmin/fields/number/_show.html.erb +1 -0
  37. data/app/views/goodmin/fields/password/_form.html.erb +5 -0
  38. data/app/views/goodmin/fields/password/_index.html.erb +1 -0
  39. data/app/views/goodmin/fields/password/_show.html.erb +1 -0
  40. data/app/views/goodmin/fields/select/_form.html.erb +5 -0
  41. data/app/views/goodmin/fields/select/_index.html.erb +1 -0
  42. data/app/views/goodmin/fields/select/_show.html.erb +1 -0
  43. data/app/views/goodmin/fields/string/_form.html.erb +5 -0
  44. data/app/views/goodmin/fields/string/_index.html.erb +1 -0
  45. data/app/views/goodmin/fields/string/_show.html.erb +1 -0
  46. data/app/views/goodmin/fields/text/_form.html.erb +5 -0
  47. data/app/views/goodmin/fields/text/_index.html.erb +1 -0
  48. data/app/views/goodmin/fields/text/_show.html.erb +1 -0
  49. data/app/views/goodmin/resource/_actions.html.erb +9 -0
  50. data/app/views/goodmin/resource/_batch_actions.html.erb +12 -0
  51. data/app/views/goodmin/resource/_breadcrumb.html.erb +33 -0
  52. data/app/views/goodmin/resource/_breadcrumb_actions.html.erb +41 -0
  53. data/app/views/goodmin/resource/_button_actions.html.erb +3 -0
  54. data/app/views/goodmin/resource/_errors.html.erb +9 -0
  55. data/app/views/goodmin/resource/_export_actions.html.erb +15 -0
  56. data/app/views/goodmin/resource/_filters.html.erb +22 -0
  57. data/app/views/goodmin/resource/_form.html.erb +26 -0
  58. data/app/views/goodmin/resource/_pagination.html.erb +40 -0
  59. data/app/views/goodmin/resource/_scopes.html.erb +14 -0
  60. data/app/views/goodmin/resource/_table.html.erb +45 -0
  61. data/app/views/goodmin/resource/columns/_actions.html.erb +28 -0
  62. data/app/views/goodmin/resource/edit.html.erb +5 -0
  63. data/app/views/goodmin/resource/index.csv.csvbuilder +5 -0
  64. data/app/views/goodmin/resource/index.html.erb +10 -0
  65. data/app/views/goodmin/resource/index.json.jbuilder +3 -0
  66. data/app/views/goodmin/resource/new.html.erb +5 -0
  67. data/app/views/goodmin/resource/show.html.erb +11 -0
  68. data/app/views/goodmin/resource/show.json.jbuilder +1 -0
  69. data/app/views/goodmin/sessions/new.html.erb +11 -0
  70. data/app/views/goodmin/shared/_navigation.html.erb +0 -0
  71. data/app/views/goodmin/shared/_navigation_aside.html.erb +7 -0
  72. data/app/views/layouts/goodmin/_content.html.erb +13 -0
  73. data/app/views/layouts/goodmin/_layout.html.erb +22 -0
  74. data/app/views/layouts/goodmin/application.html.erb +28 -0
  75. data/app/views/layouts/goodmin/login.html.erb +18 -0
  76. data/config/importmap.rb +5 -0
  77. data/config/locales/en.yml +49 -0
  78. data/config/locales/pl-BR.yml +49 -0
  79. data/config/locales/pt-BR.yml +49 -0
  80. data/config/locales/sv.yml +49 -0
  81. data/config/routes.rb +3 -0
  82. data/lib/generators/goodmin/authentication/authentication_generator.rb +41 -0
  83. data/lib/generators/goodmin/authentication/templates/sessions_controller.rb +9 -0
  84. data/lib/generators/goodmin/install/install_generator.rb +41 -0
  85. data/lib/generators/goodmin/policy/policy_generator.rb +7 -0
  86. data/lib/generators/goodmin/policy/templates/policy.rb +23 -0
  87. data/lib/generators/goodmin/resource/resource_generator.rb +31 -0
  88. data/lib/generators/goodmin/resource/templates/resource.rb +25 -0
  89. data/lib/generators/goodmin/resource/templates/resource_controller.rb +9 -0
  90. data/lib/generators/goodmin/resource/templates/resource_model.rb +4 -0
  91. data/lib/generators/goodmin/resource/templates/resource_service.rb +23 -0
  92. data/lib/goodmin/authentication/sessions_controller.rb +46 -0
  93. data/lib/goodmin/authentication/user.rb +27 -0
  94. data/lib/goodmin/authentication.rb +35 -0
  95. data/lib/goodmin/authorization/policy.rb +41 -0
  96. data/lib/goodmin/authorization.rb +69 -0
  97. data/lib/goodmin/engine.rb +30 -0
  98. data/lib/goodmin/fields/association.rb +62 -0
  99. data/lib/goodmin/fields/base.rb +57 -0
  100. data/lib/goodmin/fields/boolean.rb +6 -0
  101. data/lib/goodmin/fields/date.rb +6 -0
  102. data/lib/goodmin/fields/date_time.rb +6 -0
  103. data/lib/goodmin/fields/enum.rb +15 -0
  104. data/lib/goodmin/fields/nested_has_one.rb +41 -0
  105. data/lib/goodmin/fields/number.rb +6 -0
  106. data/lib/goodmin/fields/password.rb +6 -0
  107. data/lib/goodmin/fields/select.rb +19 -0
  108. data/lib/goodmin/fields/string.rb +6 -0
  109. data/lib/goodmin/fields/text.rb +6 -0
  110. data/lib/goodmin/generators/base.rb +49 -0
  111. data/lib/goodmin/generators/named_base.rb +31 -0
  112. data/lib/goodmin/helpers/application.rb +47 -0
  113. data/lib/goodmin/helpers/batch_actions.rb +21 -0
  114. data/lib/goodmin/helpers/filters.rb +123 -0
  115. data/lib/goodmin/helpers/forms.rb +42 -0
  116. data/lib/goodmin/helpers/navigation.rb +52 -0
  117. data/lib/goodmin/helpers/tables.rb +25 -0
  118. data/lib/goodmin/helpers/translations.rb +19 -0
  119. data/lib/goodmin/paginator.rb +55 -0
  120. data/lib/goodmin/resolver.rb +141 -0
  121. data/lib/goodmin/resources/attribute.rb +46 -0
  122. data/lib/goodmin/resources/form_builder.rb +96 -0
  123. data/lib/goodmin/resources/form_component.rb +65 -0
  124. data/lib/goodmin/resources/form_components/col.rb +33 -0
  125. data/lib/goodmin/resources/form_components/row.rb +25 -0
  126. data/lib/goodmin/resources/form_components/section.rb +51 -0
  127. data/lib/goodmin/resources/form_components/tab.rb +49 -0
  128. data/lib/goodmin/resources/resource/associations.rb +23 -0
  129. data/lib/goodmin/resources/resource/batch_actions.rb +56 -0
  130. data/lib/goodmin/resources/resource/filters.rb +44 -0
  131. data/lib/goodmin/resources/resource/ordering.rb +41 -0
  132. data/lib/goodmin/resources/resource/pagination.rb +22 -0
  133. data/lib/goodmin/resources/resource/scopes.rb +61 -0
  134. data/lib/goodmin/resources/resource.rb +199 -0
  135. data/lib/goodmin/resources/resource_controller/batch_actions.rb +49 -0
  136. data/lib/goodmin/resources/resource_service/associations.rb +23 -0
  137. data/lib/goodmin/resources/resource_service/batch_actions.rb +52 -0
  138. data/lib/goodmin/resources/resource_service/filters.rb +44 -0
  139. data/lib/goodmin/resources/resource_service/ordering.rb +41 -0
  140. data/lib/goodmin/resources/resource_service/pagination.rb +22 -0
  141. data/lib/goodmin/resources/resource_service/scopes.rb +61 -0
  142. data/lib/goodmin/resources/resource_service.rb +199 -0
  143. data/lib/goodmin/service_locator.rb +25 -0
  144. data/lib/goodmin/version.rb +3 -0
  145. data/lib/goodmin.rb +44 -0
  146. data/lib/tasks/goodmin_tasks.rake +4 -0
  147. metadata +461 -0
data/Rakefile ADDED
@@ -0,0 +1,56 @@
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 = "Goodmin"
12
+ rdoc.options << "--line-numbers"
13
+ rdoc.rdoc_files.include("README.rdoc")
14
+ rdoc.rdoc_files.include("lib/**/*.rb")
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load "rails/tasks/engine.rake"
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
22
+ require "rake/testtask"
23
+
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << "lib"
26
+ t.libs << "test"
27
+ t.pattern = "test/**/*_test.rb"
28
+ t.warning = false
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
33
+
34
+ namespace :sandbox do
35
+ desc "Generate the Sandbox app then push it to GitHub which deploys it to Heroku"
36
+ task :deploy do
37
+ message = "Generated from: https://github.com/varvet/goodmin/commit/#{`git rev-parse HEAD`.strip}"
38
+ template_path = File.expand_path("../template.rb", __FILE__)
39
+ Bundler.with_unbundled_env do
40
+ Dir.mktmpdir do |dir|
41
+ Dir.chdir(dir)
42
+ system("git clone git@github.com:varvet/goodmin-sandbox.git")
43
+ if $CHILD_STATUS.success?
44
+ Dir.chdir("goodmin-sandbox")
45
+ system("rm -rf *")
46
+ system("rails _5.2.5_ new . -d postgresql -m #{template_path} --without-engine --skip-spring")
47
+ if $CHILD_STATUS.success?
48
+ system("git add --all")
49
+ system("git commit -m '#{message}'")
50
+ system("git push origin master")
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,75 @@
1
+ body {
2
+ margin-bottom: 20px;
3
+ }
4
+
5
+ #login {
6
+ margin-top: 20px;
7
+ }
8
+
9
+ #breadcrumb .dropdown:before {
10
+ content: " ";
11
+ }
12
+
13
+ #scopes {
14
+ margin-bottom: 20px;
15
+ }
16
+
17
+ #filters .filter .control-label {
18
+ display: block;
19
+ }
20
+
21
+ #filters .filter .form-control {
22
+ min-width: 170px;
23
+ }
24
+
25
+ #actions {
26
+ margin-bottom: 20px;
27
+ }
28
+
29
+ #actions::after {
30
+ display: block;
31
+ content: "";
32
+ clear: both;
33
+ }
34
+
35
+ #table td {
36
+ vertical-align: middle;
37
+ }
38
+
39
+ #table td .btn-group {
40
+ display: flex;
41
+ }
42
+
43
+ .pagination {
44
+ margin: 0 0 10px 0;
45
+ }
46
+
47
+ .pagination-entries {
48
+ line-height: 35px;
49
+ }
50
+
51
+ .highlight {
52
+ -moz-animation: highlight 3s;
53
+ -webkit-animation: highlight 3s;
54
+ animation: highlight 3s;
55
+ }
56
+
57
+ @-moz-keyframes highlight {
58
+ from {
59
+ background: #fffbcc;
60
+ }
61
+
62
+ to {
63
+ background: none;
64
+ }
65
+ }
66
+
67
+ @-webkit-keyframes highlight {
68
+ from {
69
+ background: #fffbcc;
70
+ }
71
+
72
+ to {
73
+ background: none;
74
+ }
75
+ }
@@ -0,0 +1,43 @@
1
+ module Goodmin
2
+ class ApplicationController < ActionController::Base
3
+ include Goodmin::Helpers::Translations
4
+
5
+ helper Goodmin::Helpers::Application
6
+ helper Goodmin::Helpers::Forms
7
+ helper Goodmin::Helpers::Navigation
8
+ helper Goodmin::Helpers::Translations
9
+
10
+ helper_method :authentication_enabled?
11
+ helper_method :authorization_enabled?
12
+
13
+ before_action :append_view_paths
14
+
15
+ layout "goodmin/application"
16
+
17
+ def welcome; end
18
+
19
+ protected
20
+
21
+ private
22
+
23
+ def append_view_paths
24
+ append_view_path Goodmin::Resolver.resolvers(controller_path)
25
+ end
26
+
27
+ def disable_authentication
28
+ @_disable_authentication = true
29
+ end
30
+
31
+ def disable_authorization
32
+ @_disable_authorization = true
33
+ end
34
+
35
+ def authentication_enabled?
36
+ !@_disable_authentication && singleton_class.include?(Goodmin::Authentication)
37
+ end
38
+
39
+ def authorization_enabled?
40
+ !@_disable_authorization && singleton_class.include?(Goodmin::Authorization)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,235 @@
1
+ require "goodmin/helpers/batch_actions"
2
+
3
+ module Goodmin
4
+ class ResourceController < ApplicationController
5
+ include Goodmin::Resources::Resource::BatchActions
6
+
7
+ helper Goodmin::Helpers::BatchActions
8
+ helper Goodmin::Helpers::Filters
9
+ helper Goodmin::Helpers::Tables
10
+
11
+ before_action :set_resource_service
12
+ before_action :set_resource_class
13
+ before_action :set_resource_parents
14
+ before_action :set_resources, only: :index
15
+ before_action :handle_batch_action, only: :update, if: -> { params[:batch_action].present? }
16
+ before_action :set_resource, only: [:show, :new, :edit, :create, :update, :destroy]
17
+
18
+ def index
19
+ respond_to do |format|
20
+ format.html
21
+ format.json
22
+ format.csv
23
+ end
24
+ end
25
+
26
+ def show
27
+ respond_to do |format|
28
+ format.html
29
+ format.json
30
+ end
31
+ end
32
+
33
+ def new; end
34
+
35
+ def edit; end
36
+
37
+ def create
38
+ respond_to do |format|
39
+ if @resource_service.create_resource(@resource)
40
+ format.html { redirect_to redirect_after_create, notice: redirect_flash_message }
41
+ format.json { render :show, status: :created, location: @resource }
42
+ else
43
+ format.html { render :edit }
44
+ format.json { render json: @resource.errors, status: :unprocessable_entity }
45
+ end
46
+ end
47
+ end
48
+
49
+ def update
50
+ respond_to do |format|
51
+ if @resource_service.update_resource(@resource, resource_params)
52
+ format.html { redirect_to redirect_after_update, notice: redirect_flash_message }
53
+ format.json { render :show, status: :ok, location: @resource }
54
+ else
55
+ format.html { render :edit }
56
+ format.json { render json: @resource.errors, status: :unprocessable_entity }
57
+ end
58
+ end
59
+ end
60
+
61
+ def destroy
62
+ @resource_service.destroy_resource(@resource)
63
+
64
+ respond_to do |format|
65
+ format.html { redirect_to redirect_after_destroy, notice: redirect_flash_message }
66
+ format.json { head :no_content }
67
+ end
68
+ end
69
+
70
+ protected
71
+
72
+ def set_resource_service
73
+ @resource_service = resource_service
74
+ end
75
+
76
+ def set_resource_class
77
+ @resource_class = resource_class
78
+ end
79
+
80
+ def set_resource_parents
81
+ @resource_parents = resource_parents
82
+ end
83
+
84
+ def set_resources
85
+ @resources = resources
86
+ authorize(@resources) if authorization_enabled?
87
+ end
88
+
89
+ def set_resource
90
+ @resource = resource
91
+ authorize(@resource) if authorization_enabled?
92
+ end
93
+
94
+ def resource_service_class
95
+ resource_name = controller_path.split("/").last.singularize.classify
96
+ "Goodmin::Resources::#{resource_name}Resource".constantize
97
+ end
98
+
99
+ def resource_service
100
+ resource_service = resource_service_class.new
101
+
102
+ if authentication_enabled?
103
+ resource_service.options[:admin_user] = admin_user
104
+ end
105
+
106
+ if resource_parents.present?
107
+ resource_service.options[:resource_parent] = resource_parents.last
108
+ end
109
+
110
+ resource_service
111
+ end
112
+
113
+ def resource_class
114
+ @resource_service.resource_class
115
+ end
116
+
117
+ def resource_parents
118
+ params.to_unsafe_h.each_with_object([]) do |(name, value), parents|
119
+ if name =~ /(.+)_id$/
120
+ parents << $1.classify.constantize.find(value)
121
+ end
122
+ end
123
+ end
124
+
125
+ def resources
126
+ @resource_service.resources(params)
127
+ end
128
+
129
+ def resource
130
+ if params[:id]
131
+ @resource_service.find_resource(params[:id])
132
+ else
133
+ case action_name
134
+ when "create"
135
+ @resource_service.build_resource(resource_params)
136
+ when "new"
137
+ @resource_service.build_resource(nil)
138
+ end
139
+ end
140
+ end
141
+
142
+ def resource_params
143
+ params.require(@resource_class.model_name.param_key.to_sym).permit(resource_params_defaults)
144
+ end
145
+
146
+ def resource_params_defaults
147
+ @resource_service.attrs_for_form.map do |attribute|
148
+ association = @resource_class.reflect_on_association(attribute.name)
149
+
150
+ if association && association.macro == :belongs_to
151
+ association.foreign_key.to_sym
152
+ elsif association && (many_to_many_association?(association) || has_many_association?(association))
153
+ { "#{attribute.name.to_s.singularize}_ids".to_sym => [] }
154
+ elsif association && nested_attributes_accepted?(attribute.name)
155
+ { "#{attribute.name}_attributes".to_sym => nested_attribute_permit_list(association) }
156
+ else
157
+ attribute.name
158
+ end
159
+ end
160
+ end
161
+
162
+ def nested_attributes_accepted?(attribute_name)
163
+ @resource_class.method_defined?("#{attribute_name}_attributes=")
164
+ end
165
+
166
+ def nested_attribute_permit_list(association)
167
+ service_class = Goodmin::ServiceLocator.find_service_class_for(association.klass, context_service_class: @resource_service.class)
168
+ attrs = service_class&.attrs_for_form || []
169
+ [:id] + attrs.map(&:name)
170
+ end
171
+
172
+ def many_to_many_association?(association)
173
+ association.macro == :has_and_belongs_to_many ||
174
+ (association.macro == :has_many && association.options[:through].present?)
175
+ end
176
+
177
+ def has_many_association?(association)
178
+ association.macro == :has_many && !association.options[:through].present?
179
+ end
180
+
181
+ def redirect_after_create
182
+ redirect_after_save
183
+ end
184
+
185
+ def redirect_after_update
186
+ redirect_after_save
187
+ end
188
+
189
+ def redirect_after_save
190
+ [*@resource_parents, @resource]
191
+ end
192
+
193
+ def redirect_after_destroy
194
+ [*@resource_parents, resource_class.model_name.route_key.to_sym]
195
+ end
196
+
197
+ def redirect_flash_message
198
+ translate_scoped("flash.#{action_name}", resource: @resource.class.model_name.human)
199
+ end
200
+
201
+ def handle_batch_action
202
+ action = params[:batch_action].to_sym
203
+ ids = params[:id].to_s.split(",")
204
+ records = @resource_class.where(id: ids)
205
+
206
+ @resource_service.batch_action(action, records)
207
+
208
+ redirect_path = if respond_to?("redirect_after_batch_action_#{action}", true)
209
+ send("redirect_after_batch_action_#{action}")
210
+ else
211
+ [*@resource_parents, @resource_class.model_name.route_key.to_sym]
212
+ end
213
+
214
+ redirect_to redirect_path
215
+ end
216
+
217
+ private
218
+
219
+ def disable_authentication
220
+ @_disable_authentication = true
221
+ end
222
+
223
+ def disable_authorization
224
+ @_disable_authorization = true
225
+ end
226
+
227
+ def authentication_enabled?
228
+ !@_disable_authentication && singleton_class.include?(Goodmin::Authentication)
229
+ end
230
+
231
+ def authorization_enabled?
232
+ !@_disable_authorization && singleton_class.include?(Goodmin::Authorization)
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,45 @@
1
+ module Goodmin
2
+ module ApplicationHelper
3
+ # Renders the provided partial with locals if it exists, otherwise
4
+ # yields the given block. The lookup context call is cached for
5
+ # each partial.
6
+ # Returns the display name for a resource record by looking up its service
7
+ # class via ServiceLocator and calling display_name on it. Falls back to
8
+ # record.to_s if no service class is found.
9
+ def resource_display_name(record)
10
+ service_class = Goodmin::ServiceLocator.find_service_class_for(
11
+ record.class,
12
+ context_service_class: @resource_service&.class
13
+ )
14
+ service = service_class&.new
15
+ service ? service.display_name(record) : record.to_s
16
+ rescue StandardError
17
+ record.to_s
18
+ end
19
+
20
+ def partial_override(partial, locals = {}, &block)
21
+ @_partial_override ||= {}
22
+
23
+ unless @_partial_override.key?(partial)
24
+ @_partial_override[partial] = lookup_context.exists?(partial, nil, true)
25
+ end
26
+
27
+ if @_partial_override[partial]
28
+ render partial: partial, locals: locals
29
+ else
30
+ capture(&block)
31
+ end
32
+ end
33
+
34
+ # Wraps the policy helper so that it is always accessible, even when
35
+ # authorization is not enabled. When that is the case, it returns a
36
+ # policy that always returns true.
37
+ def policy(resource)
38
+ if authorization_enabled?
39
+ controller.policy(resource)
40
+ else
41
+ Authorization::Policy.new(nil, nil, default: true)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,8 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
3
+
4
+ const application = Application.start()
5
+ application.debug = false
6
+ window.Stimulus = application
7
+
8
+ eagerLoadControllersFrom("controllers", application)
@@ -0,0 +1,101 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["selectAll", "selectNone", "checkbox", "checkboxContainer", "actionLink"]
5
+
6
+ connect() {
7
+ this.updateState()
8
+ }
9
+
10
+ toggle(event) {
11
+ event.preventDefault()
12
+ if (this.checkedIds().length > 0) {
13
+ this.checkboxTargets.forEach(cb => { cb.checked = false })
14
+ } else {
15
+ this.checkboxTargets.forEach(cb => { cb.checked = true })
16
+ }
17
+ this.updateActions()
18
+ }
19
+
20
+ containerClick(event) {
21
+ if (event.currentTarget === event.target) {
22
+ event.currentTarget.querySelector("[data-batch-actions-target='checkbox']").click()
23
+ }
24
+ }
25
+
26
+ checkboxChange() {
27
+ this.updateActions()
28
+ }
29
+
30
+ triggerAction(event) {
31
+ event.preventDefault()
32
+ const link = event.currentTarget
33
+ const confirmMessage = link.dataset.confirm
34
+ if (confirmMessage && !window.confirm(confirmMessage)) return
35
+
36
+ const action = link.getAttribute("href") + "/" + this.checkedIds().join(",")
37
+ const batchAction = link.dataset.value
38
+ const csrfToken = document.querySelector("meta[name='csrf-token']")?.content
39
+
40
+ const form = document.createElement("form")
41
+ form.method = "post"
42
+ form.action = action
43
+
44
+ const methodInput = document.createElement("input")
45
+ methodInput.type = "hidden"
46
+ methodInput.name = "_method"
47
+ methodInput.value = "patch"
48
+ form.appendChild(methodInput)
49
+
50
+ const batchInput = document.createElement("input")
51
+ batchInput.type = "hidden"
52
+ batchInput.name = "batch_action"
53
+ batchInput.value = batchAction
54
+ form.appendChild(batchInput)
55
+
56
+ if (csrfToken) {
57
+ const csrfInput = document.createElement("input")
58
+ csrfInput.type = "hidden"
59
+ csrfInput.name = "authenticity_token"
60
+ csrfInput.value = csrfToken
61
+ form.appendChild(csrfInput)
62
+ }
63
+
64
+ document.body.appendChild(form)
65
+ form.submit()
66
+ }
67
+
68
+ checkedIds() {
69
+ return this.checkboxTargets
70
+ .filter(cb => cb.checked)
71
+ .map(cb => {
72
+ const match = cb.id.match(/\d+/)
73
+ return match ? match[0] : null
74
+ })
75
+ .filter(id => id !== null)
76
+ }
77
+
78
+ showSelectAll() {
79
+ this.selectAllTargets.forEach(el => el.classList.remove("hidden"))
80
+ this.selectNoneTargets.forEach(el => el.classList.add("hidden"))
81
+ }
82
+
83
+ showSelectNone() {
84
+ this.selectAllTargets.forEach(el => el.classList.add("hidden"))
85
+ this.selectNoneTargets.forEach(el => el.classList.remove("hidden"))
86
+ }
87
+
88
+ updateActions() {
89
+ if (this.checkedIds().length > 0) {
90
+ this.actionLinkTargets.forEach(el => el.classList.remove("hidden"))
91
+ this.showSelectNone()
92
+ } else {
93
+ this.actionLinkTargets.forEach(el => el.classList.add("hidden"))
94
+ this.showSelectAll()
95
+ }
96
+ }
97
+
98
+ updateState() {
99
+ this.updateActions()
100
+ }
101
+ }
@@ -0,0 +1,24 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = { type: String }
5
+
6
+ connect() {
7
+ if (typeof $ === "undefined" || !$.fn.datetimepicker) return
8
+
9
+ const options = {}
10
+ if (this.typeValue === "date") {
11
+ options.pickTime = false
12
+ } else if (this.typeValue === "time") {
13
+ options.pickDate = false
14
+ }
15
+
16
+ $(this.element).datetimepicker(options)
17
+ }
18
+
19
+ disconnect() {
20
+ if (typeof $ === "undefined" || !$.fn.datetimepicker) return
21
+ const picker = $(this.element).data("DateTimePicker")
22
+ if (picker) picker.destroy()
23
+ }
24
+ }
@@ -0,0 +1,30 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ this.setActiveLink()
6
+ this.removeEmptyDropdowns()
7
+ }
8
+
9
+ setActiveLink() {
10
+ const pathWithSearch = window.location.pathname + window.location.search
11
+ let links = this.element.querySelectorAll(`.nav.navbar-nav a[href="${pathWithSearch}"]`)
12
+
13
+ if (links.length === 0) {
14
+ links = this.element.querySelectorAll(`.nav.navbar-nav a[href="${window.location.pathname}"]`)
15
+ }
16
+
17
+ links.forEach(link => {
18
+ link.closest("li")?.classList.add("active")
19
+ link.closest("li.dropdown")?.classList.add("active")
20
+ })
21
+ }
22
+
23
+ removeEmptyDropdowns() {
24
+ this.element.querySelectorAll(".navbar-nav .dropdown, .breadcrumb .dropdown").forEach(dropdown => {
25
+ if (dropdown.querySelectorAll("li").length === 0) {
26
+ dropdown.remove()
27
+ }
28
+ })
29
+ }
30
+ }
@@ -0,0 +1,4 @@
1
+ module Goodmin
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Goodmin
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Goodmin
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,17 @@
1
+ <div class="row">
2
+ <div class="col-md-6 col-md-offset-3">
3
+ <div class="jumbotron">
4
+ <h1>Welcome</h1>
5
+ <p>
6
+ You are running Goodmin <%= Goodmin::VERSION %>.
7
+ Add your own root route to the routes file in order to remove this page.
8
+ </p>
9
+ <p>
10
+ <a href="https://github.com/mbajur/goodmin/" class="btn btn-primary btn-lg" role="button">
11
+ <span class="glyphicon glyphicon-book"></span>
12
+ Browse the documentation
13
+ </a>
14
+ </p>
15
+ </div>
16
+ </div>
17
+ </div>
@@ -0,0 +1,19 @@
1
+ <% if field.belongs_to? %>
2
+ <div class="form-group">
3
+ <%= f.label field.select_attribute %>
4
+ <%= f.select field.select_attribute, field.collection, {}, class: "form-control" %>
5
+ <%= content_tag(:p, field.hint, class: "help-block") if field.hint.present? %>
6
+ </div>
7
+ <% elsif field.many_to_many? || field.has_many? %>
8
+ <div class="form-group">
9
+ <%= f.label field.select_attribute, field.attribute.to_s.humanize %>
10
+ <%= f.select field.select_attribute, field.collection, {}, class: "form-control", multiple: true %>
11
+ <%= content_tag(:p, field.hint, class: "help-block") if field.hint.present? %>
12
+ </div>
13
+ <% else %>
14
+ <div class="form-group">
15
+ <%= f.label field.attribute %>
16
+ <%= f.text_field field.attribute, class: "form-control" %>
17
+ <%= content_tag(:p, field.hint, class: "help-block") if field.hint.present? %>
18
+ </div>
19
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <% value = field.value %>
2
+ <% if value.respond_to?(:each) %>
3
+ <%= value.map { |v| field.display_name_for(v) }.join(", ") %>
4
+ <% else %>
5
+ <%= field.display_name_for(value) %>
6
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <% value = field.value %>
2
+ <% if value.respond_to?(:each) %>
3
+ <%= value.map { |v| field.display_name_for(v) }.join(", ") %>
4
+ <% else %>
5
+ <%= field.display_name_for(value) %>
6
+ <% end %>