sidekiq 7.3.9 → 8.0.0.beta2
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 +28 -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_retry.rb +17 -5
- data/lib/sidekiq/job_util.rb +5 -1
- data/lib/sidekiq/launcher.rb +1 -1
- data/lib/sidekiq/logger.rb +6 -10
- data/lib/sidekiq/manager.rb +0 -1
- data/lib/sidekiq/metrics/query.rb +71 -45
- data/lib/sidekiq/metrics/shared.rb +4 -1
- 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/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 +116 -0
- data/lib/sidekiq/web/helpers.rb +41 -16
- data/lib/sidekiq/web/router.rb +60 -76
- data/lib/sidekiq/web.rb +51 -156
- data/lib/sidekiq.rb +1 -1
- data/sidekiq.gemspec +5 -4
- 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 +32 -18
- 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 "base64"
|
4
|
+
require "sidekiq/paginator"
|
5
|
+
require "sidekiq/web/helpers"
|
6
|
+
|
3
7
|
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
|
-
|
8
|
+
class Web
|
9
|
+
class Application
|
10
|
+
extend Router
|
11
|
+
include Router
|
12
|
+
|
13
|
+
REDIS_KEYS = %w[redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human]
|
14
|
+
|
15
|
+
CSP_HEADER_TEMPLATE = [
|
16
|
+
"default-src 'self' https: http:",
|
17
|
+
"child-src 'self'",
|
18
|
+
"connect-src 'self' https: http: wss: ws:",
|
19
|
+
"font-src 'none'",
|
20
|
+
"frame-src 'self'",
|
21
|
+
"img-src 'self' https: http: data:",
|
22
|
+
"manifest-src 'self'",
|
23
|
+
"media-src 'self'",
|
24
|
+
"object-src 'none'",
|
25
|
+
"script-src 'self' 'nonce-!placeholder!'",
|
26
|
+
"style-src 'self' 'nonce-!placeholder!'",
|
27
|
+
"worker-src 'self'",
|
28
|
+
"base-uri 'self'"
|
29
|
+
].join("; ").freeze
|
30
|
+
|
31
|
+
METRICS_PERIODS = {
|
32
|
+
"1h" => {minutes: 60},
|
33
|
+
"2h" => {minutes: 120},
|
34
|
+
"4h" => {minutes: 240},
|
35
|
+
"8h" => {minutes: 480},
|
36
|
+
"24h" => {hours: 24},
|
37
|
+
"48h" => {hours: 48},
|
38
|
+
"72h" => {hours: 72}
|
39
|
+
}
|
33
40
|
|
34
|
-
|
35
|
-
|
36
|
-
|
41
|
+
def initialize(inst)
|
42
|
+
@app = inst
|
43
|
+
end
|
37
44
|
|
38
|
-
|
39
|
-
|
40
|
-
|
45
|
+
head "/" do
|
46
|
+
# HEAD / is the cheapest heartbeat possible,
|
47
|
+
# it hits Redis to ensure connectivity and returns
|
48
|
+
# the size of the default queue
|
49
|
+
Sidekiq.redis { |c| c.llen("queue:default") }.to_s
|
50
|
+
end
|
41
51
|
|
42
|
-
|
43
|
-
|
44
|
-
|
52
|
+
get "/" do
|
53
|
+
@redis_info = redis_info.select { |k, v| REDIS_KEYS.include? k }
|
54
|
+
days = (url_params("days") || 30).to_i
|
55
|
+
return halt(401) if days < 1 || days > 180
|
45
56
|
|
46
|
-
|
47
|
-
|
48
|
-
|
57
|
+
stats_history = Sidekiq::Stats::History.new(days)
|
58
|
+
@processed_history = stats_history.processed
|
59
|
+
@failed_history = stats_history.failed
|
49
60
|
|
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
|
61
|
+
erb(:dashboard)
|
62
|
+
end
|
56
63
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
return halt(401) if days < 1 || days > 180
|
64
|
+
get "/metrics" do
|
65
|
+
x = url_params("substr")
|
66
|
+
class_filter = (x.nil? || x == "") ? nil : Regexp.new(Regexp.escape(x), Regexp::IGNORECASE)
|
61
67
|
|
62
|
-
|
63
|
-
|
64
|
-
|
68
|
+
q = Sidekiq::Metrics::Query.new
|
69
|
+
@period = h(url_params("period") || "1h")
|
70
|
+
@periods = METRICS_PERIODS
|
71
|
+
args = @periods.fetch(@period, @periods.values.first)
|
72
|
+
@query_result = q.top_jobs(**args.merge(class_filter: class_filter))
|
65
73
|
|
66
|
-
|
67
|
-
|
74
|
+
header "refresh", 60 if @period == "1h"
|
75
|
+
erb(:metrics)
|
76
|
+
end
|
68
77
|
|
69
|
-
|
70
|
-
|
71
|
-
|
78
|
+
get "/metrics/:name" do
|
79
|
+
@name = route_params(:name)
|
80
|
+
@period = h(url_params("period") || "1h")
|
81
|
+
# Periods larger than 8 hours are not supported for histogram chart
|
82
|
+
@period = "8h" if @period.to_i > 8
|
83
|
+
@periods = METRICS_PERIODS.reject { |k, v| k.to_i > 8 }
|
84
|
+
args = @periods.fetch(@period, @periods.values.first)
|
85
|
+
q = Sidekiq::Metrics::Query.new
|
86
|
+
@query_result = q.for_job(@name, **args)
|
87
|
+
|
88
|
+
header "refresh", 60 if @period == "1h"
|
89
|
+
erb(:metrics_for_job)
|
90
|
+
end
|
72
91
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
minutes = @periods.fetch(@period, @periods.values.first)
|
77
|
-
@query_result = q.top_jobs(minutes: minutes, class_filter: class_filter)
|
92
|
+
get "/busy" do
|
93
|
+
@count = (url_params("count") || 100).to_i
|
94
|
+
(@current_page, @total_size, @workset) = page_items(workset, url_params("page"), @count)
|
78
95
|
|
79
|
-
|
80
|
-
|
96
|
+
erb(:busy)
|
97
|
+
end
|
81
98
|
|
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
|
99
|
+
post "/busy" do
|
100
|
+
if url_params("identity")
|
101
|
+
pro = Sidekiq::ProcessSet[url_params("identity")]
|
91
102
|
|
92
|
-
|
93
|
-
|
94
|
-
|
103
|
+
pro.quiet! if url_params("quiet")
|
104
|
+
pro.stop! if url_params("stop")
|
105
|
+
else
|
106
|
+
processes.each do |pro|
|
107
|
+
next if pro.embedded?
|
95
108
|
|
96
|
-
|
97
|
-
|
109
|
+
pro.quiet! if url_params("quiet")
|
110
|
+
pro.stop! if url_params("stop")
|
111
|
+
end
|
112
|
+
end
|
98
113
|
|
99
|
-
|
100
|
-
|
101
|
-
pro = Sidekiq::ProcessSet[params["identity"]]
|
114
|
+
redirect "#{root_path}busy"
|
115
|
+
end
|
102
116
|
|
103
|
-
|
104
|
-
|
105
|
-
else
|
106
|
-
processes.each do |pro|
|
107
|
-
next if pro.embedded?
|
117
|
+
get "/queues" do
|
118
|
+
@queues = Sidekiq::Queue.all
|
108
119
|
|
109
|
-
|
110
|
-
pro.stop! if params["stop"]
|
111
|
-
end
|
120
|
+
erb(:queues)
|
112
121
|
end
|
113
122
|
|
114
|
-
|
115
|
-
end
|
123
|
+
QUEUE_NAME = /\A[a-z_:.\-0-9]+\z/i
|
116
124
|
|
117
|
-
|
118
|
-
|
125
|
+
get "/queues/:name" do
|
126
|
+
@name = route_params(:name)
|
119
127
|
|
120
|
-
|
121
|
-
end
|
128
|
+
halt(404) if !@name || @name !~ QUEUE_NAME
|
122
129
|
|
123
|
-
|
130
|
+
@count = (url_params("count") || 25).to_i
|
131
|
+
@queue = Sidekiq::Queue.new(@name)
|
132
|
+
(@current_page, @total_size, @jobs) = page("queue:#{@name}", url_params("page"), @count, reverse: url_params("direction") == "asc")
|
133
|
+
@jobs = @jobs.map { |msg| Sidekiq::JobRecord.new(msg, @name) }
|
124
134
|
|
125
|
-
|
126
|
-
|
135
|
+
erb(:queue)
|
136
|
+
end
|
127
137
|
|
128
|
-
|
138
|
+
post "/queues/:name" do
|
139
|
+
queue = Sidekiq::Queue.new(route_params(:name))
|
129
140
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
141
|
+
if Sidekiq.pro? && url_params("pause")
|
142
|
+
queue.pause!
|
143
|
+
elsif Sidekiq.pro? && url_params("unpause")
|
144
|
+
queue.unpause!
|
145
|
+
else
|
146
|
+
queue.clear
|
147
|
+
end
|
134
148
|
|
135
|
-
|
136
|
-
|
149
|
+
redirect "#{root_path}queues"
|
150
|
+
end
|
137
151
|
|
138
|
-
|
139
|
-
|
152
|
+
post "/queues/:name/delete" do
|
153
|
+
name = route_params(:name)
|
154
|
+
Sidekiq::JobRecord.new(url_params("key_val"), name).delete
|
140
155
|
|
141
|
-
|
142
|
-
queue.pause!
|
143
|
-
elsif Sidekiq.pro? && params["unpause"]
|
144
|
-
queue.unpause!
|
145
|
-
else
|
146
|
-
queue.clear
|
156
|
+
redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
|
147
157
|
end
|
148
158
|
|
149
|
-
|
150
|
-
|
159
|
+
get "/morgue" do
|
160
|
+
x = url_params("substr")
|
151
161
|
|
152
|
-
|
153
|
-
|
154
|
-
|
162
|
+
if x && x != ""
|
163
|
+
@dead = search(Sidekiq::DeadSet.new, x)
|
164
|
+
else
|
165
|
+
@count = (url_params("count") || 25).to_i
|
166
|
+
(@current_page, @total_size, @dead) = page("dead", url_params("page"), @count, reverse: true)
|
167
|
+
@dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
168
|
+
end
|
155
169
|
|
156
|
-
|
157
|
-
|
170
|
+
erb(:morgue)
|
171
|
+
end
|
172
|
+
|
173
|
+
get "/morgue/:key" do
|
174
|
+
key = route_params(:key)
|
175
|
+
halt(404) unless key
|
158
176
|
|
159
|
-
|
160
|
-
x = params[:substr]
|
177
|
+
@dead = Sidekiq::DeadSet.new.fetch(*parse_key(key)).first
|
161
178
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
@dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
179
|
+
if @dead.nil?
|
180
|
+
redirect "#{root_path}morgue"
|
181
|
+
else
|
182
|
+
erb(:dead)
|
183
|
+
end
|
168
184
|
end
|
169
185
|
|
170
|
-
|
171
|
-
|
186
|
+
post "/morgue" do
|
187
|
+
redirect(request.path) unless url_params("key")
|
188
|
+
|
189
|
+
url_params("key").each do |key|
|
190
|
+
job = Sidekiq::DeadSet.new.fetch(*parse_key(key)).first
|
191
|
+
retry_or_delete_or_kill job, request.params if job
|
192
|
+
end
|
172
193
|
|
173
|
-
|
174
|
-
|
175
|
-
halt(404) unless key
|
194
|
+
redirect_with_query("#{root_path}morgue")
|
195
|
+
end
|
176
196
|
|
177
|
-
|
197
|
+
post "/morgue/all/delete" do
|
198
|
+
Sidekiq::DeadSet.new.clear
|
178
199
|
|
179
|
-
if @dead.nil?
|
180
200
|
redirect "#{root_path}morgue"
|
181
|
-
else
|
182
|
-
erb(:dead)
|
183
201
|
end
|
184
|
-
end
|
185
202
|
|
186
|
-
|
187
|
-
|
203
|
+
post "/morgue/all/retry" do
|
204
|
+
Sidekiq::DeadSet.new.retry_all
|
188
205
|
|
189
|
-
|
190
|
-
job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
|
191
|
-
retry_or_delete_or_kill job, params if job
|
206
|
+
redirect "#{root_path}morgue"
|
192
207
|
end
|
193
208
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
post "/morgue/all/delete" do
|
198
|
-
Sidekiq::DeadSet.new.clear
|
209
|
+
post "/morgue/:key" do
|
210
|
+
key = route_params(:key)
|
211
|
+
halt(404) unless key
|
199
212
|
|
200
|
-
|
201
|
-
|
213
|
+
job = Sidekiq::DeadSet.new.fetch(*parse_key(key)).first
|
214
|
+
retry_or_delete_or_kill job, request.params if job
|
202
215
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
redirect "#{root_path}morgue"
|
207
|
-
end
|
216
|
+
redirect_with_query("#{root_path}morgue")
|
217
|
+
end
|
208
218
|
|
209
|
-
|
210
|
-
|
211
|
-
halt(404) unless key
|
219
|
+
get "/retries" do
|
220
|
+
x = url_params("substr")
|
212
221
|
|
213
|
-
|
214
|
-
|
222
|
+
if x && x != ""
|
223
|
+
@retries = search(Sidekiq::RetrySet.new, x)
|
224
|
+
else
|
225
|
+
@count = (url_params("count") || 25).to_i
|
226
|
+
(@current_page, @total_size, @retries) = page("retry", url_params("page"), @count)
|
227
|
+
@retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
228
|
+
end
|
215
229
|
|
216
|
-
|
217
|
-
|
230
|
+
erb(:retries)
|
231
|
+
end
|
218
232
|
|
219
|
-
|
220
|
-
|
233
|
+
get "/retries/:key" do
|
234
|
+
@retry = Sidekiq::RetrySet.new.fetch(*parse_key(route_params(:key))).first
|
221
235
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
@retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
236
|
+
if @retry.nil?
|
237
|
+
redirect "#{root_path}retries"
|
238
|
+
else
|
239
|
+
erb(:retry)
|
240
|
+
end
|
228
241
|
end
|
229
242
|
|
230
|
-
|
231
|
-
|
243
|
+
post "/retries" do
|
244
|
+
route_params("mike")
|
245
|
+
url_params(:mike)
|
246
|
+
redirect(request.path) unless url_params("key")
|
232
247
|
|
233
|
-
|
234
|
-
|
248
|
+
url_params("key").each do |key|
|
249
|
+
job = Sidekiq::RetrySet.new.fetch(*parse_key(key)).first
|
250
|
+
retry_or_delete_or_kill job, request.params if job
|
251
|
+
end
|
235
252
|
|
236
|
-
|
237
|
-
redirect "#{root_path}retries"
|
238
|
-
else
|
239
|
-
erb(:retry)
|
253
|
+
redirect_with_query("#{root_path}retries")
|
240
254
|
end
|
241
|
-
end
|
242
255
|
|
243
|
-
|
244
|
-
|
256
|
+
post "/retries/all/delete" do
|
257
|
+
Sidekiq::RetrySet.new.clear
|
258
|
+
redirect "#{root_path}retries"
|
259
|
+
end
|
245
260
|
|
246
|
-
|
247
|
-
|
248
|
-
|
261
|
+
post "/retries/all/retry" do
|
262
|
+
Sidekiq::RetrySet.new.retry_all
|
263
|
+
redirect "#{root_path}retries"
|
249
264
|
end
|
250
265
|
|
251
|
-
|
252
|
-
|
266
|
+
post "/retries/all/kill" do
|
267
|
+
Sidekiq::RetrySet.new.kill_all
|
268
|
+
redirect "#{root_path}retries"
|
269
|
+
end
|
253
270
|
|
254
|
-
|
255
|
-
|
271
|
+
post "/retries/:key" do
|
272
|
+
job = Sidekiq::RetrySet.new.fetch(*parse_key(route_params(:key))).first
|
256
273
|
|
257
|
-
|
258
|
-
end
|
274
|
+
retry_or_delete_or_kill job, request.params if job
|
259
275
|
|
260
|
-
|
261
|
-
|
276
|
+
redirect_with_query("#{root_path}retries")
|
277
|
+
end
|
262
278
|
|
263
|
-
|
264
|
-
|
279
|
+
get "/scheduled" do
|
280
|
+
x = url_params("substr")
|
265
281
|
|
266
|
-
|
267
|
-
|
282
|
+
if x && x != ""
|
283
|
+
@scheduled = search(Sidekiq::ScheduledSet.new, x)
|
284
|
+
else
|
285
|
+
@count = (url_params("count") || 25).to_i
|
286
|
+
(@current_page, @total_size, @scheduled) = page("schedule", url_params("page"), @count)
|
287
|
+
@scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
288
|
+
end
|
268
289
|
|
269
|
-
|
270
|
-
|
290
|
+
erb(:scheduled)
|
291
|
+
end
|
271
292
|
|
272
|
-
|
273
|
-
|
293
|
+
get "/scheduled/:key" do
|
294
|
+
@job = Sidekiq::ScheduledSet.new.fetch(*parse_key(route_params(:key))).first
|
274
295
|
|
275
|
-
|
296
|
+
if @job.nil?
|
297
|
+
redirect "#{root_path}scheduled"
|
298
|
+
else
|
299
|
+
erb(:scheduled_job_info)
|
300
|
+
end
|
301
|
+
end
|
276
302
|
|
277
|
-
|
278
|
-
|
303
|
+
post "/scheduled" do
|
304
|
+
redirect(request.path) unless url_params("key")
|
279
305
|
|
280
|
-
|
281
|
-
|
306
|
+
url_params("key").each do |key|
|
307
|
+
job = Sidekiq::ScheduledSet.new.fetch(*parse_key(key)).first
|
308
|
+
delete_or_add_queue job, request.params if job
|
309
|
+
end
|
282
310
|
|
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) }
|
311
|
+
redirect_with_query("#{root_path}scheduled")
|
289
312
|
end
|
290
313
|
|
291
|
-
|
292
|
-
|
314
|
+
post "/scheduled/:key" do
|
315
|
+
key = route_params(:key)
|
316
|
+
halt(404) unless key
|
293
317
|
|
294
|
-
|
295
|
-
|
318
|
+
job = Sidekiq::ScheduledSet.new.fetch(*parse_key(key)).first
|
319
|
+
delete_or_add_queue job, request.params if job
|
296
320
|
|
297
|
-
|
298
|
-
redirect "#{root_path}scheduled"
|
299
|
-
else
|
300
|
-
erb(:scheduled_job_info)
|
321
|
+
redirect_with_query("#{root_path}scheduled")
|
301
322
|
end
|
302
|
-
end
|
303
323
|
|
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
|
324
|
+
get "/dashboard/stats" do
|
325
|
+
redirect "#{root_path}stats"
|
310
326
|
end
|
311
327
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
328
|
+
get "/stats" do
|
329
|
+
sidekiq_stats = Sidekiq::Stats.new
|
330
|
+
redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
|
331
|
+
json(
|
332
|
+
sidekiq: {
|
333
|
+
processed: sidekiq_stats.processed,
|
334
|
+
failed: sidekiq_stats.failed,
|
335
|
+
busy: sidekiq_stats.workers_size,
|
336
|
+
processes: sidekiq_stats.processes_size,
|
337
|
+
enqueued: sidekiq_stats.enqueued,
|
338
|
+
scheduled: sidekiq_stats.scheduled_size,
|
339
|
+
retries: sidekiq_stats.retry_size,
|
340
|
+
dead: sidekiq_stats.dead_size,
|
341
|
+
default_latency: sidekiq_stats.default_queue_latency
|
342
|
+
},
|
343
|
+
redis: redis_stats,
|
344
|
+
server_utc_time: server_utc_time
|
345
|
+
)
|
346
|
+
end
|
318
347
|
|
319
|
-
|
320
|
-
|
348
|
+
get "/stats/queues" do
|
349
|
+
json Sidekiq::Stats.new.queues
|
350
|
+
end
|
321
351
|
|
322
|
-
|
323
|
-
|
352
|
+
get "/profiles" do
|
353
|
+
erb(:profiles)
|
354
|
+
end
|
324
355
|
|
325
|
-
|
326
|
-
|
327
|
-
|
356
|
+
get "/profiles/:key" do
|
357
|
+
store = config[:profile_store_url]
|
358
|
+
return redirect_to "#{root_path}profiles" unless store
|
359
|
+
|
360
|
+
key = route_params(:key)
|
361
|
+
sid = Sidekiq.redis { |c| c.hget(key, "sid") }
|
362
|
+
|
363
|
+
unless sid
|
364
|
+
require "net/http"
|
365
|
+
data = Sidekiq.redis { |c| c.hget(key, "data") }
|
366
|
+
resp = Net::HTTP.post(URI(store),
|
367
|
+
data,
|
368
|
+
{"Accept" => "application/vnd.firefox-profiler+json;version=1.0",
|
369
|
+
"User-Agent" => "Sidekiq #{Sidekiq::VERSION} job profiler"})
|
370
|
+
# https://raw.githubusercontent.com/firefox-devtools/profiler-server/master/tools/decode_jwt_payload.py
|
371
|
+
sid = Sidekiq.load_json(Base64.decode64(resp.body.split(".")[1]))["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
|