solid_job_monitor 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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +106 -0
- data/Rakefile +8 -0
- data/app/assets/config/mission_control_jobs_manifest.js +4 -0
- data/app/assets/stylesheets/mission_control/jobs/application.css +16 -0
- data/app/assets/stylesheets/mission_control/jobs/bulma.min.css +21013 -0
- data/app/assets/stylesheets/mission_control/jobs/forms.css +8 -0
- data/app/assets/stylesheets/mission_control/jobs/jobs.css +68 -0
- data/app/controllers/concerns/mission_control/jobs/adapter_features.rb +24 -0
- data/app/controllers/concerns/mission_control/jobs/application_scoped.rb +38 -0
- data/app/controllers/concerns/mission_control/jobs/basic_authentication.rb +33 -0
- data/app/controllers/concerns/mission_control/jobs/failed_jobs_bulk_operations.rb +17 -0
- data/app/controllers/concerns/mission_control/jobs/job_filters.rb +41 -0
- data/app/controllers/concerns/mission_control/jobs/job_scoped.rb +16 -0
- data/app/controllers/concerns/mission_control/jobs/not_found_redirections.rb +36 -0
- data/app/controllers/concerns/mission_control/jobs/queue_scoped.rb +12 -0
- data/app/controllers/mission_control/jobs/application_controller.rb +32 -0
- data/app/controllers/mission_control/jobs/bulk_discards_controller.rb +20 -0
- data/app/controllers/mission_control/jobs/bulk_retries_controller.rb +10 -0
- data/app/controllers/mission_control/jobs/discards_controller.rb +18 -0
- data/app/controllers/mission_control/jobs/dispatches_controller.rb +18 -0
- data/app/controllers/mission_control/jobs/jobs_controller.rb +54 -0
- data/app/controllers/mission_control/jobs/progresses_controller.rb +27 -0
- data/app/controllers/mission_control/jobs/queues/pauses_controller.rb +15 -0
- data/app/controllers/mission_control/jobs/queues_controller.rb +16 -0
- data/app/controllers/mission_control/jobs/recurring_tasks_controller.rb +38 -0
- data/app/controllers/mission_control/jobs/retries_controller.rb +13 -0
- data/app/controllers/mission_control/jobs/stops_controller.rb +22 -0
- data/app/controllers/mission_control/jobs/workers_controller.rb +23 -0
- data/app/helpers/mission_control/jobs/application_helper.rb +9 -0
- data/app/helpers/mission_control/jobs/dates_helper.rb +19 -0
- data/app/helpers/mission_control/jobs/interface_helper.rb +23 -0
- data/app/helpers/mission_control/jobs/jobs_helper.rb +94 -0
- data/app/helpers/mission_control/jobs/navigation_helper.rb +60 -0
- data/app/javascript/mission_control/jobs/application.js +4 -0
- data/app/javascript/mission_control/jobs/controllers/application.js +9 -0
- data/app/javascript/mission_control/jobs/controllers/form_controller.js +21 -0
- data/app/javascript/mission_control/jobs/controllers/index.js +11 -0
- data/app/javascript/mission_control/jobs/controllers/progress_controller.js +55 -0
- data/app/javascript/mission_control/jobs/helpers/debounce_helpers.js +9 -0
- data/app/javascript/mission_control/jobs/helpers/index.js +1 -0
- data/app/models/mission_control/jobs/application_record.rb +7 -0
- data/app/models/mission_control/jobs/current.rb +3 -0
- data/app/models/mission_control/jobs/job_progress.rb +7 -0
- data/app/models/mission_control/jobs/page.rb +48 -0
- data/app/models/mission_control/jobs/recurring_task.rb +25 -0
- data/app/models/mission_control/jobs/worker.rb +17 -0
- data/app/views/layouts/mission_control/jobs/_application_selection.html.erb +14 -0
- data/app/views/layouts/mission_control/jobs/_flash.html.erb +9 -0
- data/app/views/layouts/mission_control/jobs/_navigation.html.erb +9 -0
- data/app/views/layouts/mission_control/jobs/application.html.erb +27 -0
- data/app/views/layouts/mission_control/jobs/application_selection/_applications.html.erb +13 -0
- data/app/views/layouts/mission_control/jobs/application_selection/_servers.html.erb +15 -0
- data/app/views/mission_control/jobs/jobs/_error_information.html.erb +22 -0
- data/app/views/mission_control/jobs/jobs/_filters.html.erb +55 -0
- data/app/views/mission_control/jobs/jobs/_general_information.html.erb +85 -0
- data/app/views/mission_control/jobs/jobs/_job.html.erb +13 -0
- data/app/views/mission_control/jobs/jobs/_jobs_page.html.erb +13 -0
- data/app/views/mission_control/jobs/jobs/_progress_bar.html.erb +16 -0
- data/app/views/mission_control/jobs/jobs/_raw_data.html.erb +4 -0
- data/app/views/mission_control/jobs/jobs/_title.html.erb +22 -0
- data/app/views/mission_control/jobs/jobs/_toolbar.html.erb +18 -0
- data/app/views/mission_control/jobs/jobs/blocked/_actions.html.erb +3 -0
- data/app/views/mission_control/jobs/jobs/blocked/_job.html.erb +8 -0
- data/app/views/mission_control/jobs/jobs/failed/_actions.html.erb +5 -0
- data/app/views/mission_control/jobs/jobs/failed/_backtrace_toggle.html.erb +14 -0
- data/app/views/mission_control/jobs/jobs/failed/_job.html.erb +7 -0
- data/app/views/mission_control/jobs/jobs/finished/_job.html.erb +2 -0
- data/app/views/mission_control/jobs/jobs/in_progress/_actions.html.erb +6 -0
- data/app/views/mission_control/jobs/jobs/in_progress/_job.html.erb +13 -0
- data/app/views/mission_control/jobs/jobs/index.html.erb +19 -0
- data/app/views/mission_control/jobs/jobs/scheduled/_actions.html.erb +5 -0
- data/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb +13 -0
- data/app/views/mission_control/jobs/jobs/show.html.erb +6 -0
- data/app/views/mission_control/jobs/queues/_actions.html.erb +7 -0
- data/app/views/mission_control/jobs/queues/_job.html.erb +15 -0
- data/app/views/mission_control/jobs/queues/_queue.html.erb +16 -0
- data/app/views/mission_control/jobs/queues/_queue_title.html.erb +17 -0
- data/app/views/mission_control/jobs/queues/index.html.erb +16 -0
- data/app/views/mission_control/jobs/queues/show.html.erb +25 -0
- data/app/views/mission_control/jobs/recurring_tasks/_actions.html.erb +5 -0
- data/app/views/mission_control/jobs/recurring_tasks/_general_information.html.erb +37 -0
- data/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb +22 -0
- data/app/views/mission_control/jobs/recurring_tasks/_title.html.erb +10 -0
- data/app/views/mission_control/jobs/recurring_tasks/index.html.erb +18 -0
- data/app/views/mission_control/jobs/recurring_tasks/show.html.erb +14 -0
- data/app/views/mission_control/jobs/shared/_job.html.erb +27 -0
- data/app/views/mission_control/jobs/shared/_jobs.html.erb +14 -0
- data/app/views/mission_control/jobs/shared/_pagination_toolbar.html.erb +7 -0
- data/app/views/mission_control/jobs/workers/_configuration.html.erb +6 -0
- data/app/views/mission_control/jobs/workers/_raw_data.html.erb +6 -0
- data/app/views/mission_control/jobs/workers/_title.html.erb +11 -0
- data/app/views/mission_control/jobs/workers/_worker.html.erb +21 -0
- data/app/views/mission_control/jobs/workers/_workers_page.html.erb +15 -0
- data/app/views/mission_control/jobs/workers/index.html.erb +6 -0
- data/app/views/mission_control/jobs/workers/show.html.erb +14 -0
- data/config/importmap.rb +6 -0
- data/config/routes.rb +35 -0
- data/db/migrate/20260828105144_create_mission_control_job_progresses.rb +16 -0
- data/lib/active_job/errors/invalid_operation.rb +5 -0
- data/lib/active_job/errors/job_not_found_error.rb +14 -0
- data/lib/active_job/errors/query_error.rb +5 -0
- data/lib/active_job/executing.rb +40 -0
- data/lib/active_job/execution_error.rb +8 -0
- data/lib/active_job/failed.rb +7 -0
- data/lib/active_job/job_proxy.rb +38 -0
- data/lib/active_job/jobs_relation.rb +318 -0
- data/lib/active_job/progress_reporting.rb +21 -0
- data/lib/active_job/querying.rb +44 -0
- data/lib/active_job/queue.rb +62 -0
- data/lib/active_job/queue_adapters/async_ext.rb +49 -0
- data/lib/active_job/queue_adapters/resque_ext.rb +317 -0
- data/lib/active_job/queue_adapters/solid_queue_ext/recurring_tasks.rb +52 -0
- data/lib/active_job/queue_adapters/solid_queue_ext/workers.rb +41 -0
- data/lib/active_job/queue_adapters/solid_queue_ext.rb +313 -0
- data/lib/active_job/queues.rb +33 -0
- data/lib/mission_control/jobs/adapter.rb +156 -0
- data/lib/mission_control/jobs/application.rb +20 -0
- data/lib/mission_control/jobs/applications.rb +8 -0
- data/lib/mission_control/jobs/arguments_filter.rb +24 -0
- data/lib/mission_control/jobs/authentication.rb +67 -0
- data/lib/mission_control/jobs/console/connect_to.rb +15 -0
- data/lib/mission_control/jobs/console/context.rb +11 -0
- data/lib/mission_control/jobs/console/jobs_help.rb +23 -0
- data/lib/mission_control/jobs/engine.rb +118 -0
- data/lib/mission_control/jobs/errors/incompatible_adapter.rb +2 -0
- data/lib/mission_control/jobs/errors/resource_not_found.rb +2 -0
- data/lib/mission_control/jobs/i18n_config.rb +17 -0
- data/lib/mission_control/jobs/identified_by_name.rb +18 -0
- data/lib/mission_control/jobs/identified_elements.rb +23 -0
- data/lib/mission_control/jobs/job_stopped_error.rb +5 -0
- data/lib/mission_control/jobs/progress/null_store.rb +25 -0
- data/lib/mission_control/jobs/progress/reporter.rb +63 -0
- data/lib/mission_control/jobs/progress/store.rb +72 -0
- data/lib/mission_control/jobs/progress.rb +17 -0
- data/lib/mission_control/jobs/server/recurring_tasks.rb +15 -0
- data/lib/mission_control/jobs/server/serializable.rb +24 -0
- data/lib/mission_control/jobs/server/workers.rb +13 -0
- data/lib/mission_control/jobs/server.rb +27 -0
- data/lib/mission_control/jobs/tasks.rb +8 -0
- data/lib/mission_control/jobs/version.rb +5 -0
- data/lib/mission_control/jobs/workers_relation.rb +78 -0
- data/lib/mission_control/jobs.rb +43 -0
- data/lib/resque/thread_safe_redis.rb +34 -0
- data/lib/solid_job_monitor.rb +6 -0
- metadata +527 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
.filter input {
|
|
2
|
+
width: 15rem;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
table.jobs {
|
|
6
|
+
td {
|
|
7
|
+
word-break: break-all;
|
|
8
|
+
}
|
|
9
|
+
th.job-header{
|
|
10
|
+
width: 30%;
|
|
11
|
+
}
|
|
12
|
+
th.duration-header {
|
|
13
|
+
width: 20%;
|
|
14
|
+
}
|
|
15
|
+
&.queues th.job-header {
|
|
16
|
+
width: 30%;
|
|
17
|
+
}
|
|
18
|
+
&.failed th.job-header {
|
|
19
|
+
width: 35%;
|
|
20
|
+
}
|
|
21
|
+
&.in_progress th.job-header {
|
|
22
|
+
width: 35%;
|
|
23
|
+
}
|
|
24
|
+
&.blocked th.job-header {
|
|
25
|
+
width: 45%;
|
|
26
|
+
}
|
|
27
|
+
&.scheduled th.job-header {
|
|
28
|
+
width: 40%;
|
|
29
|
+
}
|
|
30
|
+
&.finished th.job-header {
|
|
31
|
+
width: 65%;
|
|
32
|
+
}
|
|
33
|
+
&.workers th.job-header {
|
|
34
|
+
width: 40%;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.job-progress-wrap {
|
|
39
|
+
min-width: 7rem;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.job-progress {
|
|
43
|
+
position: relative;
|
|
44
|
+
flex: 1;
|
|
45
|
+
height: 1.25rem;
|
|
46
|
+
min-width: 6rem;
|
|
47
|
+
background: #ededed;
|
|
48
|
+
border-radius: 4px;
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.job-progress-bar {
|
|
53
|
+
width: 0;
|
|
54
|
+
height: 100%;
|
|
55
|
+
background: #00d1b2;
|
|
56
|
+
transition: width 0.3s ease;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.job-progress-label {
|
|
60
|
+
position: absolute;
|
|
61
|
+
inset: 0;
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
font-size: 0.75rem;
|
|
66
|
+
font-weight: 600;
|
|
67
|
+
color: #363636;
|
|
68
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module MissionControl::Jobs::AdapterFeatures
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
helper_method :supported_job_statuses, :queue_pausing_supported?, :workers_exposed?, :recurring_tasks_supported?
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
def supported_job_statuses
|
|
10
|
+
MissionControl::Jobs::Current.server.queue_adapter.supported_job_statuses & ActiveJob::JobsRelation::STATUSES
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def queue_pausing_supported?
|
|
14
|
+
MissionControl::Jobs::Current.server.queue_adapter.supports_queue_pausing?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def workers_exposed?
|
|
18
|
+
MissionControl::Jobs::Current.server.queue_adapter.exposes_workers?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def recurring_tasks_supported?
|
|
22
|
+
MissionControl::Jobs::Current.server.queue_adapter.supports_recurring_tasks?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module MissionControl::Jobs::ApplicationScoped
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
before_action :set_application
|
|
6
|
+
around_action :activating_job_server
|
|
7
|
+
|
|
8
|
+
delegate :applications, to: MissionControl::Jobs
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
def set_application
|
|
13
|
+
@application = find_application or raise MissionControl::Jobs::Errors::ResourceNotFound, "Application not found"
|
|
14
|
+
MissionControl::Jobs::Current.application = @application
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def find_application
|
|
18
|
+
if params[:application_id]
|
|
19
|
+
applications[params[:application_id]]
|
|
20
|
+
else
|
|
21
|
+
applications.first
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def activating_job_server(&block)
|
|
26
|
+
@server = find_server or raise MissionControl::Jobs::Errors::ResourceNotFound, "Server not found"
|
|
27
|
+
MissionControl::Jobs::Current.server = @server
|
|
28
|
+
@server.activating(&block)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def find_server
|
|
32
|
+
if params[:server_id]
|
|
33
|
+
MissionControl::Jobs::Current.application.servers[params[:server_id]]
|
|
34
|
+
else
|
|
35
|
+
@application.servers.first
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -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
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module MissionControl::Jobs::FailedJobsBulkOperations
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
MAX_NUMBER_OF_JOBS_FOR_BULK_OPERATIONS = 3000
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
include MissionControl::Jobs::JobFilters
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
# We set a hard limit to prevent problems with the data store (for example, overloading Redis
|
|
12
|
+
# or causing replication lag in MySQL). This should be enough for most scenarios. For
|
|
13
|
+
# cases where we need to retry a huge sets of jobs, we offer a runbook that uses the API.
|
|
14
|
+
def bulk_limited_filtered_failed_jobs
|
|
15
|
+
ActiveJob.jobs.failed.where(**@job_filters).limit(MAX_NUMBER_OF_JOBS_FOR_BULK_OPERATIONS)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module MissionControl::Jobs::JobFilters
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
before_action :set_filters
|
|
6
|
+
|
|
7
|
+
helper_method :active_filters?, :jobs_filter_param
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
def set_filters
|
|
12
|
+
@job_filters = {
|
|
13
|
+
job_class_name: params.dig(:filter, :job_class_name).to_s.strip.presence,
|
|
14
|
+
queue_name: params.dig(:filter, :queue_name).to_s.strip.presence,
|
|
15
|
+
finished_at: finished_at_range_params
|
|
16
|
+
}.compact
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def active_filters?
|
|
20
|
+
@job_filters.any?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def jobs_filter_param
|
|
24
|
+
if @job_filters&.any?
|
|
25
|
+
{ filter: @job_filters }
|
|
26
|
+
else
|
|
27
|
+
{}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def finished_at_range_params
|
|
32
|
+
range_start, range_end = params.dig(:filter, :finished_at_start), params.dig(:filter, :finished_at_end)
|
|
33
|
+
if range_start || range_end
|
|
34
|
+
(parse_with_time_zone(range_start)..parse_with_time_zone(range_end))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def parse_with_time_zone(date)
|
|
39
|
+
Time.zone.parse(date) if date.present?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module MissionControl::Jobs::JobScoped
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
before_action :set_job
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
def set_job
|
|
10
|
+
@job = jobs_relation.find_by_id!(params[:job_id] || params[:id])
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def jobs_relation
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module MissionControl::Jobs::NotFoundRedirections
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
rescue_from(ActiveJob::Errors::JobNotFoundError) do |error|
|
|
6
|
+
redirect_to best_location_for_job_relation(error.job_relation), alert: error.message
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
rescue_from(MissionControl::Jobs::Errors::ResourceNotFound) do |error|
|
|
10
|
+
redirect_to best_location_for_resource_not_found_error(error), alert: error.message
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
def best_location_for_job_relation(job_relation)
|
|
16
|
+
case
|
|
17
|
+
when job_relation.failed?
|
|
18
|
+
application_jobs_path(@application, :failed)
|
|
19
|
+
when job_relation.queue_name.present?
|
|
20
|
+
application_queue_path(@application, job_relation.queue_name)
|
|
21
|
+
else
|
|
22
|
+
root_path
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def best_location_for_resource_not_found_error(error)
|
|
27
|
+
case error.message
|
|
28
|
+
when /recurring task/i
|
|
29
|
+
application_recurring_tasks_path(@application)
|
|
30
|
+
when /queue/i
|
|
31
|
+
application_queues_path(@application)
|
|
32
|
+
else
|
|
33
|
+
root_url
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module MissionControl::Jobs::QueueScoped
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
before_action :set_queue
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
def set_queue
|
|
10
|
+
@queue = ActiveJob.queues[params[:queue_id]] or raise MissionControl::Jobs::Errors::ResourceNotFound, "Queue '#{params[:queue_id]}' not found"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
class MissionControl::Jobs::ApplicationController < MissionControl::Jobs.base_controller_class.constantize
|
|
2
|
+
ActionController::Base::MODULES.each do |mod|
|
|
3
|
+
include mod unless self < mod
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
layout "mission_control/jobs/application"
|
|
7
|
+
|
|
8
|
+
# Include helpers if not already included
|
|
9
|
+
helper MissionControl::Jobs::ApplicationHelper unless self < MissionControl::Jobs::ApplicationHelper
|
|
10
|
+
helper Importmap::ImportmapTagsHelper unless self < Importmap::ImportmapTagsHelper
|
|
11
|
+
|
|
12
|
+
include MissionControl::Jobs::BasicAuthentication
|
|
13
|
+
include MissionControl::Jobs::ApplicationScoped, MissionControl::Jobs::NotFoundRedirections
|
|
14
|
+
include MissionControl::Jobs::AdapterFeatures
|
|
15
|
+
include MissionControl::Jobs::JobFilters
|
|
16
|
+
|
|
17
|
+
around_action :set_current_locale
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
def default_url_options
|
|
21
|
+
{ server_id: MissionControl::Jobs::Current.server }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def set_current_locale(&block)
|
|
25
|
+
@previous_config = I18n.config
|
|
26
|
+
I18n.config = MissionControl::Jobs::I18nConfig.new
|
|
27
|
+
I18n.with_locale(:en, &block)
|
|
28
|
+
ensure
|
|
29
|
+
I18n.config = @previous_config
|
|
30
|
+
@previous_config = nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class MissionControl::Jobs::BulkDiscardsController < MissionControl::Jobs::ApplicationController
|
|
2
|
+
include MissionControl::Jobs::FailedJobsBulkOperations
|
|
3
|
+
|
|
4
|
+
def create
|
|
5
|
+
jobs_to_discard_count = jobs_to_discard.count
|
|
6
|
+
jobs_to_discard.discard_all
|
|
7
|
+
|
|
8
|
+
redirect_to application_jobs_url(@application, :failed), notice: "Discarded #{jobs_to_discard_count} jobs"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
def jobs_to_discard
|
|
13
|
+
if active_filters?
|
|
14
|
+
bulk_limited_filtered_failed_jobs
|
|
15
|
+
else
|
|
16
|
+
# we don't want to apply any limit since "discarding all" without parameters can be optimized in the adapter as a much faster operation
|
|
17
|
+
ActiveJob.jobs.failed
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class MissionControl::Jobs::BulkRetriesController < MissionControl::Jobs::ApplicationController
|
|
2
|
+
include MissionControl::Jobs::FailedJobsBulkOperations
|
|
3
|
+
|
|
4
|
+
def create
|
|
5
|
+
jobs_to_retry_count = bulk_limited_filtered_failed_jobs.count
|
|
6
|
+
bulk_limited_filtered_failed_jobs.retry_all
|
|
7
|
+
|
|
8
|
+
redirect_to application_jobs_url(@application, :failed), notice: "Retried #{jobs_to_retry_count} jobs"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class MissionControl::Jobs::DiscardsController < MissionControl::Jobs::ApplicationController
|
|
2
|
+
include MissionControl::Jobs::JobScoped
|
|
3
|
+
|
|
4
|
+
def create
|
|
5
|
+
@job.discard
|
|
6
|
+
redirect_to redirect_location, notice: "Discarded job with id #{@job.job_id}"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
def jobs_relation
|
|
11
|
+
ActiveJob.jobs
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def redirect_location
|
|
15
|
+
status = @job.status.presence_in(supported_job_statuses) || :failed
|
|
16
|
+
application_jobs_url(@application, status, **jobs_filter_param)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class MissionControl::Jobs::DispatchesController < MissionControl::Jobs::ApplicationController
|
|
2
|
+
include MissionControl::Jobs::JobScoped
|
|
3
|
+
|
|
4
|
+
def create
|
|
5
|
+
@job.dispatch
|
|
6
|
+
redirect_to redirect_location, notice: "Dispatched job with id #{@job.job_id}"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
def jobs_relation
|
|
11
|
+
ActiveJob.jobs
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def redirect_location
|
|
15
|
+
status = @job.status.presence_in(supported_job_statuses) || :blocked
|
|
16
|
+
application_jobs_url(@application, status)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
class MissionControl::Jobs::JobsController < MissionControl::Jobs::ApplicationController
|
|
2
|
+
include MissionControl::Jobs::JobScoped
|
|
3
|
+
|
|
4
|
+
skip_before_action :set_job, only: :index
|
|
5
|
+
|
|
6
|
+
def index
|
|
7
|
+
@job_class_names = jobs_with_status.job_class_names
|
|
8
|
+
@queue_names = ActiveJob.queues.map(&:name)
|
|
9
|
+
|
|
10
|
+
@jobs_page = MissionControl::Jobs::Page.new(filtered_jobs_with_status, page: params[:page].to_i)
|
|
11
|
+
@jobs_count = @jobs_page.total_count
|
|
12
|
+
load_progress_for_page
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def show
|
|
16
|
+
load_progress_for_job
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
def jobs_relation
|
|
21
|
+
filtered_jobs
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def filtered_jobs_with_status
|
|
25
|
+
filtered_jobs.with_status(jobs_status)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def jobs_with_status
|
|
29
|
+
ActiveJob.jobs.with_status(jobs_status)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def filtered_jobs
|
|
33
|
+
ActiveJob.jobs.where(**@job_filters)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
helper_method :jobs_status
|
|
37
|
+
|
|
38
|
+
def jobs_status
|
|
39
|
+
params[:status].presence&.inquiry
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def load_progress_for_page
|
|
43
|
+
return unless jobs_status&.in_progress? && MissionControl::Jobs::Progress.available?
|
|
44
|
+
|
|
45
|
+
job_ids = @jobs_page.records.map(&:job_id)
|
|
46
|
+
@progress_by_job_id = MissionControl::Jobs::Progress.find_multi(job_ids)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def load_progress_for_job
|
|
50
|
+
return unless MissionControl::Jobs::Progress.available?
|
|
51
|
+
|
|
52
|
+
@progress = MissionControl::Jobs::Progress.find(@job.job_id)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class MissionControl::Jobs::ProgressesController < MissionControl::Jobs::ApplicationController
|
|
2
|
+
def show
|
|
3
|
+
progress = MissionControl::Jobs::Progress.find(params[:job_id]) if MissionControl::Jobs::Progress.available?
|
|
4
|
+
|
|
5
|
+
respond_to do |format|
|
|
6
|
+
format.json { render json: progress_payload(progress) }
|
|
7
|
+
format.html { render partial: "mission_control/jobs/jobs/progress_bar", locals: { progress: progress, job_id: params[:job_id] } }
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
def progress_payload(progress)
|
|
13
|
+
if progress
|
|
14
|
+
{
|
|
15
|
+
job_id: progress.job_id,
|
|
16
|
+
current: progress.current,
|
|
17
|
+
total: progress.total,
|
|
18
|
+
percent: progress.percent,
|
|
19
|
+
message: progress.message,
|
|
20
|
+
stop_requested: progress.stop_requested,
|
|
21
|
+
missing: false
|
|
22
|
+
}
|
|
23
|
+
else
|
|
24
|
+
{ job_id: params[:job_id], missing: true }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class MissionControl::Jobs::Queues::PausesController < MissionControl::Jobs::ApplicationController
|
|
2
|
+
include MissionControl::Jobs::QueueScoped
|
|
3
|
+
|
|
4
|
+
def create
|
|
5
|
+
@queue.pause
|
|
6
|
+
|
|
7
|
+
redirect_back fallback_location: application_queues_url(@application)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def destroy
|
|
11
|
+
@queue.resume
|
|
12
|
+
|
|
13
|
+
redirect_back fallback_location: application_queues_url(@application)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class MissionControl::Jobs::QueuesController < MissionControl::Jobs::ApplicationController
|
|
2
|
+
before_action :set_queue, only: :show
|
|
3
|
+
|
|
4
|
+
def index
|
|
5
|
+
@queues = ActiveJob.queues.sort_by(&:name)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def show
|
|
9
|
+
@jobs_page = MissionControl::Jobs::Page.new(@queue.jobs, page: params[:page].to_i)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
def set_queue
|
|
14
|
+
@queue = ActiveJob.queues[params[:id]] or raise MissionControl::Jobs::Errors::ResourceNotFound, "Queue '#{params[:id]}' not found"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class MissionControl::Jobs::RecurringTasksController < MissionControl::Jobs::ApplicationController
|
|
2
|
+
before_action :ensure_supported_recurring_tasks
|
|
3
|
+
before_action :set_recurring_task, only: [ :show, :update ]
|
|
4
|
+
before_action :ensure_recurring_task_can_be_enqueued, only: :update
|
|
5
|
+
|
|
6
|
+
def index
|
|
7
|
+
@recurring_tasks = MissionControl::Jobs::Current.server.recurring_tasks
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def show
|
|
11
|
+
@jobs_page = MissionControl::Jobs::Page.new(@recurring_task.jobs, page: params[:page].to_i)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def update
|
|
15
|
+
if (job = @recurring_task.enqueue) && job.successfully_enqueued?
|
|
16
|
+
redirect_to application_job_path(@application, job.job_id), notice: "Enqueued recurring task #{@recurring_task.id}"
|
|
17
|
+
else
|
|
18
|
+
redirect_to application_recurring_task_path(@application, @recurring_task.id), alert: "Something went wrong enqueuing this recurring task"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
def ensure_supported_recurring_tasks
|
|
24
|
+
unless recurring_tasks_supported?
|
|
25
|
+
redirect_to root_url, alert: "This server doesn't support recurring tasks"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def set_recurring_task
|
|
30
|
+
@recurring_task = MissionControl::Jobs::Current.server.find_recurring_task(params[:id])
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def ensure_recurring_task_can_be_enqueued
|
|
34
|
+
unless @recurring_task.runnable?
|
|
35
|
+
redirect_to application_recurring_task_path(@application, @recurring_task.id), alert: "This task can't be enqueued"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class MissionControl::Jobs::RetriesController < MissionControl::Jobs::ApplicationController
|
|
2
|
+
include MissionControl::Jobs::JobScoped
|
|
3
|
+
|
|
4
|
+
def create
|
|
5
|
+
@job.retry
|
|
6
|
+
redirect_to application_jobs_url(@application, :failed, **jobs_filter_param), notice: "Retried job with id #{@job.job_id}"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
def jobs_relation
|
|
11
|
+
ActiveJob.jobs.failed
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class MissionControl::Jobs::StopsController < MissionControl::Jobs::ApplicationController
|
|
2
|
+
include MissionControl::Jobs::JobScoped
|
|
3
|
+
|
|
4
|
+
def create
|
|
5
|
+
unless MissionControl::Jobs::Progress.available?
|
|
6
|
+
redirect_to redirect_location, alert: "Progress tracking is not installed. Run rails db:migrate to enable cooperative stop."
|
|
7
|
+
return
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
MissionControl::Jobs::Progress.request_stop(@job.job_id)
|
|
11
|
+
redirect_to redirect_location, notice: "Stop requested for job #{@job.job_id}. It will stop the next time it reports progress or calls check_stopped!."
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
def jobs_relation
|
|
16
|
+
ActiveJob.jobs
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def redirect_location
|
|
20
|
+
application_jobs_url(@application, :in_progress, **jobs_filter_param)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class MissionControl::Jobs::WorkersController < MissionControl::Jobs::ApplicationController
|
|
2
|
+
before_action :ensure_exposed_workers
|
|
3
|
+
|
|
4
|
+
def index
|
|
5
|
+
@workers_page = MissionControl::Jobs::Page.new(workers_relation, page: params[:page].to_i)
|
|
6
|
+
@workers_count = @workers_page.total_count
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def show
|
|
10
|
+
@worker = MissionControl::Jobs::Current.server.find_worker(params[:id])
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
def ensure_exposed_workers
|
|
15
|
+
unless workers_exposed?
|
|
16
|
+
redirect_to root_url, alert: "This server doesn't expose workers"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def workers_relation
|
|
21
|
+
MissionControl::Jobs::Current.server.workers_relation
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module MissionControl::Jobs
|
|
2
|
+
module ApplicationHelper
|
|
3
|
+
# Explicit helper inclusion because ApplicationController inherits from the host app.
|
|
4
|
+
#
|
|
5
|
+
# We can't rely on +config.action_controller.include_all_helpers = true+ in the host app.
|
|
6
|
+
include DatesHelper, JobsHelper, NavigationHelper, InterfaceHelper
|
|
7
|
+
include MissionControl::Jobs::Engine.routes.url_helpers
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module MissionControl::Jobs::DatesHelper
|
|
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)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module MissionControl::Jobs::InterfaceHelper
|
|
2
|
+
def blank_status_notice(message)
|
|
3
|
+
tag.div message, class: "mt-6 has-text-centered is-size-3 has-text-grey"
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def blank_status_emoji(status)
|
|
7
|
+
case status.to_s
|
|
8
|
+
when "failed", "blocked" then "😌"
|
|
9
|
+
else ""
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def modifier_for_status(status)
|
|
14
|
+
case status.to_s
|
|
15
|
+
when "failed" then "is-danger"
|
|
16
|
+
when "blocked" then "is-warning"
|
|
17
|
+
when "finished" then "is-success"
|
|
18
|
+
when "scheduled" then "is-info"
|
|
19
|
+
when "in_progress" then "is-primary"
|
|
20
|
+
else "is-primary is-light"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|