motor-admin 0.2.37 → 0.2.43
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/api_configs_controller.rb +54 -0
- data/app/controllers/motor/forms_controller.rb +2 -1
- data/app/controllers/motor/queries_controller.rb +5 -1
- data/app/controllers/motor/run_api_requests_controller.rb +49 -0
- data/app/models/motor/api_config.rb +29 -0
- data/app/models/motor/form.rb +1 -0
- data/config/locales/en.yml +6 -0
- data/config/locales/es.yml +6 -0
- data/config/locales/pt.yml +6 -0
- data/config/routes.rb +2 -0
- data/lib/generators/motor/templates/install.rb +21 -0
- data/lib/generators/motor/templates/install_api_configs.rb +80 -0
- data/lib/generators/motor/upgrade_generator.rb +28 -0
- data/lib/motor/active_record_utils/types.rb +1 -1
- data/lib/motor/alerts/persistance.rb +2 -2
- data/lib/motor/api_configs.rb +25 -0
- data/lib/motor/api_query/apply_scope.rb +11 -1
- data/lib/motor/build_schema/find_searchable_columns.rb +1 -1
- data/lib/motor/configs/build_configs_hash.rb +11 -2
- data/lib/motor/configs/load_from_cache.rb +8 -1
- data/lib/motor/configs/sync_from_hash.rb +23 -0
- data/lib/motor/dashboards/persistance.rb +2 -2
- data/lib/motor/forms/persistance.rb +23 -3
- data/lib/motor/net_http_utils.rb +14 -2
- data/lib/motor/queries/persistance.rb +19 -2
- data/lib/motor/queries/run_query.rb +3 -1
- data/lib/motor/resources/fetch_configured_model.rb +1 -1
- data/lib/motor/resources.rb +2 -1
- data/lib/motor/version.rb +1 -1
- data/lib/motor.rb +1 -0
- data/ui/dist/main-90db17316da397cbfa9c.css.gz +0 -0
- data/ui/dist/main-90db17316da397cbfa9c.js.gz +0 -0
- data/ui/dist/manifest.json +5 -5
- metadata +10 -4
- data/ui/dist/main-caf0e2eb1b4372864962.css.gz +0 -0
- data/ui/dist/main-caf0e2eb1b4372864962.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: 89d36e62222311f43cf9df24d7d090d72cb067e9cc713431ff2f7ce39ed8079c
|
4
|
+
data.tar.gz: 40f0520672effff61094e2518afcb216c6da4c2402749bac024648a5a8c491e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89e962e2d87c0171a38961d1f1d0ede97021d5eb8bb0ba071b92d3c114a919d201f11dd25647793efd656823fc7b19c7e913c51a73b7e69b7f472f8772a59fa5
|
7
|
+
data.tar.gz: 38d3105d72a4aca9fa91f0096e92d7c4ef015e0544e877ceb7b844eac6617cbdc1c25a6df20772358b5a93b29b661985d6112719cfae9e62737499306b05c695
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class ApiConfigsController < ApiBaseController
|
5
|
+
wrap_parameters :data, except: %i[include fields]
|
6
|
+
|
7
|
+
before_action :find_or_initialize_api_config, only: :create
|
8
|
+
load_and_authorize_resource
|
9
|
+
|
10
|
+
def index
|
11
|
+
render json: { data: Motor::ApiQuery::BuildJson.call(@api_configs.active.order(:id), params, current_ability) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def create
|
15
|
+
@api_config.save!
|
16
|
+
|
17
|
+
Motor::Configs::WriteToFile.call
|
18
|
+
|
19
|
+
render json: { data: Motor::ApiQuery::BuildJson.call(@api_config, params, current_ability) }
|
20
|
+
rescue ActiveRecord::RecordNotUnique
|
21
|
+
find_or_initialize_api_config
|
22
|
+
|
23
|
+
retry
|
24
|
+
end
|
25
|
+
|
26
|
+
def destroy
|
27
|
+
@api_config&.update!(deleted_at: Time.current)
|
28
|
+
|
29
|
+
Motor::Configs::WriteToFile.call
|
30
|
+
|
31
|
+
head :ok
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def find_or_initialize_api_config
|
37
|
+
name, url, description, preferences, credentials =
|
38
|
+
api_config_params.values_at(:name, :url, :description, :preferences, :credentials)
|
39
|
+
|
40
|
+
@api_config =
|
41
|
+
Motor::ApiConfig.find_or_initialize_by(name: name).tap do |config|
|
42
|
+
config.url = url.delete_suffix('/')
|
43
|
+
config.description = description
|
44
|
+
config.preferences.merge!(preferences)
|
45
|
+
config.credentials.merge!(credentials)
|
46
|
+
config.deleted_at = nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def api_config_params
|
51
|
+
params.require(:data).permit(:name, :url, :description, preferences: {}, credentials: {})
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -54,7 +54,8 @@ module Motor
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def form_params
|
57
|
-
params.require(:data).permit(:name, :description, :api_path, :http_method,
|
57
|
+
params.require(:data).permit(:name, :description, :api_path, :http_method,
|
58
|
+
:api_config_name, preferences: {}, tags: [])
|
58
59
|
end
|
59
60
|
end
|
60
61
|
end
|
@@ -21,7 +21,11 @@ module Motor
|
|
21
21
|
if Motor::Queries::Persistance.name_already_exists?(@query)
|
22
22
|
render json: { errors: [{ source: 'name', detail: 'Name already exists' }] }, status: :unprocessable_entity
|
23
23
|
else
|
24
|
-
ApplicationRecord.transaction
|
24
|
+
ApplicationRecord.transaction do
|
25
|
+
Motor::Queries::Persistance.assign_or_create_api_config!(@query)
|
26
|
+
@query.save!
|
27
|
+
end
|
28
|
+
|
25
29
|
Motor::Configs::WriteToFile.call
|
26
30
|
|
27
31
|
render json: { data: Motor::ApiQuery::BuildJson.call(@query, params, current_ability) }
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class RunApiRequestsController < ApiBaseController
|
5
|
+
JWT_TTL = 2.hours
|
6
|
+
|
7
|
+
wrap_parameters :data
|
8
|
+
|
9
|
+
def show
|
10
|
+
respond_with_result
|
11
|
+
end
|
12
|
+
|
13
|
+
def create
|
14
|
+
respond_with_result
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def respond_with_result
|
20
|
+
response = Motor::ApiConfigs.run(find_or_initialize_api_config,
|
21
|
+
method: request_params[:method],
|
22
|
+
path: request_params[:path],
|
23
|
+
body: request_params[:params],
|
24
|
+
params: request_params[:body],
|
25
|
+
headers: { 'Authorization' => "Bearer #{current_user_jwt}" })
|
26
|
+
|
27
|
+
self.response_body = response.body
|
28
|
+
self.status = response.code.to_i
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_or_initialize_api_config
|
32
|
+
Motor::ApiConfig.find_by(name: request_params[:api_config_name]) ||
|
33
|
+
Motor::ApiConfig.new(url: request_params[:api_config_name])
|
34
|
+
end
|
35
|
+
|
36
|
+
def current_user_jwt
|
37
|
+
return '' unless defined?(JWT)
|
38
|
+
return '' unless current_user
|
39
|
+
|
40
|
+
payload = { uid: current_user.id, email: current_user.email, exp: JWT_TTL.from_now.to_i }
|
41
|
+
|
42
|
+
JWT.encode(payload, Rails.application.secrets.secret_key_base)
|
43
|
+
end
|
44
|
+
|
45
|
+
def request_params
|
46
|
+
params.require(:data).permit!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class ApiConfig < ::Motor::ApplicationRecord
|
5
|
+
unless table_exists?
|
6
|
+
puts
|
7
|
+
puts ' => Run `rails g motor:upgrade && rake db:migrate` to perform data migration and enable the latest features'
|
8
|
+
puts
|
9
|
+
end
|
10
|
+
|
11
|
+
encrypts :credentials if defined?(::Motor::EncryptedConfig)
|
12
|
+
|
13
|
+
serialize :credentials, Motor::HashSerializer
|
14
|
+
serialize :preferences, Motor::HashSerializer
|
15
|
+
|
16
|
+
attribute :preferences, default: -> { HashWithIndifferentAccess.new }
|
17
|
+
attribute :credentials, default: -> { HashWithIndifferentAccess.new }
|
18
|
+
|
19
|
+
has_one :form, dependent: nil, foreign_key: :api_config_name, primary_key: :name, inverse_of: :api_config
|
20
|
+
|
21
|
+
scope :active, -> { where(deleted_at: nil) }
|
22
|
+
|
23
|
+
def headers
|
24
|
+
credentials.fetch(:headers, []).each_with_object({}) do |item, acc|
|
25
|
+
acc[item[:key]] = item[:value]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/app/models/motor/form.rb
CHANGED
@@ -5,6 +5,7 @@ module Motor
|
|
5
5
|
audited
|
6
6
|
|
7
7
|
belongs_to :author, polymorphic: true, optional: true
|
8
|
+
belongs_to :api_config, foreign_key: :api_config_name, primary_key: :name, inverse_of: :form
|
8
9
|
|
9
10
|
has_many :taggable_tags, as: :taggable, dependent: :destroy
|
10
11
|
has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
|
data/config/locales/en.yml
CHANGED
data/config/locales/es.yml
CHANGED
@@ -293,6 +293,12 @@ es:
|
|
293
293
|
regexp: RegExp
|
294
294
|
message: Mensaje
|
295
295
|
should_be_a_valid_regexp: Debe ser una cadena RegExp válida
|
296
|
+
order_by: Ordenar por
|
297
|
+
ascending: Ascendente
|
298
|
+
descending: Descendente
|
299
|
+
url: URL
|
300
|
+
expand: Expandir
|
301
|
+
add_api: De API
|
296
302
|
i:
|
297
303
|
locale: es
|
298
304
|
select:
|
data/config/locales/pt.yml
CHANGED
@@ -289,6 +289,12 @@ pt:
|
|
289
289
|
regexp: RegExp
|
290
290
|
message: Mensagem
|
291
291
|
should_be_a_valid_regexp: Deve ser uma string RegExp válida
|
292
|
+
order_by: Encomendar por
|
293
|
+
ascending: Ascendente
|
294
|
+
descending: Decrescente
|
295
|
+
url: URL
|
296
|
+
expand: Expandir
|
297
|
+
add_api: De API
|
292
298
|
i:
|
293
299
|
locale: pt
|
294
300
|
select:
|
data/config/routes.rb
CHANGED
@@ -14,6 +14,8 @@ Motor::Admin.routes.draw do
|
|
14
14
|
resources :resource_default_queries, only: %i[show], param: 'resource'
|
15
15
|
resources :schema, only: %i[index show], param: 'resource'
|
16
16
|
resources :dashboards, only: %i[index show create update destroy]
|
17
|
+
resource :run_api_request, only: %i[show create]
|
18
|
+
resources :api_configs, only: %i[index create destroy]
|
17
19
|
resources :forms, only: %i[index show create update destroy]
|
18
20
|
resources :alerts, only: %i[index show create update destroy]
|
19
21
|
resources :icons, only: %i[index]
|
@@ -44,6 +44,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
|
|
44
44
|
t.column :author_id, :bigint
|
45
45
|
t.column :author_type, :string
|
46
46
|
t.column :deleted_at, :datetime
|
47
|
+
t.column :api_config_name, :string, null: false
|
47
48
|
|
48
49
|
t.timestamps
|
49
50
|
|
@@ -138,6 +139,22 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
|
|
138
139
|
t.column :created_at, :datetime
|
139
140
|
end
|
140
141
|
|
142
|
+
create_table :motor_api_configs do |t|
|
143
|
+
t.column :name, :string, null: false
|
144
|
+
t.column :url, :string, null: false
|
145
|
+
t.column :preferences, :text, null: false
|
146
|
+
t.column :credentials, :text, null: false
|
147
|
+
t.column :description, :text
|
148
|
+
t.column :deleted_at, :datetime
|
149
|
+
|
150
|
+
t.timestamps
|
151
|
+
|
152
|
+
t.index 'name',
|
153
|
+
name: 'motor_api_configs_name_unique_index',
|
154
|
+
unique: true,
|
155
|
+
where: 'deleted_at IS NULL'
|
156
|
+
end
|
157
|
+
|
141
158
|
add_index :motor_audits, %i[auditable_type auditable_id version], name: 'motor_auditable_index'
|
142
159
|
add_index :motor_audits, %i[associated_type associated_id], name: 'motor_auditable_associated_index'
|
143
160
|
add_index :motor_audits, %i[user_id user_type], name: 'motor_auditable_user_index'
|
@@ -152,6 +169,10 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
|
|
152
169
|
name: '⭐ Star on GitHub',
|
153
170
|
path: 'https://github.com/motor-admin/motor-admin-rails'
|
154
171
|
}].to_json)
|
172
|
+
|
173
|
+
model.table_name = 'motor_api_configs'
|
174
|
+
|
175
|
+
model.create!(name: 'origin', path: '/')
|
155
176
|
end
|
156
177
|
|
157
178
|
def self.down
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
2
|
+
class MotorApiConfig < ActiveRecord::Base
|
3
|
+
self.table_name = 'motor_api_configs'
|
4
|
+
|
5
|
+
encrypts :credentials if defined?(::Motor::EncryptedConfig)
|
6
|
+
|
7
|
+
serialize :credentials, Motor::HashSerializer
|
8
|
+
serialize :preferences, Motor::HashSerializer
|
9
|
+
|
10
|
+
attribute :preferences, default: -> { HashWithIndifferentAccess.new }
|
11
|
+
attribute :credentials, default: -> { HashWithIndifferentAccess.new }
|
12
|
+
end
|
13
|
+
|
14
|
+
class MotorForm < ActiveRecord::Base
|
15
|
+
self.table_name = 'motor_forms'
|
16
|
+
end
|
17
|
+
|
18
|
+
class MotorQuery < ActiveRecord::Base
|
19
|
+
self.table_name = 'motor_queries'
|
20
|
+
|
21
|
+
serialize :preferences, Motor::HashSerializer
|
22
|
+
end
|
23
|
+
|
24
|
+
def up
|
25
|
+
create_table :motor_api_configs do |t|
|
26
|
+
t.column :name, :string, null: false
|
27
|
+
t.column :url, :string, null: false
|
28
|
+
t.column :preferences, :text, null: false
|
29
|
+
t.column :credentials, :text, null: false
|
30
|
+
t.column :description, :text
|
31
|
+
t.column :deleted_at, :datetime
|
32
|
+
|
33
|
+
t.timestamps
|
34
|
+
|
35
|
+
t.index 'name',
|
36
|
+
name: 'motor_api_configs_name_unique_index',
|
37
|
+
unique: true,
|
38
|
+
where: 'deleted_at IS NULL'
|
39
|
+
end
|
40
|
+
|
41
|
+
add_column :motor_forms, :api_config_name, :string
|
42
|
+
|
43
|
+
MotorForm.all.each do |form|
|
44
|
+
if form.api_path.starts_with?('http')
|
45
|
+
url = form.api_path[%r{\Ahttps?://[^/]+}]
|
46
|
+
|
47
|
+
form.preferences[:default_values_api_path]&.delete(url)
|
48
|
+
form.update!(api_config_name: MotorApiConfig.find_or_create_by!(name: url, url: url).name,
|
49
|
+
api_path: form.api_path.delete(url))
|
50
|
+
else
|
51
|
+
form.update!(api_config_name: MotorApiConfig.find_or_create_by!(name: 'origin', url: '/').name)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
MotorQuery.all.each do |query|
|
56
|
+
next if query.preferences['api_path'].blank?
|
57
|
+
|
58
|
+
if query.preferences['api_path'].starts_with?('http')
|
59
|
+
url = query.preferences['api_path'][%r{\Ahttps?://[^/]+}]
|
60
|
+
|
61
|
+
query.preferences['api_path'].delete(url)
|
62
|
+
|
63
|
+
query.preferences['api_config_name'] = MotorApiConfig.find_or_create_by!(name: url, url: url).name
|
64
|
+
else
|
65
|
+
query.preferences['api_config_name'] = MotorApiConfig.find_or_create_by!(name: 'origin', url: '/').name
|
66
|
+
end
|
67
|
+
|
68
|
+
query.save!
|
69
|
+
end
|
70
|
+
|
71
|
+
change_column_null :motor_forms, :api_config_name, false
|
72
|
+
|
73
|
+
MotorApiConfig.find_or_create_by!(name: 'origin', url: '/')
|
74
|
+
end
|
75
|
+
|
76
|
+
def down
|
77
|
+
remove_column :motor_forms, :api_config_name
|
78
|
+
drop_table :motor_api_configs
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
require 'rails/generators/migration'
|
5
|
+
require 'active_record'
|
6
|
+
require 'rails/generators/active_record'
|
7
|
+
require 'generators/motor/migration'
|
8
|
+
|
9
|
+
module Motor
|
10
|
+
module Generators
|
11
|
+
class UpgradeGenerator < Rails::Generators::Base
|
12
|
+
include Rails::Generators::Migration
|
13
|
+
extend Motor::Generators::Migration
|
14
|
+
|
15
|
+
source_root File.expand_path('templates', __dir__)
|
16
|
+
|
17
|
+
def copy_migration
|
18
|
+
if Motor::ApiConfig.table_exists?
|
19
|
+
puts 'The latest Motor Admin features are already configured'
|
20
|
+
else
|
21
|
+
migration_template 'install_api_configs.rb', 'db/migrate/install_motor_api_configs.rb'
|
22
|
+
|
23
|
+
puts 'Run `rake db:migrate` to update DB schema'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -53,7 +53,7 @@ module Motor
|
|
53
53
|
|
54
54
|
type_map = connection_class.connection.send(:type_map)
|
55
55
|
|
56
|
-
type_map.instance_variable_get(
|
56
|
+
type_map.instance_variable_get(:@mapping).map do |name, type|
|
57
57
|
next unless name.is_a?(String)
|
58
58
|
|
59
59
|
[type.call.class.to_s, name]
|
@@ -87,9 +87,9 @@ module Motor
|
|
87
87
|
|
88
88
|
def name_already_exists?(alert)
|
89
89
|
if alert.new_record?
|
90
|
-
Alert.exists?(name: alert.name)
|
90
|
+
Alert.exists?(name: alert.name, deleted_at: nil)
|
91
91
|
else
|
92
|
-
Alert.exists?(['name = ? AND id != ?', alert.name, alert.id])
|
92
|
+
Alert.exists?(['name = ? AND id != ? AND deleted_at IS NULL', alert.name, alert.id])
|
93
93
|
end
|
94
94
|
end
|
95
95
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module ApiConfigs
|
5
|
+
METHODS = %w[get post put delete].freeze
|
6
|
+
|
7
|
+
InvalidHttpMethod = Class.new(StandardError)
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def run(api_config, method: nil, path: nil, body: nil, params: {}, headers: {})
|
12
|
+
method ||= 'get'
|
13
|
+
|
14
|
+
raise InvalidHttpMethod unless METHODS.include?(method.downcase)
|
15
|
+
|
16
|
+
Motor::NetHttpUtils.public_send(
|
17
|
+
method.downcase.to_sym,
|
18
|
+
api_config.url.to_s.sub(%r{/?\z}, '/') + path.delete_prefix('/'),
|
19
|
+
params,
|
20
|
+
headers.merge(api_config.headers),
|
21
|
+
body
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -26,7 +26,17 @@ module Motor
|
|
26
26
|
|
27
27
|
return rel unless scope_configs
|
28
28
|
|
29
|
-
ApiQuery::Filter.call(rel, scope_configs[:preferences][:filter])
|
29
|
+
rel = ApiQuery::Filter.call(rel, scope_configs[:preferences][:filter])
|
30
|
+
|
31
|
+
apply_order(rel, scope_configs[:preferences][:sort])
|
32
|
+
end
|
33
|
+
|
34
|
+
def apply_order(rel, params)
|
35
|
+
return rel if params.blank?
|
36
|
+
|
37
|
+
sort_key, sort_order = params.values_at(:key, :order)
|
38
|
+
|
39
|
+
rel.order(sort_key => sort_order)
|
30
40
|
end
|
31
41
|
end
|
32
42
|
end
|
@@ -5,6 +5,7 @@ module Motor
|
|
5
5
|
module BuildConfigsHash
|
6
6
|
module_function
|
7
7
|
|
8
|
+
# rubocop:disable Metrics/AbcSize
|
8
9
|
def call
|
9
10
|
cache_keys = LoadFromCache.load_cache_keys
|
10
11
|
|
@@ -16,9 +17,11 @@ module Motor
|
|
16
17
|
queries: build_queries_hash(cache_keys[:queries]),
|
17
18
|
dashboards: build_dashboards_hash(cache_keys[:dashboards]),
|
18
19
|
forms: build_forms_hash(cache_keys[:forms]),
|
19
|
-
alerts: build_alerts_hash(cache_keys[:alerts])
|
20
|
+
alerts: build_alerts_hash(cache_keys[:alerts]),
|
21
|
+
api_configs: build_api_configs_hash(cache_keys[:api_configs])
|
20
22
|
)
|
21
23
|
end
|
24
|
+
# rubocop:enable Metrics/AbcSize
|
22
25
|
|
23
26
|
def build_queries_hash(cache_key = nil)
|
24
27
|
Motor::Configs::LoadFromCache.load_queries(cache_key: cache_key).sort_by(&:id).map do |query|
|
@@ -43,11 +46,17 @@ module Motor
|
|
43
46
|
|
44
47
|
def build_forms_hash(cache_key = nil)
|
45
48
|
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])
|
49
|
+
form.slice(%i[id name http_method api_path description preferences api_config_name])
|
47
50
|
.merge(tags: form.tags.map(&:name), updated_at: form.updated_at.to_time)
|
48
51
|
end
|
49
52
|
end
|
50
53
|
|
54
|
+
def build_api_configs_hash(cache_key = nil)
|
55
|
+
Motor::Configs::LoadFromCache.load_api_configs(cache_key: cache_key).sort_by(&:id).map do |config|
|
56
|
+
config.slice(%i[id name url preferences description]).merge(updated_at: config.updated_at.to_time)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
51
60
|
def build_configs_hash(cache_key = nil)
|
52
61
|
Motor::Configs::LoadFromCache.load_configs(cache_key: cache_key).sort_by(&:key).map do |config|
|
53
62
|
{
|
@@ -68,6 +68,12 @@ module Motor
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
+
def load_api_configs(cache_key: nil)
|
72
|
+
maybe_fetch_from_cache('forms', cache_key) do
|
73
|
+
Motor::ApiConfig.all.active.load
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
71
77
|
def maybe_fetch_from_cache(type, cache_key)
|
72
78
|
return yield unless cache_key
|
73
79
|
|
@@ -95,7 +101,8 @@ module Motor
|
|
95
101
|
Motor::Dashboard.select("'dashboards', MAX(updated_at)").to_sql,
|
96
102
|
Motor::Alert.select("'alerts', MAX(updated_at)").to_sql,
|
97
103
|
Motor::Query.select("'queries', MAX(updated_at)").to_sql,
|
98
|
-
Motor::Form.select("'forms', MAX(updated_at)").to_sql
|
104
|
+
Motor::Form.select("'forms', MAX(updated_at)").to_sql,
|
105
|
+
Motor::ApiConfig.select("'api_configs', MAX(updated_at)").to_sql
|
99
106
|
].join(' UNION ')
|
100
107
|
end
|
101
108
|
end
|
@@ -17,6 +17,7 @@ module Motor
|
|
17
17
|
sync_forms(configs_hash)
|
18
18
|
sync_configs(configs_hash)
|
19
19
|
sync_resources(configs_hash)
|
20
|
+
sync_api_configs(configs_hash)
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
@@ -68,6 +69,28 @@ module Motor
|
|
68
69
|
end
|
69
70
|
end
|
70
71
|
|
72
|
+
def sync_api_configs(configs_hash)
|
73
|
+
return if configs_hash[:api_configs].blank?
|
74
|
+
|
75
|
+
configs_index = Motor::Configs::LoadFromCache.load_api_configs.index_by(&:name)
|
76
|
+
|
77
|
+
configs_hash[:api_configs].each do |attrs|
|
78
|
+
record = configs_index[attrs[:name]] || Motor::ApiConfig.new
|
79
|
+
|
80
|
+
next if record.updated_at && attrs[:updated_at] <= record.updated_at
|
81
|
+
|
82
|
+
record.update!(attrs.merge(deleted_at: nil))
|
83
|
+
end
|
84
|
+
|
85
|
+
archive_api_configs(configs_index, configs_hash[:api_configs])
|
86
|
+
end
|
87
|
+
|
88
|
+
def archive_api_configs(configs_index, api_configs)
|
89
|
+
configs_index.except(*api_configs.pluck('name')).each_value do |config|
|
90
|
+
config.update!(deleted_at: Time.current) if config.deleted_at.blank?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
71
94
|
def sync_resources(configs_hash)
|
72
95
|
resources_index = Motor::Configs::LoadFromCache.load_resources.index_by(&:name)
|
73
96
|
|
@@ -63,9 +63,9 @@ module Motor
|
|
63
63
|
|
64
64
|
def title_already_exists?(dashboard)
|
65
65
|
if dashboard.new_record?
|
66
|
-
Motor::Dashboard.exists?(title: dashboard.title)
|
66
|
+
Motor::Dashboard.exists?(title: dashboard.title, deleted_at: nil)
|
67
67
|
else
|
68
|
-
Motor::Dashboard.exists?(['title = ? AND id != ?', dashboard.title, dashboard.id])
|
68
|
+
Motor::Dashboard.exists?(['title = ? AND id != ? AND deleted_at IS NULL', dashboard.title, dashboard.id])
|
69
69
|
end
|
70
70
|
end
|
71
71
|
end
|
@@ -38,6 +38,7 @@ module Motor
|
|
38
38
|
|
39
39
|
ApplicationRecord.transaction do
|
40
40
|
archive_with_existing_name(form) if force_replace
|
41
|
+
find_or_assign_api_config(form)
|
41
42
|
|
42
43
|
form.save!
|
43
44
|
end
|
@@ -50,12 +51,31 @@ module Motor
|
|
50
51
|
end
|
51
52
|
|
52
53
|
def assign_attributes(form, params)
|
53
|
-
form.assign_attributes(params.slice(:name, :description, :api_path, :http_method, :preferences
|
54
|
+
form.assign_attributes(params.slice(:name, :description, :api_path, :http_method, :preferences,
|
55
|
+
:api_config_name))
|
54
56
|
form.updated_at = [params[:updated_at], Time.current].min if params[:updated_at].present?
|
55
57
|
|
58
|
+
find_or_assign_api_config(form)
|
59
|
+
|
56
60
|
Motor::Tags.assign_tags(form, params[:tags])
|
57
61
|
end
|
58
62
|
|
63
|
+
def find_or_assign_api_config(form)
|
64
|
+
return if form.api_config.present?
|
65
|
+
|
66
|
+
config = Motor::ApiConfig.find_by(url: [form.api_config_name, form.api_config_name.delete_suffix('/')])
|
67
|
+
|
68
|
+
if config
|
69
|
+
config.update!(deleted_at: nil)
|
70
|
+
|
71
|
+
form.api_config_name = config.name
|
72
|
+
else
|
73
|
+
form.api_config = Motor::ApiConfig.new(url: form.api_config_name.delete_suffix('/')).tap do |c|
|
74
|
+
c.name = c.url.sub(%r{\Ahttps?://}, '').delete_suffix('/')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
59
79
|
def archive_with_existing_name(form)
|
60
80
|
Motor::Form.where(['name = ? AND id != ?', form.name, form.id])
|
61
81
|
.update_all(deleted_at: Time.current)
|
@@ -63,9 +83,9 @@ module Motor
|
|
63
83
|
|
64
84
|
def name_already_exists?(form)
|
65
85
|
if form.new_record?
|
66
|
-
Motor::Form.exists?(['name = ?', form.name])
|
86
|
+
Motor::Form.exists?(['name = ? AND deleted_at IS NULL', form.name])
|
67
87
|
else
|
68
|
-
Motor::Form.exists?(['name = ? AND id != ?', form.name, form.id])
|
88
|
+
Motor::Form.exists?(['name = ? AND id != ? AND deleted_at IS NULL', form.name, form.id])
|
69
89
|
end
|
70
90
|
end
|
71
91
|
end
|
data/lib/motor/net_http_utils.rb
CHANGED
@@ -4,7 +4,7 @@ module Motor
|
|
4
4
|
module NetHttpUtils
|
5
5
|
module_function
|
6
6
|
|
7
|
-
def get(url, params = {}, headers = {})
|
7
|
+
def get(url, params = {}, headers = {}, _body = nil)
|
8
8
|
request = build_request(Net::HTTP::Get, url, params, headers, nil)
|
9
9
|
|
10
10
|
execute_request(request)
|
@@ -16,9 +16,21 @@ module Motor
|
|
16
16
|
execute_request(request)
|
17
17
|
end
|
18
18
|
|
19
|
+
def put(url, params = {}, headers = {}, body = '')
|
20
|
+
request = build_request(Net::HTTP::Put, url, params, headers, body)
|
21
|
+
|
22
|
+
execute_request(request)
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(url, params = {}, headers = {}, body = '')
|
26
|
+
request = build_request(Net::HTTP::Delete, url, params, headers, body)
|
27
|
+
|
28
|
+
execute_request(request)
|
29
|
+
end
|
30
|
+
|
19
31
|
def build_request(method_class, url, params, headers, body)
|
20
32
|
uri = URI(url)
|
21
|
-
uri.query = params.to_query
|
33
|
+
uri.query = params.to_query if params.present?
|
22
34
|
|
23
35
|
request = method_class.new(uri)
|
24
36
|
request.body = body if body.present?
|
@@ -38,6 +38,7 @@ module Motor
|
|
38
38
|
|
39
39
|
ApplicationRecord.transaction do
|
40
40
|
archive_with_existing_name(query) if force_replace
|
41
|
+
assign_or_create_api_config!(query)
|
41
42
|
|
42
43
|
query.save!
|
43
44
|
end
|
@@ -61,11 +62,27 @@ module Motor
|
|
61
62
|
.update_all(deleted_at: Time.current)
|
62
63
|
end
|
63
64
|
|
65
|
+
def assign_or_create_api_config!(query)
|
66
|
+
api_config_name = query.preferences[:api_config_name]
|
67
|
+
|
68
|
+
return if api_config_name.blank?
|
69
|
+
return if Motor::ApiConfig.find_by(name: api_config_name)
|
70
|
+
|
71
|
+
config = Motor::ApiConfig.find_by(url: [api_config_name, api_config_name.delete_suffix('/')])
|
72
|
+
|
73
|
+
config&.update!(deleted_at: nil)
|
74
|
+
|
75
|
+
config ||= Motor::ApiConfig.create!(name: api_config_name.sub(%r{\Ahttps?://}, '').delete_suffix('/'),
|
76
|
+
url: api_config_name.delete_suffix('/'))
|
77
|
+
|
78
|
+
query.preferences[:api_config_name] = config.name
|
79
|
+
end
|
80
|
+
|
64
81
|
def name_already_exists?(query)
|
65
82
|
if query.new_record?
|
66
|
-
Query.exists?(name: query.name)
|
83
|
+
Query.exists?(name: query.name, deleted_at: nil)
|
67
84
|
else
|
68
|
-
Query.exists?(['name = ? AND id != ?', query.name, query.id])
|
85
|
+
Query.exists?(['name = ? AND id != ? AND deleted_at IS NULL', query.name, query.id])
|
69
86
|
end
|
70
87
|
end
|
71
88
|
end
|
@@ -194,7 +194,9 @@ module Motor
|
|
194
194
|
def normalize_statement_for_sql(statement)
|
195
195
|
sql, _, attributes = statement
|
196
196
|
|
197
|
-
sql = ActiveRecord::Base.
|
197
|
+
sql = ActiveRecord::Base.send(:replace_bind_variables,
|
198
|
+
sql.gsub(STATEMENT_VARIABLE_REGEXP, '?'),
|
199
|
+
attributes.map(&:value))
|
198
200
|
|
199
201
|
[sql, 'SQL', attributes]
|
200
202
|
end
|
@@ -41,7 +41,7 @@ module Motor
|
|
41
41
|
def define_default_scope(klass, config)
|
42
42
|
return klass if config[:custom_sql].blank?
|
43
43
|
|
44
|
-
klass.instance_variable_set(:@__motor_custom_sql, config[:custom_sql].squish)
|
44
|
+
klass.instance_variable_set(:@__motor_custom_sql, config[:custom_sql].squish.delete_suffix(';'))
|
45
45
|
|
46
46
|
klass.instance_eval do
|
47
47
|
default_scope do
|
data/lib/motor/resources.rb
CHANGED
@@ -4,7 +4,8 @@ module Motor
|
|
4
4
|
module Resources
|
5
5
|
RESOURCE_ATTRS = %w[display_name display_column icon custom_sql visible display_primary_key
|
6
6
|
searchable_columns].freeze
|
7
|
-
COLUMN_ATTRS = %w[name display_name column_type access_type default_value reference virtual format
|
7
|
+
COLUMN_ATTRS = %w[name display_name column_type access_type default_value reference virtual format
|
8
|
+
validators].freeze
|
8
9
|
ASSOCIATION_ATTRS = %w[name display_name model_name icon visible foreign_key primary_key options virtual
|
9
10
|
polymorphic slug].freeze
|
10
11
|
SCOPE_ATTRS = %w[name display_name scope_type preferences visible].freeze
|
data/lib/motor/version.rb
CHANGED
data/lib/motor.rb
CHANGED
Binary file
|
Binary file
|
data/ui/dist/manifest.json
CHANGED
@@ -2601,9 +2601,9 @@
|
|
2601
2601
|
"icons/zoom-out.svg.gz": "icons/zoom-out.svg.gz",
|
2602
2602
|
"icons/zoom-question.svg": "icons/zoom-question.svg",
|
2603
2603
|
"icons/zoom-question.svg.gz": "icons/zoom-question.svg.gz",
|
2604
|
-
"main-
|
2605
|
-
"main-
|
2606
|
-
"main-
|
2607
|
-
"main.css": "main-
|
2608
|
-
"main.js": "main-
|
2604
|
+
"main-90db17316da397cbfa9c.css.gz": "main-90db17316da397cbfa9c.css.gz",
|
2605
|
+
"main-90db17316da397cbfa9c.js.LICENSE.txt": "main-90db17316da397cbfa9c.js.LICENSE.txt",
|
2606
|
+
"main-90db17316da397cbfa9c.js.gz": "main-90db17316da397cbfa9c.js.gz",
|
2607
|
+
"main.css": "main-90db17316da397cbfa9c.css",
|
2608
|
+
"main.js": "main-90db17316da397cbfa9c.js"
|
2609
2609
|
}
|
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.2.
|
4
|
+
version: 0.2.43
|
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-12-
|
11
|
+
date: 2021-12-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord-filter
|
@@ -114,6 +114,7 @@ files:
|
|
114
114
|
- app/controllers/motor/active_storage_attachments_controller.rb
|
115
115
|
- app/controllers/motor/alerts_controller.rb
|
116
116
|
- app/controllers/motor/api_base_controller.rb
|
117
|
+
- app/controllers/motor/api_configs_controller.rb
|
117
118
|
- app/controllers/motor/application_controller.rb
|
118
119
|
- app/controllers/motor/assets_controller.rb
|
119
120
|
- app/controllers/motor/audits_controller.rb
|
@@ -127,6 +128,7 @@ files:
|
|
127
128
|
- app/controllers/motor/resource_default_queries_controller.rb
|
128
129
|
- app/controllers/motor/resource_methods_controller.rb
|
129
130
|
- app/controllers/motor/resources_controller.rb
|
131
|
+
- app/controllers/motor/run_api_requests_controller.rb
|
130
132
|
- app/controllers/motor/run_queries_controller.rb
|
131
133
|
- app/controllers/motor/schema_controller.rb
|
132
134
|
- app/controllers/motor/send_alerts_controller.rb
|
@@ -139,6 +141,7 @@ files:
|
|
139
141
|
- app/mailers/motor/application_mailer.rb
|
140
142
|
- app/models/motor/alert.rb
|
141
143
|
- app/models/motor/alert_lock.rb
|
144
|
+
- app/models/motor/api_config.rb
|
142
145
|
- app/models/motor/application_record.rb
|
143
146
|
- app/models/motor/audit.rb
|
144
147
|
- app/models/motor/config.rb
|
@@ -158,6 +161,8 @@ files:
|
|
158
161
|
- lib/generators/motor/install_generator.rb
|
159
162
|
- lib/generators/motor/migration.rb
|
160
163
|
- lib/generators/motor/templates/install.rb
|
164
|
+
- lib/generators/motor/templates/install_api_configs.rb
|
165
|
+
- lib/generators/motor/upgrade_generator.rb
|
161
166
|
- lib/motor-admin.rb
|
162
167
|
- lib/motor.rb
|
163
168
|
- lib/motor/active_record_utils.rb
|
@@ -175,6 +180,7 @@ files:
|
|
175
180
|
- lib/motor/alerts/persistance.rb
|
176
181
|
- lib/motor/alerts/scheduled_alerts_cache.rb
|
177
182
|
- lib/motor/alerts/scheduler.rb
|
183
|
+
- lib/motor/api_configs.rb
|
178
184
|
- lib/motor/api_query.rb
|
179
185
|
- lib/motor/api_query/apply_scope.rb
|
180
186
|
- lib/motor/api_query/build_json.rb
|
@@ -1526,8 +1532,8 @@ files:
|
|
1526
1532
|
- ui/dist/icons/zoom-money.svg.gz
|
1527
1533
|
- ui/dist/icons/zoom-out.svg.gz
|
1528
1534
|
- ui/dist/icons/zoom-question.svg.gz
|
1529
|
-
- ui/dist/main-
|
1530
|
-
- ui/dist/main-
|
1535
|
+
- ui/dist/main-90db17316da397cbfa9c.css.gz
|
1536
|
+
- ui/dist/main-90db17316da397cbfa9c.js.gz
|
1531
1537
|
- ui/dist/manifest.json
|
1532
1538
|
homepage:
|
1533
1539
|
licenses:
|
Binary file
|
Binary file
|