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.
- checksums.yaml +4 -4
- data/app/controllers/motor/alerts_controller.rb +12 -6
- data/app/controllers/motor/configs_controller.rb +1 -0
- data/app/controllers/motor/dashboards_controller.rb +4 -0
- data/app/controllers/motor/forms_controller.rb +4 -0
- data/app/controllers/motor/queries_controller.rb +4 -0
- data/app/controllers/motor/resources_controller.rb +1 -0
- data/app/controllers/motor/ui_controller.rb +4 -0
- data/app/views/motor/ui/show.html.erb +1 -1
- data/lib/motor.rb +10 -2
- data/lib/motor/admin.rb +8 -0
- data/lib/motor/alerts/persistance.rb +4 -1
- data/lib/motor/alerts/scheduler.rb +1 -1
- data/lib/motor/build_schema.rb +3 -3
- data/lib/motor/build_schema/merge_schema_configs.rb +8 -4
- data/lib/motor/build_schema/reorder_schema.rb +10 -4
- data/lib/motor/configs.rb +16 -0
- data/lib/motor/configs/build_configs_hash.rb +83 -0
- data/lib/motor/configs/build_ui_app_tag.rb +71 -0
- data/lib/motor/configs/load_from_cache.rb +81 -0
- data/lib/motor/configs/sync_from_file.rb +34 -0
- data/lib/motor/configs/sync_from_hash.rb +124 -0
- data/lib/motor/configs/sync_middleware.rb +72 -0
- data/lib/motor/configs/sync_with_remote.rb +39 -0
- data/lib/motor/configs/write_to_file.rb +36 -0
- data/lib/motor/dashboards/persistance.rb +4 -1
- data/lib/motor/forms/persistance.rb +4 -1
- data/lib/motor/net_http_utils.rb +37 -0
- data/lib/motor/queries/persistance.rb +4 -1
- data/lib/motor/railtie.rb +11 -0
- data/lib/motor/tasks/motor.rake +29 -0
- data/lib/motor/version.rb +1 -1
- data/ui/dist/{main-358ea31cd7020f915067.css.gz → main-e443f49b386b119ff25a.css.gz} +0 -0
- data/ui/dist/main-e443f49b386b119ff25a.js.gz +0 -0
- data/ui/dist/manifest.json +5 -5
- metadata +16 -5
- data/lib/motor/ui_configs.rb +0 -82
- data/ui/dist/main-358ea31cd7020f915067.js.gz +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e0ab742c32ae1b59bfa12c6eb1f838e5d379cb5a964521fe7f345de5fabf953
|
4
|
+
data.tar.gz: 9f018c3475ae06b4104e35f30774c80b2098a8a23b7fa3a8e4ac35df089ee98e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
|
@@ -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
|
|
@@ -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::
|
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?('
|
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/
|
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
|
data/lib/motor/build_schema.rb
CHANGED
@@ -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::
|
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::
|
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,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
Binary file
|
Binary file
|
data/ui/dist/manifest.json
CHANGED
@@ -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-
|
9
|
-
"main-
|
10
|
-
"main-
|
11
|
-
"main.css": "main-
|
12
|
-
"main.js": "main-
|
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.
|
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-
|
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/
|
242
|
+
- lib/motor/tasks/motor.rake
|
232
243
|
- lib/motor/version.rb
|
233
244
|
- ui/dist/fonts/ionicons.woff2
|
234
|
-
- ui/dist/main-
|
235
|
-
- ui/dist/main-
|
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:
|
data/lib/motor/ui_configs.rb
DELETED
@@ -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
|
Binary file
|