motor-admin 0.1.46 → 0.1.51

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +129 -9
  4. data/app/controllers/concerns/motor/current_user_method.rb +15 -0
  5. data/app/controllers/motor/api_base_controller.rb +11 -17
  6. data/app/controllers/motor/application_controller.rb +1 -0
  7. data/app/controllers/motor/assets_controller.rb +2 -2
  8. data/app/controllers/motor/icons_controller.rb +1 -1
  9. data/app/controllers/motor/ui_controller.rb +12 -4
  10. data/app/mailers/motor/alerts_mailer.rb +5 -5
  11. data/app/models/motor/alert.rb +1 -0
  12. data/app/models/motor/dashboard.rb +1 -0
  13. data/app/models/motor/form.rb +1 -0
  14. data/app/models/motor/query.rb +1 -0
  15. data/app/models/motor/resource.rb +1 -0
  16. data/app/models/motor/tag.rb +1 -1
  17. data/app/views/motor/alerts_mailer/alert_email.html.erb +1 -1
  18. data/app/views/motor/ui/show.html.erb +1 -1
  19. data/config/routes.rb +8 -9
  20. data/lib/generators/motor/templates/install.rb +74 -65
  21. data/lib/motor.rb +0 -1
  22. data/lib/motor/active_record_utils.rb +13 -1
  23. data/lib/motor/admin.rb +1 -1
  24. data/lib/motor/alerts/persistance.rb +4 -4
  25. data/lib/motor/alerts/scheduled_alerts_cache.rb +2 -1
  26. data/lib/motor/alerts/scheduler.rb +1 -1
  27. data/lib/motor/api_query/apply_scope.rb +1 -1
  28. data/lib/motor/api_query/build_json.rb +6 -6
  29. data/lib/motor/api_query/paginate.rb +1 -0
  30. data/lib/motor/api_query/search.rb +1 -7
  31. data/lib/motor/assets.rb +1 -1
  32. data/lib/motor/build_schema/find_display_column.rb +1 -0
  33. data/lib/motor/build_schema/find_icon.rb +9 -7
  34. data/lib/motor/build_schema/load_from_rails.rb +5 -2
  35. data/lib/motor/configs/build_ui_app_tag.rb +8 -7
  36. data/lib/motor/configs/load_from_cache.rb +19 -14
  37. data/lib/motor/configs/sync_from_hash.rb +1 -1
  38. data/lib/motor/configs/sync_with_remote.rb +1 -1
  39. data/lib/motor/configs/write_to_file.rb +1 -0
  40. data/lib/motor/dashboards/persistance.rb +4 -4
  41. data/lib/motor/forms/persistance.rb +4 -4
  42. data/lib/motor/queries/persistance.rb +4 -4
  43. data/lib/motor/queries/run_query.rb +3 -3
  44. data/lib/motor/tags.rb +2 -2
  45. data/lib/motor/tasks/motor.rake +4 -0
  46. data/lib/motor/version.rb +1 -1
  47. data/ui/dist/{main-c0cfd92d021794d8cf13.css.gz → main-36a9ee6a9b0427a77d87.css.gz} +0 -0
  48. data/ui/dist/main-36a9ee6a9b0427a77d87.js.gz +0 -0
  49. data/ui/dist/manifest.json +5 -5
  50. metadata +5 -4
  51. data/ui/dist/main-c0cfd92d021794d8cf13.js.gz +0 -0
data/lib/motor.rb CHANGED
@@ -40,7 +40,6 @@ module Motor
40
40
  (defined?(::Puma) && File.basename($PROGRAM_NAME) == 'puma') ||
41
41
  defined?(::Unicorn::HttpServer) ||
42
42
  defined?(::Mongrel::HttpServer) ||
43
- defined?(::WEBrick::VERSION) ||
44
43
  defined?(JRuby::Rack::VERSION) ||
45
44
  defined?(::Trinidad::Server)
46
45
  end
@@ -1,6 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActiveRecordUtils
3
+ module Motor
4
+ module ActiveRecordUtils
5
+ module_function
6
+
7
+ def reset_id_sequence!(model)
8
+ case ActiveRecord::Base.connection.class.name
9
+ when 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
10
+ ActiveRecord::Base.connection.reset_pk_sequence!(model.table_name)
11
+ else
12
+ ActiveRecord::Base.connection.reset_sequence!(model.table_name, 'id')
13
+ end
14
+ end
15
+ end
4
16
  end
5
17
 
6
18
  require_relative './active_record_utils/types'
data/lib/motor/admin.rb CHANGED
@@ -43,7 +43,7 @@ module Motor
43
43
  end
44
44
 
45
45
  initializer 'motor.alerts.scheduler' do
46
- config.after_initialize do |_app|
46
+ config.after_initialize do
47
47
  next unless Motor.server?
48
48
 
49
49
  Motor::Alerts::Scheduler::SCHEDULER_TASK.execute
@@ -28,7 +28,7 @@ module Motor
28
28
  end
29
29
 
30
30
  def create_from_params!(params, current_user = nil)
31
- raise NameAlreadyExists if Alert.exists?(['lower(name) = ?', params[:name].to_s.downcase])
31
+ raise NameAlreadyExists if Alert.exists?(name: params[:name])
32
32
 
33
33
  alert = build_from_params(params, current_user)
34
34
 
@@ -77,7 +77,7 @@ module Motor
77
77
  end
78
78
 
79
79
  def archive_with_existing_name(alert)
80
- Motor::Alert.where(['lower(name) = ? AND id != ?', alert.name.to_s.downcase, alert.id])
80
+ Motor::Alert.where(['name = ? AND id != ?', alert.name, alert.id])
81
81
  .update_all(deleted_at: Time.current)
82
82
  end
83
83
 
@@ -87,9 +87,9 @@ module Motor
87
87
 
88
88
  def name_already_exists?(alert)
89
89
  if alert.new_record?
90
- Alert.exists?(['lower(name) = ?', alert.name.to_s.downcase])
90
+ Alert.exists?(name: alert.name)
91
91
  else
92
- Alert.exists?(['lower(name) = ? AND id != ?', alert.name.to_s.downcase, alert.id])
92
+ Alert.exists?(['name = ? AND id != ?', alert.name, alert.id])
93
93
  end
94
94
  end
95
95
  end
@@ -3,7 +3,8 @@
3
3
  module Motor
4
4
  module Alerts
5
5
  module ScheduledAlertsCache
6
- CACHE_STORE = ActiveSupport::Cache::MemoryStore.new(size: 5.megabytes)
6
+ CACHE_STORE = ActiveSupport::Cache::MemoryStore.new(size: 5.megabytes,
7
+ coder: ActiveSupport::Cache::NullCoder)
7
8
 
8
9
  module_function
9
10
 
@@ -3,7 +3,7 @@
3
3
  module Motor
4
4
  module Alerts
5
5
  module Scheduler
6
- SCHEDULER_INTERVAL = 10.seconds
6
+ SCHEDULER_INTERVAL = 1.minute
7
7
  CHECK_BEHIND_DURATION = 15.minutes
8
8
 
9
9
  SCHEDULER_TASK = Concurrent::TimerTask.new(
@@ -18,7 +18,7 @@ module Motor
18
18
  end
19
19
 
20
20
  def apply_filter_scope(rel, scope)
21
- configs = Motor::Resource.find_by_name(rel.klass.name.underscore)
21
+ configs = Motor::Resource.find_by(name: rel.klass.name.underscore)
22
22
 
23
23
  return rel unless configs
24
24
 
@@ -10,12 +10,12 @@ module Motor
10
10
 
11
11
  rel = rel.preload_associations_lazily if rel.is_a?(ActiveRecord::Relation)
12
12
 
13
- json_params = {}
13
+ json_params = {}.with_indifferent_access
14
14
 
15
15
  assign_include_params(json_params, rel, params)
16
16
  assign_fields_params(json_params, rel, params)
17
17
 
18
- rel.as_json(json_params.with_indifferent_access)
18
+ rel.as_json(json_params)
19
19
  end
20
20
 
21
21
  def assign_include_params(json_params, _rel, api_params)
@@ -51,11 +51,11 @@ module Motor
51
51
  params[:fields].each do |key, fields|
52
52
  fields = fields.split(',') if fields.is_a?(String)
53
53
 
54
- merge_fields_params!(key, fields, json_params, model)
54
+ merge_fields_params!(json_params, key, fields, model)
55
55
  end
56
56
  end
57
57
 
58
- def merge_fields_params!(key, fields, json_params, model)
58
+ def merge_fields_params!(json_params, key, fields, model)
59
59
  model_name = model.name.underscore
60
60
 
61
61
  if key == model_name || model_name.split('/').last == key
@@ -96,8 +96,8 @@ module Motor
96
96
  def normalize_include_params(params)
97
97
  case params
98
98
  when Array
99
- params.each_with_object({}) do |name, hash|
100
- hash[name] = { 'include' => {} }
99
+ params.index_with do |_|
100
+ { 'include' => {} }
101
101
  end
102
102
  when String
103
103
  { params => { 'include' => {} } }
@@ -11,6 +11,7 @@ module Motor
11
11
  params ||= {}
12
12
 
13
13
  rel = rel.limit([MAX_PER_PAGE, (params[:limit] || MAX_PER_PAGE).to_i].min)
14
+
14
15
  rel.offset(params[:offset].to_i)
15
16
  end
16
17
  end
@@ -57,13 +57,7 @@ module Motor
57
57
  def find_searchable_columns(model)
58
58
  model.columns.map do |column|
59
59
  next unless column.type.in?(COLUMN_TYPES)
60
-
61
- has_inclusion_validator =
62
- model.validators_on(column.name).any? do |e|
63
- e.is_a?(ActiveModel::Validations::InclusionValidator)
64
- end
65
-
66
- next if has_inclusion_validator
60
+ next if model.validators_on(column.name).any?(ActiveModel::Validations::InclusionValidator)
67
61
 
68
62
  column.name
69
63
  end.compact
data/lib/motor/assets.rb CHANGED
@@ -16,7 +16,7 @@ module Motor
16
16
 
17
17
  def icons
18
18
  manifest.select do |k, v|
19
- !k.ends_with?('.gz') && v.starts_with?('icons/') && !v.include?('DS_Store')
19
+ !k.ends_with?('.gz') && v.starts_with?('icons/') && v.exclude?('DS_Store')
20
20
  end.keys
21
21
  end
22
22
 
@@ -31,6 +31,7 @@ module Motor
31
31
  manufacturer
32
32
  model
33
33
  address
34
+ code
34
35
  ]
35
36
  ).freeze
36
37
 
@@ -3,10 +3,17 @@
3
3
  module Motor
4
4
  module FindIcon
5
5
  ICONS_MAP = {
6
+ 'audit' => 'history',
7
+ 'block' => 'ban',
8
+ 'blocked' => 'ban',
9
+ 'blacklisted' => 'ban',
10
+ 'blocklisted' => 'ban',
11
+ 'banned' => 'ban',
6
12
  'website' => 'world',
7
13
  'location' => 'gps',
8
14
  'photo' => 'photo',
9
15
  'image' => 'photo',
16
+ 'screenshot' => 'photo',
10
17
  'picture' => 'photo',
11
18
  'video' => 'video',
12
19
  'file' => 'file',
@@ -28,7 +35,6 @@ module Motor
28
35
  'token' => 'key',
29
36
  'secret' => 'lock',
30
37
  'automation' => 'manual-gearbox',
31
- 'email' => 'mail',
32
38
  'relationship' => 'hierarchy',
33
39
  'person' => 'user',
34
40
  'people' => 'users',
@@ -43,7 +49,6 @@ module Motor
43
49
  'rule' => 'manual-gearbox',
44
50
  'tracking' => 'zoom-question',
45
51
  'github' => 'brand-github',
46
- 'block' => 'ban',
47
52
  'tag' => 'hash',
48
53
  'category' => 'hash',
49
54
  'label' => 'hash',
@@ -60,15 +65,11 @@ module Motor
60
65
  'model' => 'hash',
61
66
  'taxon' => 'hash',
62
67
  'affiliate' => 'affiliate',
63
- 'blocked' => 'ban',
64
- 'blacklisted' => 'ban',
65
- 'blocklisted' => 'ban',
66
68
  'chat' => 'message-circle',
67
69
  'message' => 'messages',
68
70
  'poll' => 'messages',
69
71
  'feedpack' => 'messages',
70
72
  'attachment' => 'paperclip',
71
- 'banned' => 'ban',
72
73
  'certificate' => 'certificate',
73
74
  'approval' => 'certificate',
74
75
  'bank' => 'building-bank',
@@ -105,7 +106,8 @@ module Motor
105
106
  'subscriber' => 'user-plus',
106
107
  'product' => 'building-store',
107
108
  'html' => 'code',
108
- 'stripe' => 'brand-stripe'
109
+ 'stripe' => 'brand-stripe',
110
+ 'email' => 'mail'
109
111
  }.freeze
110
112
 
111
113
  DEFAULT_ICON = 'database'
@@ -12,6 +12,8 @@ module Motor
12
12
  models.map do |model|
13
13
  Object.const_get(model.name)
14
14
 
15
+ next unless ActiveRecord::Base.connection.table_exists?(model.table_name)
16
+
15
17
  schema = build_model_schema(model)
16
18
 
17
19
  if model.respond_to?(:devise_modules)
@@ -20,7 +22,7 @@ module Motor
20
22
 
21
23
  schema
22
24
  rescue StandardError, NotImplementedError => e
23
- Rails.logger.error(e) if model.name != 'Audited::Audit'
25
+ Rails.logger.error(e)
24
26
 
25
27
  next
26
28
  end.compact
@@ -34,6 +36,7 @@ module Motor
34
36
  models -= Motor::ApplicationRecord.descendants
35
37
  models -= [Motor::Audit]
36
38
  models -= [ActiveRecord::SchemaMigration] if defined?(ActiveRecord::SchemaMigration)
39
+ models -= [ActiveRecord::InternalMetadata] if defined?(ActiveRecord::InternalMetadata)
37
40
  models -= [ActiveStorage::Blob] if defined?(ActiveStorage::Blob)
38
41
  models -= [ActiveStorage::VariantRecord] if defined?(ActiveStorage::VariantRecord)
39
42
 
@@ -101,7 +104,7 @@ module Motor
101
104
  name: column.name,
102
105
  display_name: Utils.humanize_column_name(column.name),
103
106
  column_type: is_enum ? 'string' : UNIFIED_TYPES[column.type.to_s] || column.type.to_s,
104
- is_array: column.array?,
107
+ is_array: column.respond_to?(:array?) && column.array?,
105
108
  access_type: COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE),
106
109
  default_value: default_attrs[column.name],
107
110
  validators: fetch_validators(model, column.name),
@@ -7,26 +7,27 @@ module Motor
7
7
  if Motor.development?
8
8
  ActiveSupport::Cache::NullStore.new
9
9
  else
10
- ActiveSupport::Cache::MemoryStore.new(size: 5.megabytes)
10
+ ActiveSupport::Cache::MemoryStore.new(size: 5.megabytes,
11
+ coder: ActiveSupport::Cache::NullCoder)
11
12
  end
12
13
 
13
14
  module_function
14
15
 
15
- def call
16
+ def call(current_user = nil)
16
17
  cache_keys = LoadFromCache.load_cache_keys
17
18
 
18
- CACHE_STORE.fetch(cache_keys.hash) do
19
+ CACHE_STORE.fetch("#{cache_keys.hash}#{current_user&.id}") do
19
20
  CACHE_STORE.clear
20
21
 
21
- Motor::ApplicationController.helpers.content_tag(
22
- :div, '', id: 'app', data: build_data(cache_keys)
23
- )
22
+ Motor::ApplicationController.helpers.tag.div('', id: 'app', data: build_data(cache_keys, current_user))
24
23
  end
25
24
  end
26
25
 
27
26
  # @return [Hash]
28
- def build_data(cache_keys = {})
27
+ def build_data(cache_keys = {}, current_user = nil)
29
28
  {
29
+ current_user: current_user&.as_json(only: %i[id email]),
30
+ audits_count: Motor::Audit.count,
30
31
  base_path: Motor::Admin.routes.url_helpers.motor_path,
31
32
  schema: Motor::BuildSchema.call(cache_keys),
32
33
  header_links: header_links_data_hash(cache_keys[:configs]),
@@ -3,7 +3,8 @@
3
3
  module Motor
4
4
  module Configs
5
5
  module LoadFromCache
6
- CACHE_STORE = ActiveSupport::Cache::MemoryStore.new(size: 10.megabytes)
6
+ CACHE_STORE = ActiveSupport::Cache::MemoryStore.new(size: 10.megabytes,
7
+ coder: ActiveSupport::Cache::NullCoder)
7
8
 
8
9
  module_function
9
10
 
@@ -57,24 +58,28 @@ module Motor
57
58
  end
58
59
 
59
60
  def maybe_fetch_from_cache(type, cache_key, &block)
60
- return block.call unless cache_key
61
+ return yield unless cache_key
61
62
 
62
63
  CACHE_STORE.fetch(type + cache_key.to_s, &block)
63
64
  end
64
65
 
65
66
  def load_cache_keys
66
- ActiveRecord::Base.connection.execute(
67
- "(#{
68
- [
69
- Motor::Config.select("'configs', MAX(updated_at)").to_sql,
70
- Motor::Resource.select("'resources', MAX(updated_at)").to_sql,
71
- Motor::Dashboard.select("'dashboards', MAX(updated_at)").to_sql,
72
- Motor::Alert.select("'alerts', MAX(updated_at)").to_sql,
73
- Motor::Query.select("'queries', MAX(updated_at)").to_sql,
74
- Motor::Form.select("'forms', MAX(updated_at)").to_sql
75
- ].join(') UNION (')
76
- })"
77
- ).to_a.map(&:values).to_h.with_indifferent_access
67
+ result = ActiveRecord::Base.connection.execute(cache_keys_sql).to_a
68
+
69
+ result = result.map(&:values) if result.first.is_a?(Hash)
70
+
71
+ result.to_h.with_indifferent_access
72
+ end
73
+
74
+ def cache_keys_sql
75
+ [
76
+ Motor::Config.select("'configs', MAX(updated_at)").to_sql,
77
+ Motor::Resource.select("'resources', MAX(updated_at)").to_sql,
78
+ Motor::Dashboard.select("'dashboards', MAX(updated_at)").to_sql,
79
+ Motor::Alert.select("'alerts', MAX(updated_at)").to_sql,
80
+ Motor::Query.select("'queries', MAX(updated_at)").to_sql,
81
+ Motor::Form.select("'forms', MAX(updated_at)").to_sql
82
+ ].join(' UNION ')
78
83
  end
79
84
  end
80
85
  end
@@ -87,7 +87,7 @@ module Motor
87
87
 
88
88
  archive_taggable_items(records - processed_records, configs_timestamp)
89
89
 
90
- ActiveRecord::Base.connection.reset_pk_sequence!(records.klass.table_name)
90
+ ActiveRecordUtils.reset_id_sequence!(records.klass)
91
91
  end
92
92
 
93
93
  def update_taggable_items(records, config_items, update_proc)
@@ -9,7 +9,7 @@ module Motor
9
9
  module_function
10
10
 
11
11
  def call(remote_url, api_key)
12
- url = remote_url.sub(%r{/\z}, '') + Motor::Configs::SYNC_API_PATH
12
+ url = remote_url.delete_suffix('/') + Motor::Configs::SYNC_API_PATH
13
13
 
14
14
  sync_from_remote!(url, api_key)
15
15
  sync_to_remote!(url, api_key)
@@ -9,6 +9,7 @@ module Motor
9
9
  module_function
10
10
 
11
11
  def call
12
+ return if Rails.env.test?
12
13
  return if THREAD_POOL.queue_length.positive?
13
14
 
14
15
  THREAD_POOL.post do
@@ -16,7 +16,7 @@ module Motor
16
16
  end
17
17
 
18
18
  def create_from_params!(params, current_user = nil)
19
- raise TitleAlreadyExists if Dashboard.exists?(['lower(title) = ?', params[:title].to_s.downcase])
19
+ raise TitleAlreadyExists if Dashboard.exists?(title: params[:title])
20
20
 
21
21
  dashboard = build_from_params(params, current_user)
22
22
 
@@ -57,15 +57,15 @@ module Motor
57
57
  end
58
58
 
59
59
  def archive_with_existing_name(dashboard)
60
- Motor::Dashboard.where(['lower(title) = ? AND id != ?', dashboard.title.to_s.downcase, dashboard.id])
60
+ Motor::Dashboard.where(['title = ? AND id != ?', dashboard.title, dashboard.id])
61
61
  .update_all(deleted_at: Time.current)
62
62
  end
63
63
 
64
64
  def title_already_exists?(dashboard)
65
65
  if dashboard.new_record?
66
- Motor::Dashboard.exists?(['lower(title) = ?', dashboard.title.to_s.downcase])
66
+ Motor::Dashboard.exists?(title: dashboard.title)
67
67
  else
68
- Motor::Dashboard.exists?(['lower(title) = ? AND id != ?', dashboard.title.to_s.downcase, dashboard.id])
68
+ Motor::Dashboard.exists?(['title = ? AND id != ?', dashboard.title, dashboard.id])
69
69
  end
70
70
  end
71
71
  end
@@ -16,7 +16,7 @@ module Motor
16
16
  end
17
17
 
18
18
  def create_from_params!(params, current_user = nil)
19
- raise NameAlreadyExists if Form.exists?(['lower(name) = ?', params[:name].to_s.downcase])
19
+ raise NameAlreadyExists if Form.exists?(name: params[:name])
20
20
 
21
21
  form = build_from_params(params, current_user)
22
22
 
@@ -57,15 +57,15 @@ module Motor
57
57
  end
58
58
 
59
59
  def archive_with_existing_name(form)
60
- Motor::Form.where(['lower(name) = ? AND id != ?', form.name.to_s.downcase, form.id])
60
+ Motor::Form.where(['name = ? AND id != ?', form.name, form.id])
61
61
  .update_all(deleted_at: Time.current)
62
62
  end
63
63
 
64
64
  def name_already_exists?(form)
65
65
  if form.new_record?
66
- Motor::Form.exists?(['lower(name) = ?', form.name.to_s.downcase])
66
+ Motor::Form.exists?(['name = ?', form.name])
67
67
  else
68
- Motor::Form.exists?(['lower(name) = ? AND id != ?', form.name.to_s.downcase, form.id])
68
+ Motor::Form.exists?(['name = ? AND id != ?', form.name, form.id])
69
69
  end
70
70
  end
71
71
  end