motor-admin 0.3.17 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/app/channels/motor/application_cable/channel.rb +14 -0
  3. data/app/channels/motor/application_cable/connection.rb +27 -0
  4. data/app/channels/motor/notes_channel.rb +9 -0
  5. data/app/channels/motor/notifications_channel.rb +9 -0
  6. data/app/controllers/concerns/motor/current_user_method.rb +2 -0
  7. data/app/controllers/motor/note_tags_controller.rb +13 -0
  8. data/app/controllers/motor/notes_controller.rb +58 -0
  9. data/app/controllers/motor/notifications_controller.rb +33 -0
  10. data/app/controllers/motor/reminders_controller.rb +38 -0
  11. data/app/controllers/motor/run_queries_controller.rb +1 -1
  12. data/app/controllers/motor/send_alerts_controller.rb +2 -2
  13. data/app/controllers/motor/slack_conversations_controller.rb +11 -0
  14. data/app/controllers/motor/tags_controller.rb +1 -1
  15. data/app/controllers/motor/ui_controller.rb +9 -1
  16. data/app/controllers/motor/users_for_autocomplete_controller.rb +23 -0
  17. data/app/jobs/motor/alert_sending_job.rb +1 -1
  18. data/app/jobs/motor/notify_note_mentions_job.rb +9 -0
  19. data/app/jobs/motor/notify_reminder_job.rb +9 -0
  20. data/app/mailers/motor/alerts_mailer.rb +6 -29
  21. data/app/mailers/motor/application_mailer.rb +27 -1
  22. data/app/mailers/motor/notifications_mailer.rb +33 -0
  23. data/app/models/motor/note.rb +18 -0
  24. data/app/models/motor/note_tag.rb +7 -0
  25. data/app/models/motor/note_tag_tag.rb +8 -0
  26. data/app/models/motor/notification.rb +14 -0
  27. data/app/models/motor/reminder.rb +13 -0
  28. data/app/views/layouts/motor/application.html.erb +4 -1
  29. data/app/views/layouts/motor/mailer.html.erb +72 -0
  30. data/app/views/motor/alerts_mailer/alert_email.html.erb +52 -124
  31. data/app/views/motor/notifications_mailer/notify_mention_email.html.erb +28 -0
  32. data/app/views/motor/notifications_mailer/notify_reminder_email.html.erb +28 -0
  33. data/config/locales/el.yml +25 -0
  34. data/config/locales/en.yml +33 -2
  35. data/config/locales/es.yml +33 -2
  36. data/config/locales/pt.yml +33 -2
  37. data/config/routes.rb +9 -0
  38. data/lib/generators/motor/install_notes_generator.rb +22 -0
  39. data/lib/generators/motor/templates/install.rb +77 -0
  40. data/lib/generators/motor/templates/install_notes.rb +83 -0
  41. data/lib/generators/motor/upgrade_generator.rb +13 -6
  42. data/lib/motor/admin.rb +13 -1
  43. data/lib/motor/alerts/slack_sender.rb +74 -0
  44. data/lib/motor/alerts.rb +42 -0
  45. data/lib/motor/api_query/apply_scope.rb +1 -0
  46. data/lib/motor/build_schema/apply_permissions.rb +8 -0
  47. data/lib/motor/build_schema/defaults.rb +15 -1
  48. data/lib/motor/build_schema/load_from_rails.rb +4 -1
  49. data/lib/motor/build_schema.rb +7 -0
  50. data/lib/motor/configs/build_ui_app_tag.rb +73 -8
  51. data/lib/motor/configs.rb +3 -4
  52. data/lib/motor/notes/notify_mentions.rb +73 -0
  53. data/lib/motor/notes/notify_reminder.rb +49 -0
  54. data/lib/motor/notes/persist.rb +36 -0
  55. data/lib/motor/notes/reminders_scheduler.rb +39 -0
  56. data/lib/motor/notes/tags.rb +34 -0
  57. data/lib/motor/notes.rb +12 -0
  58. data/lib/motor/queries/run_query.rb +66 -3
  59. data/lib/motor/resources/fetch_configured_model.rb +19 -3
  60. data/lib/motor/resources.rb +1 -1
  61. data/lib/motor/slack/client.rb +62 -0
  62. data/lib/motor/slack.rb +16 -0
  63. data/lib/motor/version.rb +1 -1
  64. data/lib/motor.rb +19 -0
  65. data/ui/dist/{main-726aa7f6805676af4d21.css.gz → main-99bab2664944ee03d10f.css.gz} +0 -0
  66. data/ui/dist/main-99bab2664944ee03d10f.js.gz +0 -0
  67. data/ui/dist/manifest.json +5 -5
  68. metadata +36 -4
  69. data/ui/dist/main-726aa7f6805676af4d21.js.gz +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53345b043e43a3765e5e8cc0d1b0e336b92a59cad3af656eaea11153c5c04269
4
- data.tar.gz: 1fb5e54fa20a6e57fe805759a76b635b5995d798b8ac4feba0b3a11f06a48842
3
+ metadata.gz: ca54cd5d999594ce72fba85a996c63bb99ef87cb58dddd02f91af2c97e8d9508
4
+ data.tar.gz: 26c2f9525f0324a30a1ae241a466e85cce271434a2b2d29f144387f01486d712
5
5
  SHA512:
6
- metadata.gz: 15bc1b334c227e309e6d2c2ded39924d3ab23ff7028b86245d9d668dc26eb9071bd198a00d7f4d936ee384595272cd04395fa4f32a7aa286ae7bde6729ddb0c2
7
- data.tar.gz: d30a7110abaa69547fd1b620397646644cd3e4abd619d8225b6f1e8b2cfdd8a9929320de0c6b270f85e6e390ee3c9791e354a1448231ad0f17f0444a35237883
6
+ metadata.gz: 97ade114c8bedf11dcf2586ec76fd629efd7517b4319e32fe3ce23a3c6e38f8e179e233e0fc202147785b9b0e9d3757af0064eadfb0eb84a177ce87f63b646ce
7
+ data.tar.gz: ccd281c10b4950741da0ecca8d3a9e1296186a8d96fb7db04b41e16809064dbed7334f0c0250f56779ae1fed660da9572c8abc0aa8dfc137684b551126176ce0
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class DummyChannel
5
+ def self.broadcast_to(*_args)
6
+ nil
7
+ end
8
+ end
9
+
10
+ module ApplicationCable
11
+ class Channel < defined?(ActionCable) ? ActionCable::Channel::Base : Motor::DummyChannel
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module ApplicationCable
5
+ class Connection < defined?(ActionCable) ? ActionCable::Connection::Base : Object
6
+ identified_by :current_user if defined?(ActionCable)
7
+
8
+ def connect
9
+ self.current_user = find_verified_user
10
+ end
11
+
12
+ private
13
+
14
+ def find_verified_user
15
+ return unless env['warden']
16
+
17
+ if env['warden'].respond_to?(:admin_user)
18
+ env['warden'].admin_user
19
+ elsif env['warden'].respond_to?(:admin)
20
+ env['warden'].admin
21
+ else
22
+ env['warden'].user
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class NotesChannel < ::Motor::ApplicationCable::Channel
5
+ def subscribed
6
+ stream_from "motor:notes:#{params[:room]}"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class NotificationsChannel < ::Motor::ApplicationCable::Channel
5
+ def subscribed
6
+ stream_for current_user if respond_to?(:current_user)
7
+ end
8
+ end
9
+ end
@@ -7,6 +7,8 @@ module Motor
7
7
  if defined?(current_admin)
8
8
  current_admin
9
9
  elsif defined?(current_admin_user)
10
+ return Motor::AdminUser.public if current_admin_user.nil? && Motor.with_public_access?
11
+
10
12
  current_admin_user
11
13
  elsif defined?(super)
12
14
  super
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class NoteTagsController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
7
+ load_and_authorize_resource :note_tag
8
+
9
+ def index
10
+ render json: { data: @note_tags.to_a }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class NotesController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
7
+ load_and_authorize_resource :note, only: %i[index show update destroy]
8
+ before_action :build_note, only: :create
9
+ authorize_resource :note, only: :create
10
+
11
+ def index
12
+ @notes = Motor::ApiQuery.call(@notes.active.order(created_at: :asc), params)
13
+
14
+ render json: { data: Motor::ApiQuery::BuildJson.call(@notes, params, current_ability) }
15
+ end
16
+
17
+ def show
18
+ render json: { data: Motor::ApiQuery::BuildJson.call(@note, params, current_ability) }
19
+ end
20
+
21
+ def create
22
+ Motor::Notes::Persist.call(@note, current_user)
23
+ Motor::NotifyNoteMentionsJob.perform_later(@note, current_user)
24
+
25
+ render json: { data: Motor::ApiQuery::BuildJson.call(@note, params, current_ability) }
26
+ end
27
+
28
+ def update
29
+ @note.assign_attributes(note_params)
30
+
31
+ Motor::Notes::Persist.call(@note, current_user)
32
+ Motor::NotifyNoteMentionsJob.perform_later(@note, current_user)
33
+
34
+ render json: { data: Motor::ApiQuery::BuildJson.call(@note, params, current_ability) }
35
+ end
36
+
37
+ def destroy
38
+ @note.update!(deleted_at: Time.current)
39
+
40
+ Motor::NotesChannel.broadcast_to(
41
+ @note.values_at(:record_type, :record_id).join(':'),
42
+ ['remove', @note.as_json(only: %i[id body created_at updated_at])]
43
+ )
44
+
45
+ head :ok
46
+ end
47
+
48
+ private
49
+
50
+ def build_note
51
+ @note = Motor::Note.new(note_params.merge(author: current_user))
52
+ end
53
+
54
+ def note_params
55
+ params.require(:data).permit(:body, :record_id, :record_type)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class NotificationsController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
7
+ load_and_authorize_resource :notification
8
+
9
+ def index
10
+ @notifications =
11
+ Motor::ApiQuery.call(@notifications.where(recipient: current_user).order(created_at: :desc), params)
12
+
13
+ render json: {
14
+ data: Motor::ApiQuery::BuildJson.call(@notifications, params, current_ability),
15
+ meta: Motor::ApiQuery::BuildMeta.call(@notifications, params)
16
+ }
17
+ end
18
+
19
+ def update
20
+ @notification.update!(notification_params)
21
+
22
+ Motor::NotificationsChannel.broadcast_to(current_user, ['update', @notification.as_json(include: %i[record])])
23
+
24
+ render json: { data: Motor::ApiQuery::BuildJson.call(@notification, params, current_ability) }
25
+ end
26
+
27
+ private
28
+
29
+ def notification_params
30
+ params.require(:data).permit(:status)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class RemindersController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
7
+ load_and_authorize_resource :reminder
8
+
9
+ def create
10
+ @reminder.update!(author: current_user, recipient: current_user)
11
+
12
+ broadcast_note_update(@reminder.record)
13
+
14
+ render json: { data: Motor::ApiQuery::BuildJson.call(@reminder, params, current_ability) }
15
+ end
16
+
17
+ def destroy
18
+ @reminder.destroy!
19
+
20
+ broadcast_note_update(@reminder.record)
21
+
22
+ head :ok
23
+ end
24
+
25
+ private
26
+
27
+ def broadcast_note_update(note)
28
+ Motor::NotesChannel.broadcast_to(
29
+ note.values_at(:record_type, :record_id).join(':'),
30
+ ['update', note.as_json(include: %i[author tags reminders])]
31
+ )
32
+ end
33
+
34
+ def reminder_params
35
+ params.require(:data).permit(:record_id, :record_type, :scheduled_at)
36
+ end
37
+ end
38
+ end
@@ -20,7 +20,7 @@ module Motor
20
20
  private
21
21
 
22
22
  def render_result
23
- query_result = Queries::RunQuery.call(@query, variables_hash: variables_params,
23
+ query_result = Queries::RunQuery.call(@query, variables_hash: variables_params.to_unsafe_h,
24
24
  limit: params[:limit].presence,
25
25
  filters: filter_params)
26
26
 
@@ -8,7 +8,7 @@ module Motor
8
8
  authorize_resource :alert, only: :create
9
9
 
10
10
  def create
11
- AlertsMailer.alert_email(@alert).deliver_now!
11
+ Motor::Alerts.send_alert(@alert)
12
12
 
13
13
  head :ok
14
14
  end
@@ -20,7 +20,7 @@ module Motor
20
20
  end
21
21
 
22
22
  def alert_params
23
- params.require(:data).permit(:query_id, :name, :to_emails)
23
+ params.require(:data).permit(:query_id, :name, :to_emails, :description, preferences: {})
24
24
  end
25
25
  end
26
26
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class SlackConversationsController < ApiBaseController
5
+ def index
6
+ authorize!(:create, Motor::Alert)
7
+
8
+ render json: { data: Motor::Slack.conversations }
9
+ end
10
+ end
11
+ end
@@ -5,7 +5,7 @@ module Motor
5
5
  load_and_authorize_resource :tag
6
6
 
7
7
  def index
8
- render json: { data: @tags }
8
+ render json: { data: @tags.to_a }
9
9
  end
10
10
  end
11
11
  end
@@ -4,7 +4,7 @@ module Motor
4
4
  class UiController < ApplicationController
5
5
  layout 'motor/application'
6
6
 
7
- helper_method :current_user, :current_ability, :cache_keys
7
+ helper_method :current_user, :current_ability, :cache_keys, :custom_html
8
8
 
9
9
  before_action :set_i18n_locale
10
10
 
@@ -30,6 +30,14 @@ module Motor
30
30
  render :show
31
31
  end
32
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
+
33
41
  def set_i18n_locale
34
42
  configs = Motor::Configs::LoadFromCache.load_configs(cache_key: cache_keys[:configs])
35
43
 
@@ -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
@@ -5,7 +5,7 @@ module Motor
5
5
  def perform(alert)
6
6
  Motor::AlertLock.create!(alert_id: alert.id, lock_timestamp: alert.cron.previous_time.to_i)
7
7
 
8
- Motor::AlertsMailer.alert_email(alert).deliver_now!
8
+ Motor::Alerts.send_alert(alert)
9
9
  rescue ActiveRecord::RecordNotUnique
10
10
  nil
11
11
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class NotifyNoteMentionsJob < ApplicationJob
5
+ def perform(note, current_user)
6
+ Motor::Notes::NotifyMentions.call(note, current_user)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class NotifyReminderJob < ApplicationJob
5
+ def perform(reminder)
6
+ Motor::Notes::NotifyReminder.call(reminder)
7
+ end
8
+ end
9
+ end
@@ -6,9 +6,10 @@ module Motor
6
6
  SENDER_NOT_SET_ERROR_MESSAGE =
7
7
  'Please specify your sender address via MOTOR_ALERTS_FROM_ADDRESS environment variable'
8
8
 
9
- def alert_email(alert)
9
+ def alert_email(alert, email: nil)
10
10
  @alert = alert
11
- @query_result = Queries::RunQuery.call(alert.query)
11
+ @query_result = Queries::RunQuery.call(alert.query,
12
+ variables_hash: { current_user_email: email })
12
13
 
13
14
  return if @alert.preferences[:send_empty].blank? && @query_result.data.blank?
14
15
 
@@ -18,7 +19,7 @@ module Motor
18
19
 
19
20
  mail(
20
21
  from: from_address,
21
- to: alert.to_emails,
22
+ to: email || alert.to_emails,
22
23
  subject: alert.name.presence || @alert.query.name
23
24
  )
24
25
  end
@@ -29,34 +30,10 @@ module Motor
29
30
  attachments["#{alert.name.presence || 'data'}.csv"] = generate_csv(query_result)
30
31
  end
31
32
 
32
- def generate_csv(_query_result)
33
- rows = [@query_result.columns.pluck(:name)] + @query_result.data
33
+ def generate_csv(query_result)
34
+ rows = [query_result.columns.pluck(:name)] + query_result.data
34
35
 
35
36
  rows.map(&:to_csv).join
36
37
  end
37
-
38
- def from_address
39
- from = ENV['MOTOR_ALERTS_FROM_ADDRESS'].presence || ENV['MOTOR_EMAIL_ADDRESS'].presence
40
-
41
- from ||= application_mailer_default_from
42
- from ||= mailer_config_from_address
43
- from ||= "reports@#{ENV['HOST'].delete_prefix('www.')}" if ENV['HOST'].present?
44
-
45
- from ||= 'reports@example.com' if Rails.env.development?
46
-
47
- from
48
- end
49
-
50
- def application_mailer_default_from
51
- return if !defined?(::ApplicationMailer) || ::ApplicationMailer.default[:from].to_s.include?('example.com')
52
-
53
- ::ApplicationMailer.default[:from].presence
54
- end
55
-
56
- def mailer_config_from_address
57
- return if Rails.application.config.action_mailer.default_url_options&.dig(:host).blank?
58
-
59
- "reports@#{Rails.application.config.action_mailer.default_url_options[:host].delete_prefix('www.')}"
60
- end
61
38
  end
62
39
  end
@@ -2,6 +2,32 @@
2
2
 
3
3
  module Motor
4
4
  class ApplicationMailer < ActionMailer::Base
5
- layout 'mailer'
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
6
32
  end
7
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,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class Note < ::Motor::ApplicationRecord
5
+ audited
6
+
7
+ belongs_to :author, polymorphic: true, optional: true
8
+ belongs_to :record, polymorphic: true
9
+
10
+ has_many :note_tag_tags, dependent: :destroy
11
+ has_many :tags, through: :note_tag_tags, class_name: 'Motor::NoteTag'
12
+
13
+ has_many :reminders, as: :record, dependent: :destroy
14
+ has_many :notifications, as: :record, dependent: :destroy
15
+
16
+ scope :active, -> { where(deleted_at: nil) }
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class NoteTag < ::Motor::ApplicationRecord
5
+ has_many :note_tag_tags, dependent: :destroy
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class NoteTagTag < ::Motor::ApplicationRecord
5
+ belongs_to :tag, class_name: 'Motor::NoteTag'
6
+ belongs_to :note
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class Notification < ::Motor::ApplicationRecord
5
+ STATUSES = [
6
+ PENDING = 'pending'
7
+ ].freeze
8
+
9
+ belongs_to :record, polymorphic: true, optional: true
10
+ belongs_to :recipient, polymorphic: true
11
+
12
+ attribute :status, type: :string, default: PENDING
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class Reminder < ::Motor::ApplicationRecord
5
+ audited
6
+
7
+ belongs_to :record, polymorphic: true, optional: true
8
+ belongs_to :author, polymorphic: true
9
+ belongs_to :recipient, polymorphic: true
10
+
11
+ has_many :notifications, as: :record, dependent: :destroy
12
+ end
13
+ end
@@ -1,7 +1,7 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="<%= I18n.locale %>">
3
3
  <head>
4
- <title>Motor Admin</title>
4
+ <title><%= Motor.company_name %></title>
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
6
6
  <link rel="stylesheet" media="all" href="<%= Motor::Assets.asset_path('main.css') %>">
7
7
  <%= csrf_meta_tags %>
@@ -10,5 +10,8 @@
10
10
  <body>
11
11
  <%= yield %>
12
12
  <script src="<%= Motor::Assets.asset_path('main.js') %>"></script>
13
+ <% if params[:controller] === 'motor/ui' %>
14
+ <%= raw(custom_html) %>
15
+ <% end %>
13
16
  </body>
14
17
  </html>
@@ -0,0 +1,72 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta name="viewport" content="width=device-width">
5
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
6
+ <style>
7
+ @media only screen and (max-width: 620px) {
8
+ table[class=body] h1 {
9
+ font-size: 28px !important;
10
+ margin-bottom: 10px !important;
11
+ }
12
+ table[class=body] p,
13
+ table[class=body] ul,
14
+ table[class=body] ol,
15
+ table[class=body] td,
16
+ table[class=body] span,
17
+ table[class=body] a {
18
+ font-size: 14px !important;
19
+ }
20
+ table[class=body] .wrapper,
21
+ table[class=body] .article {
22
+ padding: 10px !important;
23
+ }
24
+ table[class=body] .content {
25
+ padding: 0 !important;
26
+ }
27
+ table[class=body] .container {
28
+ padding: 0 !important;
29
+ width: 100% !important;
30
+ }
31
+ table[class=body] .main {
32
+ border-left-width: 0 !important;
33
+ border-radius: 0 !important;
34
+ border-right-width: 0 !important;
35
+ }
36
+ }
37
+
38
+ @media all {
39
+ .ExternalClass {
40
+ width: 100%;
41
+ }
42
+ .ExternalClass,
43
+ .ExternalClass p,
44
+ .ExternalClass span,
45
+ .ExternalClass font,
46
+ .ExternalClass td,
47
+ .ExternalClass div {
48
+ line-height: 100%;
49
+ }
50
+ .apple-link a {
51
+ color: inherit !important;
52
+ font-family: inherit !important;
53
+ font-size: inherit !important;
54
+ font-weight: inherit !important;
55
+ line-height: inherit !important;
56
+ text-decoration: none !important;
57
+ }
58
+ #MessageViewBody a {
59
+ color: inherit;
60
+ text-decoration: none;
61
+ font-size: inherit;
62
+ font-family: inherit;
63
+ font-weight: inherit;
64
+ line-height: inherit;
65
+ }
66
+ }
67
+ </style>
68
+ </head>
69
+ <body class="" style="background-color: #f9fbfc; font-family: sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.4; margin: 0; padding: 0; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;">
70
+ <%= yield %>
71
+ </body>
72
+ </html>