sidekiq 7.3.7 → 8.0.0.beta1

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