motor-admin 0.1.33 → 0.1.39

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 (54) 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 +2 -0
  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 +2 -0
  17. data/app/models/motor/form.rb +2 -0
  18. data/app/models/motor/query.rb +2 -0
  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 +4 -1
  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 +16 -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 +34 -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 +39 -0
  40. data/lib/motor/configs/write_to_file.rb +36 -0
  41. data/lib/motor/dashboards/persistance.rb +4 -1
  42. data/lib/motor/forms/persistance.rb +4 -1
  43. data/lib/motor/net_http_utils.rb +37 -0
  44. data/lib/motor/queries/persistance.rb +4 -1
  45. data/lib/motor/railtie.rb +11 -0
  46. data/lib/motor/tags.rb +2 -1
  47. data/lib/motor/tasks/motor.rake +29 -0
  48. data/lib/motor/version.rb +1 -1
  49. data/ui/dist/{main-b3096c53c77bd4641e52.css.gz → main-e443f49b386b119ff25a.css.gz} +0 -0
  50. data/ui/dist/main-e443f49b386b119ff25a.js.gz +0 -0
  51. data/ui/dist/manifest.json +5 -5
  52. metadata +33 -5
  53. data/lib/motor/ui_configs.rb +0 -82
  54. data/ui/dist/main-b3096c53c77bd4641e52.js.gz +0 -0
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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'motor/configs/sync_middleware'
4
+
3
5
  module Motor
4
6
  class Admin < ::Rails::Engine
5
7
  initializer 'motor.startup_message' do
@@ -28,6 +30,12 @@ module Motor
28
30
  end
29
31
  end
30
32
 
33
+ initializer 'motor.configs.sync_middleware' do
34
+ if Motor::Configs::SyncMiddleware::ACCESS_KEY.present?
35
+ Rails.application.config.middleware.insert_before(0, Motor::Configs::SyncMiddleware)
36
+ end
37
+ end
38
+
31
39
  initializer 'motor.filter_params' do
32
40
  Rails.application.config.filter_parameters += %i[io]
33
41
  end
@@ -44,6 +44,8 @@ module Motor
44
44
  end
45
45
 
46
46
  def update_from_params!(alert, params)
47
+ tag_ids = alert.tags.ids
48
+
47
49
  alert = assign_attributes(alert, params)
48
50
 
49
51
  raise NameAlreadyExists if name_already_exists?(alert)
@@ -53,7 +55,7 @@ module Motor
53
55
  alert.save!
54
56
  end
55
57
 
56
- alert.tags.reload
58
+ alert.touch if tag_ids.sort != alert.tags.reload.ids.sort && params[:updated_at].blank?
57
59
 
58
60
  alert
59
61
  rescue ActiveRecord::RecordNotUnique
@@ -63,6 +65,7 @@ module Motor
63
65
  def assign_attributes(alert, params)
64
66
  alert.assign_attributes(params.slice(*ALERT_ATTRIBUTES))
65
67
  alert.preferences[:interval] = normalize_interval(alert.preferences[:interval])
68
+ alert.updated_at = [params[:updated_at], Time.current].min if params[:updated_at].present?
66
69
 
67
70
  Motor::Tags.assign_tags(alert, params[:tags])
68
71
  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,16 @@
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
+ end
8
+ end
9
+
10
+ require_relative './configs/load_from_cache'
11
+ require_relative './configs/build_ui_app_tag'
12
+ require_relative './configs/build_configs_hash'
13
+ require_relative './configs/write_to_file'
14
+ require_relative './configs/sync_from_hash'
15
+ require_relative './configs/sync_from_file'
16
+ 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
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module Configs
5
+ module LoadFromCache
6
+ CACHE_STORE = ActiveSupport::Cache::MemoryStore.new(size: 10.megabytes)
7
+
8
+ module_function
9
+
10
+ def call
11
+ cache_keys = load_cache_keys
12
+
13
+ {
14
+ configs: load_configs(cache_key: cache_keys[:configs]),
15
+ resources: load_resources(cache_key: cache_keys[:resources]),
16
+ queries: load_queries(cache_key: cache_keys[:queries]),
17
+ dashboards: load_dashboards(cache_key: cache_keys[:dashboards]),
18
+ alerts: load_alerts(cache_key: cache_keys[:alerts]),
19
+ forms: load_forms(cache_key: cache_keys[:forms])
20
+ }
21
+ end
22
+
23
+ def load_configs(cache_key: nil)
24
+ maybe_fetch_from_cache('configs', cache_key) do
25
+ Motor::Config.all.load
26
+ end
27
+ end
28
+
29
+ def load_resources(cache_key: nil)
30
+ maybe_fetch_from_cache('resources', cache_key) do
31
+ Motor::Resource.all.load
32
+ end
33
+ end
34
+
35
+ def load_queries(cache_key: nil)
36
+ maybe_fetch_from_cache('queries', cache_key) do
37
+ Motor::Query.all.active.preload(:tags).load
38
+ end
39
+ end
40
+
41
+ def load_dashboards(cache_key: nil)
42
+ maybe_fetch_from_cache('dashboards', cache_key) do
43
+ Motor::Dashboard.all.active.preload(:tags).load
44
+ end
45
+ end
46
+
47
+ def load_alerts(cache_key: nil)
48
+ maybe_fetch_from_cache('alerts', cache_key) do
49
+ Motor::Alert.all.active.preload(:tags).load
50
+ end
51
+ end
52
+
53
+ def load_forms(cache_key: nil)
54
+ maybe_fetch_from_cache('forms', cache_key) do
55
+ Motor::Form.all.active.preload(:tags).load
56
+ end
57
+ end
58
+
59
+ def maybe_fetch_from_cache(type, cache_key, &block)
60
+ return block.call unless cache_key
61
+
62
+ CACHE_STORE.fetch(type + cache_key.to_s, &block)
63
+ end
64
+
65
+ 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
78
+ end
79
+ end
80
+ end
81
+ end