sidekiq 7.3.7 → 8.0.0.beta1

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.
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