motor-admin 0.1.37 → 0.1.39

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/motor/alerts_controller.rb +12 -6
  3. data/app/controllers/motor/configs_controller.rb +1 -0
  4. data/app/controllers/motor/dashboards_controller.rb +4 -0
  5. data/app/controllers/motor/forms_controller.rb +4 -0
  6. data/app/controllers/motor/queries_controller.rb +4 -0
  7. data/app/controllers/motor/resources_controller.rb +1 -0
  8. data/app/controllers/motor/ui_controller.rb +4 -0
  9. data/app/views/motor/ui/show.html.erb +1 -1
  10. data/lib/motor.rb +10 -2
  11. data/lib/motor/admin.rb +8 -0
  12. data/lib/motor/alerts/persistance.rb +4 -1
  13. data/lib/motor/alerts/scheduler.rb +1 -1
  14. data/lib/motor/build_schema.rb +3 -3
  15. data/lib/motor/build_schema/merge_schema_configs.rb +8 -4
  16. data/lib/motor/build_schema/reorder_schema.rb +10 -4
  17. data/lib/motor/configs.rb +16 -0
  18. data/lib/motor/configs/build_configs_hash.rb +83 -0
  19. data/lib/motor/configs/build_ui_app_tag.rb +71 -0
  20. data/lib/motor/configs/load_from_cache.rb +81 -0
  21. data/lib/motor/configs/sync_from_file.rb +34 -0
  22. data/lib/motor/configs/sync_from_hash.rb +124 -0
  23. data/lib/motor/configs/sync_middleware.rb +72 -0
  24. data/lib/motor/configs/sync_with_remote.rb +39 -0
  25. data/lib/motor/configs/write_to_file.rb +36 -0
  26. data/lib/motor/dashboards/persistance.rb +4 -1
  27. data/lib/motor/forms/persistance.rb +4 -1
  28. data/lib/motor/net_http_utils.rb +37 -0
  29. data/lib/motor/queries/persistance.rb +4 -1
  30. data/lib/motor/railtie.rb +11 -0
  31. data/lib/motor/tasks/motor.rake +29 -0
  32. data/lib/motor/version.rb +1 -1
  33. data/ui/dist/{main-358ea31cd7020f915067.css.gz → main-e443f49b386b119ff25a.css.gz} +0 -0
  34. data/ui/dist/main-e443f49b386b119ff25a.js.gz +0 -0
  35. data/ui/dist/manifest.json +5 -5
  36. metadata +16 -5
  37. data/lib/motor/ui_configs.rb +0 -82
  38. data/ui/dist/main-358ea31cd7020f915067.js.gz +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c003af4059ae5bd3a60d30031526c1ed2aefd267c7e1f93f33e5bac94eaff5c7
4
- data.tar.gz: aa7403bca65f0d936b86bcc58f1fe88b4081439d74843f1ca1c3d25fd4da8a81
3
+ metadata.gz: 2e0ab742c32ae1b59bfa12c6eb1f838e5d379cb5a964521fe7f345de5fabf953
4
+ data.tar.gz: 9f018c3475ae06b4104e35f30774c80b2098a8a23b7fa3a8e4ac35df089ee98e
5
5
  SHA512:
6
- metadata.gz: a195dd04aceadc74e1d7fa89dc0100c15fc045c0fad238230567069801698d341d6a9e598b861ae9e802e61dd5a48915d15c74b88a1228deae5a985fad283762
7
- data.tar.gz: 7d6dc23a2750dc941b20d83170dc3f2eb3018d02897b2500b99831285ac139fcde46ebfa7ae044be856504bac6f09e1d11e70c02a443eba6d70278b959784f74
6
+ metadata.gz: '0651978240824b17dc1f379047faf9f5c5a393b9bebe6e37ae692a606630a300454a2c56c88173383defef451787555006d355fe26c2dd38173f0ece03e116a0'
7
+ data.tar.gz: b15c006847a5c9dcea315741ccc3a46e3a2bba49ab05d2aceab44c75a530ba0f5fad79067aae0b8e31abad9cda105a827cbdb892d93fd1c2c1fe72a2f62a8637
@@ -18,12 +18,15 @@ module Motor
18
18
  end
19
19
 
20
20
  def create
21
- ApplicationRecord.transaction { @alert.save! }
22
- Motor::Alerts::ScheduledAlertsCache.clear
23
-
24
- render json: { data: Motor::ApiQuery::BuildJson.call(@alert, params) }
25
- rescue Motor::Alerts::Persistance::NameAlreadyExists
26
- name_already_exists_response
21
+ if Motor::Alerts::Persistance.name_already_exists?(@alert)
22
+ name_already_exists_response
23
+ else
24
+ ApplicationRecord.transaction { @alert.save! }
25
+ Motor::Alerts::ScheduledAlertsCache.clear
26
+ Motor::Configs::WriteToFile.call
27
+
28
+ render json: { data: Motor::ApiQuery::BuildJson.call(@alert, params) }
29
+ end
27
30
  rescue Motor::Alerts::Persistance::InvalidInterval
28
31
  invalid_interval_response
29
32
  end
@@ -31,6 +34,7 @@ module Motor
31
34
  def update
32
35
  Motor::Alerts::Persistance.update_from_params!(@alert, alert_params)
33
36
  Motor::Alerts::ScheduledAlertsCache.clear
37
+ Motor::Configs::WriteToFile.call
34
38
 
35
39
  render json: { data: Motor::ApiQuery::BuildJson.call(@alert, params) }
36
40
  rescue Motor::Alerts::Persistance::NameAlreadyExists
@@ -42,6 +46,8 @@ module Motor
42
46
  def destroy
43
47
  @alert.update!(deleted_at: Time.current)
44
48
 
49
+ Motor::Configs::WriteToFile.call
50
+
45
51
  head :ok
46
52
  end
47
53
 
@@ -17,6 +17,7 @@ module Motor
17
17
  end
18
18
 
19
19
  @config.save!
20
+ Motor::Configs::WriteToFile.call
20
21
 
21
22
  render json: { data: Motor::ApiQuery::BuildJson.call(@config, params) }
22
23
  rescue ActiveRecord::RecordNotUnique
@@ -22,6 +22,7 @@ module Motor
22
22
  render json: { errors: [{ source: 'title', detail: 'Title already exists' }] }, status: :unprocessable_entity
23
23
  else
24
24
  ApplicationRecord.transaction { @dashboard.save! }
25
+ Motor::Configs::WriteToFile.call
25
26
 
26
27
  render json: { data: Motor::ApiQuery::BuildJson.call(@dashboard, params) }
27
28
  end
@@ -31,6 +32,7 @@ module Motor
31
32
 
32
33
  def update
33
34
  Motor::Dashboards::Persistance.update_from_params!(@dashboard, dashboard_params)
35
+ Motor::Configs::WriteToFile.call
34
36
 
35
37
  render json: { data: Motor::ApiQuery::BuildJson.call(@dashboard, params) }
36
38
  rescue Motor::Dashboards::Persistance::TitleAlreadyExists
@@ -40,6 +42,8 @@ module Motor
40
42
  def destroy
41
43
  @dashboard.update!(deleted_at: Time.current)
42
44
 
45
+ Motor::Configs::WriteToFile.call
46
+
43
47
  head :ok
44
48
  end
45
49
 
@@ -22,6 +22,7 @@ module Motor
22
22
  render json: { errors: [{ source: 'name', detail: 'Name already exists' }] }, status: :unprocessable_entity
23
23
  else
24
24
  ApplicationRecord.transaction { @form.save! }
25
+ Motor::Configs::WriteToFile.call
25
26
 
26
27
  render json: { data: Motor::ApiQuery::BuildJson.call(@form, params) }
27
28
  end
@@ -31,6 +32,7 @@ module Motor
31
32
 
32
33
  def update
33
34
  Motor::Forms::Persistance.update_from_params!(@form, form_params)
35
+ Motor::Configs::WriteToFile.call
34
36
 
35
37
  render json: { data: Motor::ApiQuery::BuildJson.call(@form, params) }
36
38
  rescue Motor::Forms::Persistance::NameAlreadyExists
@@ -40,6 +42,8 @@ module Motor
40
42
  def destroy
41
43
  @form.update!(deleted_at: Time.current)
42
44
 
45
+ Motor::Configs::WriteToFile.call
46
+
43
47
  head :ok
44
48
  end
45
49
 
@@ -22,6 +22,7 @@ module Motor
22
22
  render json: { errors: [{ source: 'name', detail: 'Name already exists' }] }, status: :unprocessable_entity
23
23
  else
24
24
  ApplicationRecord.transaction { @query.save! }
25
+ Motor::Configs::WriteToFile.call
25
26
 
26
27
  render json: { data: Motor::ApiQuery::BuildJson.call(@query, params) }
27
28
  end
@@ -31,6 +32,7 @@ module Motor
31
32
 
32
33
  def update
33
34
  Motor::Queries::Persistance.update_from_params!(@query, query_params)
35
+ Motor::Configs::WriteToFile.call
34
36
 
35
37
  render json: { data: Motor::ApiQuery::BuildJson.call(@query, params) }
36
38
  rescue Motor::Queries::Persistance::NameAlreadyExists
@@ -40,6 +42,8 @@ module Motor
40
42
  def destroy
41
43
  @query.update!(deleted_at: Time.current)
42
44
 
45
+ Motor::Configs::WriteToFile.call
46
+
43
47
  head :ok
44
48
  end
45
49
 
@@ -12,6 +12,7 @@ module Motor
12
12
 
13
13
  def create
14
14
  Motor::BuildSchema::PersistResourceConfigs.call(@resource)
15
+ Motor::Configs::WriteToFile.call
15
16
 
16
17
  render json: { data: Motor::ApiQuery::BuildJson.call(@resource, params) }
17
18
  end
@@ -7,12 +7,16 @@ module Motor
7
7
  def index
8
8
  Motor.reload! if Motor.development?
9
9
 
10
+ Motor::Configs::SyncFromFile.call
11
+
10
12
  render :show
11
13
  end
12
14
 
13
15
  def show
14
16
  Motor.reload! if Motor.development?
15
17
 
18
+ Motor::Configs::SyncFromFile.call
19
+
16
20
  render :show
17
21
  end
18
22
  end
@@ -1 +1 @@
1
- <%= raw(Motor::UiConfigs.app_tag) %>
1
+ <%= raw(Motor::Configs::BuildUiAppTag.call) %>
data/lib/motor.rb CHANGED
@@ -1,5 +1,7 @@
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'
@@ -7,6 +9,9 @@ require 'fugit'
7
9
  require 'csv'
8
10
  require 'active_record/filter'
9
11
  require 'audited'
12
+ require 'uri'
13
+ require 'net/http'
14
+ require 'net/https'
10
15
 
11
16
  module Motor
12
17
  PATH = Pathname.new(__dir__)
@@ -18,7 +23,8 @@ module Motor
18
23
  Dir[PATH.join('./motor/**/*.rb')].each do |f|
19
24
  next if f.ends_with?('alerts/scheduler.rb')
20
25
  next if f.ends_with?('alerts/scheduled_alerts_cache.rb')
21
- 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')
22
28
 
23
29
  load f
24
30
  end
@@ -50,10 +56,12 @@ require 'motor/assets'
50
56
  require 'motor/build_schema'
51
57
  require 'motor/api_query'
52
58
  require 'motor/tags'
53
- require 'motor/ui_configs'
59
+ require 'motor/configs'
54
60
  require 'motor/queries'
55
61
  require 'motor/dashboards'
56
62
  require 'motor/forms'
57
63
  require 'motor/alerts'
58
64
  require 'motor/hash_serializer'
59
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
@@ -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
@@ -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
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module Configs
5
+ module SyncFromFile
6
+ FILE_PATH = Motor::Configs::FILE_PATH
7
+ MUTEXT = Mutex.new
8
+ FILE_TIMESTAMPS_STORE = ActiveSupport::Cache::MemoryStore.new(size: 1.megabyte)
9
+
10
+ module_function
11
+
12
+ def call
13
+ MUTEXT.synchronize do
14
+ file = Rails.root.join(FILE_PATH)
15
+
16
+ file_timestamp =
17
+ begin
18
+ file.ctime
19
+ rescue Errno::ENOENT
20
+ nil
21
+ end
22
+
23
+ next unless file_timestamp
24
+
25
+ FILE_TIMESTAMPS_STORE.fetch(file_timestamp.to_s) do
26
+ Motor::Configs::SyncFromHash.call(
27
+ YAML.safe_load(file.read, permitted_classes: [Time, Date])
28
+ )
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module Configs
5
+ module SyncFromHash
6
+ module_function
7
+
8
+ def call(configs_hash)
9
+ configs_hash = configs_hash.with_indifferent_access
10
+
11
+ Motor::ApplicationRecord.transaction do
12
+ sync_queries(configs_hash)
13
+ sync_alerts(configs_hash)
14
+ sync_dashboards(configs_hash)
15
+ sync_forms(configs_hash)
16
+ sync_configs(configs_hash)
17
+ sync_resources(configs_hash)
18
+ end
19
+ end
20
+
21
+ def sync_queries(configs_hash)
22
+ sync_taggable(
23
+ Motor::Configs::LoadFromCache.load_queries,
24
+ configs_hash[:queries],
25
+ configs_hash[:file_version],
26
+ Motor::Queries::Persistance.method(:update_from_params!)
27
+ )
28
+ end
29
+
30
+ def sync_alerts(configs_hash)
31
+ sync_taggable(
32
+ Motor::Configs::LoadFromCache.load_alerts,
33
+ configs_hash[:alerts],
34
+ configs_hash[:file_version],
35
+ Motor::Alerts::Persistance.method(:update_from_params!)
36
+ )
37
+ end
38
+
39
+ def sync_forms(configs_hash)
40
+ sync_taggable(
41
+ Motor::Configs::LoadFromCache.load_forms,
42
+ configs_hash[:forms],
43
+ configs_hash[:file_version],
44
+ Motor::Forms::Persistance.method(:update_from_params!)
45
+ )
46
+ end
47
+
48
+ def sync_dashboards(configs_hash)
49
+ sync_taggable(
50
+ Motor::Configs::LoadFromCache.load_dashboards,
51
+ configs_hash[:dashboards],
52
+ configs_hash[:file_version],
53
+ Motor::Dashboards::Persistance.method(:update_from_params!)
54
+ )
55
+ end
56
+
57
+ def sync_configs(configs_hash)
58
+ configs_index = Motor::Configs::LoadFromCache.load_configs.index_by(&:key)
59
+
60
+ configs_hash[:configs].each do |attrs|
61
+ record = configs_index[attrs[:key]] || Motor::Config.new
62
+
63
+ next if record.updated_at && attrs[:updated_at] <= record.updated_at
64
+
65
+ record.update!(attrs)
66
+ end
67
+ end
68
+
69
+ def sync_resources(configs_hash)
70
+ resources_index = Motor::Configs::LoadFromCache.load_resources.index_by(&:name)
71
+
72
+ configs_hash[:resources].each do |attrs|
73
+ record = resources_index[attrs[:name]] || Motor::Resource.new
74
+
75
+ next if record.updated_at && attrs[:updated_at] <= record.updated_at
76
+
77
+ record.update!(attrs)
78
+ end
79
+ end
80
+
81
+ def sync_taggable(records, config_items, configs_timestamp, update_proc)
82
+ processed_records, create_items = update_taggable_items(records, config_items, update_proc)
83
+
84
+ create_taggable_items(create_items, records.klass, update_proc)
85
+
86
+ archive_taggable_items(records - processed_records, configs_timestamp)
87
+
88
+ ActiveRecord::Base.connection.reset_pk_sequence!(records.klass.table_name)
89
+ end
90
+
91
+ def update_taggable_items(records, config_items, update_proc)
92
+ record_ids_hash = records.index_by(&:id)
93
+
94
+ config_items.each_with_object([[], []]) do |attrs, (processed_acc, create_acc)|
95
+ record = record_ids_hash[attrs[:id]]
96
+
97
+ next create_acc << attrs unless record
98
+
99
+ processed_acc << record if record
100
+
101
+ next if record.updated_at >= attrs[:updated_at]
102
+
103
+ update_proc.call(record, attrs)
104
+ end
105
+ end
106
+
107
+ def create_taggable_items(create_items, records_class, update_proc)
108
+ create_items.each do |attrs|
109
+ record = records_class.find_or_initialize_by(id: attrs[:id]).tap { |e| e.deleted_at = nil }
110
+
111
+ update_proc.call(record, attrs)
112
+ end
113
+ end
114
+
115
+ def archive_taggable_items(records_to_remove, configs_timestamp)
116
+ records_to_remove.each do |record|
117
+ next if record.updated_at > configs_timestamp
118
+
119
+ record.update!(deleted_at: Time.current) if record.deleted_at.blank?
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module Configs
5
+ class SyncMiddleware
6
+ ACCESS_KEY = ENV.fetch('MOTOR_SYNC_API_KEY', '')
7
+
8
+ KeyNotSpecified = Class.new(StandardError)
9
+ NotAuthenticated = Class.new(StandardError)
10
+
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+
15
+ def call(env)
16
+ if env['PATH_INFO'] == Motor::Configs::SYNC_API_PATH
17
+ authenticate!(env['QUERY_STRING'])
18
+
19
+ case env['REQUEST_METHOD']
20
+ when 'GET'
21
+ respond_with_configs
22
+ when 'POST'
23
+ sync_configs(env['rack.input'].read)
24
+ else
25
+ @app.call(env)
26
+ end
27
+ else
28
+ @app.call(env)
29
+ end
30
+ rescue NotAuthenticated
31
+ [403, {}, ['Invalid synchronization API key']]
32
+ rescue KeyNotSpecified
33
+ [404, {}, ['Set `MOTOR_SYNC_API_KEY` environment variable in order to sync configs']]
34
+ end
35
+
36
+ private
37
+
38
+ def authenticate!(query_string)
39
+ raise KeyNotSpecified if ACCESS_KEY.blank?
40
+ raise NotAuthenticated if query_string.blank?
41
+
42
+ is_token_valid =
43
+ ActiveSupport::SecurityUtils.secure_compare(
44
+ Digest::SHA256.hexdigest(query_string),
45
+ Digest::SHA256.hexdigest("token=#{ACCESS_KEY}")
46
+ )
47
+
48
+ raise NotAuthenticated unless is_token_valid
49
+ end
50
+
51
+ def respond_with_configs
52
+ [
53
+ 200,
54
+ { 'Content-Type' => 'application/json' },
55
+ [Motor::Configs::BuildConfigsHash.call.to_json]
56
+ ]
57
+ rescue StandardError => e
58
+ [500, {}, [e.message]]
59
+ end
60
+
61
+ def sync_configs(body)
62
+ configs_hash = JSON.parse(body)
63
+
64
+ Motor::Configs::SyncFromHash.call(configs_hash)
65
+
66
+ [200, {}, []]
67
+ rescue StandardError => e
68
+ [500, {}, [e.message]]
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module Configs
5
+ module SyncWithRemote
6
+ UnableToSync = Class.new(StandardError)
7
+ ApiNotFound = Class.new(StandardError)
8
+
9
+ module_function
10
+
11
+ def call(remote_url, api_key)
12
+ url = remote_url.sub(%r{/\z}, '') + Motor::Configs::SYNC_API_PATH
13
+
14
+ sync_from_remote!(url, api_key)
15
+ sync_to_remote!(url, api_key)
16
+ end
17
+
18
+ def sync_from_remote!(remote_url, api_key)
19
+ response = Motor::NetHttpUtils.get(remote_url, { token: api_key })
20
+
21
+ raise ApiNotFound if response.is_a?(Net::HTTPNotFound)
22
+ raise UnableToSync, [response.message, response.body].join(': ') unless response.is_a?(Net::HTTPSuccess)
23
+
24
+ configs_hash = JSON.parse(response.body)
25
+
26
+ Motor::Configs::SyncFromHash.call(configs_hash)
27
+ end
28
+
29
+ def sync_to_remote!(remote_url, api_key)
30
+ configs_hash = Motor::Configs::BuildConfigsHash.call
31
+
32
+ response = Motor::NetHttpUtils.post(remote_url, { token: api_key }, configs_hash.to_json)
33
+
34
+ raise ApiNotFound if response.is_a?(Net::HTTPNotFound)
35
+ raise UnableToSync, [response.message, response.body].join(': ') unless response.is_a?(Net::HTTPSuccess)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module Configs
5
+ module WriteToFile
6
+ THREAD_POOL = Concurrent::FixedThreadPool.new(1)
7
+ FILE_PATH = Motor::Configs::FILE_PATH
8
+
9
+ module_function
10
+
11
+ def call
12
+ return if THREAD_POOL.queue_length.positive?
13
+
14
+ THREAD_POOL.post do
15
+ ActiveRecord::Base.logger.silence do
16
+ write_with_lock
17
+ end
18
+ rescue StandardError => e
19
+ Rails.logger.error(e)
20
+ end
21
+ end
22
+
23
+ def write_with_lock
24
+ File.open(Rails.root.join(FILE_PATH), 'w') do |file|
25
+ file.flock(File::LOCK_EX)
26
+
27
+ YAML.dump(Motor::Configs::BuildConfigsHash.call, file)
28
+
29
+ file.flock(File::LOCK_UN)
30
+
31
+ file
32
+ end.close
33
+ end
34
+ end
35
+ end
36
+ end
@@ -30,6 +30,8 @@ module Motor
30
30
  end
31
31
 
32
32
  def update_from_params!(dashboard, params)
33
+ tag_ids = dashboard.tags.ids
34
+
33
35
  dashboard = assign_attributes(dashboard, params)
34
36
 
35
37
  raise TitleAlreadyExists if title_already_exists?(dashboard)
@@ -38,7 +40,7 @@ module Motor
38
40
  dashboard.save!
39
41
  end
40
42
 
41
- dashboard.tags.reload
43
+ dashboard.touch if tag_ids.sort != dashboard.tags.reload.ids.sort && params[:updated_at].blank?
42
44
 
43
45
  dashboard
44
46
  rescue ActiveRecord::RecordNotUnique
@@ -47,6 +49,7 @@ module Motor
47
49
 
48
50
  def assign_attributes(dashboard, params)
49
51
  dashboard.assign_attributes(params.slice(:title, :description, :preferences))
52
+ dashboard.updated_at = [params[:updated_at], Time.current].min if params[:updated_at].present?
50
53
 
51
54
  Motor::Tags.assign_tags(dashboard, params[:tags])
52
55
  end
@@ -30,6 +30,8 @@ module Motor
30
30
  end
31
31
 
32
32
  def update_from_params!(form, params)
33
+ tag_ids = form.tags.ids
34
+
33
35
  form = assign_attributes(form, params)
34
36
 
35
37
  raise NameAlreadyExists if name_already_exists?(form)
@@ -38,7 +40,7 @@ module Motor
38
40
  form.save!
39
41
  end
40
42
 
41
- form.tags.reload
43
+ form.touch if tag_ids.sort != form.tags.reload.ids.sort && params[:updated_at].blank?
42
44
 
43
45
  form
44
46
  rescue ActiveRecord::RecordNotUnique
@@ -47,6 +49,7 @@ module Motor
47
49
 
48
50
  def assign_attributes(form, params)
49
51
  form.assign_attributes(params.slice(:name, :description, :api_path, :http_method, :preferences))
52
+ form.updated_at = [params[:updated_at], Time.current].min if params[:updated_at].present?
50
53
 
51
54
  Motor::Tags.assign_tags(form, params[:tags])
52
55
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module NetHttpUtils
5
+ module_function
6
+
7
+ def get(url, params = {})
8
+ request = build_request(Net::HTTP::Get, url, params, nil)
9
+
10
+ execute_request(request)
11
+ end
12
+
13
+ def post(url, params = {}, body = '')
14
+ request = build_request(Net::HTTP::Post, url, params, body)
15
+
16
+ execute_request(request)
17
+ end
18
+
19
+ def build_request(method_class, url, params, body)
20
+ uri = URI(url)
21
+ uri.query = params.to_query
22
+
23
+ request = method_class.new(uri)
24
+ request.body = body if body.present?
25
+
26
+ request
27
+ end
28
+
29
+ def execute_request(request)
30
+ uri = request.uri
31
+
32
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.port == 443) do |http|
33
+ http.request(request)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -30,6 +30,8 @@ module Motor
30
30
  end
31
31
 
32
32
  def update_from_params!(query, params)
33
+ tag_ids = query.tags.ids
34
+
33
35
  query = assign_attributes(query, params)
34
36
 
35
37
  raise NameAlreadyExists if name_already_exists?(query)
@@ -38,7 +40,7 @@ module Motor
38
40
  query.save!
39
41
  end
40
42
 
41
- query.tags.reload
43
+ query.touch if tag_ids.sort != query.tags.reload.ids.sort && params[:updated_at].blank?
42
44
 
43
45
  query
44
46
  rescue ActiveRecord::RecordNotUnique
@@ -47,6 +49,7 @@ module Motor
47
49
 
48
50
  def assign_attributes(query, params)
49
51
  query.assign_attributes(params.slice(:name, :description, :sql_body, :preferences))
52
+ query.updated_at = [params[:updated_at], Time.current].min if params[:updated_at].present?
50
53
 
51
54
  Motor::Tags.assign_tags(query, params[:tags])
52
55
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class Railtie < Rails::Railtie
5
+ railtie_name :motor_admin
6
+
7
+ rake_tasks do
8
+ Dir[Motor::PATH.join('./motor/tasks/**/*.rake')].each { |f| load f }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :motor do
4
+ desc 'Update configs/motor.yml file'
5
+
6
+ task dump: :environment do
7
+ Motor::Configs::WriteToFile.write_with_lock
8
+
9
+ puts '✅ configs/motor.yml has been updated'
10
+ end
11
+
12
+ desc 'Synchronize configs with remote application'
13
+
14
+ task sync: :environment do
15
+ remote_url = ENV['MOTOR_SYNC_REMOTE_URL']
16
+ api_key = ENV['MOTOR_SYNC_API_KEY']
17
+
18
+ raise 'Specify target app url using `MOTOR_SYNC_REMOTE_URL` env variable' if remote_url.blank?
19
+ raise 'Specify sync api key using `MOTOR_SYNC_API_KEY` env variable' if api_key.blank?
20
+
21
+ Motor::Configs::SyncWithRemote.call(remote_url, api_key)
22
+ Motor::Configs::WriteToFile.write_with_lock
23
+
24
+ puts "✅ Motor Admin configurations have been synced with #{remote_url}"
25
+ rescue Motor::Configs::SyncWithRemote::ApiNotFound
26
+ puts '⚠️ Synchronization failed: you need to specify `MOTOR_SYNC_API_KEY` ' \
27
+ 'env variable in your remote app in order to enable this feature'
28
+ end
29
+ end
data/lib/motor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Motor
4
- VERSION = '0.1.37'
4
+ VERSION = '0.1.39'
5
5
  end
@@ -5,9 +5,9 @@
5
5
  "fonts/ionicons.ttf?v=3.0.0-alpha.3": "fonts/ionicons.ttf",
6
6
  "fonts/ionicons.woff2?v=3.0.0-alpha.3": "fonts/ionicons.woff2",
7
7
  "fonts/ionicons.woff?v=3.0.0-alpha.3": "fonts/ionicons.woff",
8
- "main-358ea31cd7020f915067.css.gz": "main-358ea31cd7020f915067.css.gz",
9
- "main-358ea31cd7020f915067.js.LICENSE.txt": "main-358ea31cd7020f915067.js.LICENSE.txt",
10
- "main-358ea31cd7020f915067.js.gz": "main-358ea31cd7020f915067.js.gz",
11
- "main.css": "main-358ea31cd7020f915067.css",
12
- "main.js": "main-358ea31cd7020f915067.js"
8
+ "main-e443f49b386b119ff25a.css.gz": "main-e443f49b386b119ff25a.css.gz",
9
+ "main-e443f49b386b119ff25a.js.LICENSE.txt": "main-e443f49b386b119ff25a.js.LICENSE.txt",
10
+ "main-e443f49b386b119ff25a.js.gz": "main-e443f49b386b119ff25a.js.gz",
11
+ "main.css": "main-e443f49b386b119ff25a.css",
12
+ "main.js": "main-e443f49b386b119ff25a.js"
13
13
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motor-admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.37
4
+ version: 0.1.39
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pete Matsyburka
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-13 00:00:00.000000000 Z
11
+ date: 2021-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord-filter
@@ -217,22 +217,33 @@ files:
217
217
  - lib/motor/build_schema/persist_resource_configs.rb
218
218
  - lib/motor/build_schema/reorder_schema.rb
219
219
  - lib/motor/build_schema/utils.rb
220
+ - lib/motor/configs.rb
221
+ - lib/motor/configs/build_configs_hash.rb
222
+ - lib/motor/configs/build_ui_app_tag.rb
223
+ - lib/motor/configs/load_from_cache.rb
224
+ - lib/motor/configs/sync_from_file.rb
225
+ - lib/motor/configs/sync_from_hash.rb
226
+ - lib/motor/configs/sync_middleware.rb
227
+ - lib/motor/configs/sync_with_remote.rb
228
+ - lib/motor/configs/write_to_file.rb
220
229
  - lib/motor/dashboards.rb
221
230
  - lib/motor/dashboards/persistance.rb
222
231
  - lib/motor/forms.rb
223
232
  - lib/motor/forms/persistance.rb
224
233
  - lib/motor/hash_serializer.rb
234
+ - lib/motor/net_http_utils.rb
225
235
  - lib/motor/queries.rb
226
236
  - lib/motor/queries/persistance.rb
227
237
  - lib/motor/queries/postgresql_exec_query.rb
228
238
  - lib/motor/queries/render_sql_template.rb
229
239
  - lib/motor/queries/run_query.rb
240
+ - lib/motor/railtie.rb
230
241
  - lib/motor/tags.rb
231
- - lib/motor/ui_configs.rb
242
+ - lib/motor/tasks/motor.rake
232
243
  - lib/motor/version.rb
233
244
  - ui/dist/fonts/ionicons.woff2
234
- - ui/dist/main-358ea31cd7020f915067.css.gz
235
- - ui/dist/main-358ea31cd7020f915067.js.gz
245
+ - ui/dist/main-e443f49b386b119ff25a.css.gz
246
+ - ui/dist/main-e443f49b386b119ff25a.js.gz
236
247
  - ui/dist/manifest.json
237
248
  homepage:
238
249
  licenses:
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Motor
4
- module UiConfigs
5
- CACHE_STORE =
6
- if Motor.development?
7
- ActiveSupport::Cache::NullStore.new
8
- else
9
- ActiveSupport::Cache::MemoryStore.new(size: 5.megabytes)
10
- end
11
-
12
- module_function
13
-
14
- # @return [String]
15
- def app_tag
16
- CACHE_STORE.fetch(cache_key) do
17
- CACHE_STORE.clear
18
-
19
- Motor::ApplicationController.helpers.content_tag(
20
- :div, '', id: 'app', data: ui_data
21
- )
22
- end
23
- end
24
-
25
- # @return [Hash]
26
- def ui_data
27
- {
28
- base_path: Motor::Admin.routes.url_helpers.motor_path,
29
- schema: Motor::BuildSchema.call,
30
- header_links: header_links_data_hash,
31
- queries: queries_data_hash,
32
- dashboards: dashboards_data_hash,
33
- alerts: alerts_data_hash,
34
- forms: forms_data_hash
35
- }
36
- end
37
-
38
- def header_links_data_hash
39
- Motor::Config.find_by(key: 'header.links')&.value || []
40
- end
41
-
42
- def queries_data_hash
43
- Motor::Query.all.active.preload(:tags)
44
- .as_json(only: %i[id name updated_at],
45
- include: { tags: { only: %i[id name] } })
46
- end
47
-
48
- def dashboards_data_hash
49
- Motor::Dashboard.all.active.preload(:tags)
50
- .as_json(only: %i[id title updated_at],
51
- include: { tags: { only: %i[id name] } })
52
- end
53
-
54
- def alerts_data_hash
55
- Motor::Alert.all.active.preload(:tags)
56
- .as_json(only: %i[id name is_enabled updated_at],
57
- include: { tags: { only: %i[id name] } })
58
- end
59
-
60
- def forms_data_hash
61
- Motor::Form.all.active.preload(:tags)
62
- .as_json(only: %i[id name updated_at],
63
- include: { tags: { only: %i[id name] } })
64
- end
65
-
66
- # @return [String]
67
- def cache_key
68
- ActiveRecord::Base.connection.execute(
69
- "(#{
70
- [
71
- Motor::Config.select('MAX(updated_at)').to_sql,
72
- Motor::Resource.select('MAX(updated_at)').to_sql,
73
- Motor::Dashboard.select('MAX(updated_at)').to_sql,
74
- Motor::Alert.select('MAX(updated_at)').to_sql,
75
- Motor::Query.select('MAX(updated_at)').to_sql,
76
- Motor::Form.select('MAX(updated_at)').to_sql
77
- ].join(') UNION (')
78
- })"
79
- ).to_a.hash.to_s
80
- end
81
- end
82
- end