web47core 2.0.1 → 2.2.15

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +111 -41
  3. data/app/controllers/status_controller.rb +8 -3
  4. data/app/helpers/core_form_helper.rb +6 -6
  5. data/app/helpers/core_helper.rb +1 -1
  6. data/app/helpers/core_link_helper.rb +47 -8
  7. data/app/helpers/core_nav_bar_helper.rb +5 -4
  8. data/app/helpers/core_table_helper.rb +80 -0
  9. data/app/helpers/model_modal_helper.rb +3 -3
  10. data/app/views/common/_create_actions.html.haml +12 -0
  11. data/app/views/common/_update_actions.html.haml +10 -0
  12. data/app/views/cron/_edit.html.haml +15 -17
  13. data/app/views/cron/_index.html.haml +74 -67
  14. data/app/views/delayed_job_metrics/_index.html.haml +27 -0
  15. data/app/views/delayed_job_metrics/index.html.haml +1 -0
  16. data/app/views/delayed_job_workers/_index.html.haml +27 -0
  17. data/app/views/delayed_job_workers/index.html.haml +1 -0
  18. data/app/views/delayed_jobs/_index.html.haml +47 -52
  19. data/app/views/delayed_jobs/_show.html.haml +15 -13
  20. data/app/views/system_configurations/_edit.html.haml +14 -9
  21. data/app/views/system_configurations/_show.html.haml +18 -12
  22. data/config/brakeman.ignore +26 -0
  23. data/config/brakeman.yml +2 -0
  24. data/config/locales/en.yml +21 -3
  25. data/lib/app/controllers/concerns/core_delayed_job_metrics_controller.rb +35 -0
  26. data/lib/app/controllers/concerns/core_delayed_job_workers_controller.rb +35 -0
  27. data/lib/app/controllers/concerns/core_delayed_jobs_controller.rb +2 -3
  28. data/lib/app/jobs/cron/command.rb +1 -4
  29. data/lib/app/jobs/cron/record_delayed_job_metrics.rb +25 -0
  30. data/lib/app/jobs/cron/restart_orphaned_delayed_jobs.rb +44 -0
  31. data/lib/app/jobs/cron/server.rb +32 -15
  32. data/lib/app/jobs/cron/switchboard_sync_configuration.rb +2 -0
  33. data/lib/app/jobs/cron/switchboard_sync_models.rb +2 -0
  34. data/lib/app/jobs/cron/trim_collection.rb +1 -1
  35. data/lib/app/jobs/cron/trim_delayed_job_metrics.rb +29 -0
  36. data/lib/app/jobs/cron/trim_delayed_job_workers.rb +39 -0
  37. data/lib/app/jobs/cron/trim_failed_delayed_jobs.rb +2 -0
  38. data/lib/app/models/api_token.rb +9 -0
  39. data/lib/app/models/command_job.rb +12 -11
  40. data/lib/app/models/concerns/api_tokenable.rb +38 -0
  41. data/lib/app/models/concerns/aws_configuration.rb +65 -0
  42. data/lib/app/models/concerns/cdn_url.rb +7 -0
  43. data/lib/app/models/concerns/core_smtp_configuration.rb +65 -0
  44. data/lib/app/models/concerns/core_system_configuration.rb +18 -201
  45. data/lib/app/models/concerns/delayed_job_configuration.rb +24 -0
  46. data/lib/app/models/concerns/email_able.rb +6 -2
  47. data/lib/app/models/concerns/google_sso_configuration.rb +32 -0
  48. data/lib/app/models/concerns/search_able.rb +16 -0
  49. data/lib/app/models/concerns/server_process_able.rb +69 -0
  50. data/lib/app/models/concerns/slack_configuration.rb +38 -0
  51. data/lib/app/models/concerns/standard_model.rb +5 -2
  52. data/lib/app/models/concerns/switchboard_configuration.rb +43 -0
  53. data/lib/app/models/concerns/twilio_configuration.rb +37 -0
  54. data/lib/app/models/concerns/zendesk_configuration.rb +92 -0
  55. data/lib/app/models/{delayed_job.rb → delayed/backend/delayed_job.rb} +41 -0
  56. data/lib/app/models/delayed/jobs/metric.rb +61 -0
  57. data/lib/app/models/delayed/jobs/run.rb +40 -0
  58. data/lib/app/models/delayed/jobs/worker.rb +43 -0
  59. data/lib/app/models/delayed/plugins/time_keeper.rb +33 -0
  60. data/lib/app/models/delayed/worker.rb +24 -0
  61. data/lib/app/models/email_notification.rb +2 -1
  62. data/lib/app/models/email_template.rb +5 -6
  63. data/lib/app/models/notification.rb +12 -2
  64. data/lib/app/models/sms_notification.rb +9 -6
  65. data/lib/app/models/template.rb +12 -12
  66. data/lib/web47core/version.rb +1 -1
  67. data/lib/web47core.rb +33 -9
  68. metadata +114 -69
@@ -0,0 +1,44 @@
1
+ #
2
+ # Run in cron
3
+ #
4
+ module Cron
5
+ #
6
+ # Cycle through all members and tell them to sync with with switchboard
7
+ #
8
+ class RestartOrphanedDelayedJobs < Job
9
+ cron_tab_entry :hourly
10
+
11
+ #
12
+ # Only run when we have background jobs and we have the time keeper plugin installed
13
+ #
14
+ def self.valid_environment?
15
+ Delayed::Worker.plugins.include?(Delayed::Plugins::TimeKeeper) &&
16
+ SystemConfiguration.delayed_job_restart_orphaned? &&
17
+ Delayed::Backend::Mongoid::Job.count.positive?
18
+ rescue StandardError
19
+ false
20
+ end
21
+
22
+ #
23
+ # Cycle through delayed jobs, looking for running jobs
24
+ # skip if we don't have any records or low sample set
25
+ # skip if the allowed time is less than allowed by the job
26
+ # look to see if have a worker associated with the delayed job
27
+ # If we dont have a worker or the worker is dead, restart the job
28
+ #
29
+ def execute
30
+ Delayed::Backend::Mongoid::Job.each do |delayed_job|
31
+ next unless delayed_job.running?
32
+
33
+ metric = Delayed::Jobs::Metric.where(name: delayed_job.display_name).first
34
+ next if metric.blank? || metric.count < 30 # not enough data to make a call
35
+
36
+ run_time = Time.now.utc - delayed_job.locked_at
37
+ next if run_time < metric.max_allowed_seconds # still within parameters
38
+
39
+ worker = delayed_job.worker
40
+ delayed_job.resubmit if worker.blank? || worker.dead?
41
+ end
42
+ end
43
+ end
44
+ end
@@ -6,6 +6,7 @@ module Cron
6
6
  #
7
7
  class Server
8
8
  include StandardModel
9
+ include ServerProcessAble
9
10
  #
10
11
  # Constants
11
12
  #
@@ -15,20 +16,19 @@ module Cron
15
16
  #
16
17
  # Fields
17
18
  #
18
- field :host_name, type: String
19
- field :pid, type: Integer
20
19
  field :desired_server_count, type: Integer, default: 0
21
20
  field :current_server_count, type: Integer, default: 0
22
- field :last_check_in_at, type: Time, default: Time.now.utc
23
21
  field :state, type: String, default: STATE_SECONDARY
24
22
  #
25
23
  # Validations
26
24
  #
27
- validates :host_name, presence: true
28
- validates :pid, presence: true
29
- validates :last_check_in_at, presence: true
30
25
  validates :state, inclusion: { in: ALL_STATES }
31
26
  validate :high_lander
27
+ #
28
+ # Callback
29
+ #
30
+ before_validation :ensure_last_check_in
31
+
32
32
  #
33
33
  # Go through the logic once a minute
34
34
  #
@@ -89,7 +89,7 @@ module Cron
89
89
  # Warm up a server on the next evaluation
90
90
  #
91
91
  def self.warm_up_server
92
- return unless SystemConfiguration.aws_auto_scaling_configured?
92
+ return unless auto_scaling_configured?
93
93
 
94
94
  primary_server.auto_scale([primary_server.desired_server_count + 1, 10].min)
95
95
  end
@@ -148,18 +148,11 @@ module Cron
148
148
  !alive?
149
149
  end
150
150
 
151
- #
152
- # Perform a check in for the server
153
- #
154
- def check_in
155
- set({ last_check_in_at: Time.now.utc })
156
- end
157
-
158
151
  #
159
152
  # Auto scale environment
160
153
  #
161
154
  def check_auto_scale
162
- return unless SystemConfiguration.aws_auto_scaling_configured?
155
+ return unless auto_scaling_configured?
163
156
 
164
157
  if delayed_jobs_count.eql?(0)
165
158
  handle_zero_job_count
@@ -181,6 +174,24 @@ module Cron
181
174
  @sys_config ||= SystemConfiguration.configuration
182
175
  end
183
176
 
177
+ #
178
+ # Test if autoscaling is configured, return false if there is an error
179
+ #
180
+ def auto_scaling_configured?
181
+ @auto_scaling_configured ||= sys_config.aws_auto_scaling_configured?
182
+ rescue StandardError
183
+ false
184
+ end
185
+
186
+ #
187
+ # Test if autoscaling is configured, return false if there is an error
188
+ #
189
+ def self.auto_scaling_configured?
190
+ SystemConfiguration.aws_auto_scaling_configured?
191
+ rescue StandardError
192
+ false
193
+ end
194
+
184
195
  #
185
196
  # Returns the AutoScalingGroup associated with the account
186
197
  #
@@ -258,6 +269,8 @@ module Cron
258
269
  client.update_auto_scaling_group(auto_scaling_group_name: sys_config.aws_auto_scaling_group_name,
259
270
  min_size: desired_count,
260
271
  desired_capacity: desired_count)
272
+ rescue StandardError => error
273
+ App47Logger.log_error "Unable to set auto scaler to #{desired_count}", error
261
274
  end
262
275
 
263
276
  #
@@ -283,5 +296,9 @@ module Cron
283
296
  def inactive_count
284
297
  desired_server_count
285
298
  end
299
+
300
+ def ensure_last_check_in
301
+ self.last_check_in_at ||= Time.now.utc
302
+ end
286
303
  end
287
304
  end
@@ -13,6 +13,8 @@ module Cron
13
13
  #
14
14
  def self.valid_environment?
15
15
  SystemConfiguration.switchboard_configured?
16
+ rescue StandardError
17
+ false
16
18
  end
17
19
 
18
20
  #
@@ -13,6 +13,8 @@ module Cron
13
13
  #
14
14
  def self.valid_environment?
15
15
  SystemConfiguration.switchboard_configured? && Web47core::Config.switchboard_able_models.present?
16
+ rescue StandardError
17
+ false
16
18
  end
17
19
 
18
20
  #
@@ -57,7 +57,7 @@ module Cron
57
57
  # Allowed time the amount of time allowed to exists before deleting
58
58
  #
59
59
  def allowed_time
60
- 30.days.ago
60
+ 30.days.ago.utc
61
61
  end
62
62
  end
63
63
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cron
4
+ #
5
+ # Clean up Audit Logs that have not been updated in 90 days
6
+ #
7
+ class TrimDelayedJobMetrics < TrimCollection
8
+ #
9
+ # Fetch each Audit Log and delete it if hasn't been updated in 90 days
10
+ #
11
+ def collection
12
+ Delayed::Jobs::Metric.all
13
+ end
14
+
15
+ #
16
+ # Return which field to use for comparison when trimming objects
17
+ #
18
+ def comparison_field
19
+ :last_run_at
20
+ end
21
+
22
+ #
23
+ # Allowed time the amount of time allowed to exists before deleting
24
+ #
25
+ def allowed_time
26
+ 12.months.ago.utc
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cron
4
+ #
5
+ # Clean up Audit Logs that have not been updated in 90 days
6
+ #
7
+ class TrimDelayedJobWorkers < TrimCollection
8
+ #
9
+ # Fetch each Audit Log and delete it if hasn't been updated in 90 days
10
+ #
11
+ def collection
12
+ Delayed::Jobs::Worker.all
13
+ end
14
+
15
+ #
16
+ # Check if the cron job server hasn't reported in a while
17
+ #
18
+ def archive?(worker)
19
+ super && worker.runs.blank?
20
+ rescue StandardError => error
21
+ App47Logger.log_warn "Unable to archive item #{worker.inspect}", error
22
+ false
23
+ end
24
+
25
+ #
26
+ # Return which field to use for comparison when trimming objects
27
+ #
28
+ def comparison_field
29
+ :last_check_in_at
30
+ end
31
+
32
+ #
33
+ # Allowed time the amount of time allowed to exists before deleting
34
+ #
35
+ def allowed_time
36
+ 2.days.ago.utc
37
+ end
38
+ end
39
+ end
@@ -35,6 +35,8 @@ module Cron
35
35
  #
36
36
  def self.valid_environment?
37
37
  SystemConfiguration.slack_api_url.present?
38
+ rescue StandardError
39
+ false
38
40
  end
39
41
  end
40
42
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # A specific API token to be used as part of a collection of tokens
5
+ #
6
+ class ApiToken
7
+ include StandardModel
8
+ include ApiTokenable
9
+ end
@@ -110,7 +110,7 @@ class CommandJob
110
110
  end
111
111
 
112
112
  def failure_or_cancelled?
113
- job_state?([STATE_FAIL, STATE_CANCELLED], true)
113
+ job_state?([STATE_FAIL, STATE_CANCELLED], default_state: true)
114
114
  end
115
115
 
116
116
  #
@@ -118,11 +118,11 @@ class CommandJob
118
118
  # state. If there is a match, then return true, otherwise return false.
119
119
  # If there is an error, return the default.
120
120
  #
121
- def job_state?(states, default = false)
121
+ def job_state?(states, default_state: false)
122
122
  states.is_a?(Array) ? states.include?(state) : states.eql?(state)
123
123
  rescue StandardError => error
124
- App47Logger.log_warn "Unable to check job failed or cancelled #{self.inspect}", error
125
- default
124
+ App47Logger.log_warn "Unable to check job failed or cancelled #{inspect}", error
125
+ default_state
126
126
  end
127
127
 
128
128
  #
@@ -323,8 +323,9 @@ class CommandJob
323
323
  else
324
324
  logs.create!(dir: dir, command: command, message: output)
325
325
  end
326
- check_for_text(output, options[:error_texts], true)
327
- check_for_text(output, options[:required_texts], false)
326
+ options[:output_limit] ||= -1
327
+ check_for_text(output, options[:error_texts], output_limit: options[:output_limit])
328
+ check_for_text(output, options[:required_texts], inclusive_check: false, output_limit: options[:output_limit])
328
329
  output
329
330
  end
330
331
 
@@ -343,16 +344,17 @@ class CommandJob
343
344
 
344
345
  #
345
346
  # Check if any occurrences were found (or not found)
347
+ # For most command jobs, we want to see the full output. -1 accomplishes this
346
348
  #
347
- def check_for_text(output, texts = [], inclusive = true)
349
+ def check_for_text(output, texts = [], inclusive_check: true, output_limit: -1)
348
350
  return if texts.blank?
349
351
 
350
352
  texts = [texts] if texts.is_a?(String)
351
353
  texts.each do |text|
352
- if inclusive
353
- raise "Error: found text (#{text}) - #{output}" if output.match?(/#{text}/)
354
+ if inclusive_check
355
+ raise "Error: found text (#{text}) - #{output[0...output_limit]}" if output.match?(/#{text}/)
354
356
  else
355
- raise "Error: missing text (#{text}) - #{output}" unless output.match?(/#{text}/)
357
+ raise "Error: missing text (#{text}) - #{output[0...output_limit]}" unless output.match?(/#{text}/)
356
358
  end
357
359
  end
358
360
  end
@@ -370,5 +372,4 @@ class CommandJob
370
372
  def sort_fields
371
373
  %i[created_at]
372
374
  end
373
-
374
375
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # API tokenable, support for the api token in user, but also may be applied elsewhere
5
+ #
6
+ module ApiTokenable
7
+ extend ActiveSupport::Concern
8
+
9
+ def self.included(base)
10
+ base.class_eval do
11
+ # store api token
12
+ field :api_token, type: String
13
+ # if the api token should be reset
14
+ field :reset_api_token, type: Boolean, default: true
15
+ field :last_authenticated_at, type: Time
16
+ field :last_authenticated_ip, type: String
17
+ # call back to reset the api token.
18
+ before_save :assign_api_token, if: :reset_api_token
19
+ # set the index on api token
20
+ index({ api_token: 1 }, background: true)
21
+
22
+ def cycle_api_token
23
+ self.set api_token: SecureRandom.urlsafe_base64
24
+ api_token
25
+ end
26
+
27
+ private
28
+
29
+ #
30
+ # set the api token
31
+ #
32
+ def assign_api_token
33
+ self.reset_api_token = false
34
+ self.api_token = SecureRandom.urlsafe_base64
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # AWS Configuration
5
+ #
6
+ module AwsConfiguration
7
+ extend ActiveSupport::Concern
8
+
9
+ def self.included(base)
10
+ base.class_eval do
11
+ #
12
+ # Fields
13
+ #
14
+ field :aws_region, type: String
15
+ field :aws_access_key_id, type: String
16
+ field :aws_secret_access_key, type: String
17
+ field :aws_auto_scaling_group_name, type: String
18
+ end
19
+ end
20
+
21
+ #
22
+ # Make sure the password doesn't get blanked out on an update
23
+ #
24
+ def secure_fields
25
+ super + %i[aws_secret_access_key]
26
+ end
27
+
28
+ #
29
+ # Determine if AWS is configured
30
+ #
31
+ def aws_configured?
32
+ [aws_region.present?, aws_access_key_id.present?, aws_secret_access_key.present?].all?
33
+ end
34
+
35
+ #
36
+ # Determine if auto scaling group is configured
37
+ #
38
+ def aws_auto_scaling_configured?
39
+ aws_configured? && aws_auto_scaling_group_name.present?
40
+ end
41
+
42
+ #
43
+ # AWS client.
44
+ #
45
+ def aws_ec2_client
46
+ return nil unless aws_configured?
47
+
48
+ @aws_ec2_client ||= Aws::EC2::Client.new(region: aws_region,
49
+ credentials: Aws::Credentials.new(aws_access_key_id,
50
+ aws_secret_access_key))
51
+ end
52
+
53
+ #
54
+ # S3 Client
55
+ #
56
+ def aws_s3_client
57
+ return nil unless aws_configured?
58
+
59
+ # We want this to remake itself each time because it is possible that the
60
+ # => user would change the access keys in between actions. Huh?
61
+ @aws_s3_client ||= Aws::S3::Client.new(region: aws_vault_bucket_region,
62
+ access_key_id: aws_access_key_id,
63
+ secret_access_key: aws_secret_access_key)
64
+ end
65
+ end
@@ -17,6 +17,13 @@
17
17
  #
18
18
  module CdnUrl
19
19
  extend ActiveSupport::Concern
20
+ #
21
+ # Constants
22
+ #
23
+ STYLE_S3_FILE_PATH = ':class/:attachment/:id/:style.:extension' unless defined? STYLE_S3_FILE_PATH
24
+ STYLE_FILE_PATH = 'public/system/:class/:attachment/:id/:style.:extension' unless defined? STYLE_FILE_PATH
25
+ STYLE_S3_FILE_URL = ':s3_domain_url' unless defined? STYLE_S3_FILE_URL
26
+ STYLE_FILE_URL = ':rails_root/public/system/:class/:attachment/:id/:style.:extension' unless defined? STYLE_FILE_URL
20
27
 
21
28
  def method_missing(method, *args)
22
29
  if method.to_s.start_with? 'cdn_'
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # SMTP Configuration
5
+ #
6
+ module CoreSmtpConfiguration
7
+ extend ActiveSupport::Concern
8
+
9
+ def self.included(base)
10
+ base.class_eval do
11
+ #
12
+ # Fields
13
+ #
14
+ field :default_email, type: String, default: 'support@app47.com'
15
+ field :support_email, type: String, default: 'support@app47.com'
16
+ field :smtp_name, type: String
17
+ field :smtp_address, type: String
18
+ field :smtp_domain, type: String
19
+ field :smtp_port, type: Integer, default: 587
20
+ field :smtp_user_name, type: String
21
+ field :smtp_password, type: String
22
+ field :smtp_enable_starttls_auto, type: Boolean, default: false
23
+ field :mailgun_api_key, type: String
24
+ field :email_notification_ttl, type: Integer, default: 180
25
+ end
26
+ base.extend SmtpClassMethods
27
+ end
28
+
29
+ #
30
+ # Class methods for smtp configuration
31
+ #
32
+ module SmtpClassMethods
33
+ def smtp_configuration
34
+ output = {}
35
+ config = configuration
36
+ fields = %w[name address domain port user_name password enable_starttls_auto]
37
+ fields.each do |field|
38
+ field_name = "smtp_#{field}".to_sym
39
+ output[field.to_sym] = config.send(field_name)
40
+ end
41
+ output
42
+ end
43
+ end
44
+
45
+ #
46
+ # Make sure the password doesn't get blanked out on an update
47
+ #
48
+ def secure_fields
49
+ super + %i[smtp_password mailgun_api_key]
50
+ end
51
+
52
+ #
53
+ # Determine if SMTP is configured
54
+ #
55
+ def smtp_configured?
56
+ smtp_name.present? && smtp_address.present? && smtp_domain.present?
57
+ end
58
+
59
+ #
60
+ # Determine if mailgun is configured
61
+ #
62
+ def mail_gun_configured?
63
+ smtp_configured? && mailgun_api_key.present?
64
+ end
65
+ end