motor-admin 0.1.39 → 0.1.44
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/active_storage_attachments_controller.rb +1 -1
- data/app/models/motor/alert.rb +2 -2
- data/app/models/motor/dashboard.rb +1 -1
- data/app/models/motor/form.rb +1 -1
- data/app/models/motor/query.rb +2 -1
- data/config/routes.rb +0 -2
- data/lib/generators/motor/templates/install.rb +10 -10
- data/lib/motor.rb +1 -1
- data/lib/motor/active_record_utils.rb +1 -0
- data/lib/motor/active_record_utils/active_storage_blob_patch.rb +25 -0
- data/lib/motor/active_record_utils/defined_scopes_extension.rb +1 -1
- data/lib/motor/admin.rb +12 -6
- data/lib/motor/alerts/persistance.rb +14 -3
- data/lib/motor/api_query/apply_scope.rb +12 -5
- data/lib/motor/build_schema.rb +1 -0
- data/lib/motor/build_schema/adjust_devise_model_schema.rb +60 -0
- data/lib/motor/build_schema/find_display_column.rb +32 -29
- data/lib/motor/build_schema/load_from_rails.rb +61 -31
- data/lib/motor/configs.rb +1 -0
- data/lib/motor/configs/build_configs_hash.rb +2 -2
- data/lib/motor/configs/sync_from_file.rb +3 -1
- data/lib/motor/configs/sync_from_hash.rb +4 -2
- data/lib/motor/configs/sync_middleware.rb +9 -9
- data/lib/motor/configs/sync_with_remote.rb +10 -2
- data/lib/motor/dashboards/persistance.rb +11 -4
- data/lib/motor/forms/persistance.rb +11 -4
- data/lib/motor/net_http_utils.rb +6 -5
- data/lib/motor/queries/persistance.rb +9 -2
- data/lib/motor/tasks/motor.rake +8 -0
- data/lib/motor/version.rb +1 -1
- data/ui/dist/{main-e443f49b386b119ff25a.css.gz → main-e763d59007bb725ea622.css.gz} +0 -0
- data/ui/dist/main-e763d59007bb725ea622.js.gz +0 -0
- data/ui/dist/manifest.json +5 -5
- metadata +6 -4
- data/ui/dist/main-e443f49b386b119ff25a.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: 74c91b2c5dbc0b48fa75a4fa9f5ec2eb8a3848b2e65cf819b9ddc3317e379a19
|
4
|
+
data.tar.gz: bee5733d821cde96004674d36c9be0467e32044d921a4b52e863b4be4e3de55b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9bb22f83f9c270696f2d32cc384b98d90a5dab99f7f377258682dbc3622bd574ea0c6bf43de5cd955bb551dd56d6d43b4e618a53803a7d91417a7ec88ae9571f
|
7
|
+
data.tar.gz: 6e1f295abe63ca50e7176789523747810384307c03bcf5c4c719cc41a85296573fb0f6c3e193f06e625ed5801c204a519675539719d966357398293fe3a10977
|
data/app/models/motor/alert.rb
CHANGED
@@ -7,8 +7,8 @@ module Motor
|
|
7
7
|
belongs_to :query
|
8
8
|
belongs_to :author, polymorphic: true, optional: true
|
9
9
|
|
10
|
-
has_many :alert_locks
|
11
|
-
has_many :taggable_tags, as: :taggable
|
10
|
+
has_many :alert_locks, dependent: :destroy
|
11
|
+
has_many :taggable_tags, as: :taggable, dependent: :destroy
|
12
12
|
has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
|
13
13
|
|
14
14
|
serialize :preferences, HashSerializer
|
@@ -6,7 +6,7 @@ module Motor
|
|
6
6
|
|
7
7
|
belongs_to :author, polymorphic: true, optional: true
|
8
8
|
|
9
|
-
has_many :taggable_tags, as: :taggable
|
9
|
+
has_many :taggable_tags, as: :taggable, dependent: :destroy
|
10
10
|
has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
|
11
11
|
|
12
12
|
serialize :preferences, HashSerializer
|
data/app/models/motor/form.rb
CHANGED
@@ -6,7 +6,7 @@ module Motor
|
|
6
6
|
|
7
7
|
belongs_to :author, polymorphic: true, optional: true
|
8
8
|
|
9
|
-
has_many :taggable_tags, as: :taggable
|
9
|
+
has_many :taggable_tags, as: :taggable, dependent: :destroy
|
10
10
|
has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
|
11
11
|
|
12
12
|
serialize :preferences, HashSerializer
|
data/app/models/motor/query.rb
CHANGED
@@ -6,8 +6,9 @@ module Motor
|
|
6
6
|
|
7
7
|
belongs_to :author, polymorphic: true, optional: true
|
8
8
|
|
9
|
-
has_many :taggable_tags, as: :taggable
|
9
|
+
has_many :taggable_tags, as: :taggable, dependent: :destroy
|
10
10
|
has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
|
11
|
+
has_many :alerts, dependent: :destroy
|
11
12
|
|
12
13
|
serialize :preferences, HashSerializer
|
13
14
|
|
data/config/routes.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
2
2
|
def self.up
|
3
|
-
create_table :motor_queries
|
3
|
+
create_table :motor_queries do |t|
|
4
4
|
t.column :name, :string, null: false
|
5
5
|
t.column :description, :string
|
6
6
|
t.column :sql_body, :string, null: false
|
@@ -18,7 +18,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
|
|
18
18
|
where: 'deleted_at IS NULL'
|
19
19
|
end
|
20
20
|
|
21
|
-
create_table :motor_dashboards
|
21
|
+
create_table :motor_dashboards do |t|
|
22
22
|
t.column :title, :string, null: false
|
23
23
|
t.column :description, :string
|
24
24
|
t.column :preferences, :string, null: false, default: '{}'
|
@@ -35,7 +35,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
|
|
35
35
|
where: 'deleted_at IS NULL'
|
36
36
|
end
|
37
37
|
|
38
|
-
create_table :motor_forms
|
38
|
+
create_table :motor_forms do |t|
|
39
39
|
t.column :name, :string, null: false
|
40
40
|
t.column :description, :string
|
41
41
|
t.column :api_path, :string, null: false
|
@@ -54,7 +54,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
|
|
54
54
|
where: 'deleted_at IS NULL'
|
55
55
|
end
|
56
56
|
|
57
|
-
create_table :motor_resources
|
57
|
+
create_table :motor_resources do |t|
|
58
58
|
t.column :name, :string, null: false, index: { unique: true }
|
59
59
|
t.column :preferences, :string, null: false, default: '{}'
|
60
60
|
|
@@ -63,7 +63,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
|
|
63
63
|
t.index :updated_at
|
64
64
|
end
|
65
65
|
|
66
|
-
create_table :motor_configs
|
66
|
+
create_table :motor_configs do |t|
|
67
67
|
t.column :key, :string, null: false, index: { unique: true }
|
68
68
|
t.column :value, :string, null: false, default: '{}'
|
69
69
|
|
@@ -72,7 +72,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
|
|
72
72
|
t.index :updated_at
|
73
73
|
end
|
74
74
|
|
75
|
-
create_table :motor_alerts
|
75
|
+
create_table :motor_alerts do |t|
|
76
76
|
t.references :query, null: false, foreign_key: { to_table: :motor_queries }, index: true
|
77
77
|
t.column :name, :string, null: false
|
78
78
|
t.column :description, :string
|
@@ -92,7 +92,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
|
|
92
92
|
where: 'deleted_at IS NULL'
|
93
93
|
end
|
94
94
|
|
95
|
-
create_table :motor_alert_locks
|
95
|
+
create_table :motor_alert_locks do |t|
|
96
96
|
t.references :alert, null: false, foreign_key: { to_table: :motor_alerts }
|
97
97
|
t.column :lock_timestamp, :string, null: false
|
98
98
|
|
@@ -101,7 +101,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
|
|
101
101
|
t.index %i[alert_id lock_timestamp], unique: true
|
102
102
|
end
|
103
103
|
|
104
|
-
create_table :motor_tags
|
104
|
+
create_table :motor_tags do |t|
|
105
105
|
t.column :name, :string, null: false
|
106
106
|
|
107
107
|
t.timestamps
|
@@ -111,7 +111,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
|
|
111
111
|
unique: true
|
112
112
|
end
|
113
113
|
|
114
|
-
create_table :motor_taggable_tags
|
114
|
+
create_table :motor_taggable_tags do |t|
|
115
115
|
t.references :tag, null: false, foreign_key: { to_table: :motor_tags }, index: true
|
116
116
|
t.column :taggable_id, :integer, null: false
|
117
117
|
t.column :taggable_type, :string, null: false
|
@@ -121,7 +121,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
|
|
121
121
|
unique: true
|
122
122
|
end
|
123
123
|
|
124
|
-
create_table :motor_audits
|
124
|
+
create_table :motor_audits do |t|
|
125
125
|
t.column :auditable_id, :integer
|
126
126
|
t.column :auditable_type, :string
|
127
127
|
t.column :associated_id, :integer
|
data/lib/motor.rb
CHANGED
@@ -53,6 +53,7 @@ end
|
|
53
53
|
require 'motor/version'
|
54
54
|
require 'motor/admin'
|
55
55
|
require 'motor/assets'
|
56
|
+
require 'motor/active_record_utils'
|
56
57
|
require 'motor/build_schema'
|
57
58
|
require 'motor/api_query'
|
58
59
|
require 'motor/tags'
|
@@ -62,6 +63,5 @@ require 'motor/dashboards'
|
|
62
63
|
require 'motor/forms'
|
63
64
|
require 'motor/alerts'
|
64
65
|
require 'motor/hash_serializer'
|
65
|
-
require 'motor/active_record_utils'
|
66
66
|
require 'motor/net_http_utils'
|
67
67
|
require 'motor/railtie'
|
@@ -7,3 +7,4 @@ require_relative './active_record_utils/types'
|
|
7
7
|
require_relative './active_record_utils/fetch_methods'
|
8
8
|
require_relative './active_record_utils/defined_scopes_extension'
|
9
9
|
require_relative './active_record_utils/active_storage_links_extension'
|
10
|
+
require_relative './active_record_utils/active_storage_blob_patch'
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module ActiveRecordUtils
|
5
|
+
module ActiveStorageBlobPatch
|
6
|
+
KEYWORD_ARGS = %i[io filename content_type metadata identify].freeze
|
7
|
+
|
8
|
+
def build_after_upload(hash)
|
9
|
+
super(**hash.with_indifferent_access.slice(*KEYWORD_ARGS).symbolize_keys)
|
10
|
+
end
|
11
|
+
|
12
|
+
def build_after_unfurling(hash)
|
13
|
+
super(**hash.with_indifferent_access.slice(*KEYWORD_ARGS).symbolize_keys)
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_after_unfurling!(hash)
|
17
|
+
super(**hash.with_indifferent_access.slice(*KEYWORD_ARGS).symbolize_keys)
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_and_upload!(hash)
|
21
|
+
super(**hash.with_indifferent_access.slice(*KEYWORD_ARGS).symbolize_keys)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/motor/admin.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'motor/configs/sync_middleware'
|
4
|
-
|
5
3
|
module Motor
|
6
4
|
class Admin < ::Rails::Engine
|
7
5
|
initializer 'motor.startup_message' do
|
8
|
-
|
6
|
+
config.after_initialize do
|
9
7
|
next unless Motor.server?
|
10
8
|
|
9
|
+
Rails.application.reload_routes!
|
10
|
+
|
11
11
|
if Rails.application.routes.url_helpers.respond_to?(:motor_admin_path)
|
12
12
|
url =
|
13
13
|
begin
|
@@ -31,9 +31,11 @@ module Motor
|
|
31
31
|
end
|
32
32
|
|
33
33
|
initializer 'motor.configs.sync_middleware' do
|
34
|
-
if Motor::Configs::
|
35
|
-
|
36
|
-
|
34
|
+
next if Motor::Configs::SYNC_ACCESS_KEY.blank?
|
35
|
+
|
36
|
+
require 'motor/configs/sync_middleware'
|
37
|
+
|
38
|
+
Rails.application.config.middleware.insert_after(Rails::Rack::Logger, Motor::Configs::SyncMiddleware)
|
37
39
|
end
|
38
40
|
|
39
41
|
initializer 'motor.filter_params' do
|
@@ -67,6 +69,10 @@ module Motor
|
|
67
69
|
ActiveSupport.on_load(:active_storage_attachment) do
|
68
70
|
ActiveStorage::Attachment.include(Motor::ActiveRecordUtils::ActiveStorageLinksExtension)
|
69
71
|
end
|
72
|
+
|
73
|
+
ActiveSupport.on_load(:active_storage_blob) do
|
74
|
+
ActiveStorage::Blob.singleton_class.prepend(Motor::ActiveRecordUtils::ActiveStorageBlobPatch)
|
75
|
+
end
|
70
76
|
end
|
71
77
|
end
|
72
78
|
end
|
@@ -43,25 +43,31 @@ module Motor
|
|
43
43
|
retry
|
44
44
|
end
|
45
45
|
|
46
|
-
def update_from_params!(alert, params)
|
46
|
+
def update_from_params!(alert, params, force_replace: false)
|
47
47
|
tag_ids = alert.tags.ids
|
48
48
|
|
49
49
|
alert = assign_attributes(alert, params)
|
50
50
|
|
51
|
-
raise NameAlreadyExists if name_already_exists?(alert)
|
51
|
+
raise NameAlreadyExists if !force_replace && name_already_exists?(alert)
|
52
52
|
raise InvalidInterval unless alert.cron
|
53
53
|
|
54
54
|
ApplicationRecord.transaction do
|
55
|
+
archive_with_existing_name(alert) if force_replace
|
56
|
+
|
55
57
|
alert.save!
|
56
58
|
end
|
57
59
|
|
58
|
-
alert.touch if tag_ids
|
60
|
+
alert.touch if tags_changed?(tag_ids, alert) && params[:updated_at].blank?
|
59
61
|
|
60
62
|
alert
|
61
63
|
rescue ActiveRecord::RecordNotUnique
|
62
64
|
retry
|
63
65
|
end
|
64
66
|
|
67
|
+
def tags_changed?(previous_ids, alert)
|
68
|
+
previous_ids.sort != alert.tags.reload.ids.sort
|
69
|
+
end
|
70
|
+
|
65
71
|
def assign_attributes(alert, params)
|
66
72
|
alert.assign_attributes(params.slice(*ALERT_ATTRIBUTES))
|
67
73
|
alert.preferences[:interval] = normalize_interval(alert.preferences[:interval])
|
@@ -70,6 +76,11 @@ module Motor
|
|
70
76
|
Motor::Tags.assign_tags(alert, params[:tags])
|
71
77
|
end
|
72
78
|
|
79
|
+
def archive_with_existing_name(alert)
|
80
|
+
Motor::Alert.where(['lower(name) = ? AND id != ?', alert.name.to_s.downcase, alert.id])
|
81
|
+
.update_all(deleted_at: Time.current)
|
82
|
+
end
|
83
|
+
|
73
84
|
def normalize_interval(interval)
|
74
85
|
interval.to_s.gsub(NORMALIZE_INTERVAL_REGEXP, 'every ')
|
75
86
|
end
|
@@ -13,13 +13,20 @@ module Motor
|
|
13
13
|
if rel.klass.defined_scopes.include?(scope_symbol)
|
14
14
|
rel.public_send(scope_symbol)
|
15
15
|
else
|
16
|
-
|
17
|
-
|
16
|
+
apply_filter_scope(rel, scope)
|
17
|
+
end
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
+
def apply_filter_scope(rel, scope)
|
21
|
+
configs = Motor::Resource.find_by_name(rel.klass.name.underscore)
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
+
return rel unless configs
|
24
|
+
|
25
|
+
scope_configs = configs.preferences[:scopes].find { |s| s[:name] == scope }
|
26
|
+
|
27
|
+
return rel unless scope_configs
|
28
|
+
|
29
|
+
ApiQuery::Filter.call(rel, scope_configs[:preferences][:filter])
|
23
30
|
end
|
24
31
|
end
|
25
32
|
end
|
data/lib/motor/build_schema.rb
CHANGED
@@ -68,6 +68,7 @@ module Motor
|
|
68
68
|
end
|
69
69
|
|
70
70
|
require_relative './build_schema/active_storage_attachment_schema'
|
71
|
+
require_relative './build_schema/adjust_devise_model_schema'
|
71
72
|
require_relative './build_schema/load_from_rails'
|
72
73
|
require_relative './build_schema/find_display_column'
|
73
74
|
require_relative './build_schema/persist_resource_configs'
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module BuildSchema
|
5
|
+
module AdjustDeviseModelSchema
|
6
|
+
HIDDEN_COLUMNS = %w[
|
7
|
+
encrypted_password
|
8
|
+
reset_password_token
|
9
|
+
confirmation_token
|
10
|
+
].freeze
|
11
|
+
|
12
|
+
READ_ONLY_COLUMNS = %w[
|
13
|
+
reset_password_sent_at
|
14
|
+
remember_created_at
|
15
|
+
sign_in_count
|
16
|
+
current_sign_in_at
|
17
|
+
last_sign_in_at
|
18
|
+
current_sign_in_ip
|
19
|
+
last_sign_in_ip
|
20
|
+
confirmed_at
|
21
|
+
confirmation_sent_at
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
module_function
|
25
|
+
|
26
|
+
def call(schema, devise_modules)
|
27
|
+
modify_column_access_types!(schema[:columns])
|
28
|
+
add_password_column!(schema[:columns]) if devise_modules.include?(:database_authenticatable)
|
29
|
+
|
30
|
+
schema
|
31
|
+
end
|
32
|
+
|
33
|
+
def modify_column_access_types!(columns)
|
34
|
+
columns.each do |column|
|
35
|
+
column[:access_type] =
|
36
|
+
case column[:name]
|
37
|
+
when *HIDDEN_COLUMNS
|
38
|
+
ColumnAccessTypes::HIDDEN
|
39
|
+
when *READ_ONLY_COLUMNS
|
40
|
+
ColumnAccessTypes::READ_ONLY
|
41
|
+
else
|
42
|
+
column[:access_type]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_password_column!(columns)
|
48
|
+
columns << {
|
49
|
+
name: 'password',
|
50
|
+
display_name: 'Password',
|
51
|
+
column_type: 'string',
|
52
|
+
access_type: 'write_only',
|
53
|
+
default_value: nil,
|
54
|
+
validators: [],
|
55
|
+
virtual: true
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -3,35 +3,38 @@
|
|
3
3
|
module Motor
|
4
4
|
module BuildSchema
|
5
5
|
module FindDisplayColumn
|
6
|
-
DISPLAY_NAMES =
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
6
|
+
DISPLAY_NAMES = Set.new(
|
7
|
+
%w[
|
8
|
+
name
|
9
|
+
full_name
|
10
|
+
fullname
|
11
|
+
last_name
|
12
|
+
lastname
|
13
|
+
first_name
|
14
|
+
firstname
|
15
|
+
fname
|
16
|
+
lname
|
17
|
+
sname
|
18
|
+
company
|
19
|
+
domain
|
20
|
+
website
|
21
|
+
title
|
22
|
+
phone
|
23
|
+
phone_number
|
24
|
+
email
|
25
|
+
phone
|
26
|
+
filename
|
27
|
+
file_name
|
28
|
+
url
|
29
|
+
make
|
30
|
+
brand
|
31
|
+
manufacturer
|
32
|
+
model
|
33
|
+
address
|
34
|
+
]
|
35
|
+
).freeze
|
36
|
+
|
37
|
+
DISPLAY_NAME_REGEXP = Regexp.new(Regexp.union(DISPLAY_NAMES.to_a).source, Regexp::IGNORECASE)
|
35
38
|
|
36
39
|
module_function
|
37
40
|
|
@@ -4,12 +4,21 @@ module Motor
|
|
4
4
|
module BuildSchema
|
5
5
|
module LoadFromRails
|
6
6
|
MUTEX = Mutex.new
|
7
|
+
UNIFIED_TYPES = ActiveRecordUtils::Types::UNIFIED_TYPES
|
7
8
|
|
8
9
|
module_function
|
9
10
|
|
10
11
|
def call
|
11
12
|
models.map do |model|
|
12
|
-
|
13
|
+
Object.const_get(model.name)
|
14
|
+
|
15
|
+
schema = build_model_schema(model)
|
16
|
+
|
17
|
+
if model.respond_to?(:devise_modules)
|
18
|
+
Motor::BuildSchema::AdjustDeviseModelSchema.call(schema, model.devise_modules)
|
19
|
+
end
|
20
|
+
|
21
|
+
schema
|
13
22
|
rescue StandardError, NotImplementedError => e
|
14
23
|
Rails.logger.error(e) if model.name != 'Audited::Audit'
|
15
24
|
|
@@ -20,8 +29,7 @@ module Motor
|
|
20
29
|
def models
|
21
30
|
eager_load_models!
|
22
31
|
|
23
|
-
models =
|
24
|
-
models = models.reject(&:abstract_class)
|
32
|
+
models = ActiveRecord::Base.descendants.reject(&:abstract_class)
|
25
33
|
|
26
34
|
models -= Motor::ApplicationRecord.descendants
|
27
35
|
models -= [Motor::Audit]
|
@@ -32,12 +40,6 @@ module Motor
|
|
32
40
|
models
|
33
41
|
end
|
34
42
|
|
35
|
-
def load_descendants(model)
|
36
|
-
model.descendants + model.descendants.flat_map do |klass|
|
37
|
-
load_descendants(klass)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
43
|
def build_model_schema(model)
|
42
44
|
model_name = model.name
|
43
45
|
|
@@ -92,10 +94,13 @@ module Motor
|
|
92
94
|
end
|
93
95
|
|
94
96
|
def build_table_column(column, model, default_attrs)
|
97
|
+
is_enum = model.defined_enums[column.name]
|
98
|
+
|
95
99
|
{
|
96
100
|
name: column.name,
|
97
101
|
display_name: column.name.humanize,
|
98
|
-
column_type:
|
102
|
+
column_type: is_enum ? 'string' : UNIFIED_TYPES[column.type.to_s] || column.type.to_s,
|
103
|
+
is_array: column.array?,
|
99
104
|
access_type: COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE),
|
100
105
|
default_value: default_attrs[column.name],
|
101
106
|
validators: fetch_validators(model, column.name),
|
@@ -111,13 +116,15 @@ module Motor
|
|
111
116
|
model.reflections.map do |name, ref|
|
112
117
|
next if !ref.has_one? && !ref.belongs_to?
|
113
118
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
+
unless ref.polymorphic?
|
120
|
+
begin
|
121
|
+
next if ref.klass.name == 'ActiveStorage::Blob'
|
122
|
+
rescue StandardError => e
|
123
|
+
Rails.logger.error(e)
|
119
124
|
|
120
|
-
|
125
|
+
next
|
126
|
+
end
|
127
|
+
end
|
121
128
|
|
122
129
|
build_reflection_column(name, model, ref, default_attrs)
|
123
130
|
end.compact
|
@@ -125,7 +132,7 @@ module Motor
|
|
125
132
|
|
126
133
|
def build_reflection_column(name, model, ref, default_attrs)
|
127
134
|
column_name = ref.belongs_to? ? ref.foreign_key.to_s : name
|
128
|
-
is_attachment = ref.klass.name == 'ActiveStorage::Attachment'
|
135
|
+
is_attachment = !ref.polymorphic? && ref.klass.name == 'ActiveStorage::Attachment'
|
129
136
|
access_type = ref.belongs_to? || is_attachment ? ColumnAccessTypes::READ_WRITE : ColumnAccessTypes::READ_ONLY
|
130
137
|
|
131
138
|
{
|
@@ -136,26 +143,26 @@ module Motor
|
|
136
143
|
default_value: default_attrs[column_name],
|
137
144
|
validators: fetch_validators(model, column_name, ref),
|
138
145
|
format: {},
|
139
|
-
reference:
|
140
|
-
name: name,
|
141
|
-
model_name: ref.klass.name.underscore,
|
142
|
-
reference_type: ref.belongs_to? ? 'belongs_to' : 'has_one',
|
143
|
-
foreign_key: ref.foreign_key,
|
144
|
-
polymorphic: ref.polymorphic? || is_attachment
|
145
|
-
},
|
146
|
+
reference: build_reference(name, ref),
|
146
147
|
virtual: false
|
147
148
|
}
|
148
149
|
end
|
149
150
|
|
151
|
+
def build_reference(name, reflection)
|
152
|
+
{
|
153
|
+
name: name,
|
154
|
+
display_name: name.humanize,
|
155
|
+
model_name: reflection.polymorphic? ? nil : reflection.klass.name.underscore,
|
156
|
+
reference_type: reflection.belongs_to? ? 'belongs_to' : 'has_one',
|
157
|
+
foreign_key: reflection.foreign_key,
|
158
|
+
polymorphic: reflection.polymorphic?
|
159
|
+
}
|
160
|
+
end
|
161
|
+
|
150
162
|
def fetch_associations(model)
|
151
163
|
model.reflections.map do |name, ref|
|
152
164
|
next if ref.has_one? || ref.belongs_to?
|
153
|
-
|
154
|
-
begin
|
155
|
-
ref.klass
|
156
|
-
rescue StandardError
|
157
|
-
next
|
158
|
-
end
|
165
|
+
next unless valid_reflection?(ref)
|
159
166
|
|
160
167
|
model_class = ref.klass
|
161
168
|
|
@@ -167,7 +174,7 @@ module Motor
|
|
167
174
|
slug: name.underscore,
|
168
175
|
model_name: model_class.name.underscore,
|
169
176
|
foreign_key: ref.foreign_key,
|
170
|
-
polymorphic: ref.
|
177
|
+
polymorphic: ref.options[:as].present?,
|
171
178
|
visible: true
|
172
179
|
}
|
173
180
|
end.compact
|
@@ -181,6 +188,10 @@ module Motor
|
|
181
188
|
[]
|
182
189
|
end
|
183
190
|
|
191
|
+
enum = model.defined_enums[column_name]
|
192
|
+
|
193
|
+
validators += [{ includes: enum.keys }] if enum
|
194
|
+
|
184
195
|
validators += model.validators_on(column_name).map do |validator|
|
185
196
|
build_validator_hash(validator)
|
186
197
|
end.compact
|
@@ -203,6 +214,17 @@ module Motor
|
|
203
214
|
end
|
204
215
|
end
|
205
216
|
|
217
|
+
def valid_reflection?(reflection)
|
218
|
+
reflection.klass
|
219
|
+
reflection.foreign_key
|
220
|
+
|
221
|
+
true
|
222
|
+
rescue StandardError => e
|
223
|
+
Rails.logger.error(e)
|
224
|
+
|
225
|
+
false
|
226
|
+
end
|
227
|
+
|
206
228
|
def eager_load_models!
|
207
229
|
MUTEX.synchronize do
|
208
230
|
if Rails::VERSION::MAJOR > 5 && defined?(Zeitwerk::Loader)
|
@@ -210,6 +232,14 @@ module Motor
|
|
210
232
|
else
|
211
233
|
Rails.application.eager_load!
|
212
234
|
end
|
235
|
+
|
236
|
+
ActiveRecord::Base.descendants.each do |model|
|
237
|
+
model.reflections.each do |_, ref|
|
238
|
+
ref.klass
|
239
|
+
rescue StandardError
|
240
|
+
next
|
241
|
+
end
|
242
|
+
end
|
213
243
|
end
|
214
244
|
end
|
215
245
|
end
|
data/lib/motor/configs.rb
CHANGED
@@ -9,8 +9,8 @@ module Motor
|
|
9
9
|
cache_keys = LoadFromCache.load_cache_keys
|
10
10
|
|
11
11
|
normalize_hash(
|
12
|
-
|
13
|
-
file_version: cache_keys.values.max.to_time,
|
12
|
+
engine_version: Motor::VERSION,
|
13
|
+
file_version: cache_keys.values.compact.max.to_time,
|
14
14
|
resources: build_resources_hash(cache_keys[:resources]),
|
15
15
|
configs: build_configs_hash(cache_keys[:configs]),
|
16
16
|
queries: build_queries_hash(cache_keys[:queries]),
|
@@ -9,7 +9,7 @@ module Motor
|
|
9
9
|
|
10
10
|
module_function
|
11
11
|
|
12
|
-
def call
|
12
|
+
def call(with_exception: false)
|
13
13
|
MUTEXT.synchronize do
|
14
14
|
file = Rails.root.join(FILE_PATH)
|
15
15
|
|
@@ -17,6 +17,8 @@ module Motor
|
|
17
17
|
begin
|
18
18
|
file.ctime
|
19
19
|
rescue Errno::ENOENT
|
20
|
+
raise if with_exception
|
21
|
+
|
20
22
|
nil
|
21
23
|
end
|
22
24
|
|
@@ -6,6 +6,8 @@ module Motor
|
|
6
6
|
module_function
|
7
7
|
|
8
8
|
def call(configs_hash)
|
9
|
+
return if configs_hash.blank?
|
10
|
+
|
9
11
|
configs_hash = configs_hash.with_indifferent_access
|
10
12
|
|
11
13
|
Motor::ApplicationRecord.transaction do
|
@@ -100,7 +102,7 @@ module Motor
|
|
100
102
|
|
101
103
|
next if record.updated_at >= attrs[:updated_at]
|
102
104
|
|
103
|
-
update_proc.call(record, attrs)
|
105
|
+
update_proc.call(record, attrs, force_replace: true)
|
104
106
|
end
|
105
107
|
end
|
106
108
|
|
@@ -108,7 +110,7 @@ module Motor
|
|
108
110
|
create_items.each do |attrs|
|
109
111
|
record = records_class.find_or_initialize_by(id: attrs[:id]).tap { |e| e.deleted_at = nil }
|
110
112
|
|
111
|
-
update_proc.call(record, attrs)
|
113
|
+
update_proc.call(record, attrs, force_replace: true)
|
112
114
|
end
|
113
115
|
end
|
114
116
|
|
@@ -3,8 +3,6 @@
|
|
3
3
|
module Motor
|
4
4
|
module Configs
|
5
5
|
class SyncMiddleware
|
6
|
-
ACCESS_KEY = ENV.fetch('MOTOR_SYNC_API_KEY', '')
|
7
|
-
|
8
6
|
KeyNotSpecified = Class.new(StandardError)
|
9
7
|
NotAuthenticated = Class.new(StandardError)
|
10
8
|
|
@@ -14,13 +12,15 @@ module Motor
|
|
14
12
|
|
15
13
|
def call(env)
|
16
14
|
if env['PATH_INFO'] == Motor::Configs::SYNC_API_PATH
|
17
|
-
authenticate!(env['
|
15
|
+
authenticate!(env['HTTP_X_AUTHORIZATION'])
|
18
16
|
|
19
17
|
case env['REQUEST_METHOD']
|
20
18
|
when 'GET'
|
21
19
|
respond_with_configs
|
22
20
|
when 'POST'
|
23
|
-
|
21
|
+
input = env['rack.input']
|
22
|
+
input.rewind
|
23
|
+
sync_configs(input.read)
|
24
24
|
else
|
25
25
|
@app.call(env)
|
26
26
|
end
|
@@ -35,14 +35,14 @@ module Motor
|
|
35
35
|
|
36
36
|
private
|
37
37
|
|
38
|
-
def authenticate!(
|
39
|
-
raise KeyNotSpecified if
|
40
|
-
raise NotAuthenticated if
|
38
|
+
def authenticate!(token)
|
39
|
+
raise KeyNotSpecified if Motor::Configs::SYNC_ACCESS_KEY.blank?
|
40
|
+
raise NotAuthenticated if token.blank?
|
41
41
|
|
42
42
|
is_token_valid =
|
43
43
|
ActiveSupport::SecurityUtils.secure_compare(
|
44
|
-
Digest::SHA256.hexdigest(
|
45
|
-
Digest::SHA256.hexdigest(
|
44
|
+
Digest::SHA256.hexdigest(token),
|
45
|
+
Digest::SHA256.hexdigest(Motor::Configs::SYNC_ACCESS_KEY)
|
46
46
|
)
|
47
47
|
|
48
48
|
raise NotAuthenticated unless is_token_valid
|
@@ -16,7 +16,7 @@ module Motor
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def sync_from_remote!(remote_url, api_key)
|
19
|
-
response = Motor::NetHttpUtils.get(remote_url, {
|
19
|
+
response = Motor::NetHttpUtils.get(remote_url, {}, { 'X-Authorization' => api_key })
|
20
20
|
|
21
21
|
raise ApiNotFound if response.is_a?(Net::HTTPNotFound)
|
22
22
|
raise UnableToSync, [response.message, response.body].join(': ') unless response.is_a?(Net::HTTPSuccess)
|
@@ -29,7 +29,15 @@ module Motor
|
|
29
29
|
def sync_to_remote!(remote_url, api_key)
|
30
30
|
configs_hash = Motor::Configs::BuildConfigsHash.call
|
31
31
|
|
32
|
-
response = Motor::NetHttpUtils.post(
|
32
|
+
response = Motor::NetHttpUtils.post(
|
33
|
+
remote_url,
|
34
|
+
{},
|
35
|
+
{
|
36
|
+
'X-Authorization' => api_key,
|
37
|
+
'Content-Type' => 'application/json'
|
38
|
+
},
|
39
|
+
configs_hash.to_json
|
40
|
+
)
|
33
41
|
|
34
42
|
raise ApiNotFound if response.is_a?(Net::HTTPNotFound)
|
35
43
|
raise UnableToSync, [response.message, response.body].join(': ') unless response.is_a?(Net::HTTPSuccess)
|
@@ -29,14 +29,16 @@ module Motor
|
|
29
29
|
retry
|
30
30
|
end
|
31
31
|
|
32
|
-
def update_from_params!(dashboard, params)
|
32
|
+
def update_from_params!(dashboard, params, force_replace: false)
|
33
33
|
tag_ids = dashboard.tags.ids
|
34
34
|
|
35
35
|
dashboard = assign_attributes(dashboard, params)
|
36
36
|
|
37
|
-
raise TitleAlreadyExists if title_already_exists?(dashboard)
|
37
|
+
raise TitleAlreadyExists if !force_replace && title_already_exists?(dashboard)
|
38
38
|
|
39
39
|
ApplicationRecord.transaction do
|
40
|
+
archive_with_existing_name(dashboard) if force_replace
|
41
|
+
|
40
42
|
dashboard.save!
|
41
43
|
end
|
42
44
|
|
@@ -54,11 +56,16 @@ module Motor
|
|
54
56
|
Motor::Tags.assign_tags(dashboard, params[:tags])
|
55
57
|
end
|
56
58
|
|
59
|
+
def archive_with_existing_name(dashboard)
|
60
|
+
Motor::Dashboard.where(['lower(title) = ? AND id != ?', dashboard.title.to_s.downcase, dashboard.id])
|
61
|
+
.update_all(deleted_at: Time.current)
|
62
|
+
end
|
63
|
+
|
57
64
|
def title_already_exists?(dashboard)
|
58
65
|
if dashboard.new_record?
|
59
|
-
Dashboard.exists?(['lower(title) = ?', dashboard.title.to_s.downcase])
|
66
|
+
Motor::Dashboard.exists?(['lower(title) = ?', dashboard.title.to_s.downcase])
|
60
67
|
else
|
61
|
-
Dashboard.exists?(['lower(title) = ? AND id != ?', dashboard.title.to_s.downcase, dashboard.id])
|
68
|
+
Motor::Dashboard.exists?(['lower(title) = ? AND id != ?', dashboard.title.to_s.downcase, dashboard.id])
|
62
69
|
end
|
63
70
|
end
|
64
71
|
end
|
@@ -29,14 +29,16 @@ module Motor
|
|
29
29
|
retry
|
30
30
|
end
|
31
31
|
|
32
|
-
def update_from_params!(form, params)
|
32
|
+
def update_from_params!(form, params, force_replace: false)
|
33
33
|
tag_ids = form.tags.ids
|
34
34
|
|
35
35
|
form = assign_attributes(form, params)
|
36
36
|
|
37
|
-
raise NameAlreadyExists if name_already_exists?(form)
|
37
|
+
raise NameAlreadyExists if !force_replace && name_already_exists?(form)
|
38
38
|
|
39
39
|
ApplicationRecord.transaction do
|
40
|
+
archive_with_existing_name(form) if force_replace
|
41
|
+
|
40
42
|
form.save!
|
41
43
|
end
|
42
44
|
|
@@ -54,11 +56,16 @@ module Motor
|
|
54
56
|
Motor::Tags.assign_tags(form, params[:tags])
|
55
57
|
end
|
56
58
|
|
59
|
+
def archive_with_existing_name(form)
|
60
|
+
Motor::Form.where(['lower(name) = ? AND id != ?', form.name.to_s.downcase, form.id])
|
61
|
+
.update_all(deleted_at: Time.current)
|
62
|
+
end
|
63
|
+
|
57
64
|
def name_already_exists?(form)
|
58
65
|
if form.new_record?
|
59
|
-
Form.exists?(['lower(name) = ?', form.name.to_s.downcase])
|
66
|
+
Motor::Form.exists?(['lower(name) = ?', form.name.to_s.downcase])
|
60
67
|
else
|
61
|
-
Form.exists?(['lower(name) = ? AND id != ?', form.name.to_s.downcase, form.id])
|
68
|
+
Motor::Form.exists?(['lower(name) = ? AND id != ?', form.name.to_s.downcase, form.id])
|
62
69
|
end
|
63
70
|
end
|
64
71
|
end
|
data/lib/motor/net_http_utils.rb
CHANGED
@@ -4,24 +4,25 @@ module Motor
|
|
4
4
|
module NetHttpUtils
|
5
5
|
module_function
|
6
6
|
|
7
|
-
def get(url, params = {})
|
8
|
-
request = build_request(Net::HTTP::Get, url, params, nil)
|
7
|
+
def get(url, params = {}, headers = {})
|
8
|
+
request = build_request(Net::HTTP::Get, url, params, headers, nil)
|
9
9
|
|
10
10
|
execute_request(request)
|
11
11
|
end
|
12
12
|
|
13
|
-
def post(url, params = {}, body = '')
|
14
|
-
request = build_request(Net::HTTP::Post, url, params, body)
|
13
|
+
def post(url, params = {}, headers = {}, body = '')
|
14
|
+
request = build_request(Net::HTTP::Post, url, params, headers, body)
|
15
15
|
|
16
16
|
execute_request(request)
|
17
17
|
end
|
18
18
|
|
19
|
-
def build_request(method_class, url, params, body)
|
19
|
+
def build_request(method_class, url, params, headers, body)
|
20
20
|
uri = URI(url)
|
21
21
|
uri.query = params.to_query
|
22
22
|
|
23
23
|
request = method_class.new(uri)
|
24
24
|
request.body = body if body.present?
|
25
|
+
headers.each { |key, value| request[key] = value }
|
25
26
|
|
26
27
|
request
|
27
28
|
end
|
@@ -29,14 +29,16 @@ module Motor
|
|
29
29
|
retry
|
30
30
|
end
|
31
31
|
|
32
|
-
def update_from_params!(query, params)
|
32
|
+
def update_from_params!(query, params, force_replace: false)
|
33
33
|
tag_ids = query.tags.ids
|
34
34
|
|
35
35
|
query = assign_attributes(query, params)
|
36
36
|
|
37
|
-
raise NameAlreadyExists if name_already_exists?(query)
|
37
|
+
raise NameAlreadyExists if !force_replace && name_already_exists?(query)
|
38
38
|
|
39
39
|
ApplicationRecord.transaction do
|
40
|
+
archive_with_existing_name(query) if force_replace
|
41
|
+
|
40
42
|
query.save!
|
41
43
|
end
|
42
44
|
|
@@ -54,6 +56,11 @@ module Motor
|
|
54
56
|
Motor::Tags.assign_tags(query, params[:tags])
|
55
57
|
end
|
56
58
|
|
59
|
+
def archive_with_existing_name(query)
|
60
|
+
Motor::Query.where(['lower(name) = ? AND id != ?', query.name.to_s.downcase, query.id])
|
61
|
+
.update_all(deleted_at: Time.current)
|
62
|
+
end
|
63
|
+
|
57
64
|
def name_already_exists?(query)
|
58
65
|
if query.new_record?
|
59
66
|
Query.exists?(['lower(name) = ?', query.name.to_s.downcase])
|
data/lib/motor/tasks/motor.rake
CHANGED
@@ -9,6 +9,14 @@ namespace :motor do
|
|
9
9
|
puts '✅ configs/motor.yml has been updated'
|
10
10
|
end
|
11
11
|
|
12
|
+
desc 'Load configs from configs/motor.yml file'
|
13
|
+
|
14
|
+
task load: :environment do
|
15
|
+
Motor::Configs::SyncFromFile.call(with_exception: true)
|
16
|
+
|
17
|
+
puts '✅ configs have been loaded from configs/motor.yml'
|
18
|
+
end
|
19
|
+
|
12
20
|
desc 'Synchronize configs with remote application'
|
13
21
|
|
14
22
|
task sync: :environment do
|
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-e763d59007bb725ea622.css.gz": "main-e763d59007bb725ea622.css.gz",
|
9
|
+
"main-e763d59007bb725ea622.js.LICENSE.txt": "main-e763d59007bb725ea622.js.LICENSE.txt",
|
10
|
+
"main-e763d59007bb725ea622.js.gz": "main-e763d59007bb725ea622.js.gz",
|
11
|
+
"main.css": "main-e763d59007bb725ea622.css",
|
12
|
+
"main.js": "main-e763d59007bb725ea622.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.44
|
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-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord-filter
|
@@ -191,6 +191,7 @@ files:
|
|
191
191
|
- lib/motor-admin.rb
|
192
192
|
- lib/motor.rb
|
193
193
|
- lib/motor/active_record_utils.rb
|
194
|
+
- lib/motor/active_record_utils/active_storage_blob_patch.rb
|
194
195
|
- lib/motor/active_record_utils/active_storage_links_extension.rb
|
195
196
|
- lib/motor/active_record_utils/defined_scopes_extension.rb
|
196
197
|
- lib/motor/active_record_utils/fetch_methods.rb
|
@@ -211,6 +212,7 @@ files:
|
|
211
212
|
- lib/motor/assets.rb
|
212
213
|
- lib/motor/build_schema.rb
|
213
214
|
- lib/motor/build_schema/active_storage_attachment_schema.rb
|
215
|
+
- lib/motor/build_schema/adjust_devise_model_schema.rb
|
214
216
|
- lib/motor/build_schema/find_display_column.rb
|
215
217
|
- lib/motor/build_schema/load_from_rails.rb
|
216
218
|
- lib/motor/build_schema/merge_schema_configs.rb
|
@@ -242,8 +244,8 @@ files:
|
|
242
244
|
- lib/motor/tasks/motor.rake
|
243
245
|
- lib/motor/version.rb
|
244
246
|
- ui/dist/fonts/ionicons.woff2
|
245
|
-
- ui/dist/main-
|
246
|
-
- ui/dist/main-
|
247
|
+
- ui/dist/main-e763d59007bb725ea622.css.gz
|
248
|
+
- ui/dist/main-e763d59007bb725ea622.js.gz
|
247
249
|
- ui/dist/manifest.json
|
248
250
|
homepage:
|
249
251
|
licenses:
|
Binary file
|