mission_control-jobs 0.6.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +39 -4
  3. data/app/controllers/concerns/mission_control/jobs/basic_authentication.rb +33 -0
  4. data/app/controllers/concerns/mission_control/jobs/job_filters.rb +16 -1
  5. data/app/controllers/mission_control/jobs/application_controller.rb +12 -0
  6. data/app/helpers/mission_control/jobs/dates_helper.rb +16 -2
  7. data/app/helpers/mission_control/jobs/jobs_helper.rb +1 -1
  8. data/app/views/mission_control/jobs/jobs/_filters.html.erb +11 -1
  9. data/app/views/mission_control/jobs/jobs/_general_information.html.erb +4 -4
  10. data/app/views/mission_control/jobs/jobs/_job.html.erb +1 -1
  11. data/app/views/mission_control/jobs/jobs/blocked/_job.html.erb +1 -1
  12. data/app/views/mission_control/jobs/jobs/failed/_job.html.erb +1 -1
  13. data/app/views/mission_control/jobs/jobs/finished/_job.html.erb +1 -1
  14. data/app/views/mission_control/jobs/jobs/in_progress/_job.html.erb +1 -1
  15. data/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb +1 -1
  16. data/app/views/mission_control/jobs/queues/_job.html.erb +1 -1
  17. data/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb +2 -2
  18. data/app/views/mission_control/jobs/shared/_job.html.erb +3 -3
  19. data/app/views/mission_control/jobs/workers/_worker.html.erb +1 -1
  20. data/lib/active_job/jobs_relation.rb +7 -5
  21. data/lib/active_job/queue_adapters/solid_queue_ext.rb +7 -2
  22. data/lib/mission_control/jobs/authentication.rb +67 -0
  23. data/lib/mission_control/jobs/engine.rb +11 -2
  24. data/lib/mission_control/jobs/i18n_config.rb +5 -0
  25. data/lib/mission_control/jobs/tasks.rb +8 -0
  26. data/lib/mission_control/jobs/version.rb +1 -1
  27. data/lib/mission_control/jobs.rb +13 -3
  28. metadata +11 -4
  29. data/lib/tasks/mission_control/jobs_tasks.rake +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90d1d2bb94c81934c6d0f2c366b76ba9d9ee778c90a4ca0385515ea7b84d658f
4
- data.tar.gz: 51f7c76c6de7a39cc18e5b813a7d84ae18c94e9cd25487c0f16cb94e650d2319
3
+ metadata.gz: 8afd612ead034dc5c0f4b3798f62943bf021a557e4af5d7a5a565e50bfe5d647
4
+ data.tar.gz: 077c09bbff77e7f73de506600c447a05f7cb08b8ee266a1b107395ac393338e9
5
5
  SHA512:
6
- metadata.gz: 69e67bae39f22105acac0e10b9c52452fb453acc6e52e868746875f9daa75cd5556eadfca06db76eecb70e6c634dfdc1ecc217cc001d726da9b93bd9606b7ebe
7
- data.tar.gz: ab9c390dfd70c8b196ee3e6c1e5c07821299c2b23fd733a62c39d7b01cab3350f53c17ae9c9bdf5aaad9becf8b50b55dfd34b555b30500585373f901296e7717
6
+ metadata.gz: 99c16735667988a38b3b9f356406f8aec967044cf17f0f81cc05f8b6bb5fb2a85965ad34c30cf537ad95c43935cd0cc6e5f3ec7185ec661f06ec3579a8e73ab2
7
+ data.tar.gz: af8726be9391650a8f27c0ba855ecd633dd5bb23d9d0be78a8d5d5f4fdb9949d8d65f95ee638aebcede5fa863fa20c76ea054b8e7a3d7021bd0408ec0877c26e
data/README.md CHANGED
@@ -32,7 +32,7 @@ And that's it. With this alone, you should be able to access Mission Control Job
32
32
 
33
33
  ### API-only apps or apps using `vite_rails` and other asset pipelines outside Rails
34
34
 
35
- If you want to use this gem with an [API-only Rails app](https://guides.rubyonrails.org/api_app.html) or an app that's using `vite_ruby`/`vite_rails`, or some other custom asset pipeline different from Sprockets and Propshaft, you need just one more thing: configure an asset pipeline so you can serve the JavaScript and CSS included in this gem. We recommend to use [`Propshaft`](https://github.com/rails/propshaft). You simply need to add this line to your application's Gemfile:
35
+ If you want to use this gem with an [API-only Rails app](https://guides.rubyonrails.org/api_app.html) or an app that's using `vite_ruby`/`vite_rails`, or some other custom asset pipeline different from Sprockets and Propshaft, you need one more thing: configure an asset pipeline so you can serve the JavaScript and CSS included in this gem. We recommend to use [`Propshaft`](https://github.com/rails/propshaft). You simply need to add this line to your application's Gemfile:
36
36
 
37
37
  ```ruby
38
38
  gem "propshaft"
@@ -43,11 +43,42 @@ Then execute
43
43
  $ bundle install
44
44
  ```
45
45
 
46
- And you should be ready to go.
46
+ Then, make sure you add a step to your deployment pipeline to precompile assets:
47
+ ```
48
+ RAILS_ENV=production rails assets:precompile
49
+ ```
50
+
51
+ For example, if you're using the Dockerfile generated by Rails with an API-only app or having skipped the assets pipeline, re-add:
52
+ ```
53
+ # Precompiling assets for production without requiring secret RAILS_MASTER_KEY
54
+ RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
55
+ ```
56
+
57
+ *Note: Legacy CSS bundlers `sass-rails` and `sassc-rails` may fail to compile some of the CSS vendored into this library from [Bulma](https://github.com/jgthms/bulma), which was created in [Dart SASS](https://sass-lang.com/dart-sass/). You will therefore need to upgrade to `dartsass-rails` or some library that relies on it, like `cssbundling-rails`.*
58
+
59
+ ### Authentication
60
+
61
+ Mission Control comes with **HTTP basic authentication enabled and closed** by default. Credentials are stored in [Rails's credentials](https://edgeguides.rubyonrails.org/security.html#custom-credentials) like this:
62
+ ```yml
63
+ mission_control:
64
+ http_basic_auth_user: dev
65
+ http_basic_auth_password: secret
66
+ ```
47
67
 
48
- ### Authentication and base controller class
68
+ If no credentials are configured, Mission Control won't be accessible. To set these up, you can run the generator provided like this:
49
69
 
50
- By default, Mission Control's controllers will extend the host app's `ApplicationController`. If no authentication is enforced, `/jobs` will be available to everyone. You might want to implement some kind of authentication for this in your app. To make this easier, you can specify a different controller as the base class for Mission Control's controllers:
70
+ ```
71
+ bin/rails mission_control:jobs:authentication:configure
72
+ ```
73
+
74
+ To set them up for different environments you can use the `RAILS_ENV` environment variable, like this:
75
+ ```
76
+ RAILS_ENV=production bin/rails mission_control:jobs:authentication:configure
77
+ ```
78
+
79
+ #### Custom authentication
80
+
81
+ You can provide your own authentication mechanism, for example, if you have a certain type of admin user in your app that can access Mission Control. To make this easier, you can specify a different controller as the base class for Mission Control's controllers. By default, Mission Control's controllers will extend the host app's `ApplicationController`, but you can change this easily:
51
82
 
52
83
  ```ruby
53
84
  Rails.application.configure do
@@ -58,7 +89,11 @@ end
58
89
  Or, in your environment config or `application.rb`:
59
90
  ```ruby
60
91
  config.mission_control.jobs.base_controller_class = "AdminController"
92
+ ```
61
93
 
94
+ If you do this, you can disable the default HTTP Basic Authentication using the following option:
95
+ ```ruby
96
+ config.mission_control.jobs.http_basic_auth_enabled = false
62
97
  ```
63
98
 
64
99
  ### Other configuration settings
@@ -0,0 +1,33 @@
1
+ module MissionControl::Jobs::BasicAuthentication
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_action :authenticate_by_http_basic
6
+ end
7
+
8
+ private
9
+ def authenticate_by_http_basic
10
+ if http_basic_authentication_enabled?
11
+ if http_basic_authentication_configured?
12
+ http_basic_authenticate_or_request_with(**http_basic_authentication_credentials)
13
+ else
14
+ head :unauthorized
15
+ end
16
+ end
17
+ end
18
+
19
+ def http_basic_authentication_enabled?
20
+ MissionControl::Jobs.http_basic_auth_enabled
21
+ end
22
+
23
+ def http_basic_authentication_configured?
24
+ http_basic_authentication_credentials.values.all?(&:present?)
25
+ end
26
+
27
+ def http_basic_authentication_credentials
28
+ {
29
+ name: MissionControl::Jobs.http_basic_auth_user,
30
+ password: MissionControl::Jobs.http_basic_auth_password
31
+ }.transform_values(&:presence)
32
+ end
33
+ end
@@ -9,10 +9,25 @@ module MissionControl::Jobs::JobFilters
9
9
 
10
10
  private
11
11
  def set_filters
12
- @job_filters = { job_class_name: params.dig(:filter, :job_class_name).presence, queue_name: params.dig(:filter, :queue_name).presence }.compact
12
+ @job_filters = {
13
+ job_class_name: params.dig(:filter, :job_class_name).presence,
14
+ queue_name: params.dig(:filter, :queue_name).presence,
15
+ finished_at: finished_at_range_params
16
+ }.compact
13
17
  end
14
18
 
15
19
  def active_filters?
16
20
  @job_filters.any?
17
21
  end
22
+
23
+ def finished_at_range_params
24
+ range_start, range_end = params.dig(:filter, :finished_at_start), params.dig(:filter, :finished_at_end)
25
+ if range_start || range_end
26
+ (parse_with_time_zone(range_start)..parse_with_time_zone(range_end))
27
+ end
28
+ end
29
+
30
+ def parse_with_time_zone(date)
31
+ DateTime.parse(date).in_time_zone if date.present?
32
+ end
18
33
  end
@@ -9,11 +9,23 @@ class MissionControl::Jobs::ApplicationController < MissionControl::Jobs.base_co
9
9
  helper MissionControl::Jobs::ApplicationHelper unless self < MissionControl::Jobs::ApplicationHelper
10
10
  helper Importmap::ImportmapTagsHelper unless self < Importmap::ImportmapTagsHelper
11
11
 
12
+ include MissionControl::Jobs::BasicAuthentication
12
13
  include MissionControl::Jobs::ApplicationScoped, MissionControl::Jobs::NotFoundRedirections
13
14
  include MissionControl::Jobs::AdapterFeatures
14
15
 
16
+ around_action :set_current_locale
17
+
15
18
  private
16
19
  def default_url_options
17
20
  { server_id: MissionControl::Jobs::Current.server }
18
21
  end
22
+
23
+ def set_current_locale(&block)
24
+ @previous_config = I18n.config
25
+ I18n.config = MissionControl::Jobs::I18nConfig.new
26
+ I18n.with_locale(:en, &block)
27
+ ensure
28
+ I18n.config = @previous_config
29
+ @previous_config = nil
30
+ end
19
31
  end
@@ -1,5 +1,19 @@
1
1
  module MissionControl::Jobs::DatesHelper
2
- def formatted_time(time)
3
- time.in_time_zone.strftime("%Y-%m-%d %H:%M:%S.%3N %Z")
2
+ def time_distance_in_words_with_title(time)
3
+ tag.span time_ago_in_words_with_default_options(time), title: "Since #{time.to_fs(:long)}"
4
+ end
5
+
6
+ def bidirectional_time_distance_in_words_with_title(time)
7
+ time_distance = if time.past?
8
+ "#{time_ago_in_words_with_default_options(time)} ago"
9
+ else
10
+ "in #{time_ago_in_words_with_default_options(time)}"
11
+ end
12
+
13
+ tag.span time_distance, title: time.to_fs(:long)
14
+ end
15
+
16
+ def time_ago_in_words_with_default_options(time)
17
+ time_ago_in_words(time, include_seconds: true, locale: :en)
4
18
  end
5
19
  end
@@ -29,7 +29,7 @@ module MissionControl::Jobs::JobsHelper
29
29
  when "blocked" then [ "Queue", "Blocked by", "" ]
30
30
  when "finished" then [ "Queue", "Finished" ]
31
31
  when "scheduled" then [ "Queue", "Scheduled", "" ]
32
- when "in_progress" then [ "Queue", "Run by", "Running since" ]
32
+ when "in_progress" then [ "Queue", "Run by", "Running for" ]
33
33
  else []
34
34
  end
35
35
  end
@@ -12,6 +12,16 @@
12
12
  <%= form.text_field :queue_name, value: @job_filters[:queue_name], class: "input", list: "queue-names", placeholder: "Filter by queue name..." %>
13
13
  </div>
14
14
 
15
+ <% if jobs_status == "finished" %>
16
+ <div class="select is-rounded">
17
+ <%= form.datetime_field :finished_at_start, value: @job_filters[:finished_at]&.begin, class: "input", placeholder: "Finished from" %>
18
+ </div>
19
+
20
+ <div class="select is-rounded">
21
+ <%= form.datetime_field :finished_at_end, value: @job_filters[:finished_at]&.end, class: "input", placeholder: "Finished to" %>
22
+ </div>
23
+ <% end %>
24
+
15
25
  <%= hidden_field_tag :server_id, MissionControl::Jobs::Current.server.id %>
16
26
 
17
27
  <datalist id="job-classes" class="is-hidden">
@@ -29,7 +39,7 @@
29
39
  </div>
30
40
 
31
41
  <div class="control">
32
- <%= link_to "Clear", application_jobs_path(MissionControl::Jobs::Current.application, jobs_status, job_class_name: nil, queue_name: nil), class: "button" %>
42
+ <%= link_to "Clear", application_jobs_path(MissionControl::Jobs::Current.application, jobs_status, job_class_name: nil, queue_name: nil, finished_at: nil..nil), class: "button" %>
33
43
  </div>
34
44
  </div>
35
45
  </div>
@@ -23,14 +23,14 @@
23
23
  <tr>
24
24
  <th>Enqueued</th>
25
25
  <td>
26
- <%= formatted_time(job.enqueued_at.to_datetime) %>
26
+ <%= time_distance_in_words_with_title(job.enqueued_at.to_datetime) %> ago
27
27
  </td>
28
28
  </tr>
29
29
  <% if job.scheduled? %>
30
30
  <tr>
31
31
  <th>Scheduled</th>
32
32
  <td>
33
- <%= formatted_time(job.scheduled_at) %>
33
+ <%= bidirectional_time_distance_in_words_with_title(job.scheduled_at) %>
34
34
  <% if job_delayed?(job) %>
35
35
  <div class="is-danger tag ml-4">delayed</div>
36
36
  <% end %>
@@ -41,7 +41,7 @@
41
41
  <tr>
42
42
  <th>Failed</th>
43
43
  <td>
44
- <%= formatted_time(job.failed_at) %>
44
+ <%= time_distance_in_words_with_title(job.failed_at) %> ago
45
45
  </td>
46
46
  </tr>
47
47
  <% end %>
@@ -49,7 +49,7 @@
49
49
  <tr>
50
50
  <th>Finished at</th>
51
51
  <td>
52
- <%= formatted_time(job.finished_at) %>
52
+ <%= time_distance_in_words_with_title(job.finished_at) %> ago
53
53
  </td>
54
54
  </tr>
55
55
  <% end %>
@@ -6,7 +6,7 @@
6
6
  <div class="is-family-monospace"><%= job_arguments(job) %></div>
7
7
  <% end %>
8
8
 
9
- <div class="has-text-grey is-size-7">Enqueued <%= formatted_time(job.enqueued_at.to_datetime) %></div>
9
+ <div class="has-text-grey is-size-7">Enqueued <%= time_distance_in_words_with_title(job.enqueued_at.to_datetime) %> ago</div>
10
10
  </td>
11
11
 
12
12
  <%= render "mission_control/jobs/jobs/#{jobs_status}/job", job: job %>
@@ -1,6 +1,6 @@
1
1
  <td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
2
2
  <td><div class="is-family-monospace is-size-7"><%= job.blocked_by %></div>
3
- <div class="has-text-grey is-size-7">Until <%= formatted_time(job.blocked_until) %></div>
3
+ <div class="has-text-grey is-size-7">Expires <%= bidirectional_time_distance_in_words_with_title(job.blocked_until) %></div>
4
4
  </td>
5
5
  <td class="pr-0">
6
6
  <%= render "mission_control/jobs/jobs/blocked/actions", job: job %>
@@ -1,6 +1,6 @@
1
1
  <td>
2
2
  <%= link_to failed_job_error(job), application_job_path(@application, job.job_id, anchor: "error") %>
3
- <div class="has-text-grey"><%= formatted_time(job.failed_at) %></div>
3
+ <div class="has-text-grey"><%= time_distance_in_words_with_title(job.failed_at) %> ago</div>
4
4
  </td>
5
5
  <td class="pr-0">
6
6
  <%= render "mission_control/jobs/jobs/failed/actions", job: job %>
@@ -1,2 +1,2 @@
1
1
  <td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
2
- <td><div class="has-text-grey"><%= formatted_time(job.finished_at) %></div></td>
2
+ <td><div class="has-text-grey"><%= time_distance_in_words_with_title(job.finished_at) %> ago</div></td>
@@ -6,4 +6,4 @@
6
6
 
7
7
  <% end %>
8
8
  </td>
9
- <td><div class="has-text-grey"><%= job.started_at ? formatted_time(job.started_at) : "(Finished)" %></div></td>
9
+ <td><div class="has-text-grey"><%= job.started_at ? time_distance_in_words_with_title(job.started_at) : "(Finished)" %></div></td>
@@ -1,6 +1,6 @@
1
1
  <td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
2
2
  <td>
3
- <%= formatted_time(job.scheduled_at) %>
3
+ <%= bidirectional_time_distance_in_words_with_title(job.scheduled_at) %>
4
4
  <% if job_delayed?(job) %>
5
5
  <div class="is-danger tag ml-4">delayed</div>
6
6
  <% end %>
@@ -3,7 +3,7 @@
3
3
  <%= link_to application_job_path(@application, job.job_id, filter: { queue_name: job.queue }) do %>
4
4
  <%= job_title(job) %>
5
5
  <% end %>
6
- <div class="has-text-grey">Enqueued on <%= formatted_time(job.enqueued_at.to_datetime) %></div>
6
+ <div class="has-text-grey">Enqueued <%= time_distance_in_words_with_title(job.enqueued_at.to_datetime) %> ago</div>
7
7
  </td>
8
8
  <td>
9
9
  <% if job.serialized_arguments.present? %>
@@ -14,8 +14,8 @@
14
14
  <% end %>
15
15
  </td>
16
16
  <td> <%= recurring_task.schedule %> </td>
17
- <td><div class="has-text-grey"><%= recurring_task.last_enqueued_at ? formatted_time(recurring_task.last_enqueued_at) : "Never" %></div></td>
18
- <td class="next_time"><div class="has-text-grey"><%= formatted_time(recurring_task.next_time) %></div></td>
17
+ <td><div class="has-text-grey"><%= recurring_task.last_enqueued_at ? bidirectional_time_distance_in_words_with_title(recurring_task.last_enqueued_at) : "Never" %></div></td>
18
+ <td class="next_time"><div class="has-text-grey"><%= bidirectional_time_distance_in_words_with_title(recurring_task.next_time) %></div></td>
19
19
  <td class="pr-0">
20
20
  <%= render "mission_control/jobs/recurring_tasks/actions", recurring_task: recurring_task %>
21
21
  </td>
@@ -3,7 +3,7 @@
3
3
  <%= link_to application_job_path(@application, job.job_id, filter: { queue_name: job.queue }) do %>
4
4
  <%= job_title(job) %>
5
5
  <% end %>
6
- <div class="has-text-grey">Enqueued on <%= formatted_time(job.enqueued_at.to_datetime) %></div>
6
+ <div class="has-text-grey">Enqueued <%= time_distance_in_words_with_title(job.enqueued_at.to_datetime) %> ago</div>
7
7
  </td>
8
8
  <td>
9
9
  <% if job.serialized_arguments.present? %>
@@ -16,9 +16,9 @@
16
16
  <td>
17
17
  <div class="has-text-grey">
18
18
  <% if job.started_at %>
19
- Running since <%= formatted_time(job.started_at) %>
19
+ Running for <%= time_distance_in_words_with_title(job.started_at) %>
20
20
  <% elsif job.finished_at %>
21
- Finished on <%= formatted_time(job.finished_at) %>
21
+ Finished <%= time_distance_in_words_with_title(job.finished_at) %> ago
22
22
  <% else %>
23
23
  Pending
24
24
  <% end %>
@@ -17,5 +17,5 @@
17
17
  <% end %>
18
18
  </td>
19
19
 
20
- <td><div class="has-text-grey"><%= formatted_time(worker.last_heartbeat_at) %></div></td>
20
+ <td><div class="has-text-grey"><%= time_distance_in_words_with_title(worker.last_heartbeat_at) %> ago</div></td>
21
21
  </tr>
@@ -25,7 +25,7 @@ class ActiveJob::JobsRelation
25
25
  STATUSES = %i[ pending failed in_progress blocked scheduled finished ]
26
26
  FILTERS = %i[ queue_name job_class_name ]
27
27
 
28
- PROPERTIES = %i[ queue_name status offset_value limit_value job_class_name worker_id recurring_task_id ]
28
+ PROPERTIES = %i[ queue_name status offset_value limit_value job_class_name worker_id recurring_task_id finished_at ]
29
29
  attr_reader *PROPERTIES, :default_page_size
30
30
 
31
31
  delegate :last, :[], :reverse, to: :to_a
@@ -51,13 +51,15 @@ class ActiveJob::JobsRelation
51
51
  # * <tt>:queue_name</tt> - To only include the jobs in the provided queue.
52
52
  # * <tt>:worker_id</tt> - To only include the jobs processed by the provided worker.
53
53
  # * <tt>:recurring_task_id</tt> - To only include the jobs corresponding to runs of a recurring task.
54
- def where(job_class_name: nil, queue_name: nil, worker_id: nil, recurring_task_id: nil)
54
+ # * <tt>:finished_at</tt> - (Range) To only include the jobs finished between the provided range
55
+ def where(job_class_name: nil, queue_name: nil, worker_id: nil, recurring_task_id: nil, finished_at: nil)
55
56
  # Remove nil arguments to avoid overriding parameters when concatenating +where+ clauses
56
57
  arguments = { job_class_name: job_class_name,
57
- queue_name: queue_name,
58
+ queue_name: queue_name&.to_s,
58
59
  worker_id: worker_id,
59
- recurring_task_id: recurring_task_id
60
- }.compact.collect { |key, value| [ key, value.to_s ] }.to_h
60
+ recurring_task_id: recurring_task_id,
61
+ finished_at: finished_at
62
+ }.compact
61
63
 
62
64
  clone_with **arguments
63
65
  end
@@ -40,7 +40,7 @@ module ActiveJob::QueueAdapters::SolidQueueExt
40
40
  end
41
41
 
42
42
  def supported_job_filters(*)
43
- [ :queue_name, :job_class_name ]
43
+ [ :queue_name, :job_class_name, :finished_at ]
44
44
  end
45
45
 
46
46
  def jobs_count(jobs_relation)
@@ -173,7 +173,7 @@ module ActiveJob::QueueAdapters::SolidQueueExt
173
173
  attr_reader :jobs_relation
174
174
 
175
175
  delegate :queue_name, :limit_value, :limit_value_provided?, :offset_value, :job_class_name,
176
- :default_page_size, :worker_id, :recurring_task_id, to: :jobs_relation
176
+ :default_page_size, :worker_id, :recurring_task_id, :finished_at, to: :jobs_relation
177
177
 
178
178
  def executions
179
179
  execution_class_by_status
@@ -190,6 +190,7 @@ module ActiveJob::QueueAdapters::SolidQueueExt
190
190
  SolidQueue::Job.finished
191
191
  .then { |jobs| filter_jobs_by_queue(jobs) }
192
192
  .then { |jobs| filter_jobs_by_class(jobs) }
193
+ .then { |jobs| filter_jobs_by_finished_at(jobs) }
193
194
  .then { |jobs| limit(jobs) }
194
195
  .then { |jobs| offset(jobs) }
195
196
  end
@@ -271,6 +272,10 @@ module ActiveJob::QueueAdapters::SolidQueueExt
271
272
  job_class_name.present? ? jobs.where(class_name: job_class_name) : jobs
272
273
  end
273
274
 
275
+ def filter_jobs_by_finished_at(jobs)
276
+ finished_at.present? ? jobs.where(finished_at: finished_at) : jobs
277
+ end
278
+
274
279
  def limit(executions_or_jobs)
275
280
  limit_value.present? ? executions_or_jobs.limit(limit_value) : executions_or_jobs
276
281
  end
@@ -0,0 +1,67 @@
1
+ require "rails/command"
2
+
3
+ class MissionControl::Jobs::Authentication < Rails::Command::Base
4
+ def self.configure
5
+ new.configure
6
+ end
7
+
8
+ def configure
9
+ if credentials_accessible?
10
+ if authentication_configured?
11
+ say "HTTP Basic Authentication is already configured for `#{Rails.env}`. You can edit it using `credentials:edit`"
12
+ else
13
+ say "Setting up credentials for HTTP Basic Authentication for `#{Rails.env}` environment."
14
+ say ""
15
+
16
+ username = ask "Enter username: "
17
+ password = SecureRandom.base58(64)
18
+
19
+ store_credentials(username, password)
20
+ say "Username and password stored in Rails encrypted credentials."
21
+ say ""
22
+ say "You can now access Mission Control – Jobs with: "
23
+ say ""
24
+ say " - Username: #{username}"
25
+ say " - password: #{password}"
26
+ say ""
27
+ say "You can also edit these in the future via `credentials:edit`"
28
+ end
29
+ else
30
+ say "Rails credentials haven't been configured or aren't accessible. Configure them following the instructions in `credentials:help`"
31
+ end
32
+ end
33
+
34
+ private
35
+ attr_reader :environment
36
+
37
+ def credentials_accessible?
38
+ credentials.read.present?
39
+ end
40
+
41
+ def authentication_configured?
42
+ %i[ http_basic_auth_user http_basic_auth_password ].any? do |key|
43
+ credentials.dig(:mission_control, key).present?
44
+ end
45
+ end
46
+
47
+ def store_credentials(username, password)
48
+ content = credentials.read + "\n" + http_authentication_entry(username, password) + "\n"
49
+ credentials.write(content)
50
+ end
51
+
52
+ def credentials
53
+ @credentials ||= Rails.application.encrypted(config.content_path, key_path: config.key_path)
54
+ end
55
+
56
+ def config
57
+ Rails.application.config.credentials
58
+ end
59
+
60
+ def http_authentication_entry(username, password)
61
+ <<~ENTRY
62
+ mission_control:
63
+ http_basic_auth_user: #{username}
64
+ http_basic_auth_password: #{password}
65
+ ENTRY
66
+ end
67
+ end
@@ -7,10 +7,14 @@ module MissionControl
7
7
  class Engine < ::Rails::Engine
8
8
  isolate_namespace MissionControl::Jobs
9
9
 
10
+ rake_tasks do
11
+ load "mission_control/jobs/tasks.rb"
12
+ end
13
+
10
14
  initializer "mission_control-jobs.middleware" do |app|
11
15
  if app.config.api_only
12
- app.middleware.use ActionDispatch::Flash
13
- app.middleware.use ::Rack::MethodOverride
16
+ config.middleware.use ActionDispatch::Flash
17
+ config.middleware.use ::Rack::MethodOverride
14
18
  end
15
19
  end
16
20
 
@@ -30,6 +34,11 @@ module MissionControl
30
34
  end
31
35
  end
32
36
 
37
+ initializer "mission_control-jobs.http_basic_auth" do |app|
38
+ MissionControl::Jobs.http_basic_auth_user = app.credentials.dig(:mission_control, :http_basic_auth_user)
39
+ MissionControl::Jobs.http_basic_auth_password = app.credentials.dig(:mission_control, :http_basic_auth_password)
40
+ end
41
+
33
42
  initializer "mission_control-jobs.active_job.extensions" do
34
43
  ActiveSupport.on_load :active_job do
35
44
  include ActiveJob::Querying
@@ -0,0 +1,5 @@
1
+ class MissionControl::Jobs::I18nConfig < ::I18n::Config
2
+ def available_locales
3
+ [ :en ]
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ namespace :mission_control do
2
+ namespace :jobs do
3
+ desc "Configure HTTP Basic Authentication"
4
+ task "authentication:configure" => :environment do
5
+ MissionControl::Jobs::Authentication.configure
6
+ end
7
+ end
8
+ end
@@ -1,5 +1,5 @@
1
1
  module MissionControl
2
2
  module Jobs
3
- VERSION = "0.6.0"
3
+ VERSION = "1.0.0"
4
4
  end
5
5
  end
@@ -7,6 +7,8 @@ loader = Zeitwerk::Loader.new
7
7
  loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
8
8
  loader.push_dir(File.expand_path("..", __dir__))
9
9
  loader.ignore("#{File.expand_path("..", __dir__)}/resque")
10
+ loader.ignore("#{File.expand_path("..", __dir__)}/mission_control/jobs/tasks.rb")
11
+ loader.ignore("#{File.expand_path("..", __dir__)}/generators")
10
12
  loader.setup
11
13
 
12
14
  module MissionControl
@@ -14,12 +16,20 @@ module MissionControl
14
16
  mattr_accessor :adapters, default: Set.new
15
17
  mattr_accessor :applications, default: MissionControl::Jobs::Applications.new
16
18
  mattr_accessor :base_controller_class, default: "::ApplicationController"
19
+
20
+ mattr_accessor :internal_query_count_limit, default: 500_000 # Hard limit to keep unlimited count queries fast enough
17
21
  mattr_accessor :delay_between_bulk_operation_batches, default: 0
22
+ mattr_accessor :scheduled_job_delay_threshold, default: 1.minute
23
+
18
24
  mattr_accessor :logger, default: ActiveSupport::Logger.new(nil)
19
- mattr_accessor :internal_query_count_limit, default: 500_000 # Hard limit to keep unlimited count queries fast enough
25
+
20
26
  mattr_accessor :show_console_help, default: true
21
- mattr_accessor :scheduled_job_delay_threshold, default: 1.minute
22
- mattr_accessor :importmap, default: Importmap::Map.new
23
27
  mattr_accessor :backtrace_cleaner
28
+
29
+ mattr_accessor :importmap, default: Importmap::Map.new
30
+
31
+ mattr_accessor :http_basic_auth_user
32
+ mattr_accessor :http_basic_auth_password
33
+ mattr_accessor :http_basic_auth_enabled, default: true
24
34
  end
25
35
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mission_control-jobs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jorge Manrubia
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-15 00:00:00.000000000 Z
11
+ date: 2024-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -363,6 +363,7 @@ files:
363
363
  - app/assets/stylesheets/mission_control/jobs/jobs.css
364
364
  - app/controllers/concerns/mission_control/jobs/adapter_features.rb
365
365
  - app/controllers/concerns/mission_control/jobs/application_scoped.rb
366
+ - app/controllers/concerns/mission_control/jobs/basic_authentication.rb
366
367
  - app/controllers/concerns/mission_control/jobs/failed_jobs_bulk_operations.rb
367
368
  - app/controllers/concerns/mission_control/jobs/job_filters.rb
368
369
  - app/controllers/concerns/mission_control/jobs/job_scoped.rb
@@ -464,29 +465,35 @@ files:
464
465
  - lib/mission_control/jobs/adapter.rb
465
466
  - lib/mission_control/jobs/application.rb
466
467
  - lib/mission_control/jobs/applications.rb
468
+ - lib/mission_control/jobs/authentication.rb
467
469
  - lib/mission_control/jobs/console/connect_to.rb
468
470
  - lib/mission_control/jobs/console/context.rb
469
471
  - lib/mission_control/jobs/console/jobs_help.rb
470
472
  - lib/mission_control/jobs/engine.rb
471
473
  - lib/mission_control/jobs/errors/incompatible_adapter.rb
472
474
  - lib/mission_control/jobs/errors/resource_not_found.rb
475
+ - lib/mission_control/jobs/i18n_config.rb
473
476
  - lib/mission_control/jobs/identified_by_name.rb
474
477
  - lib/mission_control/jobs/identified_elements.rb
475
478
  - lib/mission_control/jobs/server.rb
476
479
  - lib/mission_control/jobs/server/recurring_tasks.rb
477
480
  - lib/mission_control/jobs/server/serializable.rb
478
481
  - lib/mission_control/jobs/server/workers.rb
482
+ - lib/mission_control/jobs/tasks.rb
479
483
  - lib/mission_control/jobs/version.rb
480
484
  - lib/mission_control/jobs/workers_relation.rb
481
485
  - lib/resque/thread_safe_redis.rb
482
- - lib/tasks/mission_control/jobs_tasks.rake
483
486
  homepage: https://github.com/rails/mission_control-jobs
484
487
  licenses:
485
488
  - MIT
486
489
  metadata:
487
490
  homepage_uri: https://github.com/rails/mission_control-jobs
488
491
  source_code_uri: https://github.com/rails/mission_control-jobs
489
- post_install_message:
492
+ post_install_message: |
493
+ Upgrading to Mission Control – Jobs 1.0.0? HTTP Basic authentication has been added by default, and it needs
494
+ to be configured or disabled before you can access the dashboard.
495
+ --> Check https://github.com/rails/mission_control-jobs?tab=readme-ov-file#authentication
496
+ for more details and instructions.
490
497
  rdoc_options: []
491
498
  require_paths:
492
499
  - lib
@@ -1,4 +0,0 @@
1
- # desc "Explaining what the task does"
2
- # task :mission_control_jobs do
3
- # # Task goes here
4
- # end