motor-admin 0.1.37 → 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 (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