motor-admin 0.2.37 → 0.2.43
Sign up to get free protection for your applications and to get access to all the features.
- 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
|