motor-admin 0.1.34 → 0.1.40

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/motor/load_and_authorize_dynamic_resource.rb +70 -0
  3. data/app/controllers/motor/alerts_controller.rb +12 -6
  4. data/app/controllers/motor/audits_controller.rb +16 -0
  5. data/app/controllers/motor/configs_controller.rb +1 -0
  6. data/app/controllers/motor/dashboards_controller.rb +4 -0
  7. data/app/controllers/motor/data_controller.rb +2 -57
  8. data/app/controllers/motor/forms_controller.rb +4 -0
  9. data/app/controllers/motor/queries_controller.rb +4 -0
  10. data/app/controllers/motor/resources_controller.rb +1 -0
  11. data/app/controllers/motor/ui_controller.rb +4 -0
  12. data/app/models/motor/alert.rb +4 -2
  13. data/app/models/motor/application_record.rb +10 -0
  14. data/app/models/motor/audit.rb +9 -0
  15. data/app/models/motor/config.rb +2 -0
  16. data/app/models/motor/dashboard.rb +3 -1
  17. data/app/models/motor/form.rb +3 -1
  18. data/app/models/motor/query.rb +4 -1
  19. data/app/models/motor/resource.rb +2 -0
  20. data/app/views/motor/ui/show.html.erb +1 -1
  21. data/config/routes.rb +1 -0
  22. data/lib/generators/motor/templates/install.rb +40 -16
  23. data/lib/motor.rb +11 -2
  24. data/lib/motor/admin.rb +8 -0
  25. data/lib/motor/alerts/persistance.rb +17 -3
  26. data/lib/motor/alerts/scheduler.rb +1 -1
  27. data/lib/motor/api_query/sort.rb +1 -1
  28. data/lib/motor/build_schema.rb +3 -3
  29. data/lib/motor/build_schema/load_from_rails.rb +2 -1
  30. data/lib/motor/build_schema/merge_schema_configs.rb +8 -4
  31. data/lib/motor/build_schema/reorder_schema.rb +10 -4
  32. data/lib/motor/configs.rb +17 -0
  33. data/lib/motor/configs/build_configs_hash.rb +83 -0
  34. data/lib/motor/configs/build_ui_app_tag.rb +71 -0
  35. data/lib/motor/configs/load_from_cache.rb +81 -0
  36. data/lib/motor/configs/sync_from_file.rb +36 -0
  37. data/lib/motor/configs/sync_from_hash.rb +124 -0
  38. data/lib/motor/configs/sync_middleware.rb +72 -0
  39. data/lib/motor/configs/sync_with_remote.rb +47 -0
  40. data/lib/motor/configs/write_to_file.rb +36 -0
  41. data/lib/motor/dashboards/persistance.rb +15 -5
  42. data/lib/motor/forms/persistance.rb +15 -5
  43. data/lib/motor/net_http_utils.rb +38 -0
  44. data/lib/motor/queries/persistance.rb +13 -3
  45. data/lib/motor/railtie.rb +11 -0
  46. data/lib/motor/tasks/motor.rake +37 -0
  47. data/lib/motor/version.rb +1 -1
  48. data/ui/dist/{main-052729fa924c6434623f.css.gz → main-57d82791202293600221.css.gz} +0 -0
  49. data/ui/dist/main-57d82791202293600221.js.gz +0 -0
  50. data/ui/dist/manifest.json +5 -5
  51. metadata +33 -5
  52. data/lib/motor/ui_configs.rb +0 -82
  53. data/ui/dist/main-052729fa924c6434623f.js.gz +0 -0
@@ -13,9 +13,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
13
13
 
14
14
  t.index :updated_at
15
15
  t.index 'lower(name)',
16
- name: 'motor_queries_lower_name_unique_index',
17
- unique: true,
18
- where: 'deleted_at IS NULL'
16
+ name: 'motor_queries_lower_name_unique_index',
17
+ unique: true,
18
+ where: 'deleted_at IS NULL'
19
19
  end
20
20
 
21
21
  create_table :motor_dashboards, force: true do |t|
@@ -30,9 +30,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
30
30
 
31
31
  t.index :updated_at
32
32
  t.index 'lower(title)',
33
- name: 'motor_dashboards_lower_title_unique_index',
34
- unique: true,
35
- where: 'deleted_at IS NULL'
33
+ name: 'motor_dashboards_lower_title_unique_index',
34
+ unique: true,
35
+ where: 'deleted_at IS NULL'
36
36
  end
37
37
 
38
38
  create_table :motor_forms, force: true do |t|
@@ -49,9 +49,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
49
49
 
50
50
  t.index :updated_at
51
51
  t.index 'lower(name)',
52
- name: 'motor_forms_lower_name_unique_index',
53
- unique: true,
54
- where: 'deleted_at IS NULL'
52
+ name: 'motor_forms_lower_name_unique_index',
53
+ unique: true,
54
+ where: 'deleted_at IS NULL'
55
55
  end
56
56
 
57
57
  create_table :motor_resources, force: true do |t|
@@ -87,9 +87,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
87
87
 
88
88
  t.index :updated_at
89
89
  t.index 'lower(name)',
90
- name: 'motor_alerts_lower_name_unique_index',
91
- unique: true,
92
- where: 'deleted_at IS NULL'
90
+ name: 'motor_alerts_lower_name_unique_index',
91
+ unique: true,
92
+ where: 'deleted_at IS NULL'
93
93
  end
94
94
 
95
95
  create_table :motor_alert_locks, force: true do |t|
@@ -107,8 +107,8 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
107
107
  t.timestamps
108
108
 
109
109
  t.index 'lower(name)',
110
- name: 'motor_tags_lower_name_unique_index',
111
- unique: true
110
+ name: 'motor_tags_lower_name_unique_index',
111
+ unique: true
112
112
  end
113
113
 
114
114
  create_table :motor_taggable_tags, force: true do |t|
@@ -117,12 +117,36 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
117
117
  t.column :taggable_type, :string, null: false
118
118
 
119
119
  t.index %i[taggable_id taggable_type tag_id],
120
- name: 'motor_polymorphic_association_tag_index',
121
- unique: true
120
+ name: 'motor_polymorphic_association_tag_index',
121
+ unique: true
122
122
  end
123
+
124
+ create_table :motor_audits, force: true do |t|
125
+ t.column :auditable_id, :integer
126
+ t.column :auditable_type, :string
127
+ t.column :associated_id, :integer
128
+ t.column :associated_type, :string
129
+ t.column :user_id, :integer
130
+ t.column :user_type, :string
131
+ t.column :username, :string
132
+ t.column :action, :string
133
+ t.column :audited_changes, :text
134
+ t.column :version, :integer, default: 0
135
+ t.column :comment, :string
136
+ t.column :remote_address, :string
137
+ t.column :request_uuid, :string
138
+ t.column :created_at, :datetime
139
+ end
140
+
141
+ add_index :motor_audits, %i[auditable_type auditable_id version], name: 'motor_auditable_index'
142
+ add_index :motor_audits, %i[associated_type associated_id], name: 'motor_auditable_associated_index'
143
+ add_index :motor_audits, %i[user_id user_type], name: 'motor_auditable_user_index'
144
+ add_index :motor_audits, :request_uuid
145
+ add_index :motor_audits, :created_at
123
146
  end
124
147
 
125
148
  def self.down
149
+ drop_table :motor_audits
126
150
  drop_table :motor_alert_locks
127
151
  drop_table :motor_alerts
128
152
  drop_table :motor_taggable_tags
data/lib/motor.rb CHANGED
@@ -1,11 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'concurrent/executor/fixed_thread_pool'
4
+ require 'concurrent/timer_task'
3
5
  require 'cancancan'
4
6
  require 'ar_lazy_preload'
5
7
  require 'js_regex'
6
8
  require 'fugit'
7
9
  require 'csv'
8
10
  require 'active_record/filter'
11
+ require 'audited'
12
+ require 'uri'
13
+ require 'net/http'
14
+ require 'net/https'
9
15
 
10
16
  module Motor
11
17
  PATH = Pathname.new(__dir__)
@@ -17,7 +23,8 @@ module Motor
17
23
  Dir[PATH.join('./motor/**/*.rb')].each do |f|
18
24
  next if f.ends_with?('alerts/scheduler.rb')
19
25
  next if f.ends_with?('alerts/scheduled_alerts_cache.rb')
20
- next if f.ends_with?('ui_configs.rb')
26
+ next if f.ends_with?('configs/load_from_cache.rb')
27
+ next if f.ends_with?('configs/sync_from_file.rb')
21
28
 
22
29
  load f
23
30
  end
@@ -49,10 +56,12 @@ require 'motor/assets'
49
56
  require 'motor/build_schema'
50
57
  require 'motor/api_query'
51
58
  require 'motor/tags'
52
- require 'motor/ui_configs'
59
+ require 'motor/configs'
53
60
  require 'motor/queries'
54
61
  require 'motor/dashboards'
55
62
  require 'motor/forms'
56
63
  require 'motor/alerts'
57
64
  require 'motor/hash_serializer'
58
65
  require 'motor/active_record_utils'
66
+ require 'motor/net_http_utils'
67
+ require 'motor/railtie'
data/lib/motor/admin.rb CHANGED
@@ -28,6 +28,14 @@ module Motor
28
28
  end
29
29
  end
30
30
 
31
+ initializer 'motor.configs.sync_middleware' do
32
+ next if Motor::Configs::SYNC_ACCESS_KEY.blank?
33
+
34
+ require 'motor/configs/sync_middleware'
35
+
36
+ Rails.application.config.middleware.insert_after(Rails::Rack::Logger, Motor::Configs::SyncMiddleware)
37
+ end
38
+
31
39
  initializer 'motor.filter_params' do
32
40
  Rails.application.config.filter_parameters += %i[io]
33
41
  end
@@ -43,30 +43,44 @@ module Motor
43
43
  retry
44
44
  end
45
45
 
46
- def update_from_params!(alert, params)
46
+ def update_from_params!(alert, params, force_replace: false)
47
+ tag_ids = alert.tags.ids
48
+
47
49
  alert = assign_attributes(alert, params)
48
50
 
49
- raise NameAlreadyExists if name_already_exists?(alert)
51
+ raise NameAlreadyExists if !force_replace && name_already_exists?(alert)
50
52
  raise InvalidInterval unless alert.cron
51
53
 
52
54
  ApplicationRecord.transaction do
55
+ archive_with_existing_name(alert) if force_replace
56
+
53
57
  alert.save!
54
58
  end
55
59
 
56
- alert.tags.reload
60
+ alert.touch if tags_changed?(tag_ids, alert) && params[:updated_at].blank?
57
61
 
58
62
  alert
59
63
  rescue ActiveRecord::RecordNotUnique
60
64
  retry
61
65
  end
62
66
 
67
+ def tags_changed?(previous_ids, alert)
68
+ previous_ids.sort != alert.tags.reload.ids.sort
69
+ end
70
+
63
71
  def assign_attributes(alert, params)
64
72
  alert.assign_attributes(params.slice(*ALERT_ATTRIBUTES))
65
73
  alert.preferences[:interval] = normalize_interval(alert.preferences[:interval])
74
+ alert.updated_at = [params[:updated_at], Time.current].min if params[:updated_at].present?
66
75
 
67
76
  Motor::Tags.assign_tags(alert, params[:tags])
68
77
  end
69
78
 
79
+ def archive_with_existing_name(alert)
80
+ Motor::Alert.where(['lower(name) = ? AND id != ?', alert.name.to_s.downcase, alert.id])
81
+ .update_all(deleted_at: Time.current)
82
+ end
83
+
70
84
  def normalize_interval(interval)
71
85
  interval.to_s.gsub(NORMALIZE_INTERVAL_REGEXP, 'every ')
72
86
  end
@@ -22,7 +22,7 @@ module Motor
22
22
  Motor::AlertSendingJob.perform_later(alert).job_id
23
23
  end
24
24
  rescue StandardError => e
25
- Rials.logger.error(e)
25
+ Rails.logger.error(e)
26
26
  end
27
27
  end
28
28
  end
@@ -13,7 +13,7 @@ module Motor
13
13
  arel_order = build_arel_order(rel.klass, param)
14
14
  join_params = build_join_params(rel.klass, param)
15
15
 
16
- rel.order(arel_order).left_joins(join_params)
16
+ rel.reorder(arel_order).left_joins(join_params)
17
17
  end
18
18
 
19
19
  def build_join_params(_model, param)
@@ -58,11 +58,11 @@ module Motor
58
58
 
59
59
  module_function
60
60
 
61
- def call
61
+ def call(cache_keys = {})
62
62
  schema = LoadFromRails.call
63
- schema = MergeSchemaConfigs.call(schema)
63
+ schema = MergeSchemaConfigs.call(schema, cache_keys)
64
64
 
65
- ReorderSchema.call(schema)
65
+ ReorderSchema.call(schema, cache_keys)
66
66
  end
67
67
  end
68
68
  end
@@ -11,7 +11,7 @@ module Motor
11
11
  models.map do |model|
12
12
  build_model_schema(model)
13
13
  rescue StandardError, NotImplementedError => e
14
- Rails.logger.error(e)
14
+ Rails.logger.error(e) if model.name != 'Audited::Audit'
15
15
 
16
16
  next
17
17
  end.compact
@@ -24,6 +24,7 @@ module Motor
24
24
  models = models.reject(&:abstract_class)
25
25
 
26
26
  models -= Motor::ApplicationRecord.descendants
27
+ models -= [Motor::Audit]
27
28
  models -= [ActiveRecord::SchemaMigration] if defined?(ActiveRecord::SchemaMigration)
28
29
  models -= [ActiveStorage::Blob] if defined?(ActiveStorage::Blob)
29
30
  models -= [ActiveStorage::VariantRecord] if defined?(ActiveStorage::VariantRecord)
@@ -12,9 +12,10 @@ module Motor
12
12
  module_function
13
13
 
14
14
  # @param schema [Array<HashWithIndifferentAccess>]
15
+ # @param cache_keys [Hash]
15
16
  # @return [Array<HashWithIndifferentAccess>]
16
- def call(schema)
17
- configs = load_configs
17
+ def call(schema, cache_keys = {})
18
+ configs = load_configs(cache_keys)
18
19
 
19
20
  schema.map do |model|
20
21
  merge_model(model, configs.fetch(model[:name], {}))
@@ -120,9 +121,12 @@ module Motor
120
121
  end.compact
121
122
  end
122
123
 
124
+ # @param cache_keys [Hash]
123
125
  # @return [HashWithIndifferentAccess<String, HashWithIndifferentAccess>]
124
- def load_configs
125
- Motor::Resource.all.each_with_object(HashWithIndifferentAccess.new) do |resource, acc|
126
+ def load_configs(cache_keys)
127
+ resources = Motor::Configs::LoadFromCache.load_resources(cache_key: cache_keys[:resources])
128
+
129
+ resources.each_with_object(HashWithIndifferentAccess.new) do |resource, acc|
126
130
  acc[resource.name] = resource.preferences
127
131
  end
128
132
  end
@@ -16,10 +16,11 @@ module Motor
16
16
 
17
17
  module_function
18
18
 
19
+ # @param cache_keys [Hash]
19
20
  # @param schema [Array<HashWithIndifferentAccess>]
20
21
  # @return [Array<HashWithIndifferentAccess>]
21
- def call(schema)
22
- configs = load_configs
22
+ def call(schema, cache_keys = {})
23
+ configs = load_configs(cache_keys)
23
24
 
24
25
  schema = sort_by_name(schema, configs['resources.order'])
25
26
 
@@ -64,6 +65,8 @@ module Motor
64
65
  end
65
66
  end
66
67
 
68
+ # @param columns [Array<HashWithIndifferentAccess>]
69
+ # @return [Array<HashWithIndifferentAccess>]
67
70
  def sort_columns(columns)
68
71
  columns.each_with_object([]) do |column, acc|
69
72
  weight = COLUMNS_DEFAULT_ORDER_WEIGHTS.fetch(column[:name], COLUMNS_DEFAULT_ORDER_WEIGHT)
@@ -72,9 +75,12 @@ module Motor
72
75
  end.flatten.compact
73
76
  end
74
77
 
78
+ # @param cache_keys [Hash]
75
79
  # @return [Hash<String, HashWithIndifferentAccess>]
76
- def load_configs
77
- Motor::Config.all.each_with_object({}) do |config, acc|
80
+ def load_configs(cache_keys = {})
81
+ configs = Motor::Configs::LoadFromCache.load_configs(cache_key: cache_keys[:configs])
82
+
83
+ configs.each_with_object({}) do |config, acc|
78
84
  acc[config.key] = config.value
79
85
  end
80
86
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module Configs
5
+ FILE_PATH = 'config/motor.yml'
6
+ SYNC_API_PATH = '/motor_configs_sync'
7
+ SYNC_ACCESS_KEY = ENV.fetch('MOTOR_SYNC_API_KEY', '')
8
+ end
9
+ end
10
+
11
+ require_relative './configs/load_from_cache'
12
+ require_relative './configs/build_ui_app_tag'
13
+ require_relative './configs/build_configs_hash'
14
+ require_relative './configs/write_to_file'
15
+ require_relative './configs/sync_from_hash'
16
+ require_relative './configs/sync_from_file'
17
+ require_relative './configs/sync_with_remote'
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module Configs
5
+ module BuildConfigsHash
6
+ module_function
7
+
8
+ def call
9
+ cache_keys = LoadFromCache.load_cache_keys
10
+
11
+ normalize_hash(
12
+ app_version: Motor::VERSION,
13
+ file_version: cache_keys.values.max.to_time,
14
+ resources: build_resources_hash(cache_keys[:resources]),
15
+ configs: build_configs_hash(cache_keys[:configs]),
16
+ queries: build_queries_hash(cache_keys[:queries]),
17
+ dashboards: build_dashboards_hash(cache_keys[:dashboards]),
18
+ forms: build_forms_hash(cache_keys[:forms]),
19
+ alerts: build_alerts_hash(cache_keys[:alerts])
20
+ )
21
+ end
22
+
23
+ def build_queries_hash(cache_key = nil)
24
+ Motor::Configs::LoadFromCache.load_queries(cache_key: cache_key).sort_by(&:id).map do |query|
25
+ query.slice(%i[id name sql_body description preferences])
26
+ .merge(tags: query.tags.map(&:name), updated_at: query.updated_at.to_time)
27
+ end
28
+ end
29
+
30
+ def build_dashboards_hash(cache_key = nil)
31
+ Motor::Configs::LoadFromCache.load_dashboards(cache_key: cache_key).sort_by(&:id).map do |dashboard|
32
+ dashboard.slice(%i[id title description preferences])
33
+ .merge(tags: dashboard.tags.map(&:name), updated_at: dashboard.updated_at.to_time)
34
+ end
35
+ end
36
+
37
+ def build_alerts_hash(cache_key = nil)
38
+ Motor::Configs::LoadFromCache.load_alerts(cache_key: cache_key).sort_by(&:id).map do |alert|
39
+ alert.slice(%i[id name query_id to_emails is_enabled description preferences])
40
+ .merge(tags: alert.tags.map(&:name), updated_at: alert.updated_at.to_time)
41
+ end
42
+ end
43
+
44
+ def build_forms_hash(cache_key = nil)
45
+ Motor::Configs::LoadFromCache.load_forms(cache_key: cache_key).sort_by(&:id).map do |form|
46
+ form.slice(%i[id name http_method api_path description preferences])
47
+ .merge(tags: form.tags.map(&:name), updated_at: form.updated_at.to_time)
48
+ end
49
+ end
50
+
51
+ def build_configs_hash(cache_key = nil)
52
+ Motor::Configs::LoadFromCache.load_configs(cache_key: cache_key).sort_by(&:key).map do |config|
53
+ {
54
+ key: config.key,
55
+ value: config.value,
56
+ updated_at: config.updated_at.to_time
57
+ }
58
+ end
59
+ end
60
+
61
+ def build_resources_hash(cache_key = nil)
62
+ Motor::Configs::LoadFromCache.load_resources(cache_key: cache_key).sort_by(&:name).map do |resource|
63
+ {
64
+ name: resource.name,
65
+ preferences: resource.preferences,
66
+ updated_at: resource.updated_at.to_time
67
+ }
68
+ end
69
+ end
70
+
71
+ def normalize_hash(value)
72
+ case value
73
+ when Hash, HashWithIndifferentAccess
74
+ value.to_h.stringify_keys.transform_values { |v| normalize_hash(v) }
75
+ when Array
76
+ value.map { |e| normalize_hash(e) }
77
+ else
78
+ value
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module Configs
5
+ module BuildUiAppTag
6
+ CACHE_STORE =
7
+ if Motor.development?
8
+ ActiveSupport::Cache::NullStore.new
9
+ else
10
+ ActiveSupport::Cache::MemoryStore.new(size: 5.megabytes)
11
+ end
12
+
13
+ module_function
14
+
15
+ def call
16
+ cache_keys = LoadFromCache.load_cache_keys
17
+
18
+ CACHE_STORE.fetch(cache_keys.hash) do
19
+ CACHE_STORE.clear
20
+
21
+ Motor::ApplicationController.helpers.content_tag(
22
+ :div, '', id: 'app', data: build_data(cache_keys)
23
+ )
24
+ end
25
+ end
26
+
27
+ # @return [Hash]
28
+ def build_data(cache_keys = {})
29
+ {
30
+ base_path: Motor::Admin.routes.url_helpers.motor_path,
31
+ schema: Motor::BuildSchema.call(cache_keys),
32
+ header_links: header_links_data_hash(cache_keys[:configs]),
33
+ queries: queries_data_hash(cache_keys[:queries]),
34
+ dashboards: dashboards_data_hash(cache_keys[:dashboards]),
35
+ alerts: alerts_data_hash(cache_keys[:alerts]),
36
+ forms: forms_data_hash(cache_keys[:forms])
37
+ }
38
+ end
39
+
40
+ def header_links_data_hash(cache_key = nil)
41
+ configs = Motor::Configs::LoadFromCache.load_configs(cache_key: cache_key)
42
+
43
+ configs.find { |c| c.key == 'header.links' }&.value || []
44
+ end
45
+
46
+ def queries_data_hash(cache_key = nil)
47
+ Motor::Configs::LoadFromCache.load_queries(cache_key: cache_key)
48
+ .as_json(only: %i[id name updated_at],
49
+ include: { tags: { only: %i[id name] } })
50
+ end
51
+
52
+ def dashboards_data_hash(cache_key = nil)
53
+ Motor::Configs::LoadFromCache.load_dashboards(cache_key: cache_key)
54
+ .as_json(only: %i[id title updated_at],
55
+ include: { tags: { only: %i[id name] } })
56
+ end
57
+
58
+ def alerts_data_hash(cache_key = nil)
59
+ Motor::Configs::LoadFromCache.load_alerts(cache_key: cache_key)
60
+ .as_json(only: %i[id name is_enabled updated_at],
61
+ include: { tags: { only: %i[id name] } })
62
+ end
63
+
64
+ def forms_data_hash(cache_key = nil)
65
+ Motor::Configs::LoadFromCache.load_forms(cache_key: cache_key)
66
+ .as_json(only: %i[id name updated_at],
67
+ include: { tags: { only: %i[id name] } })
68
+ end
69
+ end
70
+ end
71
+ end