mission_control-jobs 0.6.0 → 1.0.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.
- checksums.yaml +4 -4
- data/README.md +39 -4
- data/app/controllers/concerns/mission_control/jobs/basic_authentication.rb +33 -0
- data/app/controllers/concerns/mission_control/jobs/job_filters.rb +16 -1
- data/app/controllers/mission_control/jobs/application_controller.rb +12 -0
- data/app/helpers/mission_control/jobs/dates_helper.rb +16 -2
- data/app/helpers/mission_control/jobs/jobs_helper.rb +1 -1
- data/app/views/mission_control/jobs/jobs/_filters.html.erb +11 -1
- data/app/views/mission_control/jobs/jobs/_general_information.html.erb +4 -4
- data/app/views/mission_control/jobs/jobs/_job.html.erb +1 -1
- data/app/views/mission_control/jobs/jobs/blocked/_job.html.erb +1 -1
- data/app/views/mission_control/jobs/jobs/failed/_job.html.erb +1 -1
- data/app/views/mission_control/jobs/jobs/finished/_job.html.erb +1 -1
- data/app/views/mission_control/jobs/jobs/in_progress/_job.html.erb +1 -1
- data/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb +1 -1
- data/app/views/mission_control/jobs/queues/_job.html.erb +1 -1
- data/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb +2 -2
- data/app/views/mission_control/jobs/shared/_job.html.erb +3 -3
- data/app/views/mission_control/jobs/workers/_worker.html.erb +1 -1
- data/lib/active_job/jobs_relation.rb +7 -5
- data/lib/active_job/queue_adapters/solid_queue_ext.rb +7 -2
- data/lib/mission_control/jobs/authentication.rb +67 -0
- data/lib/mission_control/jobs/engine.rb +11 -2
- data/lib/mission_control/jobs/i18n_config.rb +5 -0
- data/lib/mission_control/jobs/tasks.rb +8 -0
- data/lib/mission_control/jobs/version.rb +1 -1
- data/lib/mission_control/jobs.rb +13 -3
- metadata +11 -4
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8afd612ead034dc5c0f4b3798f62943bf021a557e4af5d7a5a565e50bfe5d647
|
4
|
+
data.tar.gz: 077c09bbff77e7f73de506600c447a05f7cb08b8ee266a1b107395ac393338e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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 = {
|
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
|
3
|
-
|
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
|
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
|
-
<%=
|
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
|
-
<%=
|
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
|
-
<%=
|
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
|
-
<%=
|
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 <%=
|
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">
|
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"><%=
|
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"><%=
|
2
|
+
<td><div class="has-text-grey"><%= time_distance_in_words_with_title(job.finished_at) %> ago</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
|
-
<%=
|
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
|
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 ?
|
18
|
-
<td class="next_time"><div class="has-text-grey"><%=
|
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
|
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
|
19
|
+
Running for <%= time_distance_in_words_with_title(job.started_at) %>
|
20
20
|
<% elsif job.finished_at %>
|
21
|
-
Finished
|
21
|
+
Finished <%= time_distance_in_words_with_title(job.finished_at) %> ago
|
22
22
|
<% else %>
|
23
23
|
Pending
|
24
24
|
<% end %>
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
13
|
-
|
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
|
data/lib/mission_control/jobs.rb
CHANGED
@@ -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
|
-
|
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.
|
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
|
+
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
|