motor-admin 0.3.16 → 0.4.0

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.
Files changed (70) 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/sessions_controller.rb +3 -0
  14. data/app/controllers/motor/slack_conversations_controller.rb +11 -0
  15. data/app/controllers/motor/tags_controller.rb +1 -1
  16. data/app/controllers/motor/ui_controller.rb +9 -1
  17. data/app/controllers/motor/users_for_autocomplete_controller.rb +23 -0
  18. data/app/jobs/motor/alert_sending_job.rb +1 -1
  19. data/app/jobs/motor/notify_note_mentions_job.rb +9 -0
  20. data/app/jobs/motor/notify_reminder_job.rb +9 -0
  21. data/app/mailers/motor/alerts_mailer.rb +6 -29
  22. data/app/mailers/motor/application_mailer.rb +27 -1
  23. data/app/mailers/motor/notifications_mailer.rb +33 -0
  24. data/app/models/motor/note.rb +18 -0
  25. data/app/models/motor/note_tag.rb +7 -0
  26. data/app/models/motor/note_tag_tag.rb +8 -0
  27. data/app/models/motor/notification.rb +14 -0
  28. data/app/models/motor/reminder.rb +13 -0
  29. data/app/views/layouts/motor/application.html.erb +4 -1
  30. data/app/views/layouts/motor/mailer.html.erb +72 -0
  31. data/app/views/motor/alerts_mailer/alert_email.html.erb +52 -124
  32. data/app/views/motor/notifications_mailer/notify_mention_email.html.erb +28 -0
  33. data/app/views/motor/notifications_mailer/notify_reminder_email.html.erb +28 -0
  34. data/config/locales/el.yml +25 -0
  35. data/config/locales/en.yml +33 -2
  36. data/config/locales/es.yml +33 -2
  37. data/config/locales/pt.yml +33 -2
  38. data/config/routes.rb +9 -0
  39. data/lib/generators/motor/install_notes_generator.rb +22 -0
  40. data/lib/generators/motor/templates/install.rb +77 -0
  41. data/lib/generators/motor/templates/install_notes.rb +83 -0
  42. data/lib/generators/motor/upgrade_generator.rb +13 -6
  43. data/lib/motor/admin.rb +13 -1
  44. data/lib/motor/alerts/slack_sender.rb +74 -0
  45. data/lib/motor/alerts.rb +42 -0
  46. data/lib/motor/api_query/apply_scope.rb +1 -0
  47. data/lib/motor/build_schema/apply_permissions.rb +8 -0
  48. data/lib/motor/build_schema/defaults.rb +15 -1
  49. data/lib/motor/build_schema/load_from_rails.rb +4 -1
  50. data/lib/motor/build_schema.rb +7 -0
  51. data/lib/motor/configs/build_ui_app_tag.rb +73 -8
  52. data/lib/motor/configs.rb +3 -4
  53. data/lib/motor/notes/notify_mentions.rb +73 -0
  54. data/lib/motor/notes/notify_reminder.rb +49 -0
  55. data/lib/motor/notes/persist.rb +36 -0
  56. data/lib/motor/notes/reminders_scheduler.rb +39 -0
  57. data/lib/motor/notes/tags.rb +34 -0
  58. data/lib/motor/notes.rb +12 -0
  59. data/lib/motor/queries/run_query.rb +66 -3
  60. data/lib/motor/resources/fetch_configured_model.rb +19 -3
  61. data/lib/motor/resources.rb +1 -1
  62. data/lib/motor/slack/client.rb +62 -0
  63. data/lib/motor/slack.rb +16 -0
  64. data/lib/motor/version.rb +1 -1
  65. data/lib/motor.rb +19 -0
  66. data/ui/dist/{main-726aa7f6805676af4d21.css.gz → main-99bab2664944ee03d10f.css.gz} +0 -0
  67. data/ui/dist/main-99bab2664944ee03d10f.js.gz +0 -0
  68. data/ui/dist/manifest.json +5 -5
  69. metadata +36 -4
  70. data/ui/dist/main-726aa7f6805676af4d21.js.gz +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b6c468515dc869a61b535b560ea767b8facd02d773aa65b1b1be47871e2a6776
4
- data.tar.gz: d70c45aa2572c20b5c5febb0a0e2c67695d0ac358f51ebcd547cd55cae316a03
3
+ metadata.gz: ca54cd5d999594ce72fba85a996c63bb99ef87cb58dddd02f91af2c97e8d9508
4
+ data.tar.gz: 26c2f9525f0324a30a1ae241a466e85cce271434a2b2d29f144387f01486d712
5
5
  SHA512:
6
- metadata.gz: 45ea6dd17f1e2d52c3c292ee152cd767674caee05578e843356ba30ec4d60f8cccae5350a17abdf45b34616231977ea1935cbcd2491e63c28b7b804dbd97462b
7
- data.tar.gz: '04914138d2c9278e85cbf68e788013cbad77725ba7cfe876c58d1504b8c8e3af2f5a05b9db50a60a7a2ea33f37bc9744f10a43deb0b0ec21ca01ef0d5467fcc7'
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
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class SessionsController < ApiBaseController
5
+ include ActionController::Cookies
6
+
5
7
  skip_authorization_check
6
8
 
7
9
  def show
@@ -13,6 +15,7 @@ module Motor
13
15
 
14
16
  def destroy
15
17
  session.clear
18
+ cookies.clear
16
19
 
17
20
  head :ok
18
21
  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>