cm-admin 0.3.0 → 0.4.0

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CODE_OF_CONDUCT.md +74 -0
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +21 -20
  5. data/README.md +24 -1
  6. data/app/assets/stylesheets/cm_admin/base/auth.scss +0 -12
  7. data/app/assets/stylesheets/cm_admin/base/filters.scss +8 -7
  8. data/app/assets/stylesheets/cm_admin/base/form.scss +7 -72
  9. data/app/assets/stylesheets/cm_admin/base/navbar.scss +1 -3
  10. data/app/assets/stylesheets/cm_admin/base/quicksearch.scss +7 -0
  11. data/app/assets/stylesheets/cm_admin/base/scaffold.scss +38 -3
  12. data/app/assets/stylesheets/cm_admin/base/show.scss +24 -2
  13. data/app/assets/stylesheets/cm_admin/base/table.scss +270 -253
  14. data/app/assets/stylesheets/cm_admin/components/_dropdown-popup.scss +22 -10
  15. data/app/assets/stylesheets/cm_admin/components/_range.scss +13 -2
  16. data/app/assets/stylesheets/cm_admin/components/_status-tag.scss +2 -1
  17. data/app/assets/stylesheets/cm_admin/helpers/_variable.scss +1 -0
  18. data/app/controllers/cm_admin/application_controller.rb +5 -0
  19. data/app/controllers/cm_admin/exports_controller.rb +1 -1
  20. data/app/controllers/cm_admin/static_controller.rb +12 -0
  21. data/app/helpers/cm_admin/custom_helper.rb +4 -0
  22. data/app/javascript/packs/cm_admin/application.js +14 -1
  23. data/app/javascript/packs/cm_admin/filters.js +331 -16
  24. data/app/javascript/packs/cm_admin/quick_search.js +67 -0
  25. data/app/javascript/packs/cm_admin/scaffolds.js +30 -1
  26. data/app/javascript/stylesheets/cm_admin/application.scss +3 -0
  27. data/app/views/cm_admin/main/_associated_table.html.slim +60 -0
  28. data/app/views/cm_admin/main/_cm_pagy_nav.html.slim +6 -6
  29. data/app/views/cm_admin/main/_filters.html.slim +1 -10
  30. data/app/views/cm_admin/main/_nested_fields.html.slim +7 -0
  31. data/app/views/cm_admin/main/_nested_table_form.html.slim +6 -0
  32. data/app/views/cm_admin/main/_table.html.slim +5 -4
  33. data/app/views/cm_admin/main/_tabs.html.slim +5 -0
  34. data/app/views/cm_admin/main/_top_navbar.html.slim +4 -4
  35. data/app/views/cm_admin/main/associated_index.html.slim +6 -0
  36. data/app/views/cm_admin/main/associated_show.html.slim +6 -0
  37. data/app/views/cm_admin/main/index.html.slim +11 -8
  38. data/app/views/cm_admin/main/show.html.slim +12 -13
  39. data/app/views/cm_admin/static/error_401.html.slim +4 -0
  40. data/app/views/layouts/_left_sidebar_nav.html.slim +7 -3
  41. data/app/views/layouts/_quick_links.html.slim +25 -0
  42. data/app/views/layouts/cm_admin.html.slim +24 -1
  43. data/app/views/layouts/static.html.slim +18 -0
  44. data/bin/console +0 -1
  45. data/cm_admin.gemspec +1 -0
  46. data/config/routes.rb +2 -1
  47. data/config/webpack/environment.js +3 -2
  48. data/lib/.DS_Store +0 -0
  49. data/lib/cm_admin/model.rb +57 -167
  50. data/lib/cm_admin/models/action.rb +15 -1
  51. data/lib/cm_admin/models/cm_show_section.rb +20 -0
  52. data/lib/cm_admin/models/column.rb +49 -4
  53. data/lib/cm_admin/models/controller_method.rb +75 -0
  54. data/lib/cm_admin/models/dsl_method.rb +122 -0
  55. data/lib/cm_admin/models/export.rb +16 -5
  56. data/lib/cm_admin/models/field.rb +2 -1
  57. data/lib/cm_admin/models/filter.rb +67 -2
  58. data/lib/cm_admin/models/form_field.rb +21 -0
  59. data/lib/cm_admin/models/tab.rb +13 -0
  60. data/lib/cm_admin/version.rb +1 -1
  61. data/lib/cm_admin/view_helpers/column_field_helper.rb +29 -0
  62. data/lib/cm_admin/view_helpers/field_display_helper.rb +65 -0
  63. data/lib/cm_admin/view_helpers/filter_helper.rb +190 -0
  64. data/lib/cm_admin/view_helpers/form_field_helper.rb +21 -4
  65. data/lib/cm_admin/view_helpers/form_helper.rb +22 -12
  66. data/lib/cm_admin/view_helpers/manage_column_popup_helper.rb +75 -0
  67. data/lib/cm_admin/view_helpers/navigation_helper.rb +22 -6
  68. data/lib/cm_admin/view_helpers/page_info_helper.rb +36 -0
  69. data/lib/cm_admin/view_helpers.rb +3 -1
  70. data/lib/cm_admin.rb +3 -1
  71. data/lib/generators/cm_admin/install_generator.rb +20 -8
  72. data/package.json +6 -1
  73. data/tmp/cache/webpacker/last-compilation-digest-development +1 -0
  74. data/yarn.lock +35 -5
  75. metadata +41 -7
  76. data/app/controllers/cm_admin/main_controller.rb +0 -8
  77. data/lib/c.png +0 -0
  78. data/lib/cm_admin/view_helpers/field_column_helper.rb +0 -23
  79. data/lib/generators/cm_admin/templates/cm_admin_initializer.rb +0 -4
  80. data/yarn-error.log +0 -44
@@ -0,0 +1,18 @@
1
+ doctype html
2
+ html
3
+ head
4
+ title
5
+ | Admin
6
+ meta[name="viewport" content="width=device-width,initial-scale=1"]
7
+ = csrf_meta_tags
8
+ = csp_meta_tag
9
+ = stylesheet_link_tag 'cm_admin/cm_admin', media: 'all', 'data-turbolinks-track': 'reload'
10
+ = stylesheet_pack_tag 'cm_admin/application', 'data-turbolinks-track': 'reload'
11
+ = javascript_pack_tag 'cm_admin/application', 'data-turbolinks-track': 'reload'
12
+ body
13
+ - flash.each do |type, msg|
14
+ .alert class="alert-#{type}"
15
+ = msg
16
+ .cm-admin-static
17
+ .d-flex.flex-column.h-100
18
+ = yield
data/bin/console CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "bundler/setup"
4
- require "cm_admin"
5
4
 
6
5
  # You can add fixtures and/or initialization code here to make experimenting
7
6
  # with your gem easier. You can also use a different console, if you like.
data/cm_admin.gemspec CHANGED
@@ -30,4 +30,5 @@ Gem::Specification.new do |spec|
30
30
  spec.add_runtime_dependency 'slim', '~> 4.1.0'
31
31
  spec.add_runtime_dependency 'webpacker', '~> 5.2.1'
32
32
  spec.add_runtime_dependency 'axlsx_rails', '~> 0.6.1'
33
+ spec.add_runtime_dependency 'cocoon', '~> 1.2.15'
33
34
  end
data/config/routes.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  CmAdmin::Engine.routes.draw do
2
- controller 'main' do
2
+ controller 'static' do
3
3
  get '/', action: 'dashboard'
4
+ get '/access-denied', action: 'error_403'
4
5
  end
5
6
 
6
7
  controller 'exports' do
@@ -3,8 +3,9 @@ const { environment } = require('@rails/webpacker')
3
3
  const webpack = require('webpack')
4
4
  environment.plugins.prepend('Provide',
5
5
  new webpack.ProvidePlugin({
6
- $: 'jquery/src/jquery',
7
- jQuery: 'jquery/src/jquery',
6
+ $: 'jquery',
7
+ jQuery: 'jquery',
8
+ moment: 'moment',
8
9
  Popper: ['popper.js', 'default']
9
10
  })
10
11
  )
data/lib/.DS_Store ADDED
Binary file
@@ -1,19 +1,26 @@
1
1
  require_relative 'constants'
2
2
  require_relative 'models/action'
3
3
  require_relative 'models/field'
4
+ require_relative 'models/form_field'
4
5
  require_relative 'models/blocks'
5
6
  require_relative 'models/column'
6
7
  require_relative 'models/filter'
7
8
  require_relative 'models/export'
9
+ require_relative 'models/cm_show_section'
10
+ require_relative 'models/tab'
11
+ require_relative 'models/dsl_method'
12
+ require_relative 'models/controller_method'
8
13
  require 'pagy'
9
14
  require 'axlsx'
10
-
15
+ require 'cocoon'
11
16
 
12
17
  module CmAdmin
13
18
  class Model
14
19
  include Pagy::Backend
15
20
  include Models::Blocks
16
- attr_accessor :available_actions, :actions_set, :available_fields, :permitted_fields, :current_action, :params, :filters
21
+ include Models::DslMethod
22
+ include Models::ControllerMethod
23
+ attr_accessor :available_actions, :actions_set, :available_fields, :permitted_fields, :current_action, :params, :filters, :available_tabs
17
24
  attr_reader :name, :ar_model
18
25
 
19
26
  # Class variable for storing all actions
@@ -25,7 +32,8 @@ module CmAdmin
25
32
  @ar_model = entity
26
33
  @available_actions ||= []
27
34
  @current_action = nil
28
- @available_fields ||= {index: [], show: [], edit: [], new: []}
35
+ @available_tabs ||= []
36
+ @available_fields ||= {index: [], show: [], edit: {fields: []}, new: {fields: []}}
29
37
  @params = nil
30
38
  @filters ||= []
31
39
  instance_eval(&block) if block_given?
@@ -39,6 +47,29 @@ module CmAdmin
39
47
  def all_actions
40
48
  @all_actions || []
41
49
  end
50
+
51
+ def find_by(search_hash)
52
+ CmAdmin.cm_admin_models.find { |x| x.name == search_hash[:name] }
53
+ end
54
+ end
55
+
56
+ def custom_controller_action(action_name, params)
57
+ current_action = CmAdmin::Models::Action.find_by(self, name: action_name.to_s)
58
+ if current_action
59
+ @current_action = current_action
60
+ @ar_object = @ar_model.find(params[:id])
61
+ if @current_action.child_records
62
+ child_records = @ar_object.send(@current_action.child_records)
63
+ @associated_model = CmAdmin::Model.find_by(name: @ar_model.reflect_on_association(@current_action.child_records).klass.name)
64
+ if child_records.is_a? ActiveRecord::Relation
65
+ @associated_ar_object = filter_by(params, child_records)
66
+ else
67
+ @associated_ar_object = child_records
68
+ end
69
+ return @ar_object, @associated_model, @associated_ar_object
70
+ end
71
+ return @ar_object
72
+ end
42
73
  end
43
74
 
44
75
  # Insert into actions according to config block
@@ -53,166 +84,7 @@ module CmAdmin
53
84
  @actions_set = true
54
85
  end
55
86
 
56
- def cm_show(&block)
57
- @current_action = CmAdmin::Models::Action.find_by(self, name: 'show')
58
- puts "Top of the line"
59
- yield
60
- # action.instance_eval(&block)
61
- puts "End of the line"
62
- end
63
-
64
- def cm_index(&block)
65
- @current_action = CmAdmin::Models::Action.find_by(self, name: 'index')
66
- yield
67
- # action.instance_eval(&block)
68
- end
69
-
70
- def cm_edit(&block)
71
- action = CmAdmin::Models::Action.find_by(self, name: 'edit')
72
- action.instance_eval(&block)
73
- end
74
-
75
- def show(params)
76
- @current_action = CmAdmin::Models::Action.find_by(self, name: 'show')
77
- @ar_object = @ar_model.find(params[:id])
78
- end
79
-
80
- def index(params)
81
- @current_action = CmAdmin::Models::Action.find_by(self, name: 'index')
82
- # Based on the params the filter and pagination object to be set
83
- @ar_object = filter_by(params, filter_params=filter_params(params))
84
- end
85
-
86
- def filter_by(params, filter_params={}, sort_params={})
87
- filtered_result = OpenStruct.new
88
- sort_column = "users.created_at"
89
- sort_direction = %w[asc desc].include?(sort_params[:sort_direction]) ? sort_params[:sort_direction] : "asc"
90
- sort_params = {sort_column: sort_column, sort_direction: sort_direction}
91
- final_data = filtered_data(filter_params)
92
- pagy, records = pagy(final_data)
93
- filtered_result.data = records
94
- filtered_result.pagy = pagy
95
- # filtered_result.facets = paginate(page, raw_data.size)
96
- # filtered_result.sort = sort_params
97
- # filtered_result.facets.sort = sort_params
98
- return filtered_result
99
- end
100
-
101
- def filtered_data(filter_params)
102
- records = self.name.constantize.where(nil)
103
- if filter_params
104
- filter_params.each do |scope, scope_value|
105
- records = self.send("cm_#{scope}", scope_value, records)
106
- end
107
- end
108
- records
109
- end
110
-
111
- def cm_search(scope_value, records)
112
- return nil if scope_value.blank?
113
- table_name = records.table_name
114
-
115
- @filters.select{|x| x if x.filter_type.eql?(:search)}.each do |filter|
116
- terms = scope_value.downcase.split(/\s+/)
117
- terms = terms.map { |e|
118
- (e.gsub('*', '%').prepend('%') + '%').gsub(/%+/, '%')
119
- }
120
- sql = ""
121
- filter.db_column_name.each.with_index do |column, i|
122
- sql.concat("#{table_name}.#{column} ILIKE ?")
123
- sql.concat(' OR ') unless filter.db_column_name.size.eql?(i+1)
124
- end
125
-
126
- records = records.where(
127
- terms.map { |term|
128
- sql
129
- }.join(' AND '),
130
- *terms.map { |e| [e] * filter.db_column_name.size }.flatten
131
- )
132
- end
133
- records
134
- end
135
-
136
- def new(params)
137
- @ar_object = @ar_model.new
138
- end
139
-
140
- def edit(params)
141
- @ar_object = @ar_model.find(params[:id])
142
- end
143
-
144
- def update(params)
145
- @ar_object = @ar_model.find(params[:id])
146
- @ar_object.update(resource_params(params))
147
- end
148
-
149
- def create(params)
150
- @ar_object = @ar_model.new(resource_params(params))
151
- end
152
-
153
- def resource_params(params)
154
- permittable_fields = @permitted_fields || @ar_model.columns.map(&:name).reject { |i| CmAdmin::REJECTABLE_FIELDS.include?(i) }.map(&:to_sym)
155
- params.require(self.name.underscore.to_sym).permit(*permittable_fields)
156
- end
157
-
158
- def page_title(title)
159
- if @current_action
160
- @current_action.page_title = title
161
- end
162
- end
163
-
164
- def page_description(description)
165
- if @current_action
166
- @current_action.page_description = description
167
- end
168
- end
169
-
170
- def field(field_name, options={})
171
- puts "For printing field #{field_name}"
172
- @available_fields[:show] << CmAdmin::Models::Field.new(field_name, options)
173
- end
174
87
 
175
- def column(field_name, options={})
176
- unless @available_fields[:index].map{|x| x.db_column_name.to_sym}.include?(field_name)
177
- puts "For printing column #{field_name}"
178
- @available_fields[:index] << CmAdmin::Models::Column.new(field_name, options)
179
- end
180
- end
181
-
182
- def all_db_columns(options={})
183
- field_names = self.instance_variable_get(:@ar_model)&.columns&.map{|x| x.name.to_sym}
184
- if options.include?(:exclude) && field_names
185
- excluded_fields = (Array.new << options[:exclude]).flatten.map(&:to_sym)
186
- field_names -= excluded_fields
187
- end
188
- field_names.each do |field_name|
189
- column field_name
190
- end
191
- end
192
-
193
- def self.find_by(search_hash)
194
- CmAdmin.cm_admin_models.find { |x| x.name == search_hash[:name] }
195
- end
196
-
197
- # Custom actions
198
- # eg
199
- # class User < ApplicationRecord
200
- # cm_admin do
201
- # custom_action name: 'submit', verb: 'post', path: ':id/submit' do
202
- # def user_submit
203
- # Code for action here...
204
- # end
205
- # end
206
- # end
207
- # end
208
- def custom_action(name: nil, verb: nil, layout: nil, partial: nil, path: nil, &block)
209
- @available_actions << CmAdmin::Models::Action.new(name: name, verb: verb, layout: layout, partial: partial, path: path)
210
- self.class.class_eval(&block)
211
- end
212
-
213
- def filter(db_column_name, filter_type, options={})
214
- @filters << CmAdmin::Models::Filter.new(db_column_name: db_column_name, filter_type: filter_type, options: options)
215
- end
216
88
  private
217
89
 
218
90
  # Controller defined for each model
@@ -227,7 +99,19 @@ module CmAdmin
227
99
  @model = CmAdmin::Model.find_by(name: controller_name.classify)
228
100
  @model.params = params
229
101
  @action = CmAdmin::Models::Action.find_by(@model, name: action_name)
230
- @ar_object = @model.send(action_name, params)
102
+ @ar_object = @model.try(action_name, params)
103
+ @ar_object, @associated_model, @associated_ar_object = @model.custom_controller_action(action_name, params.permit!) if !@ar_object.present? && params[:id].present?
104
+ nested_tables = @model.available_fields[:new].except(:fields).keys
105
+ nested_tables += @model.available_fields[:edit].except(:fields).keys
106
+ @reflections = @model.ar_model.reflect_on_all_associations
107
+ nested_tables.each do |table_name|
108
+ reflection = @reflections.select {|x| x if x.name == table_name}.first
109
+ if reflection.macro == :has_many
110
+ @ar_object.send(table_name).build if action_name == "new" || action_name == "edit"
111
+ else
112
+ @ar_object.send(('build_' + table_name.to_s).to_sym) if action_name == "new"
113
+ end
114
+ end
231
115
  respond_to do |format|
232
116
  if %w(show index new edit).include?(action_name)
233
117
  if request.xhr? && action_name.eql?('index')
@@ -242,10 +126,10 @@ module CmAdmin
242
126
  format.html { render '/cm_admin/main/new' }
243
127
  end
244
128
  elsif action.layout.present?
245
- if action.partial.present?
246
- render partial: action.partial, layout: action.layout
129
+ if request.xhr? && action.partial.present?
130
+ format.html { render partial: action.partial }
247
131
  else
248
- render layout: action.layout
132
+ format.html { render action.layout }
249
133
  end
250
134
  end
251
135
  end
@@ -256,7 +140,13 @@ module CmAdmin
256
140
  end
257
141
 
258
142
  def filter_params(params)
259
- params.require(:filters).permit(:search) if params[:filters]
143
+ # OPTIMIZE: Need to check if we can permit the filter_params in a better way
144
+ date_columns = @filters.select{|x| x.filter_type.eql?(:date)}.map(&:db_column_name)
145
+ range_columns = @filters.select{|x| x.filter_type.eql?(:range)}.map(&:db_column_name)
146
+ single_select_columns = @filters.select{|x| x.filter_type.eql?(:single_select)}.map(&:db_column_name)
147
+ multi_select_columns = @filters.select{|x| x.filter_type.eql?(:multi_select)}.map{|x| Hash["#{x.db_column_name}", []]}
148
+
149
+ params.require(:filters).permit(:search, date: date_columns, range: range_columns, single_select: single_select_columns, multi_select: multi_select_columns) if params[:filters]
260
150
  end
261
151
  end
262
152
  end
@@ -4,14 +4,28 @@ module CmAdmin
4
4
  module Models
5
5
  class Action
6
6
  include Actions::Blocks
7
- attr_accessor :name, :verb, :layout, :partial, :path, :page_title, :page_description
7
+ attr_accessor :name, :verb, :layout_type, :layout, :partial, :path, :page_title, :page_description, :child_records, :is_nested_field, :nested_table_name
8
8
 
9
9
  def initialize(attributes = {})
10
+ if attributes[:layout_type].present? && attributes[:layout].nil? && attributes[:partial].nil?
11
+ case attributes[:layout_type]
12
+ when 'cm_association_index'
13
+ attributes[:layout] = '/cm_admin/main/associated_index'
14
+ attributes[:partial] = '/cm_admin/main/associated_table'
15
+ when 'cm_association_show'
16
+ attributes[:layout] = '/cm_admin/main/associated_show'
17
+ end
18
+ end
19
+ set_default_values
10
20
  attributes.each do |key, value|
11
21
  self.send("#{key.to_s}=", value)
12
22
  end
13
23
  end
14
24
 
25
+ def set_default_values
26
+ self.is_nested_field = false
27
+ end
28
+
15
29
  class << self
16
30
  def find_by(model, search_hash)
17
31
  model.available_actions.find { |i| i.name == search_hash[:name] }
@@ -0,0 +1,20 @@
1
+ module CmAdmin
2
+ module Models
3
+ class CmShowSection
4
+
5
+ attr_accessor :section_name, :available_section_fields
6
+
7
+ def initialize(section_name, &block)
8
+ @available_section_fields = []
9
+ @section_name = section_name
10
+ puts "-- reached here --"
11
+ instance_eval(&block)
12
+ end
13
+
14
+ def field(field_name, options={})
15
+ puts "For printing field #{field_name}"
16
+ @available_section_fields << CmAdmin::Models::Field.new(field_name, options)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,14 +1,59 @@
1
1
  module CmAdmin
2
2
  module Models
3
3
  class Column
4
- attr_accessor :db_column_name, :column_type, :header, :format, :prefix, :exportable
4
+ attr_accessor :field_name, :field_type, :header, :format, :prefix, :suffix, :exportable, :round,
5
+ :cm_css_class, :link, :url, :custom_method, :helper_method, :managable, :lockable
5
6
 
6
- def initialize(db_column_name, attributes = {})
7
- @db_column_name = db_column_name
7
+ def initialize(field_name, attributes = {})
8
+ @field_name = field_name
9
+ set_default_values
8
10
  attributes.each do |key, value|
9
11
  self.send("#{key.to_s}=", value)
10
12
  end
11
- @exportable = true if attributes[:exportable].nil?
13
+
14
+ #formatting header (either field_name or value present in header attribute)
15
+ self.send("header=", format_header)
16
+ end
17
+
18
+ #returns a string value as a header (either field_name or value present in header attribute)
19
+ def format_header
20
+ self.header.present? ? self.header.to_s.gsub(/_/, ' ')&.upcase : self.field_name.to_s.gsub(/_/, ' ').upcase
21
+ end
22
+
23
+ def set_default_values
24
+ self.exportable = true
25
+ self.managable = true
26
+ self.lockable = false
27
+ end
28
+
29
+ #formatting value for different data types
30
+ def self.format_data_type(column, value)
31
+ case column.column_type
32
+ when :string
33
+ if column.format.present?
34
+ column.format = [column.format] if column.format.is_a? String
35
+ column.format.each do |formatter|
36
+ value = value.send(formatter)
37
+ end
38
+ end
39
+ when :datetime
40
+ format_value = column.format.present? ? column.format.to_s : '%d/%m/%Y'
41
+ value = value.strftime(format_value)
42
+ when :enum
43
+ value = value.titleize
44
+ when :decimal
45
+ round_to = column.round.present? ? column.round.to_i : 2
46
+ value = value.round(round_to)
47
+ when :custom
48
+
49
+ end
50
+ return value
51
+ end
52
+
53
+ class << self
54
+ def find_by(model, search_hash)
55
+ model.available_fields.find { |i| i.name == search_hash[:name] }
56
+ end
12
57
  end
13
58
 
14
59
  end