sidekiq 7.3.9 → 8.0.1
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/Changes.md +44 -0
- data/README.md +16 -13
- data/bin/sidekiqload +10 -10
- data/bin/webload +69 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +5 -5
- data/lib/sidekiq/api.rb +120 -36
- data/lib/sidekiq/capsule.rb +6 -6
- data/lib/sidekiq/cli.rb +15 -19
- data/lib/sidekiq/client.rb +13 -16
- data/lib/sidekiq/component.rb +40 -2
- data/lib/sidekiq/config.rb +18 -15
- data/lib/sidekiq/embedded.rb +1 -0
- data/lib/sidekiq/iterable_job.rb +1 -0
- data/lib/sidekiq/job/iterable.rb +13 -4
- data/lib/sidekiq/job_logger.rb +4 -4
- data/lib/sidekiq/job_retry.rb +17 -5
- data/lib/sidekiq/job_util.rb +5 -1
- data/lib/sidekiq/launcher.rb +1 -1
- data/lib/sidekiq/logger.rb +19 -70
- data/lib/sidekiq/manager.rb +0 -1
- data/lib/sidekiq/metrics/query.rb +71 -45
- data/lib/sidekiq/metrics/shared.rb +8 -5
- data/lib/sidekiq/metrics/tracking.rb +9 -7
- data/lib/sidekiq/middleware/current_attributes.rb +5 -17
- data/lib/sidekiq/paginator.rb +8 -1
- data/lib/sidekiq/processor.rb +21 -14
- data/lib/sidekiq/profiler.rb +59 -0
- data/lib/sidekiq/redis_client_adapter.rb +0 -1
- data/lib/sidekiq/redis_connection.rb +14 -3
- data/lib/sidekiq/testing.rb +2 -2
- data/lib/sidekiq/version.rb +2 -2
- data/lib/sidekiq/web/action.rb +104 -84
- data/lib/sidekiq/web/application.rb +347 -332
- data/lib/sidekiq/web/config.rb +117 -0
- data/lib/sidekiq/web/helpers.rb +41 -16
- data/lib/sidekiq/web/router.rb +60 -76
- data/lib/sidekiq/web.rb +50 -156
- data/lib/sidekiq.rb +1 -1
- data/sidekiq.gemspec +6 -6
- data/web/assets/javascripts/application.js +6 -13
- data/web/assets/javascripts/base-charts.js +30 -16
- data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
- data/web/assets/javascripts/metrics.js +16 -34
- data/web/assets/stylesheets/style.css +750 -0
- data/web/locales/ar.yml +1 -0
- data/web/locales/cs.yml +1 -0
- data/web/locales/da.yml +1 -0
- data/web/locales/de.yml +1 -0
- data/web/locales/el.yml +1 -0
- data/web/locales/en.yml +6 -0
- data/web/locales/es.yml +24 -2
- data/web/locales/fa.yml +1 -0
- data/web/locales/fr.yml +1 -0
- data/web/locales/gd.yml +1 -0
- data/web/locales/he.yml +1 -0
- data/web/locales/hi.yml +1 -0
- data/web/locales/it.yml +1 -0
- data/web/locales/ja.yml +1 -0
- data/web/locales/ko.yml +1 -0
- data/web/locales/lt.yml +1 -0
- data/web/locales/nb.yml +1 -0
- data/web/locales/nl.yml +1 -0
- data/web/locales/pl.yml +1 -0
- data/web/locales/{pt-br.yml → pt-BR.yml} +2 -1
- data/web/locales/pt.yml +1 -0
- data/web/locales/ru.yml +1 -0
- data/web/locales/sv.yml +1 -0
- data/web/locales/ta.yml +1 -0
- data/web/locales/tr.yml +1 -0
- data/web/locales/uk.yml +1 -0
- data/web/locales/ur.yml +1 -0
- data/web/locales/vi.yml +1 -0
- data/web/locales/{zh-cn.yml → zh-CN.yml} +85 -73
- data/web/locales/{zh-tw.yml → zh-TW.yml} +2 -1
- data/web/views/_footer.erb +31 -33
- data/web/views/_job_info.erb +91 -89
- data/web/views/_metrics_period_select.erb +13 -10
- data/web/views/_nav.erb +14 -21
- data/web/views/_paging.erb +23 -21
- data/web/views/_poll_link.erb +2 -2
- data/web/views/_summary.erb +16 -16
- data/web/views/busy.erb +124 -122
- data/web/views/dashboard.erb +62 -66
- data/web/views/dead.erb +31 -27
- data/web/views/filtering.erb +3 -3
- data/web/views/layout.erb +6 -22
- data/web/views/metrics.erb +75 -81
- data/web/views/metrics_for_job.erb +45 -46
- data/web/views/morgue.erb +61 -70
- data/web/views/profiles.erb +43 -0
- data/web/views/queue.erb +54 -52
- data/web/views/queues.erb +43 -41
- data/web/views/retries.erb +66 -75
- data/web/views/retry.erb +32 -27
- data/web/views/scheduled.erb +58 -54
- data/web/views/scheduled_job_info.erb +1 -1
- metadata +24 -24
- data/web/assets/stylesheets/application-dark.css +0 -147
- data/web/assets/stylesheets/application-rtl.css +0 -163
- data/web/assets/stylesheets/application.css +0 -759
- data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
- data/web/assets/stylesheets/bootstrap.css +0 -5
- data/web/views/_status.erb +0 -4
@@ -1,436 +1,451 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "sidekiq/paginator"
|
4
|
+
require "sidekiq/web/helpers"
|
5
|
+
|
3
6
|
module Sidekiq
|
4
|
-
class
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
"
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
7
|
+
class Web
|
8
|
+
class Application
|
9
|
+
extend Router
|
10
|
+
include Router
|
11
|
+
|
12
|
+
REDIS_KEYS = %w[redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human]
|
13
|
+
|
14
|
+
CSP_HEADER_TEMPLATE = [
|
15
|
+
"default-src 'self' https: http:",
|
16
|
+
"child-src 'self'",
|
17
|
+
"connect-src 'self' https: http: wss: ws:",
|
18
|
+
"font-src 'none'",
|
19
|
+
"frame-src 'self'",
|
20
|
+
"img-src 'self' https: http: data:",
|
21
|
+
"manifest-src 'self'",
|
22
|
+
"media-src 'self'",
|
23
|
+
"object-src 'none'",
|
24
|
+
"script-src 'self' 'nonce-!placeholder!'",
|
25
|
+
"style-src 'self' 'nonce-!placeholder!'",
|
26
|
+
"worker-src 'self'",
|
27
|
+
"base-uri 'self'"
|
28
|
+
].join("; ").freeze
|
29
|
+
|
30
|
+
METRICS_PERIODS = {
|
31
|
+
"1h" => {minutes: 60},
|
32
|
+
"2h" => {minutes: 120},
|
33
|
+
"4h" => {minutes: 240},
|
34
|
+
"8h" => {minutes: 480},
|
35
|
+
"24h" => {hours: 24},
|
36
|
+
"48h" => {hours: 48},
|
37
|
+
"72h" => {hours: 72}
|
38
|
+
}
|
33
39
|
|
34
|
-
|
35
|
-
|
36
|
-
|
40
|
+
def initialize(inst)
|
41
|
+
@app = inst
|
42
|
+
end
|
37
43
|
|
38
|
-
|
39
|
-
|
40
|
-
|
44
|
+
head "/" do
|
45
|
+
# HEAD / is the cheapest heartbeat possible,
|
46
|
+
# it hits Redis to ensure connectivity and returns
|
47
|
+
# the size of the default queue
|
48
|
+
Sidekiq.redis { |c| c.llen("queue:default") }.to_s
|
49
|
+
end
|
41
50
|
|
42
|
-
|
43
|
-
|
44
|
-
|
51
|
+
get "/" do
|
52
|
+
@redis_info = redis_info.slice(*REDIS_KEYS)
|
53
|
+
days = (url_params("days") || 30).to_i
|
54
|
+
return halt(401) if days < 1 || days > 180
|
45
55
|
|
46
|
-
|
47
|
-
|
48
|
-
|
56
|
+
stats_history = Sidekiq::Stats::History.new(days)
|
57
|
+
@processed_history = stats_history.processed
|
58
|
+
@failed_history = stats_history.failed
|
49
59
|
|
50
|
-
|
51
|
-
|
52
|
-
# it hits Redis to ensure connectivity and returns
|
53
|
-
# the size of the default queue
|
54
|
-
Sidekiq.redis { |c| c.llen("queue:default") }.to_s
|
55
|
-
end
|
60
|
+
erb(:dashboard)
|
61
|
+
end
|
56
62
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
return halt(401) if days < 1 || days > 180
|
63
|
+
get "/metrics" do
|
64
|
+
x = url_params("substr")
|
65
|
+
class_filter = (x.nil? || x == "") ? nil : Regexp.new(Regexp.escape(x), Regexp::IGNORECASE)
|
61
66
|
|
62
|
-
|
63
|
-
|
64
|
-
|
67
|
+
q = Sidekiq::Metrics::Query.new
|
68
|
+
@period = h(url_params("period") || "1h")
|
69
|
+
@periods = METRICS_PERIODS
|
70
|
+
args = @periods.fetch(@period, @periods.values.first)
|
71
|
+
@query_result = q.top_jobs(**args.merge(class_filter: class_filter))
|
65
72
|
|
66
|
-
|
67
|
-
|
73
|
+
header "refresh", 60 if @period == "1h"
|
74
|
+
erb(:metrics)
|
75
|
+
end
|
68
76
|
|
69
|
-
|
70
|
-
|
71
|
-
|
77
|
+
get "/metrics/:name" do
|
78
|
+
@name = route_params(:name)
|
79
|
+
@period = h(url_params("period") || "1h")
|
80
|
+
# Periods larger than 8 hours are not supported for histogram chart
|
81
|
+
@period = "8h" if @period.to_i > 8
|
82
|
+
@periods = METRICS_PERIODS.reject { |k, v| k.to_i > 8 }
|
83
|
+
args = @periods.fetch(@period, @periods.values.first)
|
84
|
+
q = Sidekiq::Metrics::Query.new
|
85
|
+
@query_result = q.for_job(@name, **args)
|
86
|
+
|
87
|
+
header "refresh", 60 if @period == "1h"
|
88
|
+
erb(:metrics_for_job)
|
89
|
+
end
|
72
90
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
minutes = @periods.fetch(@period, @periods.values.first)
|
77
|
-
@query_result = q.top_jobs(minutes: minutes, class_filter: class_filter)
|
91
|
+
get "/busy" do
|
92
|
+
@count = (url_params("count") || 100).to_i
|
93
|
+
(@current_page, @total_size, @workset) = page_items(workset, url_params("page"), @count)
|
78
94
|
|
79
|
-
|
80
|
-
|
95
|
+
erb(:busy)
|
96
|
+
end
|
81
97
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
q = Sidekiq::Metrics::Query.new
|
86
|
-
@periods = METRICS_PERIODS
|
87
|
-
minutes = @periods.fetch(@period, @periods.values.first)
|
88
|
-
@query_result = q.for_job(@name, minutes: minutes)
|
89
|
-
erb(:metrics_for_job)
|
90
|
-
end
|
98
|
+
post "/busy" do
|
99
|
+
if url_params("identity")
|
100
|
+
pro = Sidekiq::ProcessSet[url_params("identity")]
|
91
101
|
|
92
|
-
|
93
|
-
|
94
|
-
|
102
|
+
pro.quiet! if url_params("quiet")
|
103
|
+
pro.stop! if url_params("stop")
|
104
|
+
else
|
105
|
+
processes.each do |pro|
|
106
|
+
next if pro.embedded?
|
95
107
|
|
96
|
-
|
97
|
-
|
108
|
+
pro.quiet! if url_params("quiet")
|
109
|
+
pro.stop! if url_params("stop")
|
110
|
+
end
|
111
|
+
end
|
98
112
|
|
99
|
-
|
100
|
-
|
101
|
-
pro = Sidekiq::ProcessSet[params["identity"]]
|
113
|
+
redirect "#{root_path}busy"
|
114
|
+
end
|
102
115
|
|
103
|
-
|
104
|
-
|
105
|
-
else
|
106
|
-
processes.each do |pro|
|
107
|
-
next if pro.embedded?
|
116
|
+
get "/queues" do
|
117
|
+
@queues = Sidekiq::Queue.all
|
108
118
|
|
109
|
-
|
110
|
-
pro.stop! if params["stop"]
|
111
|
-
end
|
119
|
+
erb(:queues)
|
112
120
|
end
|
113
121
|
|
114
|
-
|
115
|
-
end
|
122
|
+
QUEUE_NAME = /\A[a-z_:.\-0-9]+\z/i
|
116
123
|
|
117
|
-
|
118
|
-
|
124
|
+
get "/queues/:name" do
|
125
|
+
@name = route_params(:name)
|
119
126
|
|
120
|
-
|
121
|
-
end
|
127
|
+
halt(404) if !@name || @name !~ QUEUE_NAME
|
122
128
|
|
123
|
-
|
129
|
+
@count = (url_params("count") || 25).to_i
|
130
|
+
@queue = Sidekiq::Queue.new(@name)
|
131
|
+
(@current_page, @total_size, @jobs) = page("queue:#{@name}", url_params("page"), @count, reverse: url_params("direction") == "asc")
|
132
|
+
@jobs = @jobs.map { |msg| Sidekiq::JobRecord.new(msg, @name) }
|
124
133
|
|
125
|
-
|
126
|
-
|
134
|
+
erb(:queue)
|
135
|
+
end
|
127
136
|
|
128
|
-
|
137
|
+
post "/queues/:name" do
|
138
|
+
queue = Sidekiq::Queue.new(route_params(:name))
|
129
139
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
140
|
+
if Sidekiq.pro? && url_params("pause")
|
141
|
+
queue.pause!
|
142
|
+
elsif Sidekiq.pro? && url_params("unpause")
|
143
|
+
queue.unpause!
|
144
|
+
else
|
145
|
+
queue.clear
|
146
|
+
end
|
134
147
|
|
135
|
-
|
136
|
-
|
148
|
+
redirect "#{root_path}queues"
|
149
|
+
end
|
137
150
|
|
138
|
-
|
139
|
-
|
151
|
+
post "/queues/:name/delete" do
|
152
|
+
name = route_params(:name)
|
153
|
+
Sidekiq::JobRecord.new(url_params("key_val"), name).delete
|
140
154
|
|
141
|
-
|
142
|
-
queue.pause!
|
143
|
-
elsif Sidekiq.pro? && params["unpause"]
|
144
|
-
queue.unpause!
|
145
|
-
else
|
146
|
-
queue.clear
|
155
|
+
redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
|
147
156
|
end
|
148
157
|
|
149
|
-
|
150
|
-
|
158
|
+
get "/morgue" do
|
159
|
+
x = url_params("substr")
|
151
160
|
|
152
|
-
|
153
|
-
|
154
|
-
|
161
|
+
if x && x != ""
|
162
|
+
@dead = search(Sidekiq::DeadSet.new, x)
|
163
|
+
else
|
164
|
+
@count = (url_params("count") || 25).to_i
|
165
|
+
(@current_page, @total_size, @dead) = page("dead", url_params("page"), @count, reverse: true)
|
166
|
+
@dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
167
|
+
end
|
155
168
|
|
156
|
-
|
157
|
-
|
169
|
+
erb(:morgue)
|
170
|
+
end
|
171
|
+
|
172
|
+
get "/morgue/:key" do
|
173
|
+
key = route_params(:key)
|
174
|
+
halt(404) unless key
|
158
175
|
|
159
|
-
|
160
|
-
x = params[:substr]
|
176
|
+
@dead = Sidekiq::DeadSet.new.fetch(*parse_key(key)).first
|
161
177
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
@dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
178
|
+
if @dead.nil?
|
179
|
+
redirect "#{root_path}morgue"
|
180
|
+
else
|
181
|
+
erb(:dead)
|
182
|
+
end
|
168
183
|
end
|
169
184
|
|
170
|
-
|
171
|
-
|
185
|
+
post "/morgue" do
|
186
|
+
redirect(request.path) unless url_params("key")
|
187
|
+
|
188
|
+
url_params("key").each do |key|
|
189
|
+
job = Sidekiq::DeadSet.new.fetch(*parse_key(key)).first
|
190
|
+
retry_or_delete_or_kill job, request.params if job
|
191
|
+
end
|
172
192
|
|
173
|
-
|
174
|
-
|
175
|
-
halt(404) unless key
|
193
|
+
redirect_with_query("#{root_path}morgue")
|
194
|
+
end
|
176
195
|
|
177
|
-
|
196
|
+
post "/morgue/all/delete" do
|
197
|
+
Sidekiq::DeadSet.new.clear
|
178
198
|
|
179
|
-
if @dead.nil?
|
180
199
|
redirect "#{root_path}morgue"
|
181
|
-
else
|
182
|
-
erb(:dead)
|
183
200
|
end
|
184
|
-
end
|
185
201
|
|
186
|
-
|
187
|
-
|
202
|
+
post "/morgue/all/retry" do
|
203
|
+
Sidekiq::DeadSet.new.retry_all
|
188
204
|
|
189
|
-
|
190
|
-
job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
|
191
|
-
retry_or_delete_or_kill job, params if job
|
205
|
+
redirect "#{root_path}morgue"
|
192
206
|
end
|
193
207
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
post "/morgue/all/delete" do
|
198
|
-
Sidekiq::DeadSet.new.clear
|
208
|
+
post "/morgue/:key" do
|
209
|
+
key = route_params(:key)
|
210
|
+
halt(404) unless key
|
199
211
|
|
200
|
-
|
201
|
-
|
212
|
+
job = Sidekiq::DeadSet.new.fetch(*parse_key(key)).first
|
213
|
+
retry_or_delete_or_kill job, request.params if job
|
202
214
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
redirect "#{root_path}morgue"
|
207
|
-
end
|
215
|
+
redirect_with_query("#{root_path}morgue")
|
216
|
+
end
|
208
217
|
|
209
|
-
|
210
|
-
|
211
|
-
halt(404) unless key
|
218
|
+
get "/retries" do
|
219
|
+
x = url_params("substr")
|
212
220
|
|
213
|
-
|
214
|
-
|
221
|
+
if x && x != ""
|
222
|
+
@retries = search(Sidekiq::RetrySet.new, x)
|
223
|
+
else
|
224
|
+
@count = (url_params("count") || 25).to_i
|
225
|
+
(@current_page, @total_size, @retries) = page("retry", url_params("page"), @count)
|
226
|
+
@retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
227
|
+
end
|
215
228
|
|
216
|
-
|
217
|
-
|
229
|
+
erb(:retries)
|
230
|
+
end
|
218
231
|
|
219
|
-
|
220
|
-
|
232
|
+
get "/retries/:key" do
|
233
|
+
@retry = Sidekiq::RetrySet.new.fetch(*parse_key(route_params(:key))).first
|
221
234
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
@retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
235
|
+
if @retry.nil?
|
236
|
+
redirect "#{root_path}retries"
|
237
|
+
else
|
238
|
+
erb(:retry)
|
239
|
+
end
|
228
240
|
end
|
229
241
|
|
230
|
-
|
231
|
-
|
242
|
+
post "/retries" do
|
243
|
+
route_params("mike")
|
244
|
+
url_params(:mike)
|
245
|
+
redirect(request.path) unless url_params("key")
|
232
246
|
|
233
|
-
|
234
|
-
|
247
|
+
url_params("key").each do |key|
|
248
|
+
job = Sidekiq::RetrySet.new.fetch(*parse_key(key)).first
|
249
|
+
retry_or_delete_or_kill job, request.params if job
|
250
|
+
end
|
235
251
|
|
236
|
-
|
237
|
-
redirect "#{root_path}retries"
|
238
|
-
else
|
239
|
-
erb(:retry)
|
252
|
+
redirect_with_query("#{root_path}retries")
|
240
253
|
end
|
241
|
-
end
|
242
254
|
|
243
|
-
|
244
|
-
|
255
|
+
post "/retries/all/delete" do
|
256
|
+
Sidekiq::RetrySet.new.clear
|
257
|
+
redirect "#{root_path}retries"
|
258
|
+
end
|
245
259
|
|
246
|
-
|
247
|
-
|
248
|
-
|
260
|
+
post "/retries/all/retry" do
|
261
|
+
Sidekiq::RetrySet.new.retry_all
|
262
|
+
redirect "#{root_path}retries"
|
249
263
|
end
|
250
264
|
|
251
|
-
|
252
|
-
|
265
|
+
post "/retries/all/kill" do
|
266
|
+
Sidekiq::RetrySet.new.kill_all
|
267
|
+
redirect "#{root_path}retries"
|
268
|
+
end
|
253
269
|
|
254
|
-
|
255
|
-
|
270
|
+
post "/retries/:key" do
|
271
|
+
job = Sidekiq::RetrySet.new.fetch(*parse_key(route_params(:key))).first
|
256
272
|
|
257
|
-
|
258
|
-
end
|
273
|
+
retry_or_delete_or_kill job, request.params if job
|
259
274
|
|
260
|
-
|
261
|
-
|
275
|
+
redirect_with_query("#{root_path}retries")
|
276
|
+
end
|
262
277
|
|
263
|
-
|
264
|
-
|
278
|
+
get "/scheduled" do
|
279
|
+
x = url_params("substr")
|
265
280
|
|
266
|
-
|
267
|
-
|
281
|
+
if x && x != ""
|
282
|
+
@scheduled = search(Sidekiq::ScheduledSet.new, x)
|
283
|
+
else
|
284
|
+
@count = (url_params("count") || 25).to_i
|
285
|
+
(@current_page, @total_size, @scheduled) = page("schedule", url_params("page"), @count)
|
286
|
+
@scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
287
|
+
end
|
268
288
|
|
269
|
-
|
270
|
-
|
289
|
+
erb(:scheduled)
|
290
|
+
end
|
271
291
|
|
272
|
-
|
273
|
-
|
292
|
+
get "/scheduled/:key" do
|
293
|
+
@job = Sidekiq::ScheduledSet.new.fetch(*parse_key(route_params(:key))).first
|
274
294
|
|
275
|
-
|
295
|
+
if @job.nil?
|
296
|
+
redirect "#{root_path}scheduled"
|
297
|
+
else
|
298
|
+
erb(:scheduled_job_info)
|
299
|
+
end
|
300
|
+
end
|
276
301
|
|
277
|
-
|
278
|
-
|
302
|
+
post "/scheduled" do
|
303
|
+
redirect(request.path) unless url_params("key")
|
279
304
|
|
280
|
-
|
281
|
-
|
305
|
+
url_params("key").each do |key|
|
306
|
+
job = Sidekiq::ScheduledSet.new.fetch(*parse_key(key)).first
|
307
|
+
delete_or_add_queue job, request.params if job
|
308
|
+
end
|
282
309
|
|
283
|
-
|
284
|
-
@scheduled = search(Sidekiq::ScheduledSet.new, x)
|
285
|
-
else
|
286
|
-
@count = (params["count"] || 25).to_i
|
287
|
-
(@current_page, @total_size, @scheduled) = page("schedule", params["page"], @count)
|
288
|
-
@scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
310
|
+
redirect_with_query("#{root_path}scheduled")
|
289
311
|
end
|
290
312
|
|
291
|
-
|
292
|
-
|
313
|
+
post "/scheduled/:key" do
|
314
|
+
key = route_params(:key)
|
315
|
+
halt(404) unless key
|
293
316
|
|
294
|
-
|
295
|
-
|
317
|
+
job = Sidekiq::ScheduledSet.new.fetch(*parse_key(key)).first
|
318
|
+
delete_or_add_queue job, request.params if job
|
296
319
|
|
297
|
-
|
298
|
-
redirect "#{root_path}scheduled"
|
299
|
-
else
|
300
|
-
erb(:scheduled_job_info)
|
320
|
+
redirect_with_query("#{root_path}scheduled")
|
301
321
|
end
|
302
|
-
end
|
303
322
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
params["key"].each do |key|
|
308
|
-
job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
|
309
|
-
delete_or_add_queue job, params if job
|
323
|
+
get "/dashboard/stats" do
|
324
|
+
redirect "#{root_path}stats"
|
310
325
|
end
|
311
326
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
327
|
+
get "/stats" do
|
328
|
+
sidekiq_stats = Sidekiq::Stats.new
|
329
|
+
redis_stats = redis_info.slice(*REDIS_KEYS)
|
330
|
+
json(
|
331
|
+
sidekiq: {
|
332
|
+
processed: sidekiq_stats.processed,
|
333
|
+
failed: sidekiq_stats.failed,
|
334
|
+
busy: sidekiq_stats.workers_size,
|
335
|
+
processes: sidekiq_stats.processes_size,
|
336
|
+
enqueued: sidekiq_stats.enqueued,
|
337
|
+
scheduled: sidekiq_stats.scheduled_size,
|
338
|
+
retries: sidekiq_stats.retry_size,
|
339
|
+
dead: sidekiq_stats.dead_size,
|
340
|
+
default_latency: sidekiq_stats.default_queue_latency
|
341
|
+
},
|
342
|
+
redis: redis_stats,
|
343
|
+
server_utc_time: server_utc_time
|
344
|
+
)
|
345
|
+
end
|
318
346
|
|
319
|
-
|
320
|
-
|
347
|
+
get "/stats/queues" do
|
348
|
+
json Sidekiq::Stats.new.queues
|
349
|
+
end
|
321
350
|
|
322
|
-
|
323
|
-
|
351
|
+
get "/profiles" do
|
352
|
+
erb(:profiles)
|
353
|
+
end
|
324
354
|
|
325
|
-
|
326
|
-
|
327
|
-
|
355
|
+
get "/profiles/:key" do
|
356
|
+
store = config[:profile_store_url]
|
357
|
+
return redirect_to "#{root_path}profiles" unless store
|
358
|
+
|
359
|
+
key = route_params(:key)
|
360
|
+
sid = Sidekiq.redis { |c| c.hget(key, "sid") }
|
361
|
+
|
362
|
+
unless sid
|
363
|
+
require "net/http"
|
364
|
+
data = Sidekiq.redis { |c| c.hget(key, "data") }
|
365
|
+
resp = Net::HTTP.post(URI(store),
|
366
|
+
data,
|
367
|
+
{"Accept" => "application/vnd.firefox-profiler+json;version=1.0",
|
368
|
+
"User-Agent" => "Sidekiq #{Sidekiq::VERSION} job profiler"})
|
369
|
+
# https://raw.githubusercontent.com/firefox-devtools/profiler-server/master/tools/decode_jwt_payload.py
|
370
|
+
rawjson = resp.body.split(".")[1].unpack1("m")
|
371
|
+
sid = Sidekiq.load_json(rawjson)["profileToken"]
|
372
|
+
Sidekiq.redis { |c| c.hset(key, "sid", sid) }
|
373
|
+
end
|
374
|
+
url = config[:profile_view_url] % sid
|
375
|
+
redirect_to url
|
376
|
+
end
|
328
377
|
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
json(
|
333
|
-
sidekiq: {
|
334
|
-
processed: sidekiq_stats.processed,
|
335
|
-
failed: sidekiq_stats.failed,
|
336
|
-
busy: sidekiq_stats.workers_size,
|
337
|
-
processes: sidekiq_stats.processes_size,
|
338
|
-
enqueued: sidekiq_stats.enqueued,
|
339
|
-
scheduled: sidekiq_stats.scheduled_size,
|
340
|
-
retries: sidekiq_stats.retry_size,
|
341
|
-
dead: sidekiq_stats.dead_size,
|
342
|
-
default_latency: sidekiq_stats.default_queue_latency
|
343
|
-
},
|
344
|
-
redis: redis_stats,
|
345
|
-
server_utc_time: server_utc_time
|
346
|
-
)
|
347
|
-
end
|
378
|
+
get "/profiles/:key/data" do
|
379
|
+
key = route_params(:key)
|
380
|
+
data = Sidekiq.redis { |c| c.hget(key, "data") }
|
348
381
|
|
349
|
-
|
350
|
-
|
351
|
-
|
382
|
+
[200, {
|
383
|
+
"content-type" => "application/json",
|
384
|
+
"content-encoding" => "gzip",
|
385
|
+
# allow Firefox Profiler's XHR to fetch this profile data
|
386
|
+
"access-control-allow-origin" => "*"
|
387
|
+
}, [data]]
|
388
|
+
end
|
352
389
|
|
353
|
-
|
354
|
-
|
390
|
+
post "/change_locale" do
|
391
|
+
locale = url_params("locale")
|
355
392
|
|
356
|
-
|
357
|
-
|
358
|
-
|
393
|
+
match = available_locales.find { |available|
|
394
|
+
locale == available
|
395
|
+
}
|
359
396
|
|
360
|
-
|
397
|
+
session[:locale] = match if match
|
361
398
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
def call(env)
|
366
|
-
action = self.class.match(env)
|
367
|
-
return [404, {Rack::CONTENT_TYPE => "text/plain", Web::X_CASCADE => "pass"}, ["Not Found"]] unless action
|
399
|
+
reload_page
|
400
|
+
end
|
368
401
|
|
369
|
-
|
370
|
-
|
371
|
-
self.class.run_befores(app, action)
|
372
|
-
action.instance_exec env, &action.block
|
373
|
-
ensure
|
374
|
-
self.class.run_afters(app, action)
|
402
|
+
def redis(&)
|
403
|
+
Thread.current[:sidekiq_redis_pool].with(&)
|
375
404
|
end
|
376
405
|
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
else
|
382
|
-
# rendered content goes here
|
406
|
+
def call(env)
|
407
|
+
action = match(env)
|
408
|
+
return [404, {"content-type" => "text/plain", "x-cascade" => "pass"}, ["Not Found"]] unless action
|
409
|
+
|
383
410
|
headers = {
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
411
|
+
"content-type" => "text/html",
|
412
|
+
"cache-control" => "private, no-store",
|
413
|
+
"content-language" => action.locale,
|
414
|
+
"content-security-policy" => process_csp(env, CSP_HEADER_TEMPLATE),
|
415
|
+
"x-content-type-options" => "nosniff"
|
389
416
|
}
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
end
|
417
|
+
env["response_headers"] = headers
|
418
|
+
resp = catch(:halt) do
|
419
|
+
Thread.current[:sidekiq_redis_pool] = env[:redis_pool]
|
420
|
+
action.instance_exec env, &action.block
|
421
|
+
ensure
|
422
|
+
Thread.current[:sidekiq_redis_pool] = nil
|
423
|
+
end
|
398
424
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
425
|
+
case resp
|
426
|
+
when Array
|
427
|
+
# redirects go here
|
428
|
+
resp
|
429
|
+
else
|
430
|
+
# rendered content goes here
|
431
|
+
# we'll let Rack calculate Content-Length for us.
|
432
|
+
[200, env["response_headers"], [resp]]
|
433
|
+
end
|
404
434
|
end
|
405
|
-
end
|
406
435
|
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
def self.after(path = nil, &block)
|
412
|
-
afters << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
|
413
|
-
end
|
414
|
-
|
415
|
-
def self.run_befores(app, action)
|
416
|
-
run_hooks(befores, app, action)
|
417
|
-
end
|
418
|
-
|
419
|
-
def self.run_afters(app, action)
|
420
|
-
run_hooks(afters, app, action)
|
421
|
-
end
|
422
|
-
|
423
|
-
def self.run_hooks(hooks, app, action)
|
424
|
-
hooks.select { |p, _| !p || p =~ action.env[WebRouter::PATH_INFO] }
|
425
|
-
.each { |_, b| action.instance_exec(action.env, app, &b) }
|
426
|
-
end
|
427
|
-
|
428
|
-
def self.befores
|
429
|
-
@befores ||= []
|
430
|
-
end
|
436
|
+
def process_csp(env, input)
|
437
|
+
input.gsub("!placeholder!", env[:csp_nonce])
|
438
|
+
end
|
431
439
|
|
432
|
-
|
433
|
-
|
440
|
+
# Used by extensions to add helper methods accessible to
|
441
|
+
# any defined endpoints in Application. Careful with generic
|
442
|
+
# method naming as there's no namespacing so collisions are
|
443
|
+
# possible.
|
444
|
+
def self.helpers(mod)
|
445
|
+
Sidekiq::Web::Action.send(:include, mod)
|
446
|
+
end
|
447
|
+
helpers WebHelpers
|
448
|
+
helpers Paginator
|
434
449
|
end
|
435
450
|
end
|
436
451
|
end
|