good_job 3.16.3 → 3.17.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -0
- data/README.md +41 -15
- data/app/frontend/good_job/application.js +2 -0
- data/app/frontend/good_job/modules/theme_controller.js +44 -0
- data/app/frontend/good_job/vendor/bootstrap/bootstrap.bundle.min.js +3 -3
- data/app/frontend/good_job/vendor/bootstrap/bootstrap.min.css +3 -4
- data/app/models/concerns/good_job/{lockable.rb → advisory_lockable.rb} +1 -1
- data/app/models/good_job/base_execution.rb +24 -1
- data/app/models/good_job/batch.rb +2 -2
- data/app/models/good_job/batch_record.rb +1 -1
- data/app/models/good_job/execution.rb +0 -21
- data/app/models/good_job/process.rb +5 -1
- data/app/views/good_job/batches/_jobs.erb +3 -3
- data/app/views/good_job/batches/_table.erb +1 -1
- 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/_executions.erb +2 -2
- data/app/views/good_job/jobs/_table.erb +10 -9
- data/app/views/good_job/jobs/show.html.erb +2 -2
- data/app/views/good_job/processes/index.html.erb +3 -3
- data/app/views/good_job/shared/_footer.erb +1 -1
- data/app/views/good_job/shared/_navbar.erb +50 -7
- data/app/views/good_job/shared/icons/_circle_half.html.erb +4 -0
- data/app/views/good_job/shared/icons/_moon_stars_fill.html.erb +5 -0
- data/app/views/good_job/shared/icons/_sun_fill.html.erb +4 -0
- data/app/views/layouts/good_job/application.html.erb +9 -1
- data/config/locales/de.yml +5 -0
- data/config/locales/en.yml +5 -0
- data/config/locales/es.yml +5 -0
- data/config/locales/fr.yml +5 -0
- data/config/locales/ja.yml +5 -0
- data/config/locales/nl.yml +5 -0
- data/config/locales/ru.yml +5 -0
- data/config/locales/tr.yml +5 -0
- data/config/locales/uk.yml +5 -0
- data/lib/good_job/capsule.rb +5 -4
- data/lib/good_job/cli.rb +6 -2
- data/lib/good_job/configuration.rb +2 -5
- data/lib/good_job/cron_manager.rb +3 -2
- data/lib/good_job/http_server.rb +75 -0
- data/lib/good_job/log_subscriber.rb +18 -0
- data/lib/good_job/notifier.rb +59 -44
- data/lib/good_job/probe_server.rb +4 -8
- data/lib/good_job/sd_notify.rb +157 -0
- data/lib/good_job/shared_executor.rb +69 -0
- data/lib/good_job/systemd_service.rb +69 -0
- data/lib/good_job/version.rb +1 -1
- data/lib/good_job.rb +6 -0
- metadata +11 -17
@@ -4,9 +4,9 @@ module GoodJob
|
|
4
4
|
# ActiveRecord model to share behavior between {Job} and {Execution} models
|
5
5
|
# which both read out of the same table.
|
6
6
|
class BaseExecution < BaseRecord
|
7
|
+
include AdvisoryLockable
|
7
8
|
include ErrorEvents
|
8
9
|
include Filterable
|
9
|
-
include Lockable
|
10
10
|
include Reportable
|
11
11
|
|
12
12
|
self.table_name = 'good_jobs'
|
@@ -60,5 +60,28 @@ module GoodJob
|
|
60
60
|
def discrete?
|
61
61
|
self.class.discrete_support? && is_discrete?
|
62
62
|
end
|
63
|
+
|
64
|
+
# Build an ActiveJob instance and deserialize the arguments, using `#active_job_data`.
|
65
|
+
#
|
66
|
+
# @param ignore_deserialization_errors [Boolean]
|
67
|
+
# Whether to ignore ActiveJob::DeserializationError when deserializing the arguments.
|
68
|
+
# This is most useful if you aren't planning to use the arguments directly.
|
69
|
+
def active_job(ignore_deserialization_errors: false)
|
70
|
+
ActiveJob::Base.deserialize(active_job_data).tap do |aj|
|
71
|
+
aj.send(:deserialize_arguments_if_needed)
|
72
|
+
rescue ActiveJob::DeserializationError
|
73
|
+
raise unless ignore_deserialization_errors
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def active_job_data
|
80
|
+
serialized_params.deep_dup
|
81
|
+
.tap do |job_data|
|
82
|
+
job_data["provider_job_id"] = id
|
83
|
+
job_data["good_job_concurrency_key"] = concurrency_key if concurrency_key
|
84
|
+
end
|
85
|
+
end
|
63
86
|
end
|
64
87
|
end
|
@@ -124,11 +124,11 @@ module GoodJob
|
|
124
124
|
end
|
125
125
|
|
126
126
|
def active_jobs
|
127
|
-
record.jobs.map(&:
|
127
|
+
record.jobs.map(&:active_job)
|
128
128
|
end
|
129
129
|
|
130
130
|
def callback_active_jobs
|
131
|
-
record.callback_jobs.map(&:
|
131
|
+
record.callback_jobs.map(&:active_job)
|
132
132
|
end
|
133
133
|
|
134
134
|
def assign_properties(properties)
|
@@ -508,19 +508,6 @@ module GoodJob
|
|
508
508
|
self.scheduled_at ||= current_time
|
509
509
|
end
|
510
510
|
|
511
|
-
# Build an ActiveJob instance and deserialize the arguments, using `#active_job_data`.
|
512
|
-
#
|
513
|
-
# @param ignore_deserialization_errors [Boolean]
|
514
|
-
# Whether to ignore ActiveJob::DeserializationError when deserializing the arguments.
|
515
|
-
# This is most useful if you aren't planning to use the arguments directly.
|
516
|
-
def active_job(ignore_deserialization_errors: false)
|
517
|
-
ActiveJob::Base.deserialize(active_job_data).tap do |aj|
|
518
|
-
aj.send(:deserialize_arguments_if_needed)
|
519
|
-
rescue ActiveJob::DeserializationError
|
520
|
-
raise unless ignore_deserialization_errors
|
521
|
-
end
|
522
|
-
end
|
523
|
-
|
524
511
|
# Return formatted serialized_params for display in the dashboard
|
525
512
|
# @return [Hash]
|
526
513
|
def display_serialized_params
|
@@ -565,14 +552,6 @@ module GoodJob
|
|
565
552
|
|
566
553
|
private
|
567
554
|
|
568
|
-
def active_job_data
|
569
|
-
serialized_params.deep_dup
|
570
|
-
.tap do |job_data|
|
571
|
-
job_data["provider_job_id"] = id
|
572
|
-
job_data["good_job_concurrency_key"] = concurrency_key if concurrency_key
|
573
|
-
end
|
574
|
-
end
|
575
|
-
|
576
555
|
def reset_batch_values(&block)
|
577
556
|
GoodJob::Batch.within_thread(batch_id: nil, batch_callback_id: nil, &block)
|
578
557
|
end
|
@@ -5,8 +5,8 @@ require 'socket'
|
|
5
5
|
module GoodJob # :nodoc:
|
6
6
|
# ActiveRecord model that represents an GoodJob process (either async or CLI).
|
7
7
|
class Process < BaseRecord
|
8
|
+
include AdvisoryLockable
|
8
9
|
include AssignableConnection
|
9
|
-
include Lockable
|
10
10
|
|
11
11
|
# Interval until the process record being updated
|
12
12
|
STALE_INTERVAL = 30.seconds
|
@@ -63,6 +63,10 @@ module GoodJob # :nodoc:
|
|
63
63
|
cron_enabled: GoodJob.configuration.enable_cron?,
|
64
64
|
total_succeeded_executions_count: GoodJob::Scheduler.instances.sum { |scheduler| scheduler.stats.fetch(:succeeded_executions_count) },
|
65
65
|
total_errored_executions_count: GoodJob::Scheduler.instances.sum { |scheduler| scheduler.stats.fetch(:errored_executions_count) },
|
66
|
+
database_connection_pool: {
|
67
|
+
size: connection_pool.size,
|
68
|
+
active: connection_pool.connections.count(&:in_use?),
|
69
|
+
},
|
66
70
|
}
|
67
71
|
end
|
68
72
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<div class="my-3 card" data-gj-poll-replace id="jobs-table">
|
2
2
|
<div class="list-group list-group-flush text-nowrap table-jobs" role="table">
|
3
|
-
<header class="list-group-item bg-
|
3
|
+
<header class="list-group-item bg-body-tertiary">
|
4
4
|
<div class="row small text-muted text-uppercase align-items-center">
|
5
5
|
<div class="col-4"><%=t "good_job.models.batch.jobs" %></div>
|
6
6
|
<div class="d-none d-lg-block col-lg-1 text-lg-center"><%=t "good_job.models.job.queue" %></div>
|
@@ -27,7 +27,7 @@
|
|
27
27
|
</div>
|
28
28
|
<div class="col-4 col-lg-1 text-lg-center">
|
29
29
|
<div class="d-lg-none small text-muted mt-1"><%=t "good_job.models.job.queue" %></div>
|
30
|
-
<span class="badge bg-primary
|
30
|
+
<span class="badge bg-primary text-dark font-monospace"><%= job.queue_name %></span>
|
31
31
|
</div>
|
32
32
|
<div class="col-4 col-lg-1 text-lg-end">
|
33
33
|
<div class="d-lg-none small text-muted mt-1"><%=t "good_job.models.job.priority" %>Priority</div>
|
@@ -43,7 +43,7 @@
|
|
43
43
|
bs_content: job.recent_error
|
44
44
|
} %>
|
45
45
|
<% else %>
|
46
|
-
<span class="badge bg-secondary
|
46
|
+
<span class="badge bg-secondary rounded-pill"><%= job.executions_count %></span>
|
47
47
|
<% end %>
|
48
48
|
</div>
|
49
49
|
<div class="mt-3 mt-lg-0 col d-flex gap-3 align-items-center justify-content-end">
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<div class="my-3 card" data-gj-poll-replace id="batches-table">
|
2
2
|
<div class="list-group list-group-flush text-nowrap table-batches" role="table">
|
3
|
-
<header class="list-group-item bg-
|
3
|
+
<header class="list-group-item bg-body-tertiary">
|
4
4
|
<div class="row small text-muted text-uppercase align-items-center">
|
5
5
|
<div class="col-4"><%=t "good_job.models.batch.name" %></div>
|
6
6
|
<div class="col-lg-1 d-none d-lg-block"><%=t "good_job.models.batch.created" %></div>
|
@@ -16,7 +16,7 @@
|
|
16
16
|
|
17
17
|
<div class="my-4">
|
18
18
|
<h5><%= t ".attributes" %></h5>
|
19
|
-
<div class="bg-dark text-
|
19
|
+
<div class="bg-dark text-secondary p-3 rounded">
|
20
20
|
<%= tag.pre JSON.pretty_generate @batch.display_attributes, class: 'text-wrap text-break' %>
|
21
21
|
</div>
|
22
22
|
</div>
|
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
<div class="card my-3">
|
6
6
|
<div class="list-group list-group-flush text-nowrap" role="table">
|
7
|
-
<header class="list-group-item
|
7
|
+
<header class="list-group-item body-secondary">
|
8
8
|
<div class="row small text-muted text-uppercase align-items-center">
|
9
9
|
<div class="col-12 col-lg-2"></div>
|
10
10
|
<div class="col-6 col-lg-2 d-none d-lg-block"><%= t "good_job.models.cron.class" %></div>
|
@@ -68,7 +68,7 @@
|
|
68
68
|
</div>
|
69
69
|
</div>
|
70
70
|
</div>
|
71
|
-
<%= tag.div id: dom_id(cron_entry, 'properties'), class: "collapse cron-entry-properties list-group-item collapse small bg-dark text-
|
71
|
+
<%= tag.div id: dom_id(cron_entry, 'properties'), class: "collapse cron-entry-properties list-group-item collapse small bg-dark text-secondary" do %>
|
72
72
|
<%= tag.pre JSON.pretty_generate(cron_entry.display_properties) %>
|
73
73
|
<% end %>
|
74
74
|
<% end %>
|
@@ -5,7 +5,7 @@
|
|
5
5
|
<%= tag.div id: dom_id(execution), class: "list-group-item py-3" do %>
|
6
6
|
<div class="row align-items-center text-nowrap">
|
7
7
|
<div class="col-md-5 d-flex gap-2">
|
8
|
-
<%= tag.span execution.number, class: "badge bg-secondary
|
8
|
+
<%= tag.span execution.number, class: "badge bg-secondary rounded-pill" %>
|
9
9
|
<%= tag.code link_to(execution.id, "##{dom_id(execution)}", class: "text-muted text-decoration-none small") %>
|
10
10
|
</div>
|
11
11
|
<div class="col-md-2 small">
|
@@ -36,7 +36,7 @@
|
|
36
36
|
<% if execution.error %>
|
37
37
|
<div class="mt-3 small">
|
38
38
|
<strong class="small"><%=t "good_job.shared.error" %>:</strong>
|
39
|
-
<code class="text-wrap text-break m-0 text-
|
39
|
+
<code class="text-wrap text-break m-0 text-secondary-emphasis"><%= execution.error %></code>
|
40
40
|
</div>
|
41
41
|
<% end %>
|
42
42
|
<% end %>
|
@@ -1,12 +1,13 @@
|
|
1
1
|
<%= form_with(url: mass_update_jobs_path(filter.to_params), method: :put, local: true, data: { "checkbox-toggle": "job_ids" }) do |form| %>
|
2
2
|
<div class="my-3 card" data-gj-poll-replace id="jobs-table">
|
3
3
|
<div class="list-group list-group-flush text-nowrap table-jobs" role="table">
|
4
|
-
<header class="list-group-item bg-
|
4
|
+
<header class="list-group-item bg-body-tertiary">
|
5
5
|
<div class="row small text-muted text-uppercase align-items-center">
|
6
6
|
<div class="col-lg-4 d-flex gap-2 flex-wrap">
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
<div class="form-check d-flex flex-row px-0 mb-0">
|
8
|
+
<%= check_box_tag('toggle_job_ids', "1", false, data: { "checkbox-toggle-all": "job_ids" }) %>
|
9
|
+
<%= label_tag('toggle_job_ids', t(".toggle_all_jobs"), class: "visually-hidden") %>
|
10
|
+
</div>
|
10
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: { confirm: t(".actions.confirm_reschedule_all"), disable: true } do %>
|
11
12
|
<span class="me-1"><%= render_icon "skip_forward" %></span> <%=t "good_job.actions.reschedule" %>
|
12
13
|
<% end %>
|
@@ -22,13 +23,13 @@
|
|
22
23
|
<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">
|
23
24
|
<span class="visually-hidden"><%=t ".toggle_actions" %></span>
|
24
25
|
</button>
|
25
|
-
<
|
26
|
+
<ul class="dropdown-menu" aria-labelledby="destroy-dropdown-toggle">
|
26
27
|
<li>
|
27
28
|
<%= form.button type: 'submit', name: 'mass_action', value: 'destroy', class: 'btn', title: t(".actions.destroy_all"), data: { confirm: t(".actions.confirm_destroy_all"), disable: true } do %>
|
28
29
|
<span class="me-1"><%= render_icon "trash" %></span> <%=t "good_job.actions.destroy" %>
|
29
30
|
<% end %>
|
30
31
|
</li>
|
31
|
-
</
|
32
|
+
</ul>
|
32
33
|
</div>
|
33
34
|
|
34
35
|
</div>
|
@@ -68,14 +69,14 @@
|
|
68
69
|
<% if job.error %>
|
69
70
|
<div class="mt-1 small">
|
70
71
|
<strong class="small"><%=t "good_job.shared.error" %>:</strong>
|
71
|
-
<code class="text-wrap text-break m-0 text-
|
72
|
+
<code class="text-wrap text-break m-0 text-secondary-emphasis"><%= job.error %></code>
|
72
73
|
</div>
|
73
74
|
<% end %>
|
74
75
|
</div>
|
75
76
|
</div>
|
76
77
|
<div class="col-4 col-lg-1 text-lg-center">
|
77
78
|
<div class="d-lg-none small text-muted mt-1"><%=t "good_job.models.job.queue" %></div>
|
78
|
-
<span class="badge bg-primary
|
79
|
+
<span class="badge bg-primary font-monospace"><%= job.queue_name %></span>
|
79
80
|
</div>
|
80
81
|
<div class="col-4 col-lg-1 text-lg-end">
|
81
82
|
<div class="d-lg-none small text-muted mt-1"><%=t "good_job.models.job.priority" %></div>
|
@@ -91,7 +92,7 @@
|
|
91
92
|
bs_content: job.display_error,
|
92
93
|
} %>
|
93
94
|
<% else %>
|
94
|
-
<span class="badge bg-secondary
|
95
|
+
<span class="badge bg-secondary rounded-pill"><%= job.executions_count %></span>
|
95
96
|
<% end %>
|
96
97
|
</div>
|
97
98
|
<div class="mt-3 mt-lg-0 col">
|
@@ -6,7 +6,7 @@
|
|
6
6
|
<li class="breadcrumb-item active" aria-current="page">
|
7
7
|
<%= tag.code @job.id, class: "text-muted" %>
|
8
8
|
<% if @job.discrete? %>
|
9
|
-
<span class="badge bg-info
|
9
|
+
<span class="badge bg-info">Discrete</span>
|
10
10
|
<% end %>
|
11
11
|
</li>
|
12
12
|
</ol>
|
@@ -17,7 +17,7 @@
|
|
17
17
|
</div>
|
18
18
|
<div class="col-6 col-md-2">
|
19
19
|
<div class="small text-muted text-uppercase"><%= t "good_job.models.job.queue" %></div>
|
20
|
-
<div class="badge bg-primary
|
20
|
+
<div class="badge bg-primary font-monospace my-2">
|
21
21
|
<%= tag.strong @job.queue_name %>
|
22
22
|
</div>
|
23
23
|
</div>
|
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
<div class="card my-3" data-live-poll-region="processes">
|
6
6
|
<div class="list-group list-group-flush text-nowrap" role="table">
|
7
|
-
<header class="list-group-item bg-
|
7
|
+
<header class="list-group-item bg-body-tertiary">
|
8
8
|
<div class="row small text-muted text-uppercase align-items-center">
|
9
9
|
<div class="col"><%= t ".process" %></div>
|
10
10
|
<div class="col"><%= t ".schedulers" %></div>
|
@@ -34,9 +34,9 @@
|
|
34
34
|
<% end %>
|
35
35
|
<div>
|
36
36
|
<span class="text-muted small">PID</span>
|
37
|
-
<span class="badge rounded-pill bg-
|
37
|
+
<span class="badge rounded-pill bg-body-secondary text-secondary"><%= process.state["pid"] %></span>
|
38
38
|
<span class="text-muted small">@</span>
|
39
|
-
<span class="badge rounded-pill bg-
|
39
|
+
<span class="badge rounded-pill bg-body-secondary text-secondary"><%= process.state["hostname"] %></span>
|
40
40
|
</div>
|
41
41
|
</div>
|
42
42
|
<div class="col">
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<footer class="footer mt-auto py-3 bg-
|
1
|
+
<footer class="footer mt-auto py-3 bg-body-tertiary border-top text-muted small" id="footer" data-live-poll-region="footer">
|
2
2
|
<div class="container-fluid">
|
3
3
|
<div class="row">
|
4
4
|
<div class="col-6">
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<nav class="navbar navbar-expand-lg
|
1
|
+
<nav class="navbar navbar-expand-lg border-bottom bg-body sticky-top shadow-sm">
|
2
2
|
<div class="container-fluid">
|
3
3
|
<%= link_to t(".name"), root_path, class: "navbar-brand mb-0 h1" %>
|
4
4
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
@@ -36,13 +36,24 @@
|
|
36
36
|
<% end %>
|
37
37
|
</li>
|
38
38
|
</ul>
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
<%= t(".live_poll") %>
|
43
|
-
</label>
|
44
|
-
</div>
|
39
|
+
|
40
|
+
<hr class="d-lg-none text-secondary">
|
41
|
+
|
45
42
|
<ul class="navbar-nav">
|
43
|
+
<li class="nav-item d-flex flex-column justify-content-center">
|
44
|
+
<div class="form-check form-switch m-0">
|
45
|
+
<%= check_box_tag "live_poll", params.fetch("poll", 30), params[:poll].present?, role: "switch", class: "form-check-input" %>
|
46
|
+
<label class="form-check-label navbar-text p-0" for="live_poll">
|
47
|
+
<%= t(".live_poll") %>
|
48
|
+
</label>
|
49
|
+
</div>
|
50
|
+
</li>
|
51
|
+
|
52
|
+
<li class="nav-item py-2 py-lg-1 col-12 col-lg-auto">
|
53
|
+
<div class="vr d-none d-lg-flex h-100 mx-lg-2 text-secondary"></div>
|
54
|
+
<hr class="d-lg-none my-2 text-secondary">
|
55
|
+
</li>
|
56
|
+
|
46
57
|
<li class="nav-item dropdown">
|
47
58
|
<a href="#" class="nav-link dropdown-toggle" type="button" id="localeOptions" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
48
59
|
<%= I18n.locale %>
|
@@ -55,6 +66,38 @@
|
|
55
66
|
<% end %>
|
56
67
|
</ul>
|
57
68
|
</li>
|
69
|
+
|
70
|
+
<li class="nav-item py-2 py-lg-1 col-12 col-lg-auto">
|
71
|
+
<div class="vr d-none d-lg-flex h-100 mx-lg-2 text-secondary"></div>
|
72
|
+
<hr class="d-lg-none my-2 text-secondary">
|
73
|
+
</li>
|
74
|
+
|
75
|
+
<li class="nav-item dropdown" data-controller="theme">
|
76
|
+
<button class="nav-link dropdown-toggle" data-theme-target="dropdown" type="button" aria-expanded="false" data-bs-toggle="dropdown" data-bs-display="static" aria-label="Toggle theme">
|
77
|
+
<%= render_icon "circle_half" %>
|
78
|
+
<span class="visually-hidden"><%= t(".theme.theme") %></span>
|
79
|
+
</button>
|
80
|
+
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="bd-theme-text">
|
81
|
+
<li>
|
82
|
+
<button type="button" class="dropdown-item" data-theme-target="button" data-theme-value-param="light" data-action="theme#change" aria-pressed="true">
|
83
|
+
<%= render_icon "sun_fill" %>
|
84
|
+
<%= t(".theme.light") %>
|
85
|
+
</button>
|
86
|
+
</li>
|
87
|
+
<li>
|
88
|
+
<button type="button" class="dropdown-item" data-theme-target="button" data-theme-value-param="dark" data-action="theme#change" aria-pressed="false">
|
89
|
+
<%= render_icon "moon_stars_fill" %>
|
90
|
+
<%= t(".theme.dark") %>
|
91
|
+
</button>
|
92
|
+
</li>
|
93
|
+
<li>
|
94
|
+
<button type="button" class="dropdown-item btn btn-link" data-theme-target="button" data-theme-value-param="auto" data-action="theme#change" aria-pressed="false">
|
95
|
+
<%= render_icon "circle_half" %>
|
96
|
+
<%= t(".theme.auto") %>
|
97
|
+
</button>
|
98
|
+
</li>
|
99
|
+
</ul>
|
100
|
+
</li>
|
58
101
|
</ul>
|
59
102
|
</div>
|
60
103
|
</div>
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/moon-stars-fill/ -->
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-moon-stars-fill" viewBox="0 0 16 16">
|
3
|
+
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z" />
|
4
|
+
<path d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z" />
|
5
|
+
</svg>
|
@@ -0,0 +1,4 @@
|
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/sun-fill/ -->
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-sun-fill" viewBox="0 0 16 16">
|
3
|
+
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z" />
|
4
|
+
</svg>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
|
-
<html lang="<%= I18n.locale %>">
|
2
|
+
<html lang="<%= I18n.locale %>" data-bs-theme="auto">
|
3
3
|
<head>
|
4
4
|
<title>Good Job Dashboard</title>
|
5
5
|
<meta charset="utf-8">
|
@@ -19,6 +19,14 @@
|
|
19
19
|
<% importmaps = GoodJob::FrontendsController.js_modules.keys.index_with { |module_name| frontend_module_path(module_name, format: :js, locale: nil, v: GoodJob::VERSION) } %>
|
20
20
|
<%= tag.script({ imports: importmaps }.to_json.html_safe, type: "importmap", nonce: content_security_policy_nonce) %>
|
21
21
|
<%= tag.script "", type: "module", nonce: content_security_policy_nonce do %> import "application"; <% end %>
|
22
|
+
<%= tag.script "", nonce: content_security_policy_nonce do %>
|
23
|
+
// Ensure theme is updated before dom loads to avoid flash of style
|
24
|
+
let theme = localStorage.getItem('good_job-theme');
|
25
|
+
if (!["light", "dark"].includes(theme)) {
|
26
|
+
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
27
|
+
}
|
28
|
+
document.documentElement.setAttribute('data-bs-theme', theme);
|
29
|
+
<% end %>
|
22
30
|
</head>
|
23
31
|
<body>
|
24
32
|
<div class="d-flex flex-column min-vh-100">
|
data/config/locales/de.yml
CHANGED
data/config/locales/en.yml
CHANGED
data/config/locales/es.yml
CHANGED
data/config/locales/fr.yml
CHANGED
data/config/locales/ja.yml
CHANGED
data/config/locales/nl.yml
CHANGED
data/config/locales/ru.yml
CHANGED
data/config/locales/tr.yml
CHANGED
data/config/locales/uk.yml
CHANGED
data/lib/good_job/capsule.rb
CHANGED
@@ -29,13 +29,14 @@ module GoodJob
|
|
29
29
|
@mutex.synchronize do
|
30
30
|
return unless startable?(force: force)
|
31
31
|
|
32
|
-
@
|
32
|
+
@shared_executor = GoodJob::SharedExecutor.new
|
33
|
+
@notifier = GoodJob::Notifier.new(enable_listening: @configuration.enable_listen_notify, executor: @shared_executor.executor)
|
33
34
|
@poller = GoodJob::Poller.new(poll_interval: @configuration.poll_interval)
|
34
35
|
@scheduler = GoodJob::Scheduler.from_configuration(@configuration, warm_cache_on_initialize: true)
|
35
36
|
@notifier.recipients << [@scheduler, :create_thread]
|
36
37
|
@poller.recipients << [@scheduler, :create_thread]
|
37
38
|
|
38
|
-
@cron_manager = GoodJob::CronManager.new(@configuration.cron_entries, start_on_initialize: true) if @configuration.enable_cron?
|
39
|
+
@cron_manager = GoodJob::CronManager.new(@configuration.cron_entries, start_on_initialize: true, executor: @shared_executor.executor) if @configuration.enable_cron?
|
39
40
|
|
40
41
|
@startable = false
|
41
42
|
@running = true
|
@@ -51,7 +52,7 @@ module GoodJob
|
|
51
52
|
# @return [void]
|
52
53
|
def shutdown(timeout: :default)
|
53
54
|
timeout = @configuration.shutdown_timeout if timeout == :default
|
54
|
-
GoodJob._shutdown_all([@notifier, @poller, @scheduler, @cron_manager].compact, timeout: timeout)
|
55
|
+
GoodJob._shutdown_all([@shared_executor, @notifier, @poller, @scheduler, @cron_manager].compact, timeout: timeout)
|
55
56
|
@startable = false
|
56
57
|
@running = false
|
57
58
|
end
|
@@ -73,7 +74,7 @@ module GoodJob
|
|
73
74
|
|
74
75
|
# @return [Boolean] Whether the capsule has been shutdown.
|
75
76
|
def shutdown?
|
76
|
-
[@notifier, @poller, @scheduler, @cron_manager].compact.all?(&:shutdown?)
|
77
|
+
[@shared_executor, @notifier, @poller, @scheduler, @cron_manager].compact.all?(&:shutdown?)
|
77
78
|
end
|
78
79
|
|
79
80
|
# Creates an execution thread(s) with the given attributes.
|
data/lib/good_job/cli.rb
CHANGED
@@ -94,10 +94,12 @@ module GoodJob
|
|
94
94
|
GoodJob.configuration.options.merge!(options.symbolize_keys)
|
95
95
|
configuration = GoodJob.configuration
|
96
96
|
capsule = GoodJob.capsule
|
97
|
+
systemd = GoodJob::SystemdService.new
|
97
98
|
|
98
99
|
Daemon.new(pidfile: configuration.pidfile).daemonize if configuration.daemonize?
|
99
100
|
|
100
101
|
capsule.start
|
102
|
+
systemd.start
|
101
103
|
|
102
104
|
if configuration.probe_port
|
103
105
|
probe_server = GoodJob::ProbeServer.new(port: configuration.probe_port)
|
@@ -114,8 +116,10 @@ module GoodJob
|
|
114
116
|
break if @stop_good_job_executable || capsule.shutdown?
|
115
117
|
end
|
116
118
|
|
117
|
-
|
118
|
-
|
119
|
+
systemd.stop do
|
120
|
+
capsule.shutdown(timeout: configuration.shutdown_timeout)
|
121
|
+
probe_server&.stop
|
122
|
+
end
|
119
123
|
end
|
120
124
|
|
121
125
|
default_task :start
|
@@ -51,13 +51,10 @@ module GoodJob
|
|
51
51
|
# @param warn [Boolean] whether to print a warning when over the limit
|
52
52
|
# @return [Integer]
|
53
53
|
def self.total_estimated_threads(warn: false)
|
54
|
-
|
55
|
-
|
56
|
-
cron_threads = configuration.enable_cron? ? 2 : 0
|
57
|
-
notifier_threads = 1
|
54
|
+
utility_threads = GoodJob::SharedExecutor::MAX_THREADS
|
58
55
|
scheduler_threads = GoodJob::Scheduler.instances.sum { |scheduler| scheduler.stats[:max_threads] }
|
59
56
|
|
60
|
-
good_job_threads =
|
57
|
+
good_job_threads = utility_threads + scheduler_threads
|
61
58
|
puma_threads = (Puma::Server.current&.max_threads if defined?(Puma::Server)) || 0
|
62
59
|
|
63
60
|
total_threads = good_job_threads + puma_threads
|