flight_control 2.0.0.beta.2 → 2.0.0.beta.3
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/CHANGELOG.md +4 -0
- data/app/assets/config/flight_control_manifest.js +3 -0
- data/app/assets/stylesheets/flight_control/application.css +5 -0
- data/app/assets/stylesheets/flight_control/bulma.min.css +21013 -0
- data/app/assets/stylesheets/flight_control/forms.css +8 -0
- data/app/assets/stylesheets/flight_control/jobs.css +42 -0
- data/app/controllers/concerns/flight_control/adapter_features.rb +25 -0
- data/app/controllers/concerns/flight_control/application_scoped.rb +39 -0
- data/app/controllers/concerns/flight_control/basic_authentication.rb +34 -0
- data/app/controllers/concerns/flight_control/failed_jobs_bulk_operations.rb +18 -0
- data/app/controllers/concerns/flight_control/job_filters.rb +44 -0
- data/app/controllers/concerns/flight_control/job_scoped.rb +17 -0
- data/app/controllers/concerns/flight_control/not_found_redirections.rb +36 -0
- data/app/controllers/concerns/flight_control/queue_scoped.rb +13 -0
- data/app/controllers/flight_control/application_controller.rb +35 -0
- data/app/controllers/flight_control/bulk_discards_controller.rb +21 -0
- data/app/controllers/flight_control/bulk_retries_controller.rb +10 -0
- data/app/controllers/flight_control/discards_controller.rb +19 -0
- data/app/controllers/flight_control/dispatches_controller.rb +19 -0
- data/app/controllers/flight_control/job_launches_controller.rb +28 -0
- data/app/controllers/flight_control/jobs_controller.rb +40 -0
- data/app/controllers/flight_control/queues/pauses_controller.rb +15 -0
- data/app/controllers/flight_control/queues_controller.rb +17 -0
- data/app/controllers/flight_control/recurring_tasks_controller.rb +39 -0
- data/app/controllers/flight_control/retries_controller.rb +14 -0
- data/app/controllers/flight_control/workers_controller.rb +24 -0
- data/app/helpers/flight_control/application_helper.rb +12 -0
- data/app/helpers/flight_control/dates_helper.rb +30 -0
- data/app/helpers/flight_control/interface_helper.rb +23 -0
- data/app/helpers/flight_control/jobs_helper.rb +105 -0
- data/app/helpers/flight_control/navigation_helper.rb +61 -0
- data/app/javascript/flight_control/application.js +3 -0
- data/app/javascript/flight_control/controllers/application.js +9 -0
- data/app/javascript/flight_control/controllers/form_controller.js +8 -0
- data/app/javascript/flight_control/controllers/index.js +11 -0
- data/app/javascript/flight_control/controllers/local_time_controller.js +27 -0
- data/app/models/flight_control/application_record.rb +5 -0
- data/app/models/flight_control/current.rb +3 -0
- data/app/models/flight_control/job_launch.rb +75 -0
- data/app/models/flight_control/page.rb +49 -0
- data/app/models/flight_control/recurring_task.rb +26 -0
- data/app/models/flight_control/worker.rb +18 -0
- data/app/views/flight_control/job_launches/new.html.erb +53 -0
- data/app/views/flight_control/jobs/_date_range_filter.html.erb +11 -0
- data/app/views/flight_control/jobs/_error_information.html.erb +24 -0
- data/app/views/flight_control/jobs/_filters.html.erb +49 -0
- data/app/views/flight_control/jobs/_general_information.html.erb +77 -0
- data/app/views/flight_control/jobs/_job.html.erb +13 -0
- data/app/views/flight_control/jobs/_jobs_page.html.erb +13 -0
- data/app/views/flight_control/jobs/_raw_data.html.erb +4 -0
- data/app/views/flight_control/jobs/_title.html.erb +19 -0
- data/app/views/flight_control/jobs/_toolbar.html.erb +18 -0
- data/app/views/flight_control/jobs/blocked/_actions.html.erb +3 -0
- data/app/views/flight_control/jobs/blocked/_job.html.erb +8 -0
- data/app/views/flight_control/jobs/failed/_actions.html.erb +5 -0
- data/app/views/flight_control/jobs/failed/_backtrace_toggle.html.erb +14 -0
- data/app/views/flight_control/jobs/failed/_job.html.erb +8 -0
- data/app/views/flight_control/jobs/finished/_job.html.erb +3 -0
- data/app/views/flight_control/jobs/in_progress/_job.html.erb +9 -0
- data/app/views/flight_control/jobs/index.html.erb +19 -0
- data/app/views/flight_control/jobs/scheduled/_actions.html.erb +5 -0
- data/app/views/flight_control/jobs/scheduled/_job.html.erb +13 -0
- data/app/views/flight_control/jobs/show.html.erb +6 -0
- data/app/views/flight_control/queues/_actions.html.erb +7 -0
- data/app/views/flight_control/queues/_job.html.erb +15 -0
- data/app/views/flight_control/queues/_queue.html.erb +16 -0
- data/app/views/flight_control/queues/_queue_title.html.erb +17 -0
- data/app/views/flight_control/queues/index.html.erb +21 -0
- data/app/views/flight_control/queues/show.html.erb +25 -0
- data/app/views/flight_control/recurring_tasks/_actions.html.erb +5 -0
- data/app/views/flight_control/recurring_tasks/_general_information.html.erb +37 -0
- data/app/views/flight_control/recurring_tasks/_recurring_task.html.erb +22 -0
- data/app/views/flight_control/recurring_tasks/_title.html.erb +10 -0
- data/app/views/flight_control/recurring_tasks/index.html.erb +18 -0
- data/app/views/flight_control/recurring_tasks/show.html.erb +14 -0
- data/app/views/flight_control/shared/_job.html.erb +27 -0
- data/app/views/flight_control/shared/_jobs.html.erb +14 -0
- data/app/views/flight_control/shared/_pagination_toolbar.html.erb +7 -0
- data/app/views/flight_control/workers/_configuration.html.erb +6 -0
- data/app/views/flight_control/workers/_raw_data.html.erb +6 -0
- data/app/views/flight_control/workers/_title.html.erb +11 -0
- data/app/views/flight_control/workers/_worker.html.erb +21 -0
- data/app/views/flight_control/workers/_workers_page.html.erb +15 -0
- data/app/views/flight_control/workers/index.html.erb +6 -0
- data/app/views/flight_control/workers/show.html.erb +14 -0
- data/app/views/layouts/flight_control/_application_selection.html.erb +14 -0
- data/app/views/layouts/flight_control/_flash.html.erb +9 -0
- data/app/views/layouts/flight_control/_navigation.html.erb +9 -0
- data/app/views/layouts/flight_control/application.html.erb +28 -0
- data/app/views/layouts/flight_control/application_selection/_applications.html.erb +13 -0
- data/app/views/layouts/flight_control/application_selection/_servers.html.erb +15 -0
- data/config/importmap.rb +5 -0
- data/config/routes.rb +34 -0
- data/lib/flight_control/version.rb +1 -1
- metadata +93 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
.filter,
|
|
2
|
+
.filter form {
|
|
3
|
+
max-width: 100%;
|
|
4
|
+
min-width: 0;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.filter input {
|
|
8
|
+
width: 15rem;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
table.jobs {
|
|
12
|
+
td {
|
|
13
|
+
word-break: break-all;
|
|
14
|
+
}
|
|
15
|
+
th.job-header{
|
|
16
|
+
width: 30%;
|
|
17
|
+
}
|
|
18
|
+
th.duration-header {
|
|
19
|
+
width: 20%;
|
|
20
|
+
}
|
|
21
|
+
&.queues th.job-header {
|
|
22
|
+
width: 30%;
|
|
23
|
+
}
|
|
24
|
+
&.failed th.job-header {
|
|
25
|
+
width: 35%;
|
|
26
|
+
}
|
|
27
|
+
&.in_progress th.job-header {
|
|
28
|
+
width: 50%;
|
|
29
|
+
}
|
|
30
|
+
&.blocked th.job-header {
|
|
31
|
+
width: 45%;
|
|
32
|
+
}
|
|
33
|
+
&.scheduled th.job-header {
|
|
34
|
+
width: 40%;
|
|
35
|
+
}
|
|
36
|
+
&.finished th.job-header {
|
|
37
|
+
width: 65%;
|
|
38
|
+
}
|
|
39
|
+
&.workers th.job-header {
|
|
40
|
+
width: 40%;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module FlightControl::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
|
+
|
|
10
|
+
def supported_job_statuses
|
|
11
|
+
FlightControl::Current.server.queue_adapter.supported_job_statuses & ActiveJob::JobsRelation::STATUSES
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def queue_pausing_supported?
|
|
15
|
+
FlightControl::Current.server.queue_adapter.supports_queue_pausing?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def workers_exposed?
|
|
19
|
+
FlightControl::Current.server.queue_adapter.exposes_workers?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def recurring_tasks_supported?
|
|
23
|
+
FlightControl::Current.server.queue_adapter.supports_recurring_tasks?
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module FlightControl::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: FlightControl
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def set_application
|
|
14
|
+
@application = find_application or raise FlightControl::Errors::ResourceNotFound, "Application not found"
|
|
15
|
+
FlightControl::Current.application = @application
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def find_application
|
|
19
|
+
if params[:application_id]
|
|
20
|
+
applications[params[:application_id]]
|
|
21
|
+
else
|
|
22
|
+
applications.first
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def activating_job_server(&block)
|
|
27
|
+
@server = find_server or raise FlightControl::Errors::ResourceNotFound, "Server not found"
|
|
28
|
+
FlightControl::Current.server = @server
|
|
29
|
+
@server.activating(&block)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def find_server
|
|
33
|
+
if params[:server_id]
|
|
34
|
+
FlightControl::Current.application.servers[params[:server_id]]
|
|
35
|
+
else
|
|
36
|
+
@application.servers.first
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module FlightControl::BasicAuthentication
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
before_action :authenticate_by_http_basic
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def authenticate_by_http_basic
|
|
11
|
+
if http_basic_authentication_enabled?
|
|
12
|
+
if http_basic_authentication_configured?
|
|
13
|
+
http_basic_authenticate_or_request_with(**http_basic_authentication_credentials)
|
|
14
|
+
else
|
|
15
|
+
head :unauthorized
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def http_basic_authentication_enabled?
|
|
21
|
+
FlightControl.http_basic_auth_enabled
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def http_basic_authentication_configured?
|
|
25
|
+
http_basic_authentication_credentials.values.all?(&:present?)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def http_basic_authentication_credentials
|
|
29
|
+
{
|
|
30
|
+
name: FlightControl.http_basic_auth_user,
|
|
31
|
+
password: FlightControl.http_basic_auth_password
|
|
32
|
+
}.transform_values(&:presence)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module FlightControl::FailedJobsBulkOperations
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
MAX_NUMBER_OF_JOBS_FOR_BULK_OPERATIONS = 3000
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
include FlightControl::JobFilters
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# We set a hard limit to prevent problems with the data store (for example, overloading Redis
|
|
13
|
+
# or causing replication lag in MySQL). This should be enough for most scenarios. For
|
|
14
|
+
# cases where we need to retry a huge sets of jobs, we offer a runbook that uses the API.
|
|
15
|
+
def bulk_limited_filtered_failed_jobs
|
|
16
|
+
ActiveJob.jobs.failed.where(**@job_filters).limit(MAX_NUMBER_OF_JOBS_FOR_BULK_OPERATIONS)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module FlightControl::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
|
+
|
|
12
|
+
def set_filters
|
|
13
|
+
@job_filters = {
|
|
14
|
+
job_class_name: params.dig(:filter, :job_class_name).to_s.strip.presence,
|
|
15
|
+
queue_name: params.dig(:filter, :queue_name).to_s.strip.presence,
|
|
16
|
+
finished_at: date_range_params(:finished_at),
|
|
17
|
+
scheduled_at: date_range_params(:scheduled_at),
|
|
18
|
+
enqueued_at: date_range_params(:enqueued_at)
|
|
19
|
+
}.compact
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def active_filters?
|
|
23
|
+
@job_filters.any?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def jobs_filter_param
|
|
27
|
+
if @job_filters&.any?
|
|
28
|
+
{filter: @job_filters}
|
|
29
|
+
else
|
|
30
|
+
{}
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def date_range_params(attribute)
|
|
35
|
+
range_start, range_end = params.dig(:filter, :"#{attribute}_start"), params.dig(:filter, :"#{attribute}_end")
|
|
36
|
+
if range_start || range_end
|
|
37
|
+
(parse_with_time_zone(range_start)..parse_with_time_zone(range_end))
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def parse_with_time_zone(date)
|
|
42
|
+
Time.zone.parse(date) if date.present?
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module FlightControl::JobScoped
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
before_action :set_job
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def set_job
|
|
11
|
+
@job = jobs_relation.find_by_id!(params[:job_id] || params[:id])
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def jobs_relation
|
|
15
|
+
raise NotImplementedError
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module FlightControl::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(FlightControl::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
|
+
|
|
16
|
+
def best_location_for_job_relation(job_relation)
|
|
17
|
+
if job_relation.failed?
|
|
18
|
+
application_jobs_path(@application, :failed)
|
|
19
|
+
elsif 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,13 @@
|
|
|
1
|
+
module FlightControl::QueueScoped
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
before_action :set_queue
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def set_queue
|
|
11
|
+
@queue = ActiveJob.queues[params[:queue_id]] or raise FlightControl::Errors::ResourceNotFound, "Queue '#{params[:queue_id]}' not found"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class FlightControl::ApplicationController < FlightControl.base_controller_class.constantize
|
|
2
|
+
ActionController::Base::MODULES.each do |mod|
|
|
3
|
+
include mod unless self < mod
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
layout "flight_control/application"
|
|
7
|
+
|
|
8
|
+
# Include helpers if not already included
|
|
9
|
+
helper FlightControl::ApplicationHelper unless self < FlightControl::ApplicationHelper
|
|
10
|
+
helper Importmap::ImportmapTagsHelper unless self < Importmap::ImportmapTagsHelper
|
|
11
|
+
|
|
12
|
+
include FlightControl::HostRouteHelpers
|
|
13
|
+
include FlightControl::BasicAuthentication
|
|
14
|
+
include FlightControl::NotFoundRedirections
|
|
15
|
+
include FlightControl::ApplicationScoped
|
|
16
|
+
include FlightControl::AdapterFeatures
|
|
17
|
+
include FlightControl::JobFilters
|
|
18
|
+
|
|
19
|
+
around_action :set_current_locale
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def default_url_options
|
|
24
|
+
super.merge(server_id: FlightControl::Current.server)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def set_current_locale(&block)
|
|
28
|
+
@previous_config = I18n.config
|
|
29
|
+
I18n.config = FlightControl::I18nConfig.new
|
|
30
|
+
I18n.with_locale(:en, &block)
|
|
31
|
+
ensure
|
|
32
|
+
I18n.config = @previous_config
|
|
33
|
+
@previous_config = nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class FlightControl::BulkDiscardsController < FlightControl::ApplicationController
|
|
2
|
+
include FlightControl::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
|
+
|
|
13
|
+
def jobs_to_discard
|
|
14
|
+
if active_filters?
|
|
15
|
+
bulk_limited_filtered_failed_jobs
|
|
16
|
+
else
|
|
17
|
+
# we don't want to apply any limit since "discarding all" without parameters can be optimized in the adapter as a much faster operation
|
|
18
|
+
ActiveJob.jobs.failed
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class FlightControl::BulkRetriesController < FlightControl::ApplicationController
|
|
2
|
+
include FlightControl::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,19 @@
|
|
|
1
|
+
class FlightControl::DiscardsController < FlightControl::ApplicationController
|
|
2
|
+
include FlightControl::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
|
+
|
|
11
|
+
def jobs_relation
|
|
12
|
+
ActiveJob.jobs
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def redirect_location
|
|
16
|
+
status = @job.status.presence_in(supported_job_statuses) || :failed
|
|
17
|
+
application_jobs_url(@application, status, **jobs_filter_param)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class FlightControl::DispatchesController < FlightControl::ApplicationController
|
|
2
|
+
include FlightControl::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
|
+
|
|
11
|
+
def jobs_relation
|
|
12
|
+
ActiveJob.jobs
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def redirect_location
|
|
16
|
+
status = @job.status.presence_in(supported_job_statuses) || :blocked
|
|
17
|
+
application_jobs_url(@application, status)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class FlightControl::JobLaunchesController < FlightControl::ApplicationController
|
|
2
|
+
before_action :set_form_options
|
|
3
|
+
|
|
4
|
+
def new
|
|
5
|
+
@job_launch = FlightControl::JobLaunch.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def create
|
|
9
|
+
@job_launch = FlightControl::JobLaunch.new(job_launch_params)
|
|
10
|
+
|
|
11
|
+
if (job = @job_launch.enqueue)
|
|
12
|
+
redirect_to application_job_path(@application, job.job_id), notice: "Enqueued #{@job_launch.job_class_name} with job id #{job.job_id}"
|
|
13
|
+
else
|
|
14
|
+
render :new, status: :unprocessable_entity
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def set_form_options
|
|
21
|
+
@job_classes = FlightControl::JobLaunch.job_classes
|
|
22
|
+
@queue_names = ActiveJob.queues.map(&:name)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def job_launch_params
|
|
26
|
+
params.require(:job_launch).permit(:job_class_name, :arguments_json, :queue_name)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
class FlightControl::JobsController < FlightControl::ApplicationController
|
|
2
|
+
include FlightControl::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 = FlightControl::Page.new(filtered_jobs_with_status, page: params[:page].to_i)
|
|
11
|
+
@jobs_count = @jobs_page.total_count
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def show
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def jobs_relation
|
|
20
|
+
filtered_jobs
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def filtered_jobs_with_status
|
|
24
|
+
filtered_jobs.with_status(jobs_status)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def jobs_with_status
|
|
28
|
+
ActiveJob.jobs.with_status(jobs_status)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def filtered_jobs
|
|
32
|
+
ActiveJob.jobs.where(**@job_filters)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
helper_method :jobs_status
|
|
36
|
+
|
|
37
|
+
def jobs_status
|
|
38
|
+
params[:status].presence&.inquiry
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class FlightControl::Queues::PausesController < FlightControl::ApplicationController
|
|
2
|
+
include FlightControl::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,17 @@
|
|
|
1
|
+
class FlightControl::QueuesController < FlightControl::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 = FlightControl::Page.new(@queue.jobs, page: params[:page].to_i)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def set_queue
|
|
15
|
+
@queue = ActiveJob.queues[params[:id]] or raise FlightControl::Errors::ResourceNotFound, "Queue '#{params[:id]}' not found"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class FlightControl::RecurringTasksController < FlightControl::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 = FlightControl::Current.server.recurring_tasks
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def show
|
|
11
|
+
@jobs_page = FlightControl::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
|
+
|
|
24
|
+
def ensure_supported_recurring_tasks
|
|
25
|
+
unless recurring_tasks_supported?
|
|
26
|
+
redirect_to root_url, alert: "This server doesn't support recurring tasks"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def set_recurring_task
|
|
31
|
+
@recurring_task = FlightControl::Current.server.find_recurring_task(params[:id])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def ensure_recurring_task_can_be_enqueued
|
|
35
|
+
unless @recurring_task.runnable?
|
|
36
|
+
redirect_to application_recurring_task_path(@application, @recurring_task.id), alert: "This task can't be enqueued"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class FlightControl::RetriesController < FlightControl::ApplicationController
|
|
2
|
+
include FlightControl::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
|
+
|
|
11
|
+
def jobs_relation
|
|
12
|
+
ActiveJob.jobs.failed
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class FlightControl::WorkersController < FlightControl::ApplicationController
|
|
2
|
+
before_action :ensure_exposed_workers
|
|
3
|
+
|
|
4
|
+
def index
|
|
5
|
+
@workers_page = FlightControl::Page.new(workers_relation, page: params[:page].to_i)
|
|
6
|
+
@workers_count = @workers_page.total_count
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def show
|
|
10
|
+
@worker = FlightControl::Current.server.find_worker(params[:id])
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def ensure_exposed_workers
|
|
16
|
+
unless workers_exposed?
|
|
17
|
+
redirect_to root_url, alert: "This server doesn't expose workers"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def workers_relation
|
|
22
|
+
FlightControl::Current.server.workers_relation
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module FlightControl
|
|
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 InterfaceHelper
|
|
7
|
+
include NavigationHelper
|
|
8
|
+
include JobsHelper
|
|
9
|
+
include DatesHelper
|
|
10
|
+
include FlightControl::Engine.routes.url_helpers
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module FlightControl::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
|
+
# Renders "2026-07-10 12:32:15 UTC (about 4 hours ago)". The local-time
|
|
17
|
+
# Stimulus controller prepends the timestamp in the browser's timezone,
|
|
18
|
+
# which the server can't know.
|
|
19
|
+
def timestamp_with_relative_time(time)
|
|
20
|
+
time = time.to_time.utc
|
|
21
|
+
safe_join [
|
|
22
|
+
tag.span(time.strftime("%Y-%m-%d %H:%M:%S UTC"), data: {controller: "local-time", local_time_datetime_value: time.iso8601}),
|
|
23
|
+
" (#{time_ago_in_words_with_default_options(time)} ago)"
|
|
24
|
+
]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def time_ago_in_words_with_default_options(time)
|
|
28
|
+
time_ago_in_words(time, include_seconds: true, locale: :en)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module FlightControl::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
|