sidekiq 7.3.9 → 8.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Changes.md +57 -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 +93 -57
- data/lib/sidekiq/api.rb +122 -38
- 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 +20 -16
- data/lib/sidekiq/embedded.rb +2 -1
- 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 +2 -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 +72 -0
- data/lib/sidekiq/rails.rb +43 -65
- 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 +122 -83
- data/lib/sidekiq/web/application.rb +345 -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 +2 -2
- 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 +757 -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 +8 -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 +13 -29
- 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,449 @@
|
|
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
|
47
|
+
_ = Sidekiq.redis { |c| c.llen("queue:default") }
|
48
|
+
""
|
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
|
+
redirect(request.path) unless url_params("key")
|
232
244
|
|
233
|
-
|
234
|
-
|
245
|
+
url_params("key").each do |key|
|
246
|
+
job = Sidekiq::RetrySet.new.fetch(*parse_key(key)).first
|
247
|
+
retry_or_delete_or_kill job, request.params if job
|
248
|
+
end
|
235
249
|
|
236
|
-
|
237
|
-
redirect "#{root_path}retries"
|
238
|
-
else
|
239
|
-
erb(:retry)
|
250
|
+
redirect_with_query("#{root_path}retries")
|
240
251
|
end
|
241
|
-
end
|
242
252
|
|
243
|
-
|
244
|
-
|
253
|
+
post "/retries/all/delete" do
|
254
|
+
Sidekiq::RetrySet.new.clear
|
255
|
+
redirect "#{root_path}retries"
|
256
|
+
end
|
245
257
|
|
246
|
-
|
247
|
-
|
248
|
-
|
258
|
+
post "/retries/all/retry" do
|
259
|
+
Sidekiq::RetrySet.new.retry_all
|
260
|
+
redirect "#{root_path}retries"
|
249
261
|
end
|
250
262
|
|
251
|
-
|
252
|
-
|
263
|
+
post "/retries/all/kill" do
|
264
|
+
Sidekiq::RetrySet.new.kill_all
|
265
|
+
redirect "#{root_path}retries"
|
266
|
+
end
|
253
267
|
|
254
|
-
|
255
|
-
|
268
|
+
post "/retries/:key" do
|
269
|
+
job = Sidekiq::RetrySet.new.fetch(*parse_key(route_params(:key))).first
|
256
270
|
|
257
|
-
|
258
|
-
end
|
271
|
+
retry_or_delete_or_kill job, request.params if job
|
259
272
|
|
260
|
-
|
261
|
-
|
273
|
+
redirect_with_query("#{root_path}retries")
|
274
|
+
end
|
262
275
|
|
263
|
-
|
264
|
-
|
276
|
+
get "/scheduled" do
|
277
|
+
x = url_params("substr")
|
265
278
|
|
266
|
-
|
267
|
-
|
279
|
+
if x && x != ""
|
280
|
+
@scheduled = search(Sidekiq::ScheduledSet.new, x)
|
281
|
+
else
|
282
|
+
@count = (url_params("count") || 25).to_i
|
283
|
+
(@current_page, @total_size, @scheduled) = page("schedule", url_params("page"), @count)
|
284
|
+
@scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
285
|
+
end
|
268
286
|
|
269
|
-
|
270
|
-
|
287
|
+
erb(:scheduled)
|
288
|
+
end
|
271
289
|
|
272
|
-
|
273
|
-
|
290
|
+
get "/scheduled/:key" do
|
291
|
+
@job = Sidekiq::ScheduledSet.new.fetch(*parse_key(route_params(:key))).first
|
274
292
|
|
275
|
-
|
293
|
+
if @job.nil?
|
294
|
+
redirect "#{root_path}scheduled"
|
295
|
+
else
|
296
|
+
erb(:scheduled_job_info)
|
297
|
+
end
|
298
|
+
end
|
276
299
|
|
277
|
-
|
278
|
-
|
300
|
+
post "/scheduled" do
|
301
|
+
redirect(request.path) unless url_params("key")
|
279
302
|
|
280
|
-
|
281
|
-
|
303
|
+
url_params("key").each do |key|
|
304
|
+
job = Sidekiq::ScheduledSet.new.fetch(*parse_key(key)).first
|
305
|
+
delete_or_add_queue job, request.params if job
|
306
|
+
end
|
282
307
|
|
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) }
|
308
|
+
redirect_with_query("#{root_path}scheduled")
|
289
309
|
end
|
290
310
|
|
291
|
-
|
292
|
-
|
311
|
+
post "/scheduled/:key" do
|
312
|
+
key = route_params(:key)
|
313
|
+
halt(404) unless key
|
293
314
|
|
294
|
-
|
295
|
-
|
315
|
+
job = Sidekiq::ScheduledSet.new.fetch(*parse_key(key)).first
|
316
|
+
delete_or_add_queue job, request.params if job
|
296
317
|
|
297
|
-
|
298
|
-
redirect "#{root_path}scheduled"
|
299
|
-
else
|
300
|
-
erb(:scheduled_job_info)
|
318
|
+
redirect_with_query("#{root_path}scheduled")
|
301
319
|
end
|
302
|
-
end
|
303
320
|
|
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
|
321
|
+
get "/dashboard/stats" do
|
322
|
+
redirect "#{root_path}stats"
|
310
323
|
end
|
311
324
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
325
|
+
get "/stats" do
|
326
|
+
sidekiq_stats = Sidekiq::Stats.new
|
327
|
+
redis_stats = redis_info.slice(*REDIS_KEYS)
|
328
|
+
json(
|
329
|
+
sidekiq: {
|
330
|
+
processed: sidekiq_stats.processed,
|
331
|
+
failed: sidekiq_stats.failed,
|
332
|
+
busy: sidekiq_stats.workers_size,
|
333
|
+
processes: sidekiq_stats.processes_size,
|
334
|
+
enqueued: sidekiq_stats.enqueued,
|
335
|
+
scheduled: sidekiq_stats.scheduled_size,
|
336
|
+
retries: sidekiq_stats.retry_size,
|
337
|
+
dead: sidekiq_stats.dead_size,
|
338
|
+
default_latency: sidekiq_stats.default_queue_latency
|
339
|
+
},
|
340
|
+
redis: redis_stats,
|
341
|
+
server_utc_time: server_utc_time
|
342
|
+
)
|
343
|
+
end
|
318
344
|
|
319
|
-
|
320
|
-
|
345
|
+
get "/stats/queues" do
|
346
|
+
json Sidekiq::Stats.new.queues
|
347
|
+
end
|
321
348
|
|
322
|
-
|
323
|
-
|
349
|
+
get "/profiles" do
|
350
|
+
erb(:profiles)
|
351
|
+
end
|
324
352
|
|
325
|
-
|
326
|
-
|
327
|
-
|
353
|
+
get "/profiles/:key" do
|
354
|
+
store = config[:profile_store_url]
|
355
|
+
return redirect_to "#{root_path}profiles" unless store
|
356
|
+
|
357
|
+
key = route_params(:key)
|
358
|
+
sid = Sidekiq.redis { |c| c.hget(key, "sid") }
|
359
|
+
|
360
|
+
unless sid
|
361
|
+
require "net/http"
|
362
|
+
data = Sidekiq.redis { |c| c.hget(key, "data") }
|
363
|
+
resp = Net::HTTP.post(URI(store),
|
364
|
+
data,
|
365
|
+
{"Accept" => "application/vnd.firefox-profiler+json;version=1.0",
|
366
|
+
"User-Agent" => "Sidekiq #{Sidekiq::VERSION} job profiler"})
|
367
|
+
# https://raw.githubusercontent.com/firefox-devtools/profiler-server/master/tools/decode_jwt_payload.py
|
368
|
+
rawjson = resp.body.split(".")[1].unpack1("m")
|
369
|
+
sid = Sidekiq.load_json(rawjson)["profileToken"]
|
370
|
+
Sidekiq.redis { |c| c.hset(key, "sid", sid) }
|
371
|
+
end
|
372
|
+
url = config[:profile_view_url] % sid
|
373
|
+
redirect_to url
|
374
|
+
end
|
328
375
|
|
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
|
376
|
+
get "/profiles/:key/data" do
|
377
|
+
key = route_params(:key)
|
378
|
+
data = Sidekiq.redis { |c| c.hget(key, "data") }
|
348
379
|
|
349
|
-
|
350
|
-
|
351
|
-
|
380
|
+
[200, {
|
381
|
+
"content-type" => "application/json",
|
382
|
+
"content-encoding" => "gzip",
|
383
|
+
# allow Firefox Profiler's XHR to fetch this profile data
|
384
|
+
"access-control-allow-origin" => "*"
|
385
|
+
}, [data]]
|
386
|
+
end
|
352
387
|
|
353
|
-
|
354
|
-
|
388
|
+
post "/change_locale" do
|
389
|
+
locale = url_params("locale")
|
355
390
|
|
356
|
-
|
357
|
-
|
358
|
-
|
391
|
+
match = available_locales.find { |available|
|
392
|
+
locale == available
|
393
|
+
}
|
359
394
|
|
360
|
-
|
395
|
+
session[:locale] = match if match
|
361
396
|
|
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
|
397
|
+
reload_page
|
398
|
+
end
|
368
399
|
|
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)
|
400
|
+
def redis(&)
|
401
|
+
Thread.current[:sidekiq_redis_pool].with(&)
|
375
402
|
end
|
376
403
|
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
else
|
382
|
-
# rendered content goes here
|
404
|
+
def call(env)
|
405
|
+
action = match(env)
|
406
|
+
return [404, {"content-type" => "text/plain", "x-cascade" => "pass"}, ["Not Found"]] unless action
|
407
|
+
|
383
408
|
headers = {
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
409
|
+
"content-type" => "text/html",
|
410
|
+
"cache-control" => "private, no-store",
|
411
|
+
"content-language" => action.locale,
|
412
|
+
"content-security-policy" => process_csp(env, CSP_HEADER_TEMPLATE),
|
413
|
+
"x-content-type-options" => "nosniff"
|
389
414
|
}
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
end
|
415
|
+
env["response_headers"] = headers
|
416
|
+
resp = catch(:halt) do
|
417
|
+
Thread.current[:sidekiq_redis_pool] = env[:redis_pool]
|
418
|
+
action.instance_exec env, &action.block
|
419
|
+
ensure
|
420
|
+
Thread.current[:sidekiq_redis_pool] = nil
|
421
|
+
end
|
398
422
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
423
|
+
case resp
|
424
|
+
when Array
|
425
|
+
# redirects go here
|
426
|
+
resp
|
427
|
+
else
|
428
|
+
# rendered content goes here
|
429
|
+
# we'll let Rack calculate Content-Length for us.
|
430
|
+
[200, env["response_headers"], [resp]]
|
431
|
+
end
|
404
432
|
end
|
405
|
-
end
|
406
433
|
|
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
|
434
|
+
def process_csp(env, input)
|
435
|
+
input.gsub("!placeholder!", env[:csp_nonce])
|
436
|
+
end
|
431
437
|
|
432
|
-
|
433
|
-
|
438
|
+
# Used by extensions to add helper methods accessible to
|
439
|
+
# any defined endpoints in Application. Careful with generic
|
440
|
+
# method naming as there's no namespacing so collisions are
|
441
|
+
# possible.
|
442
|
+
def self.helpers(mod)
|
443
|
+
Sidekiq::Web::Action.send(:include, mod)
|
444
|
+
end
|
445
|
+
helpers WebHelpers
|
446
|
+
helpers Paginator
|
434
447
|
end
|
435
448
|
end
|
436
449
|
end
|