motor-admin-cstham8 0.4.35
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +661 -0
- data/README.md +230 -0
- data/Rakefile +11 -0
- data/app/channels/motor/application_cable/channel.rb +14 -0
- data/app/channels/motor/application_cable/connection.rb +27 -0
- data/app/channels/motor/notes_channel.rb +9 -0
- data/app/channels/motor/notifications_channel.rb +9 -0
- data/app/controllers/concerns/motor/current_ability.rb +21 -0
- data/app/controllers/concerns/motor/current_user_method.rb +18 -0
- data/app/controllers/concerns/motor/load_and_authorize_dynamic_resource.rb +73 -0
- data/app/controllers/concerns/motor/wrap_io_params.rb +25 -0
- data/app/controllers/motor/active_storage_attachments_controller.rb +64 -0
- data/app/controllers/motor/alerts_controller.rb +82 -0
- data/app/controllers/motor/api_base_controller.rb +33 -0
- data/app/controllers/motor/api_configs_controller.rb +54 -0
- data/app/controllers/motor/application_controller.rb +8 -0
- data/app/controllers/motor/assets_controller.rb +43 -0
- data/app/controllers/motor/audits_controller.rb +16 -0
- data/app/controllers/motor/auth_tokens_controller.rb +36 -0
- data/app/controllers/motor/configs_controller.rb +33 -0
- data/app/controllers/motor/dashboards_controller.rb +64 -0
- data/app/controllers/motor/data_controller.rb +88 -0
- data/app/controllers/motor/forms_controller.rb +61 -0
- data/app/controllers/motor/icons_controller.rb +22 -0
- data/app/controllers/motor/note_tags_controller.rb +13 -0
- data/app/controllers/motor/notes_controller.rb +58 -0
- data/app/controllers/motor/notifications_controller.rb +33 -0
- data/app/controllers/motor/queries_controller.rb +64 -0
- data/app/controllers/motor/reminders_controller.rb +38 -0
- data/app/controllers/motor/resource_default_queries_controller.rb +23 -0
- data/app/controllers/motor/resource_methods_controller.rb +23 -0
- data/app/controllers/motor/resources_controller.rb +26 -0
- data/app/controllers/motor/run_api_requests_controller.rb +56 -0
- data/app/controllers/motor/run_graphql_requests_controller.rb +48 -0
- data/app/controllers/motor/run_queries_controller.rb +77 -0
- data/app/controllers/motor/schema_controller.rb +31 -0
- data/app/controllers/motor/send_alerts_controller.rb +26 -0
- data/app/controllers/motor/sessions_controller.rb +23 -0
- data/app/controllers/motor/slack_conversations_controller.rb +11 -0
- data/app/controllers/motor/tags_controller.rb +11 -0
- data/app/controllers/motor/ui_controller.rb +51 -0
- data/app/controllers/motor/users_for_autocomplete_controller.rb +23 -0
- data/app/jobs/motor/alert_sending_job.rb +13 -0
- data/app/jobs/motor/application_job.rb +6 -0
- data/app/jobs/motor/notify_note_mentions_job.rb +9 -0
- data/app/jobs/motor/notify_reminder_job.rb +9 -0
- data/app/mailers/motor/alerts_mailer.rb +39 -0
- data/app/mailers/motor/application_mailer.rb +33 -0
- data/app/mailers/motor/notifications_mailer.rb +33 -0
- data/app/models/motor/alert.rb +30 -0
- data/app/models/motor/alert_lock.rb +7 -0
- data/app/models/motor/api_config.rb +28 -0
- data/app/models/motor/application_record.rb +18 -0
- data/app/models/motor/audit.rb +13 -0
- data/app/models/motor/config.rb +13 -0
- data/app/models/motor/dashboard.rb +26 -0
- data/app/models/motor/form.rb +23 -0
- data/app/models/motor/note.rb +18 -0
- data/app/models/motor/note_tag.rb +7 -0
- data/app/models/motor/note_tag_tag.rb +8 -0
- data/app/models/motor/notification.rb +14 -0
- data/app/models/motor/query.rb +33 -0
- data/app/models/motor/reminder.rb +13 -0
- data/app/models/motor/resource.rb +15 -0
- data/app/models/motor/tag.rb +7 -0
- data/app/models/motor/taggable_tag.rb +8 -0
- data/app/views/layouts/motor/application.html.erb +17 -0
- data/app/views/layouts/motor/mailer.html.erb +72 -0
- data/app/views/motor/alerts_mailer/alert_email.html.erb +54 -0
- data/app/views/motor/notifications_mailer/notify_mention_email.html.erb +28 -0
- data/app/views/motor/notifications_mailer/notify_reminder_email.html.erb +28 -0
- data/app/views/motor/ui/show.html.erb +1 -0
- data/config/locales/el.yml +420 -0
- data/config/locales/en.yml +340 -0
- data/config/locales/es.yml +420 -0
- data/config/locales/ja.yml +340 -0
- data/config/locales/pt.yml +416 -0
- data/config/routes.rb +65 -0
- data/lib/generators/motor/install_generator.rb +24 -0
- data/lib/generators/motor/install_notes_generator.rb +22 -0
- data/lib/generators/motor/migration.rb +17 -0
- data/lib/generators/motor/templates/install.rb +271 -0
- data/lib/generators/motor/templates/install_api_configs.rb +86 -0
- data/lib/generators/motor/templates/install_notes.rb +83 -0
- data/lib/generators/motor/templates/upgrade_motor_api_actions.rb +71 -0
- data/lib/generators/motor/upgrade_generator.rb +43 -0
- data/lib/motor/active_record_utils/action_text_attribute_patch.rb +19 -0
- data/lib/motor/active_record_utils/active_record_connection_column_patch.rb +14 -0
- data/lib/motor/active_record_utils/active_record_filter.rb +405 -0
- data/lib/motor/active_record_utils/active_storage_blob_patch.rb +30 -0
- data/lib/motor/active_record_utils/active_storage_links_extension.rb +11 -0
- data/lib/motor/active_record_utils/defined_scopes_extension.rb +25 -0
- data/lib/motor/active_record_utils/fetch_methods.rb +24 -0
- data/lib/motor/active_record_utils/types.rb +64 -0
- data/lib/motor/active_record_utils.rb +45 -0
- data/lib/motor/admin.rb +141 -0
- data/lib/motor/alerts/persistance.rb +97 -0
- data/lib/motor/alerts/scheduled_alerts_cache.rb +29 -0
- data/lib/motor/alerts/scheduler.rb +30 -0
- data/lib/motor/alerts/slack_sender.rb +74 -0
- data/lib/motor/alerts.rb +52 -0
- data/lib/motor/api_configs.rb +41 -0
- data/lib/motor/api_query/apply_scope.rb +44 -0
- data/lib/motor/api_query/build_json.rb +171 -0
- data/lib/motor/api_query/build_meta.rb +20 -0
- data/lib/motor/api_query/filter.rb +125 -0
- data/lib/motor/api_query/paginate.rb +19 -0
- data/lib/motor/api_query/search.rb +60 -0
- data/lib/motor/api_query/sort.rb +64 -0
- data/lib/motor/api_query.rb +24 -0
- data/lib/motor/assets.rb +62 -0
- data/lib/motor/build_schema/active_storage_attachment_schema.rb +125 -0
- data/lib/motor/build_schema/adjust_devise_model_schema.rb +60 -0
- data/lib/motor/build_schema/apply_permissions.rb +64 -0
- data/lib/motor/build_schema/defaults.rb +66 -0
- data/lib/motor/build_schema/find_display_column.rb +65 -0
- data/lib/motor/build_schema/find_icon.rb +135 -0
- data/lib/motor/build_schema/find_searchable_columns.rb +33 -0
- data/lib/motor/build_schema/load_from_rails.rb +361 -0
- data/lib/motor/build_schema/merge_schema_configs.rb +157 -0
- data/lib/motor/build_schema/reorder_schema.rb +88 -0
- data/lib/motor/build_schema/utils.rb +31 -0
- data/lib/motor/build_schema.rb +125 -0
- data/lib/motor/cancan_utils/ability_patch.rb +31 -0
- data/lib/motor/cancan_utils/can_manage_all.rb +14 -0
- data/lib/motor/cancan_utils.rb +9 -0
- data/lib/motor/configs/build_configs_hash.rb +90 -0
- data/lib/motor/configs/build_ui_app_tag.rb +177 -0
- data/lib/motor/configs/load_from_cache.rb +110 -0
- data/lib/motor/configs/sync_from_file.rb +35 -0
- data/lib/motor/configs/sync_from_hash.rb +159 -0
- data/lib/motor/configs/sync_middleware.rb +72 -0
- data/lib/motor/configs/sync_with_remote.rb +47 -0
- data/lib/motor/configs/write_to_file.rb +36 -0
- data/lib/motor/configs.rb +39 -0
- data/lib/motor/dashboards/persistance.rb +73 -0
- data/lib/motor/dashboards.rb +8 -0
- data/lib/motor/forms/persistance.rb +93 -0
- data/lib/motor/forms.rb +8 -0
- data/lib/motor/hash_serializer.rb +21 -0
- data/lib/motor/net_http_utils.rb +50 -0
- data/lib/motor/notes/notify_mentions.rb +71 -0
- data/lib/motor/notes/notify_reminder.rb +48 -0
- data/lib/motor/notes/persist.rb +36 -0
- data/lib/motor/notes/reminders_scheduler.rb +39 -0
- data/lib/motor/notes/tags.rb +34 -0
- data/lib/motor/notes.rb +12 -0
- data/lib/motor/queries/persistance.rb +90 -0
- data/lib/motor/queries/postgresql_exec_query.rb +28 -0
- data/lib/motor/queries/render_sql_template.rb +61 -0
- data/lib/motor/queries/run_query.rb +289 -0
- data/lib/motor/queries.rb +11 -0
- data/lib/motor/railtie.rb +11 -0
- data/lib/motor/resources/custom_sql_columns_cache.rb +17 -0
- data/lib/motor/resources/fetch_configured_model.rb +269 -0
- data/lib/motor/resources/persist_configs.rb +232 -0
- data/lib/motor/resources.rb +19 -0
- data/lib/motor/slack/client.rb +62 -0
- data/lib/motor/slack.rb +16 -0
- data/lib/motor/tags.rb +32 -0
- data/lib/motor/tasks/motor.rake +54 -0
- data/lib/motor/version.rb +5 -0
- data/lib/motor-admin-cstham8.rb +3 -0
- data/lib/motor.rb +87 -0
- data/ui/dist/manifest.json +1990 -0
- metadata +303 -0
@@ -0,0 +1,56 @@
|
|
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
|
+
# rubocop:disable Metrics/AbcSize
|
20
|
+
def respond_with_result
|
21
|
+
response = Motor::ApiConfigs.run(find_or_initialize_api_config,
|
22
|
+
method: request_params[:method],
|
23
|
+
path: request_params[:path],
|
24
|
+
body: request_params[:body],
|
25
|
+
params: request_params[:params],
|
26
|
+
headers: { 'Authorization' => "Bearer #{current_user_jwt}" })
|
27
|
+
response.to_hash.each do |key, (value)|
|
28
|
+
next if key.casecmp('transfer-encoding').zero?
|
29
|
+
|
30
|
+
headers[key] = value
|
31
|
+
end
|
32
|
+
|
33
|
+
self.response_body = response.body
|
34
|
+
self.status = response.code.to_i
|
35
|
+
end
|
36
|
+
# rubocop:enable Metrics/AbcSize
|
37
|
+
|
38
|
+
def find_or_initialize_api_config
|
39
|
+
Motor::ApiConfig.find_by(name: request_params[:api_config_name]) ||
|
40
|
+
Motor::ApiConfig.new(url: request_params[:api_config_name])
|
41
|
+
end
|
42
|
+
|
43
|
+
def current_user_jwt
|
44
|
+
return '' unless defined?(JWT)
|
45
|
+
return '' unless current_user
|
46
|
+
|
47
|
+
payload = { uid: current_user.id, email: current_user.email, exp: JWT_TTL.from_now.to_i }
|
48
|
+
|
49
|
+
JWT.encode(payload, Rails.application.credentials.secret_key_base)
|
50
|
+
end
|
51
|
+
|
52
|
+
def request_params
|
53
|
+
params.require(:data).permit!
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class RunGraphqlRequestsController < ApiBaseController
|
5
|
+
JWT_TTL = 2.hours
|
6
|
+
|
7
|
+
wrap_parameters :data
|
8
|
+
|
9
|
+
def create
|
10
|
+
respond_with_result
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def respond_with_result
|
16
|
+
response = Motor::ApiConfigs.run_grapql(find_or_initialize_api_config,
|
17
|
+
query: request_params[:query],
|
18
|
+
variables: request_params[:variables],
|
19
|
+
headers: { 'Authorization' => "Bearer #{current_user_jwt}" })
|
20
|
+
response.to_hash.each do |key, (value)|
|
21
|
+
next if key.casecmp('transfer-encoding').zero?
|
22
|
+
|
23
|
+
headers[key] = value
|
24
|
+
end
|
25
|
+
|
26
|
+
self.response_body = response.body
|
27
|
+
self.status = response.code.to_i
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_or_initialize_api_config
|
31
|
+
Motor::ApiConfig.find_by(name: request_params[:api_config_name]) ||
|
32
|
+
Motor::ApiConfig.new(url: request_params[:api_config_name])
|
33
|
+
end
|
34
|
+
|
35
|
+
def current_user_jwt
|
36
|
+
return '' unless defined?(JWT)
|
37
|
+
return '' unless current_user
|
38
|
+
|
39
|
+
payload = { uid: current_user.id, email: current_user.email, exp: JWT_TTL.from_now.to_i }
|
40
|
+
|
41
|
+
JWT.encode(payload, Rails.application.credentials.secret_key_base)
|
42
|
+
end
|
43
|
+
|
44
|
+
def request_params
|
45
|
+
params.require(:data).permit!
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class RunQueriesController < ApiBaseController
|
5
|
+
wrap_parameters :data
|
6
|
+
|
7
|
+
load_and_authorize_resource :query, only: :show, parent: false
|
8
|
+
|
9
|
+
before_action :build_query, only: :create
|
10
|
+
authorize_resource :query, only: :create
|
11
|
+
|
12
|
+
def show
|
13
|
+
render_result
|
14
|
+
end
|
15
|
+
|
16
|
+
def create
|
17
|
+
render_result
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def render_result
|
23
|
+
authorize_queries!(@query)
|
24
|
+
|
25
|
+
query_result = Queries::RunQuery.call(@query, variables_hash: variables_params.to_unsafe_h,
|
26
|
+
limit: params[:limit].presence,
|
27
|
+
filters: filter_params)
|
28
|
+
|
29
|
+
if query_result.error
|
30
|
+
render json: { errors: [{ detail: query_result.error }] }, status: :unprocessable_entity
|
31
|
+
else
|
32
|
+
render json: query_result_hash(query_result)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def authorize_queries!(query)
|
37
|
+
query.sql_body.to_s.scan(/query_\d+/).each do |name|
|
38
|
+
Motor::Query.accessible_by(current_ability).find(name.split('_').last)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def current_user_variables
|
43
|
+
return {} unless current_user
|
44
|
+
|
45
|
+
current_user
|
46
|
+
.attributes
|
47
|
+
.slice('id', 'email')
|
48
|
+
.transform_keys { |key| "current_user_#{key}" }
|
49
|
+
.compact
|
50
|
+
end
|
51
|
+
|
52
|
+
def query_result_hash(query_result)
|
53
|
+
{
|
54
|
+
data: query_result.data,
|
55
|
+
meta: {
|
56
|
+
columns: query_result.columns
|
57
|
+
}
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def build_query
|
62
|
+
@query = Motor::Queries::Persistance.build_from_params(query_params)
|
63
|
+
end
|
64
|
+
|
65
|
+
def query_params
|
66
|
+
params.require(:data).permit(:sql_body, preferences: {})
|
67
|
+
end
|
68
|
+
|
69
|
+
def variables_params
|
70
|
+
params.fetch(:variables, {}).merge(current_user_variables)
|
71
|
+
end
|
72
|
+
|
73
|
+
def filter_params
|
74
|
+
(params[:filter] || params[:filters])&.to_unsafe_h
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class SchemaController < ApiBaseController
|
5
|
+
skip_authorization_check
|
6
|
+
|
7
|
+
before_action :authorize_resource, only: :show
|
8
|
+
|
9
|
+
def index
|
10
|
+
render json: { data: schema }
|
11
|
+
end
|
12
|
+
|
13
|
+
def show
|
14
|
+
render json: { data: schema.find { |model| model[:name] == resource_class.name.underscore } }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def schema
|
20
|
+
@schema ||= Motor::BuildSchema.call(Configs::LoadFromCache.load_cache_keys, current_ability)
|
21
|
+
end
|
22
|
+
|
23
|
+
def resource_class
|
24
|
+
@resource_class ||= Motor::BuildSchema::Utils.classify_slug(params[:resource])
|
25
|
+
end
|
26
|
+
|
27
|
+
def authorize_resource
|
28
|
+
authorize!(resource_class, :read)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class SendAlertsController < ApiBaseController
|
5
|
+
wrap_parameters :data
|
6
|
+
|
7
|
+
before_action :build_alert, only: :create
|
8
|
+
authorize_resource :alert, only: :create
|
9
|
+
|
10
|
+
def create
|
11
|
+
Motor::Alerts.send_alert(@alert)
|
12
|
+
|
13
|
+
head :ok
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def build_alert
|
19
|
+
@alert = Motor::Alerts::Persistance.build_from_params(alert_params)
|
20
|
+
end
|
21
|
+
|
22
|
+
def alert_params
|
23
|
+
params.require(:data).permit(:query_id, :name, :to_emails, :description, preferences: {})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class SessionsController < ApiBaseController
|
5
|
+
include ActionController::Cookies
|
6
|
+
|
7
|
+
skip_authorization_check
|
8
|
+
|
9
|
+
def show
|
10
|
+
render json: {
|
11
|
+
current_user_email: current_user&.email,
|
12
|
+
current_user_id: current_user&.id
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def destroy
|
17
|
+
session.clear
|
18
|
+
cookies.clear
|
19
|
+
|
20
|
+
head :ok
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class UiController < ApplicationController
|
5
|
+
layout 'motor/application'
|
6
|
+
|
7
|
+
helper_method :current_user, :current_ability, :cache_keys, :custom_html
|
8
|
+
|
9
|
+
before_action :set_i18n_locale
|
10
|
+
|
11
|
+
def index
|
12
|
+
render_ui
|
13
|
+
end
|
14
|
+
|
15
|
+
def show
|
16
|
+
render_ui
|
17
|
+
end
|
18
|
+
|
19
|
+
def new
|
20
|
+
render_ui
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def render_ui
|
26
|
+
Motor.reload! if Motor.development?
|
27
|
+
|
28
|
+
Motor::Configs::SyncFromFile.call
|
29
|
+
|
30
|
+
render :show
|
31
|
+
end
|
32
|
+
|
33
|
+
def custom_html
|
34
|
+
Motor::Admin.config.custom_html.presence || begin
|
35
|
+
configs = Motor::Configs::LoadFromCache.load_configs(cache_key: cache_keys[:configs])
|
36
|
+
|
37
|
+
configs.find { |c| c.key == 'ui.custom_html' }&.value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_i18n_locale
|
42
|
+
configs = Motor::Configs::LoadFromCache.load_configs(cache_key: cache_keys[:configs])
|
43
|
+
|
44
|
+
I18n.locale = configs.find { |c| c.key == 'language' }&.value&.to_sym || I18n.locale
|
45
|
+
end
|
46
|
+
|
47
|
+
def cache_keys
|
48
|
+
@cache_keys ||= Configs::LoadFromCache.load_cache_keys
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class UsersForAutocompleteController < ApiBaseController
|
5
|
+
def index
|
6
|
+
authorize!(:create, Motor::Note)
|
7
|
+
|
8
|
+
render json: { data: user_emails }
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def user_emails
|
14
|
+
user_class = Motor::AdminUser if defined?(Motor::AdminUser)
|
15
|
+
user_class ||= AdminUser if defined?(AdminUser)
|
16
|
+
user_class ||= User if defined?(User)
|
17
|
+
|
18
|
+
return user_class.distinct.limit(100).pluck(:email) if user_class
|
19
|
+
|
20
|
+
[]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class AlertSendingJob < ApplicationJob
|
5
|
+
def perform(alert)
|
6
|
+
Motor::AlertLock.create!(alert_id: alert.id, lock_timestamp: alert.cron.previous_time.to_i)
|
7
|
+
|
8
|
+
Motor::Alerts.send_alert(alert)
|
9
|
+
rescue ActiveRecord::RecordNotUnique
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class AlertsMailer < ApplicationMailer
|
5
|
+
SenderAddressNotSet = Class.new(StandardError)
|
6
|
+
SENDER_NOT_SET_ERROR_MESSAGE =
|
7
|
+
'Please specify your sender address via MOTOR_ALERTS_FROM_ADDRESS environment variable'
|
8
|
+
|
9
|
+
def alert_email(alert, email: nil)
|
10
|
+
@alert = alert
|
11
|
+
@query_result = Queries::RunQuery.call(alert.query,
|
12
|
+
variables_hash: { current_user_email: email })
|
13
|
+
|
14
|
+
return if @alert.preferences[:send_empty].blank? && @query_result.data.blank?
|
15
|
+
|
16
|
+
assign_attachment(@alert, @query_result)
|
17
|
+
|
18
|
+
raise SenderAddressNotSet, SENDER_NOT_SET_ERROR_MESSAGE unless from_address
|
19
|
+
|
20
|
+
mail(
|
21
|
+
from: from_address,
|
22
|
+
to: email || alert.to_emails,
|
23
|
+
subject: alert.name.presence || @alert.query.name
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def assign_attachment(alert, query_result)
|
30
|
+
attachments["#{alert.name.presence || 'data'}.csv"] = generate_csv(query_result)
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate_csv(query_result)
|
34
|
+
rows = [query_result.columns.pluck(:name)] + query_result.data
|
35
|
+
|
36
|
+
rows.map(&:to_csv).join
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class ApplicationMailer < ActionMailer::Base
|
5
|
+
layout 'motor/mailer'
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def from_address
|
10
|
+
from = ENV['MOTOR_ALERTS_FROM_ADDRESS'].presence || ENV['MOTOR_EMAIL_ADDRESS'].presence
|
11
|
+
|
12
|
+
from ||= application_mailer_default_from
|
13
|
+
from ||= mailer_config_from_address
|
14
|
+
from ||= "reports@#{ENV['HOST'].delete_prefix('www.')}" if ENV['HOST'].present?
|
15
|
+
|
16
|
+
from ||= 'reports@example.com' if Rails.env.development?
|
17
|
+
|
18
|
+
from
|
19
|
+
end
|
20
|
+
|
21
|
+
def application_mailer_default_from
|
22
|
+
return if !defined?(::ApplicationMailer) || ::ApplicationMailer.default[:from].to_s.include?('example.com')
|
23
|
+
|
24
|
+
::ApplicationMailer.default[:from].presence
|
25
|
+
end
|
26
|
+
|
27
|
+
def mailer_config_from_address
|
28
|
+
return if Rails.application.config.action_mailer.default_url_options&.dig(:host).blank?
|
29
|
+
|
30
|
+
"reports@#{Rails.application.config.action_mailer.default_url_options[:host].delete_prefix('www.')}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class NotificationsMailer < ApplicationMailer
|
5
|
+
def notify_mention_email(notification)
|
6
|
+
@notification = notification
|
7
|
+
|
8
|
+
@note = notification.record
|
9
|
+
|
10
|
+
subject =
|
11
|
+
"#{@note.author&.email || 'Anonymous'} mentioned you in " \
|
12
|
+
"#{@note.record.class.model_name.human} ##{@note.record.id}"
|
13
|
+
|
14
|
+
mail(
|
15
|
+
from: from_address,
|
16
|
+
to: notification.recipient.email,
|
17
|
+
subject: subject
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def notify_reminder_email(notification)
|
22
|
+
@notification = notification
|
23
|
+
|
24
|
+
@note = notification.record.record
|
25
|
+
|
26
|
+
mail(
|
27
|
+
from: from_address,
|
28
|
+
to: notification.recipient.email,
|
29
|
+
subject: "Reminder for #{@note.record.class.model_name.human} ##{@note.record.id}"
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class Alert < ::Motor::ApplicationRecord
|
5
|
+
audited
|
6
|
+
|
7
|
+
belongs_to :query
|
8
|
+
belongs_to :author, polymorphic: true, optional: true
|
9
|
+
|
10
|
+
has_many :alert_locks, dependent: :destroy
|
11
|
+
has_many :taggable_tags, as: :taggable, dependent: :destroy
|
12
|
+
has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
|
13
|
+
|
14
|
+
attribute :preferences, default: -> { ActiveSupport::HashWithIndifferentAccess.new }
|
15
|
+
|
16
|
+
if Rails.version.to_f >= 7.1
|
17
|
+
serialize :preferences, coder: HashSerializer
|
18
|
+
else
|
19
|
+
serialize :preferences, HashSerializer
|
20
|
+
end
|
21
|
+
|
22
|
+
scope :active, -> { where(deleted_at: nil) }
|
23
|
+
scope :enabled, -> { where(is_enabled: true) }
|
24
|
+
|
25
|
+
def cron
|
26
|
+
@cron ||=
|
27
|
+
Fugit::Nat.parse("#{preferences[:interval]} #{ActiveSupport::TimeZone::MAPPING[preferences[:timezone]]}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class ApiConfig < ::Motor::ApplicationRecord
|
5
|
+
encrypts :credentials if defined?(::Motor::EncryptedConfig)
|
6
|
+
|
7
|
+
attribute :preferences, default: -> { ActiveSupport::HashWithIndifferentAccess.new }
|
8
|
+
attribute :credentials, default: -> { ActiveSupport::HashWithIndifferentAccess.new }
|
9
|
+
|
10
|
+
if Rails.version.to_f >= 7.1
|
11
|
+
serialize :credentials, coder: Motor::HashSerializer
|
12
|
+
serialize :preferences, coder: Motor::HashSerializer
|
13
|
+
else
|
14
|
+
serialize :credentials, Motor::HashSerializer
|
15
|
+
serialize :preferences, Motor::HashSerializer
|
16
|
+
end
|
17
|
+
|
18
|
+
has_one :form, dependent: nil, foreign_key: :api_config_name, primary_key: :name, inverse_of: :api_config
|
19
|
+
|
20
|
+
scope :active, -> { where(deleted_at: nil) }
|
21
|
+
|
22
|
+
def headers
|
23
|
+
credentials.fetch(:headers, []).each_with_object({}) do |item, acc|
|
24
|
+
acc[item[:key]] = item[:value]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class ApplicationRecord < ActiveRecord::Base
|
5
|
+
self.abstract_class = true
|
6
|
+
self.table_name_prefix = 'motor_'
|
7
|
+
|
8
|
+
def self.audited(*args)
|
9
|
+
default_class = Audited.audit_class
|
10
|
+
|
11
|
+
Audited.audit_class = 'Motor::Audit'
|
12
|
+
|
13
|
+
super
|
14
|
+
ensure
|
15
|
+
Audited.audit_class = default_class
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class Audit < Audited::Audit
|
5
|
+
self.table_name = 'motor_audits'
|
6
|
+
|
7
|
+
if Rails.version.to_f >= 7.1
|
8
|
+
serialize :audited_changes, coder: HashSerializer
|
9
|
+
else
|
10
|
+
serialize :audited_changes, HashSerializer
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class Dashboard < ::Motor::ApplicationRecord
|
5
|
+
audited
|
6
|
+
|
7
|
+
belongs_to :author, polymorphic: true, optional: true
|
8
|
+
|
9
|
+
has_many :taggable_tags, as: :taggable, dependent: :destroy
|
10
|
+
has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
|
11
|
+
|
12
|
+
attribute :preferences, default: -> { ActiveSupport::HashWithIndifferentAccess.new }
|
13
|
+
|
14
|
+
if Rails.version.to_f >= 7.1
|
15
|
+
serialize :preferences, coder: HashSerializer
|
16
|
+
else
|
17
|
+
serialize :preferences, HashSerializer
|
18
|
+
end
|
19
|
+
|
20
|
+
scope :active, -> { where(deleted_at: nil) }
|
21
|
+
|
22
|
+
def queries
|
23
|
+
Motor::Query.where(id: preferences[:layout].pluck(:query_id))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
class Form < ::Motor::ApplicationRecord
|
5
|
+
audited
|
6
|
+
|
7
|
+
belongs_to :author, polymorphic: true, optional: true
|
8
|
+
belongs_to :api_config, foreign_key: :api_config_name, primary_key: :name, inverse_of: :form
|
9
|
+
|
10
|
+
has_many :taggable_tags, as: :taggable, dependent: :destroy
|
11
|
+
has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
|
12
|
+
|
13
|
+
attribute :preferences, default: -> { ActiveSupport::HashWithIndifferentAccess.new }
|
14
|
+
|
15
|
+
if Rails.version.to_f >= 7.1
|
16
|
+
serialize :preferences, coder: HashSerializer
|
17
|
+
else
|
18
|
+
serialize :preferences, HashSerializer
|
19
|
+
end
|
20
|
+
|
21
|
+
scope :active, -> { where(deleted_at: nil) }
|
22
|
+
end
|
23
|
+
end
|