good_job 4.13.0 → 4.13.2
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 +37 -0
- data/app/controllers/good_job/batches_controller.rb +1 -1
- data/app/controllers/good_job/cron_entries_controller.rb +3 -3
- data/app/controllers/good_job/frontends_controller.rb +15 -10
- data/app/controllers/good_job/jobs_controller.rb +7 -7
- data/app/controllers/good_job/pauses_controller.rb +2 -2
- data/app/frontend/good_job/application.js +23 -9
- data/app/frontend/good_job/modules/form_controller.js +11 -0
- data/app/frontend/good_job/vendor/turbo.js +34 -0
- data/app/models/good_job/batch.rb +8 -2
- data/app/models/good_job/batch_record.rb +35 -11
- data/app/models/good_job/job.rb +61 -29
- data/app/models/good_job/process.rb +2 -0
- data/app/models/good_job/setting.rb +2 -0
- data/app/views/good_job/batches/_jobs.erb +4 -4
- data/app/views/good_job/batches/_table.erb +2 -2
- data/app/views/good_job/batches/show.html.erb +1 -1
- data/app/views/good_job/cron_entries/index.html.erb +2 -2
- data/app/views/good_job/jobs/_table.erb +12 -9
- data/app/views/good_job/jobs/show.html.erb +4 -4
- data/app/views/good_job/pauses/_group.html.erb +1 -1
- data/app/views/good_job/pauses/_pause.html.erb +1 -1
- data/app/views/good_job/shared/_filter.erb +4 -14
- data/app/views/good_job/shared/_navbar.erb +6 -6
- data/app/views/layouts/good_job/application.html.erb +10 -6
- data/config/brakeman.ignore +25 -25
- data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +1 -1
- data/lib/generators/good_job/templates/update/migrations/05_add_index_good_jobs_finished_at_for_cleanup.rb.erb +17 -0
- data/lib/generators/good_job/templates/update/migrations/06_remove_extraneous_finished_at_index.rb.erb +17 -0
- data/lib/good_job/version.rb +1 -1
- data/lib/good_job.rb +5 -1
- metadata +6 -4
- data/app/frontend/good_job/modules/document_ready.js +0 -7
- data/app/frontend/good_job/vendor/rails_ujs.js +0 -7
|
@@ -59,25 +59,47 @@ module GoodJob
|
|
|
59
59
|
job_discarded = job && job.finished_at.present? && job.error.present?
|
|
60
60
|
buffer = GoodJob::Adapter::InlineBuffer.capture do
|
|
61
61
|
advisory_lock_maybe(lock) do
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
reload
|
|
63
|
+
|
|
64
|
+
if job_discarded && !discarded_at
|
|
65
|
+
update(discarded_at: Time.current)
|
|
66
|
+
|
|
67
|
+
if on_discard.present?
|
|
68
|
+
discard_job_class = on_discard.constantize
|
|
69
|
+
Job.defer_after_commit_maybe(discard_job_class) do
|
|
70
|
+
Batch.within_thread(batch_id: nil, batch_callback_id: id) do
|
|
71
|
+
discard_job_class.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :discard })
|
|
72
|
+
end
|
|
73
|
+
end
|
|
68
74
|
end
|
|
75
|
+
end
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
if enqueued_at && !(self.class.jobs_finished_at_migrated? ? jobs_finished_at : finished_at) && jobs.where(finished_at: nil).none?
|
|
78
|
+
self.class.jobs_finished_at_migrated? ? update(jobs_finished_at: Time.current) : update(finished_at: Time.current)
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
if !discarded_at && on_success.present?
|
|
81
|
+
success_job_class = on_success.constantize
|
|
82
|
+
Job.defer_after_commit_maybe(success_job_class) do
|
|
83
|
+
Batch.within_thread(batch_id: nil, batch_callback_id: id) do
|
|
84
|
+
success_job_class.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :success })
|
|
85
|
+
end
|
|
86
|
+
end
|
|
75
87
|
end
|
|
76
88
|
|
|
77
|
-
|
|
89
|
+
if on_finish.present?
|
|
90
|
+
finish_job_class = on_finish.constantize
|
|
91
|
+
Job.defer_after_commit_maybe(finish_job_class) do
|
|
92
|
+
Batch.within_thread(batch_id: nil, batch_callback_id: id) do
|
|
93
|
+
on_finish.constantize.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :finish })
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
78
97
|
end
|
|
98
|
+
|
|
99
|
+
update(finished_at: Time.current) if !finished_at && self.class.jobs_finished_at_migrated? && jobs_finished? && callback_jobs.where(finished_at: nil).none?
|
|
79
100
|
end
|
|
80
101
|
end
|
|
102
|
+
|
|
81
103
|
buffer.call
|
|
82
104
|
end
|
|
83
105
|
|
|
@@ -123,3 +145,5 @@ module GoodJob
|
|
|
123
145
|
end
|
|
124
146
|
end
|
|
125
147
|
end
|
|
148
|
+
|
|
149
|
+
ActiveSupport.run_load_hooks(:good_job_batch_record, GoodJob::BatchRecord)
|
data/app/models/good_job/job.rb
CHANGED
|
@@ -250,8 +250,8 @@ module GoodJob
|
|
|
250
250
|
)
|
|
251
251
|
end
|
|
252
252
|
|
|
253
|
-
def
|
|
254
|
-
return true
|
|
253
|
+
def historic_finished_at_index_migrated?
|
|
254
|
+
return true unless connection.index_name_exists?(:good_jobs, :index_good_jobs_jobs_on_finished_at)
|
|
255
255
|
|
|
256
256
|
migration_pending_warning!
|
|
257
257
|
false
|
|
@@ -420,6 +420,33 @@ module GoodJob
|
|
|
420
420
|
[error.class.to_s, ERROR_MESSAGE_SEPARATOR, error.message].join
|
|
421
421
|
end
|
|
422
422
|
|
|
423
|
+
# When code needs to optionally handle enqueue_after_transaction_commit
|
|
424
|
+
def self.defer_after_commit_maybe(good_job_or_active_job_classes)
|
|
425
|
+
if enqueue_after_commit?(good_job_or_active_job_classes)
|
|
426
|
+
ActiveRecord.after_all_transactions_commit { yield(true) }
|
|
427
|
+
else
|
|
428
|
+
yield(false)
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def self.enqueue_after_commit?(good_job_or_active_job_classes)
|
|
433
|
+
good_job_or_active_job_classes = Array(good_job_or_active_job_classes)
|
|
434
|
+
|
|
435
|
+
feature_exists = ActiveRecord.respond_to?(:after_all_transactions_commit)
|
|
436
|
+
feature_exists && good_job_or_active_job_classes.any? do |klass|
|
|
437
|
+
active_job_class = case klass
|
|
438
|
+
when String
|
|
439
|
+
klass.constantize
|
|
440
|
+
when Job
|
|
441
|
+
klass.job_class.constantize
|
|
442
|
+
else
|
|
443
|
+
klass
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
active_job_class.respond_to?(:enqueue_after_transaction_commit)
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
423
450
|
# TODO: it would be nice to enforce these values at the model
|
|
424
451
|
# validates :active_job_id, presence: true
|
|
425
452
|
# validates :scheduled_at, presence: true
|
|
@@ -499,41 +526,46 @@ module GoodJob
|
|
|
499
526
|
# This action will create a new {Execution} record for the job.
|
|
500
527
|
# @return [ActiveJob::Base]
|
|
501
528
|
def retry_job
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
529
|
+
Rails.application.executor.wrap do
|
|
530
|
+
with_advisory_lock do
|
|
531
|
+
reload
|
|
532
|
+
active_job = self.active_job(ignore_deserialization_errors: true)
|
|
533
|
+
|
|
534
|
+
raise ActiveJobDeserializationError if active_job.nil?
|
|
535
|
+
raise AdapterNotGoodJobError unless active_job.class.queue_adapter.is_a? GoodJob::Adapter
|
|
536
|
+
raise ActionForStateMismatchError if finished_at.blank? || error.blank?
|
|
537
|
+
|
|
538
|
+
# Update the executions count because the previous execution will not have been preserved
|
|
539
|
+
# Do not update `exception_executions` because that comes from rescue_from's arguments
|
|
540
|
+
active_job.executions = (active_job.executions || 0) + 1
|
|
541
|
+
|
|
542
|
+
begin
|
|
543
|
+
error_class, error_message = error.split(ERROR_MESSAGE_SEPARATOR).map(&:strip)
|
|
544
|
+
error = error_class.constantize.new(error_message)
|
|
545
|
+
rescue StandardError
|
|
546
|
+
error = StandardError.new(error)
|
|
547
|
+
end
|
|
520
548
|
|
|
521
|
-
|
|
522
|
-
GoodJob::CurrentThread.within do |current_thread|
|
|
523
|
-
current_thread.job = self
|
|
524
|
-
current_thread.retry_now = true
|
|
549
|
+
new_active_job = nil
|
|
525
550
|
|
|
526
551
|
transaction do
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
552
|
+
Job.defer_after_commit_maybe(active_job.class) do
|
|
553
|
+
GoodJob::CurrentThread.within do |current_thread|
|
|
554
|
+
current_thread.job = self
|
|
555
|
+
current_thread.retry_now = true
|
|
556
|
+
|
|
557
|
+
# NOTE: I18n.with_locale necessary until fixed in rails https://github.com/rails/rails/pull/52121
|
|
558
|
+
I18n.with_locale(active_job.locale) do
|
|
559
|
+
new_active_job = active_job.retry_job(wait: 0, error: error)
|
|
560
|
+
end
|
|
561
|
+
end
|
|
530
562
|
end
|
|
531
563
|
self.error_event = :retried if error
|
|
532
564
|
save!
|
|
533
565
|
end
|
|
534
|
-
end
|
|
535
566
|
|
|
536
|
-
|
|
567
|
+
new_active_job
|
|
568
|
+
end
|
|
537
569
|
end
|
|
538
570
|
end
|
|
539
571
|
|
|
@@ -68,26 +68,26 @@
|
|
|
68
68
|
<ul class="dropdown-menu shadow" aria-labelledby="<%= dom_id(job, :actions) %>">
|
|
69
69
|
<li>
|
|
70
70
|
<% job_reschedulable = job.status.in? [:scheduled, :retried, :queued] %>
|
|
71
|
-
<%=
|
|
71
|
+
<%= button_to reschedule_job_path(job.id), method: :put, class: "dropdown-item #{'disabled' unless job_reschedulable}", title: t(".actions.reschedule"), data: { turbo_confirm: t(".actions.confirm_reschedule") } do %>
|
|
72
72
|
<%= render_icon "skip_forward" %>
|
|
73
73
|
<%= t "good_job.actions.reschedule" %>
|
|
74
74
|
<% end %>
|
|
75
75
|
</li>
|
|
76
76
|
<li>
|
|
77
77
|
<% job_discardable = job.status.in? [:scheduled, :retried, :queued] %>
|
|
78
|
-
<%=
|
|
78
|
+
<%= button_to discard_job_path(job.id), method: :put, class: "dropdown-item #{'disabled' unless job_discardable}", title: t(".actions.discard"), data: { turbo_confirm: t(".actions.confirm_discard") } do %>
|
|
79
79
|
<%= render_icon "stop" %>
|
|
80
80
|
<%= t "good_job.actions.discard" %>
|
|
81
81
|
<% end %>
|
|
82
82
|
</li>
|
|
83
83
|
<li>
|
|
84
|
-
<%=
|
|
84
|
+
<%= button_to retry_job_path(job.id), method: :put, class: "dropdown-item #{'disabled' unless job.status == :discarded}", title: t(".actions.retry"), data: { turbo_confirm: t(".actions.confirm_retry") } do %>
|
|
85
85
|
<%= render_icon "arrow_clockwise" %>
|
|
86
86
|
<%= t "good_job.actions.retry" %>
|
|
87
87
|
<% end %>
|
|
88
88
|
</li>
|
|
89
89
|
<li>
|
|
90
|
-
<%=
|
|
90
|
+
<%= button_to job_path(job.id), method: :delete, class: "dropdown-item #{'disabled' unless job.finished?}", title: t(".actions.destroy"), data: { turbo_confirm: t(".actions.confirm_destroy") } do %>
|
|
91
91
|
<%= render_icon "trash" %>
|
|
92
92
|
<%= t "good_job.actions.destroy" %>
|
|
93
93
|
<% end %>
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
<div class="d-lg-none small text-muted mt-1"><%= t "good_job.models.batch.jobs" %></div>
|
|
59
59
|
<%= batch.jobs.size %>
|
|
60
60
|
</div>
|
|
61
|
-
<div class="col text-end">
|
|
61
|
+
<div class="col text-end d-flex flex-row justify-content-end">
|
|
62
62
|
<% if batch.discarded? %>
|
|
63
|
-
<%=
|
|
63
|
+
<%= button_to retry_batch_path(batch), method: :put, class: "btn btn-sm btn-outline-primary", title: t("good_job.batches.actions.retry"), data: { turbo_confirm: t("good_job.batches.actions.confirm_retry") } do %>
|
|
64
64
|
<%= render_icon "arrow_clockwise" %>
|
|
65
65
|
<%= t "good_job.batches.actions.retry" %>
|
|
66
66
|
<% end %>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
</div>
|
|
13
13
|
<div class="col text-end">
|
|
14
14
|
<% if @batch.discarded? %>
|
|
15
|
-
<%= button_to retry_batch_path(@batch), method: :put, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: t("good_job.batches.actions.retry") }, title: t("good_job.batches.actions.retry"), data: {
|
|
15
|
+
<%= button_to retry_batch_path(@batch), method: :put, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: t("good_job.batches.actions.retry") }, title: t("good_job.batches.actions.retry"), data: { turbo_confirm: t("good_job.batches.actions.confirm_retry") } do %>
|
|
16
16
|
<%= render_icon "arrow_clockwise" %>
|
|
17
17
|
<%= t "good_job.actions.retry" %>
|
|
18
18
|
<% end %>
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
<% end %>
|
|
59
59
|
|
|
60
60
|
<% if cron_entry.enabled? %>
|
|
61
|
-
<%= button_to disable_cron_entry_path(cron_entry), method: :put, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: t("good_job.cron_entries.actions.disable") }, title: t("good_job.cron_entries.actions.disable"), data: {
|
|
61
|
+
<%= button_to disable_cron_entry_path(cron_entry), method: :put, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: t("good_job.cron_entries.actions.disable") }, title: t("good_job.cron_entries.actions.disable"), data: { turbo_confirm: t("good_job.cron_entries.actions.confirm_disable") } do %>
|
|
62
62
|
<%= render_icon "pause" %>
|
|
63
63
|
<% end %>
|
|
64
64
|
<% else %>
|
|
65
|
-
<%= button_to enable_cron_entry_path(cron_entry), method: :put, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: t("good_job.cron_entries.actions.enable") }, title: t("good_job.cron_entries.actions.enable"), data: {
|
|
65
|
+
<%= button_to enable_cron_entry_path(cron_entry), method: :put, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: t("good_job.cron_entries.actions.enable") }, title: t("good_job.cron_entries.actions.enable"), data: { turbo_confirm: t("good_job.cron_entries.actions.confirm_enable") } do %>
|
|
66
66
|
<%= render_icon "play" %>
|
|
67
67
|
<% end %>
|
|
68
68
|
<% end %>
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
<%= form_with(id: "job_action_form", method: :put, model: false, local: true) { nil } %>
|
|
2
|
+
<%= form_with(id: "job_destroy_form", method: :delete, model: false, local: true) { nil } %>
|
|
3
|
+
|
|
1
4
|
<%= form_with(model: false, url: mass_update_jobs_path(filter.to_params), method: :put, local: true, data: { "checkbox-toggle": "job_ids" }) do |form| %>
|
|
2
5
|
<div class="my-3 card" data-gj-poll-replace id="jobs-table">
|
|
3
6
|
<div class="list-group list-group-flush text-nowrap table-jobs" role="table">
|
|
@@ -8,16 +11,16 @@
|
|
|
8
11
|
<%= check_box_tag('toggle_job_ids', "1", false, data: { "checkbox-toggle-all": "job_ids" }) %>
|
|
9
12
|
<%= label_tag('toggle_job_ids', t(".toggle_all_jobs"), class: "visually-hidden") %>
|
|
10
13
|
</div>
|
|
11
|
-
<%= form.button type: 'submit', name: 'mass_action', value: 'reschedule', class: 'ms-1 btn btn-sm btn-outline-secondary', title: t(".actions.reschedule_all"), data: {
|
|
14
|
+
<%= form.button type: 'submit', name: 'mass_action', value: 'reschedule', class: 'ms-1 btn btn-sm btn-outline-secondary', title: t(".actions.reschedule_all"), data: { turbo_confirm: t(".actions.confirm_reschedule_all") } do %>
|
|
12
15
|
<span class="me-1"><%= render_icon "skip_forward" %></span> <%= t "good_job.actions.reschedule" %>
|
|
13
16
|
<% end %>
|
|
14
17
|
|
|
15
|
-
<%= form.button type: 'submit', name: 'mass_action', value: 'retry', class: 'btn btn-sm btn-outline-secondary', title: t(".actions.retry_all"), data: {
|
|
18
|
+
<%= form.button type: 'submit', name: 'mass_action', value: 'retry', class: 'btn btn-sm btn-outline-secondary', title: t(".actions.retry_all"), data: { turbo_confirm: t(".actions.confirm_retry_all") } do %>
|
|
16
19
|
<span class="me-1"><%= render_icon "arrow_clockwise" %></span> <%= t "good_job.actions.retry" %>
|
|
17
20
|
<% end %>
|
|
18
21
|
|
|
19
22
|
<div class="btn-group" role="group">
|
|
20
|
-
<%= form.button type: 'submit', name: 'mass_action', value: 'discard', class: 'btn btn-sm btn-outline-secondary', title: t(".actions.discard_all"), data: {
|
|
23
|
+
<%= form.button type: 'submit', name: 'mass_action', value: 'discard', class: 'btn btn-sm btn-outline-secondary', title: t(".actions.discard_all"), data: { turbo_confirm: t(".actions.confirm_discard_all") } do %>
|
|
21
24
|
<span class="me-1"><%= render_icon "stop" %></span> <%= t "good_job.actions.discard" %>
|
|
22
25
|
<% end %>
|
|
23
26
|
<button id="destroy-dropdown-toggle" type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
|
@@ -25,7 +28,7 @@
|
|
|
25
28
|
</button>
|
|
26
29
|
<ul class="dropdown-menu" aria-labelledby="destroy-dropdown-toggle">
|
|
27
30
|
<li>
|
|
28
|
-
<%= form.button type: 'submit', name: 'mass_action', value: 'destroy', class: 'btn dropdown-item', title: t(".actions.destroy_all"), data: {
|
|
31
|
+
<%= form.button type: 'submit', name: 'mass_action', value: 'destroy', class: 'btn dropdown-item', title: t(".actions.destroy_all"), data: { turbo_confirm: t(".actions.confirm_destroy_all") } do %>
|
|
29
32
|
<span class="me-1"><%= render_icon "trash" %></span> <%= t "good_job.actions.destroy" %>
|
|
30
33
|
<% end %>
|
|
31
34
|
</li>
|
|
@@ -125,33 +128,33 @@
|
|
|
125
128
|
<ul class="dropdown-menu shadow" aria-labelledby="<%= dom_id(job, :actions) %>">
|
|
126
129
|
<li>
|
|
127
130
|
<% job_reschedulable = job.status.in? [:scheduled, :retried, :queued] %>
|
|
128
|
-
<%=
|
|
131
|
+
<%= tag.button form: "job_action_form", formaction: reschedule_job_path(job.id), class: "dropdown-item #{'disabled' unless job_reschedulable}", title: t("good_job.jobs.actions.reschedule"), data: { turbo_confirm: t("good_job.jobs.actions.confirm_reschedule") } do %>
|
|
129
132
|
<%= render_icon "skip_forward" %>
|
|
130
133
|
<%= t "good_job.actions.reschedule" %>
|
|
131
134
|
<% end %>
|
|
132
135
|
</li>
|
|
133
136
|
<li>
|
|
134
137
|
<% job_discardable = job.status.in? [:scheduled, :retried, :queued] %>
|
|
135
|
-
<%=
|
|
138
|
+
<%= tag.button form: "job_action_form", formaction: discard_job_path(job.id), class: "dropdown-item #{'disabled' unless job_discardable}", title: t("good_job.jobs.actions.discard"), data: { turbo_confirm: t("good_job.jobs.actions.confirm_discard") } do %>
|
|
136
139
|
<%= render_icon "stop" %>
|
|
137
140
|
<%= t "good_job.actions.discard" %>
|
|
138
141
|
<% end %>
|
|
139
142
|
</li>
|
|
140
143
|
<li>
|
|
141
144
|
<% job_force_discardable = job.status.in? [:running] %>
|
|
142
|
-
<%=
|
|
145
|
+
<%= tag.button form: "job_action_form", formaction: force_discard_job_path(job.id), class: "dropdown-item #{'disabled' unless job_force_discardable}", title: t("good_job.jobs.actions.force_discard"), data: { turbo_confirm: t("good_job.jobs.actions.confirm_force_discard") } do %>
|
|
143
146
|
<%= render_icon "eject" %>
|
|
144
147
|
<%= t "good_job.actions.force_discard" %>
|
|
145
148
|
<% end %>
|
|
146
149
|
</li>
|
|
147
150
|
<li>
|
|
148
|
-
<%=
|
|
151
|
+
<%= tag.button form: "job_action_form", formaction: retry_job_path(job.id), class: "dropdown-item #{'disabled' unless job.status == :discarded}", title: t("good_job.jobs.actions.retry"), data: { turbo_confirm: t("good_job.jobs.actions.confirm_retry") } do %>
|
|
149
152
|
<%= render_icon "arrow_clockwise" %>
|
|
150
153
|
<%= t "good_job.actions.retry" %>
|
|
151
154
|
<% end %>
|
|
152
155
|
</li>
|
|
153
156
|
<li>
|
|
154
|
-
<%=
|
|
157
|
+
<%= tag.button form: "job_destroy_form", formaction: job_path(job.id), class: "dropdown-item #{'disabled' unless job.status.in? [:discarded, :succeeded]}", title: t("good_job.jobs.actions.destroy"), data: { turbo_confirm: t("good_job.jobs.actions.confirm_destroy") } do %>
|
|
155
158
|
<%= render_icon "trash" %>
|
|
156
159
|
<%= t "good_job.actions.destroy" %>
|
|
157
160
|
<% end %>
|
|
@@ -33,28 +33,28 @@
|
|
|
33
33
|
form_class: "d-inline-block",
|
|
34
34
|
aria: { label: t("good_job.jobs.actions.reschedule") },
|
|
35
35
|
title: t("good_job.jobs.actions.reschedule"),
|
|
36
|
-
data: {
|
|
36
|
+
data: { turbo_confirm: t("good_job.jobs.actions.confirm_reschedule") } do %>
|
|
37
37
|
<%= render_icon "skip_forward" %>
|
|
38
38
|
<%= t "good_job.actions.reschedule" %>
|
|
39
39
|
<% end %>
|
|
40
40
|
<% end %>
|
|
41
41
|
|
|
42
42
|
<% if @job.status.in? [:scheduled, :retried, :queued] %>
|
|
43
|
-
<%= button_to discard_job_path(@job.id), method: :put, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: t("good_job.jobs.actions.discard") }, title: t("good_job.jobs.actions.discard"), data: {
|
|
43
|
+
<%= button_to discard_job_path(@job.id), method: :put, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: t("good_job.jobs.actions.discard") }, title: t("good_job.jobs.actions.discard"), data: { turbo_confirm: t("good_job.jobs.actions.confirm_discard") } do %>
|
|
44
44
|
<%= render_icon "stop" %>
|
|
45
45
|
<%= t "good_job.actions.discard" %>
|
|
46
46
|
<% end %>
|
|
47
47
|
<% end %>
|
|
48
48
|
|
|
49
49
|
<% if @job.status == :discarded %>
|
|
50
|
-
<%= button_to retry_job_path(@job.id), method: :put, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: t("good_job.jobs.actions.retry") }, title: t("good_job.jobs.actions.retry"), data: {
|
|
50
|
+
<%= button_to retry_job_path(@job.id), method: :put, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: t("good_job.jobs.actions.retry") }, title: t("good_job.jobs.actions.retry"), data: { turbo_confirm: t("good_job.jobs.actions.confirm_retry") } do %>
|
|
51
51
|
<%= render_icon "arrow_clockwise" %>
|
|
52
52
|
<%= t "good_job.actions.retry" %>
|
|
53
53
|
<% end %>
|
|
54
54
|
<% end %>
|
|
55
55
|
|
|
56
56
|
<% if @job.status.in? [:discarded, :succeeded] %>
|
|
57
|
-
<%= button_to job_path(@job.id), method: :delete, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: t("good_job.jobs.actions.destroy") }, title: t("good_job.jobs.actions.destroy"), data: {
|
|
57
|
+
<%= button_to job_path(@job.id), method: :delete, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: t("good_job.jobs.actions.destroy") }, title: t("good_job.jobs.actions.destroy"), data: { turbo_confirm: t("good_job.jobs.actions.confirm_destroy") } do %>
|
|
58
58
|
<%= render_icon "trash" %>
|
|
59
59
|
<%= t "good_job.actions.destroy" %>
|
|
60
60
|
<% end %>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
{ action: :destroy, type: type, value: value },
|
|
17
17
|
method: :delete,
|
|
18
18
|
class: 'btn btn-sm btn-outline-primary',
|
|
19
|
-
data: {
|
|
19
|
+
data: { turbo_confirm: t('good_job.pauses.index.confirm_unpause', value: value) }
|
|
20
20
|
) do %>
|
|
21
21
|
<%= render_icon "play" %>
|
|
22
22
|
<%= t("good_job.pauses.index.unpause") %>
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
{ action: :destroy, type: type, value: value },
|
|
5
5
|
method: :delete,
|
|
6
6
|
class: 'btn btn-sm btn-outline-primary',
|
|
7
|
-
data: {
|
|
7
|
+
data: { turbo_confirm: t('good_job.pauses.index.confirm_unpause', value: value) } %>
|
|
8
8
|
</li>
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
<h2 class="pt-3 pb-2"><%= title %></h2>
|
|
5
5
|
</div>
|
|
6
6
|
|
|
7
|
-
<%= form_with(model: false, url: "", method: :get, local: true,
|
|
7
|
+
<%= form_with(model: false, url: "", method: :get, local: true, data: { controller: "form" }) do |form| %>
|
|
8
8
|
<%= hidden_field_tag :poll, params[:poll] %>
|
|
9
9
|
<%= hidden_field_tag :state, params[:state] %>
|
|
10
10
|
<%= hidden_field_tag :locale, params[:locale] if params[:locale] %>
|
|
11
11
|
<div class="d-md-flex flex-row w-100">
|
|
12
12
|
<div class="me-md-2 mb-2 mb-md-0">
|
|
13
13
|
<%= label_tag "job_queue_filter", t(".queue_name"), class: "visually-hidden" %>
|
|
14
|
-
<select name="queue_name" id="job_queue_filter" class="form-select form-select-sm">
|
|
14
|
+
<select name="queue_name" id="job_queue_filter" class="form-select form-select-sm" data-action="change->form#submit">
|
|
15
15
|
<option value="" <%= "selected='selected'" if params[:queue_name].blank? %>><%= t ".all_queues" %></option>
|
|
16
16
|
|
|
17
17
|
<% filter.queues.each do |name, count| %>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
<div class="me-md-2 mb-2 mb-md-0">
|
|
24
24
|
<%= label_tag "job_class_filter", t(".job_name"), class: "visually-hidden" %>
|
|
25
|
-
<select name="job_class" id="job_class_filter" class="form-select form-select-sm">
|
|
25
|
+
<select name="job_class" id="job_class_filter" class="form-select form-select-sm" data-action="change->form#submit">
|
|
26
26
|
<option value="" <%= "selected='selected'" if params[:job_class].blank? %>><%= t ".all_jobs" %></option>
|
|
27
27
|
|
|
28
28
|
<% filter.job_classes.each do |name, count| %>
|
|
@@ -56,20 +56,10 @@
|
|
|
56
56
|
<li class="nav-item">
|
|
57
57
|
<%= link_to filter.to_params(state: name), class: "nav-link #{'active' if params[:state] == name}" do %>
|
|
58
58
|
<%= t(name, scope: 'good_job.status') %>
|
|
59
|
-
<span data-async-values-target="value" data-async-values-key="<%= name %>" data-async-values-zero-class="
|
|
59
|
+
<span data-async-values-target="value" data-async-values-key="<%= name %>" data-async-values-zero-class="bg-secondary" class="badge text-bg-primary rounded-pill d-none" id="filter_state_<%= name %>" data-turbo-permanent></span>
|
|
60
60
|
<% end %>
|
|
61
61
|
</li>
|
|
62
62
|
<% end %>
|
|
63
63
|
</ul>
|
|
64
64
|
</div>
|
|
65
|
-
|
|
66
|
-
<script nonce="<%= content_security_policy_nonce %>">
|
|
67
|
-
document.addEventListener("DOMContentLoaded", () => {
|
|
68
|
-
document.querySelectorAll("#job_class_filter, #job_queue_filter").forEach((filter) => {
|
|
69
|
-
filter.addEventListener("change", () => {
|
|
70
|
-
document.querySelector("#filter_form").submit();
|
|
71
|
-
});
|
|
72
|
-
})
|
|
73
|
-
})
|
|
74
|
-
</script>
|
|
75
65
|
</div>
|
|
@@ -18,25 +18,25 @@
|
|
|
18
18
|
<li class="nav-item">
|
|
19
19
|
<%= link_to jobs_path, class: ["nav-link", ("active" if controller_name == 'jobs')] do %>
|
|
20
20
|
<%= t(".jobs") %>
|
|
21
|
-
<span data-async-values-target="value" data-async-values-key="jobs_count" class="badge text-bg-secondary rounded-pill d-none"></span>
|
|
21
|
+
<span data-async-values-target="value" data-async-values-key="jobs_count" class="badge text-bg-secondary rounded-pill d-none" id="navbar_jobs_count" data-turbo-permanent></span>
|
|
22
22
|
<% end %>
|
|
23
23
|
</li>
|
|
24
24
|
<li class="nav-item">
|
|
25
25
|
<%= link_to batches_path, class: ["nav-link", ("active" if controller_name == 'batches')] do %>
|
|
26
26
|
<%= t ".batches" %>
|
|
27
|
-
<span data-async-values-target="value" data-async-values-key="batches_count" class="badge text-bg-secondary rounded-pill
|
|
27
|
+
<span data-async-values-target="value" data-async-values-key="batches_count" class="badge text-bg-secondary rounded-pill d-none" id="navbar_batches_count" data-turbo-permanent></span>
|
|
28
28
|
<% end %>
|
|
29
29
|
</li>
|
|
30
30
|
<li class="nav-item">
|
|
31
31
|
<%= link_to cron_entries_path, class: ["nav-link", ("active" if controller_name == 'cron_entries')] do %>
|
|
32
32
|
<%= t(".cron_schedules") %>
|
|
33
|
-
<span data-async-values-target="value" data-async-values-key="cron_entries_count" class="badge text-bg-secondary rounded-pill d-none"></span>
|
|
33
|
+
<span data-async-values-target="value" data-async-values-key="cron_entries_count" class="badge text-bg-secondary rounded-pill d-none" id="navbar_cron_entries_count" data-turbo-permanent></span>
|
|
34
34
|
<% end %>
|
|
35
35
|
</li>
|
|
36
36
|
<li class="nav-item">
|
|
37
37
|
<%= link_to processes_path, class: ["nav-link", ("active" if controller_name == 'processes')] do %>
|
|
38
38
|
<%= t(".processes") %>
|
|
39
|
-
<span data-async-values-target="value" data-async-values-key="processes_count" data-async-values-zero-class="
|
|
39
|
+
<span data-async-values-target="value" data-async-values-key="processes_count" data-async-values-zero-class="bg-danger" class="badge text-bg-secondary rounded-pill d-none" id="navbar_processes_count" data-turbo-permanent></span>
|
|
40
40
|
<% end %>
|
|
41
41
|
</li>
|
|
42
42
|
<li class="nav-item">
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
<li class="nav-item">
|
|
48
48
|
<%= link_to pauses_path, class: ["nav-link", ("active" if controller_name == 'pauses')] do %>
|
|
49
49
|
<%= t(".pauses") %>
|
|
50
|
-
<span data-async-values-target="value" data-async-values-key="pauses_count" data-async-values-zero-class="d-none" class="badge text-bg-warning rounded-pill d-none"></span>
|
|
50
|
+
<span data-async-values-target="value" data-async-values-key="pauses_count" data-async-values-zero-class="d-none" class="badge text-bg-warning rounded-pill d-none" id="navbar_pauses_count" data-turbo-permanent></span>
|
|
51
51
|
<% end %>
|
|
52
52
|
</li>
|
|
53
53
|
<li class="nav-item">
|
|
54
54
|
<%= link_to cleaner_index_path, class: ["nav-link", ("active" if controller_name == 'cleaner')] do %>
|
|
55
55
|
<%= t(".cleaner") %>
|
|
56
|
-
<span data-async-values-target="value" data-async-values-key="discarded_count" class="badge text-bg-secondary rounded-pill d-none"></span>
|
|
56
|
+
<span data-async-values-target="value" data-async-values-key="discarded_count" class="badge text-bg-secondary rounded-pill d-none" id="navbar_discarded_count" data-turbo-permanent></span>
|
|
57
57
|
<% end %>
|
|
58
58
|
</li>
|
|
59
59
|
</ul>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="<%= I18n.locale %>" data-bs-theme="auto">
|
|
2
|
+
<html lang="<%= I18n.locale %>" data-bs-theme="auto" data-turbo-unloaded>
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport">
|
|
@@ -18,15 +18,19 @@
|
|
|
18
18
|
</script>
|
|
19
19
|
|
|
20
20
|
<%# Do not use asset tag helpers to avoid paths being overriden by config.asset_host %>
|
|
21
|
-
<%= tag.link rel: "stylesheet", href: frontend_static_path(:bootstrap, format: :css, locale: nil), nonce: content_security_policy_nonce %>
|
|
22
|
-
<%= tag.link rel: "stylesheet", href: frontend_static_path(:style, format: :css, locale: nil), nonce: content_security_policy_nonce %>
|
|
21
|
+
<%= tag.link rel: "stylesheet", href: frontend_static_path(:bootstrap, format: :css, locale: nil), nonce: content_security_policy_nonce, data: { turbo_track: "reload" } %>
|
|
22
|
+
<%= tag.link rel: "stylesheet", href: frontend_static_path(:style, format: :css, locale: nil), nonce: content_security_policy_nonce, data: { turbo_track: "reload" } %>
|
|
23
23
|
<%= tag.script "", src: frontend_static_path(:bootstrap, format: :js, locale: nil), nonce: content_security_policy_nonce %>
|
|
24
24
|
<%= tag.script "", src: frontend_static_path(:chartjs, format: :js, locale: nil), nonce: content_security_policy_nonce %>
|
|
25
|
-
<%= tag.script "", src: frontend_static_path(:rails_ujs, format: :js, locale: nil), nonce: content_security_policy_nonce %>
|
|
26
25
|
<%= tag.script "", src: frontend_static_path(:es_module_shims, format: :js, locale: nil), async: true, nonce: content_security_policy_nonce %>
|
|
26
|
+
|
|
27
27
|
<% importmaps = GoodJob::FrontendsController.js_modules.keys.index_with { |module_name| frontend_module_path(module_name, format: :js, locale: nil) } %>
|
|
28
|
-
<%= tag.script({ imports: importmaps }.to_json.html_safe, type: "importmap", nonce: content_security_policy_nonce) %>
|
|
29
|
-
|
|
28
|
+
<%= tag.script({ imports: importmaps }.to_json.html_safe, type: "importmap", nonce: content_security_policy_nonce, data: { turbo_track: "reload" }) %>
|
|
29
|
+
<% importmaps.each do |_module_name, path| %>
|
|
30
|
+
<%= tag.link rel: "modulepreload", href: path, nonce: content_security_policy_nonce %>
|
|
31
|
+
<% end %>
|
|
32
|
+
|
|
33
|
+
<%= tag.script "import \"application\";".html_safe, type: "module", nonce: content_security_policy_nonce %>
|
|
30
34
|
|
|
31
35
|
<title>Good Job Dashboard</title>
|
|
32
36
|
<%= tag.link rel: "icon", href: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="10 0 100 100"><text y=".90em" font-size="90">👍</text></svg>' %>
|
data/config/brakeman.ignore
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"check_name": "Render",
|
|
8
8
|
"message": "Render path contains parameter value",
|
|
9
9
|
"file": "app/controllers/good_job/frontends_controller.rb",
|
|
10
|
-
"line":
|
|
10
|
+
"line": 52,
|
|
11
11
|
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
|
|
12
12
|
"code": "render(file => (self.class.js_modules[params[:id].to_sym] or raise(ActionController::RoutingError, \"Not Found\")), {})",
|
|
13
13
|
"render_path": null,
|
|
@@ -23,29 +23,6 @@
|
|
|
23
23
|
],
|
|
24
24
|
"note": "Files are explicitly enumerated in the array"
|
|
25
25
|
},
|
|
26
|
-
{
|
|
27
|
-
"warning_type": "Dynamic Render Path",
|
|
28
|
-
"warning_code": 15,
|
|
29
|
-
"fingerprint": "b0c2888c9b217671d90d0141b49b036af3b2a70c63b02968cc97ae2052c86272",
|
|
30
|
-
"check_name": "Render",
|
|
31
|
-
"message": "Render path contains parameter value",
|
|
32
|
-
"file": "app/controllers/good_job/frontends_controller.rb",
|
|
33
|
-
"line": 41,
|
|
34
|
-
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
|
|
35
|
-
"code": "render(file => ({ :css => ({ :bootstrap => GoodJob::Engine.root.join(\"app\", \"frontend\", \"good_job\", \"vendor\", \"bootstrap\", \"bootstrap.min.css\"), :style => GoodJob::Engine.root.join(\"app\", \"frontend\", \"good_job\", \"style.css\") }), :js => ({ :bootstrap => GoodJob::Engine.root.join(\"app\", \"frontend\", \"good_job\", \"vendor\", \"bootstrap\", \"bootstrap.bundle.min.js\"), :chartjs => GoodJob::Engine.root.join(\"app\", \"frontend\", \"good_job\", \"vendor\", \"chartjs\", \"chart.min.js\"), :es_module_shims => GoodJob::Engine.root.join(\"app\", \"frontend\", \"good_job\", \"vendor\", \"es_module_shims.js\"), :rails_ujs => GoodJob::Engine.root.join(\"app\", \"frontend\", \"good_job\", \"vendor\", \"rails_ujs.js\") }), :svg => ({ :icons => GoodJob::Engine.root.join(\"app\", \"frontend\", \"good_job\", \"icons.svg\") }) }.dig(params[:format].to_sym, params[:id].to_sym) or raise(ActionController::RoutingError, \"Not Found\")), {})",
|
|
36
|
-
"render_path": null,
|
|
37
|
-
"location": {
|
|
38
|
-
"type": "method",
|
|
39
|
-
"class": "GoodJob::FrontendsController",
|
|
40
|
-
"method": "static"
|
|
41
|
-
},
|
|
42
|
-
"user_input": "params[:id].to_sym",
|
|
43
|
-
"confidence": "Weak",
|
|
44
|
-
"cwe_id": [
|
|
45
|
-
22
|
|
46
|
-
],
|
|
47
|
-
"note": "Files are explicitly enumerated in the array"
|
|
48
|
-
},
|
|
49
26
|
{
|
|
50
27
|
"warning_type": "Dangerous Eval",
|
|
51
28
|
"warning_code": 13,
|
|
@@ -115,7 +92,30 @@
|
|
|
115
92
|
77
|
|
116
93
|
],
|
|
117
94
|
"note": ""
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"warning_type": "Dynamic Render Path",
|
|
98
|
+
"warning_code": 15,
|
|
99
|
+
"fingerprint": "ebc35a48169b7ee1f2e1b024f3bf6abe7138924a3a318ab2ad405c2b6e9bbb55",
|
|
100
|
+
"check_name": "Render",
|
|
101
|
+
"message": "Render path contains parameter value",
|
|
102
|
+
"file": "app/controllers/good_job/frontends_controller.rb",
|
|
103
|
+
"line": 46,
|
|
104
|
+
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
|
|
105
|
+
"code": "render(file => ({ :css => ({ :bootstrap => asset_path(\"vendor\", \"bootstrap\", \"bootstrap.min.css\"), :style => asset_path(\"style.css\") }), :js => ({ :bootstrap => asset_path(\"vendor\", \"bootstrap\", \"bootstrap.bundle.min.js\"), :chartjs => asset_path(\"vendor\", \"chartjs\", \"chart.min.js\"), :es_module_shims => asset_path(\"vendor\", \"es_module_shims.js\"), :turbo => asset_path(\"vendor\", \"turbo.js\") }), :svg => ({ :icons => asset_path(\"icons.svg\") }) }.dig(params[:format].to_sym, params[:id].to_sym) or raise(ActionController::RoutingError, \"Not Found\")), {})",
|
|
106
|
+
"render_path": null,
|
|
107
|
+
"location": {
|
|
108
|
+
"type": "method",
|
|
109
|
+
"class": "GoodJob::FrontendsController",
|
|
110
|
+
"method": "static"
|
|
111
|
+
},
|
|
112
|
+
"user_input": "params[:id].to_sym",
|
|
113
|
+
"confidence": "Weak",
|
|
114
|
+
"cwe_id": [
|
|
115
|
+
22
|
|
116
|
+
],
|
|
117
|
+
"note": ""
|
|
118
118
|
}
|
|
119
119
|
],
|
|
120
|
-
"brakeman_version": "7.
|
|
120
|
+
"brakeman_version": "7.1.1"
|
|
121
121
|
}
|