sidekiq 7.2.4 → 7.3.9
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 +116 -0
- data/README.md +1 -1
- data/bin/sidekiqload +21 -12
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +75 -0
- data/lib/generators/sidekiq/job_generator.rb +2 -0
- data/lib/sidekiq/api.rb +63 -34
- data/lib/sidekiq/capsule.rb +8 -3
- data/lib/sidekiq/cli.rb +2 -1
- data/lib/sidekiq/client.rb +21 -1
- data/lib/sidekiq/component.rb +22 -0
- data/lib/sidekiq/config.rb +27 -3
- data/lib/sidekiq/deploy.rb +2 -0
- data/lib/sidekiq/embedded.rb +2 -0
- data/lib/sidekiq/fetch.rb +1 -1
- data/lib/sidekiq/iterable_job.rb +55 -0
- data/lib/sidekiq/job/interrupt_handler.rb +24 -0
- data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
- data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
- data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
- data/lib/sidekiq/job/iterable.rb +294 -0
- data/lib/sidekiq/job.rb +13 -2
- data/lib/sidekiq/job_logger.rb +7 -6
- data/lib/sidekiq/job_retry.rb +6 -1
- data/lib/sidekiq/job_util.rb +2 -0
- data/lib/sidekiq/launcher.rb +1 -1
- data/lib/sidekiq/metrics/query.rb +2 -0
- data/lib/sidekiq/metrics/shared.rb +15 -4
- data/lib/sidekiq/metrics/tracking.rb +13 -5
- data/lib/sidekiq/middleware/current_attributes.rb +46 -13
- data/lib/sidekiq/middleware/modules.rb +2 -0
- data/lib/sidekiq/monitor.rb +2 -1
- data/lib/sidekiq/paginator.rb +6 -0
- data/lib/sidekiq/processor.rb +20 -10
- data/lib/sidekiq/rails.rb +12 -0
- data/lib/sidekiq/redis_client_adapter.rb +8 -5
- data/lib/sidekiq/redis_connection.rb +33 -2
- data/lib/sidekiq/ring_buffer.rb +2 -0
- data/lib/sidekiq/systemd.rb +2 -0
- data/lib/sidekiq/testing.rb +5 -5
- data/lib/sidekiq/version.rb +5 -1
- data/lib/sidekiq/web/action.rb +21 -4
- data/lib/sidekiq/web/application.rb +43 -82
- data/lib/sidekiq/web/helpers.rb +62 -15
- data/lib/sidekiq/web/router.rb +5 -2
- data/lib/sidekiq/web.rb +54 -2
- data/lib/sidekiq.rb +5 -3
- data/sidekiq.gemspec +3 -2
- data/web/assets/javascripts/application.js +6 -1
- data/web/assets/javascripts/dashboard-charts.js +24 -12
- data/web/assets/javascripts/dashboard.js +7 -1
- data/web/assets/stylesheets/application.css +16 -3
- data/web/locales/en.yml +3 -1
- data/web/locales/fr.yml +0 -1
- data/web/locales/gd.yml +0 -1
- data/web/locales/it.yml +32 -1
- data/web/locales/ja.yml +0 -1
- data/web/locales/pt-br.yml +1 -2
- data/web/locales/tr.yml +100 -0
- data/web/locales/uk.yml +24 -1
- data/web/locales/zh-cn.yml +0 -1
- data/web/locales/zh-tw.yml +0 -1
- data/web/views/_footer.erb +1 -2
- data/web/views/dashboard.erb +10 -7
- data/web/views/filtering.erb +1 -2
- data/web/views/layout.erb +6 -6
- data/web/views/metrics.erb +7 -8
- data/web/views/metrics_for_job.erb +4 -4
- data/web/views/morgue.erb +2 -2
- data/web/views/queue.erb +1 -1
- metadata +32 -13
@@ -5,7 +5,7 @@ module Sidekiq
|
|
5
5
|
extend WebRouter
|
6
6
|
|
7
7
|
REDIS_KEYS = %w[redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human]
|
8
|
-
|
8
|
+
CSP_HEADER_TEMPLATE = [
|
9
9
|
"default-src 'self' https: http:",
|
10
10
|
"child-src 'self'",
|
11
11
|
"connect-src 'self' https: http: wss: ws:",
|
@@ -15,8 +15,8 @@ module Sidekiq
|
|
15
15
|
"manifest-src 'self'",
|
16
16
|
"media-src 'self'",
|
17
17
|
"object-src 'none'",
|
18
|
-
"script-src 'self'
|
19
|
-
"style-src 'self' https: http: 'unsafe-inline'",
|
18
|
+
"script-src 'self' 'nonce-!placeholder!'",
|
19
|
+
"style-src 'self' https: http: 'unsafe-inline'", # TODO Nonce in 8.0
|
20
20
|
"worker-src 'self'",
|
21
21
|
"base-uri 'self'"
|
22
22
|
].join("; ").freeze
|
@@ -67,11 +67,15 @@ module Sidekiq
|
|
67
67
|
end
|
68
68
|
|
69
69
|
get "/metrics" do
|
70
|
+
x = params[:substr]
|
71
|
+
class_filter = (x.nil? || x == "") ? nil : Regexp.new(Regexp.escape(x), Regexp::IGNORECASE)
|
72
|
+
|
70
73
|
q = Sidekiq::Metrics::Query.new
|
71
74
|
@period = h((params[:period] || "")[0..1])
|
72
75
|
@periods = METRICS_PERIODS
|
73
76
|
minutes = @periods.fetch(@period, @periods.values.first)
|
74
|
-
@query_result = q.top_jobs(minutes: minutes)
|
77
|
+
@query_result = q.top_jobs(minutes: minutes, class_filter: class_filter)
|
78
|
+
|
75
79
|
erb(:metrics)
|
76
80
|
end
|
77
81
|
|
@@ -153,9 +157,15 @@ module Sidekiq
|
|
153
157
|
end
|
154
158
|
|
155
159
|
get "/morgue" do
|
156
|
-
|
157
|
-
|
158
|
-
|
160
|
+
x = params[:substr]
|
161
|
+
|
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
|
159
169
|
|
160
170
|
erb(:morgue)
|
161
171
|
end
|
@@ -174,7 +184,7 @@ module Sidekiq
|
|
174
184
|
end
|
175
185
|
|
176
186
|
post "/morgue" do
|
177
|
-
redirect(request.path) unless
|
187
|
+
redirect(request.path) unless url_params("key")
|
178
188
|
|
179
189
|
params["key"].each do |key|
|
180
190
|
job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
|
@@ -197,7 +207,7 @@ module Sidekiq
|
|
197
207
|
end
|
198
208
|
|
199
209
|
post "/morgue/:key" do
|
200
|
-
key = route_params
|
210
|
+
key = route_params(:key)
|
201
211
|
halt(404) unless key
|
202
212
|
|
203
213
|
job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
|
@@ -207,9 +217,15 @@ module Sidekiq
|
|
207
217
|
end
|
208
218
|
|
209
219
|
get "/retries" do
|
210
|
-
|
211
|
-
|
212
|
-
|
220
|
+
x = url_params("substr")
|
221
|
+
|
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
|
+
end
|
213
229
|
|
214
230
|
erb(:retries)
|
215
231
|
end
|
@@ -262,9 +278,15 @@ module Sidekiq
|
|
262
278
|
end
|
263
279
|
|
264
280
|
get "/scheduled" do
|
265
|
-
|
266
|
-
|
267
|
-
|
281
|
+
x = params[:substr]
|
282
|
+
|
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) }
|
289
|
+
end
|
268
290
|
|
269
291
|
erb(:scheduled)
|
270
292
|
end
|
@@ -328,72 +350,6 @@ module Sidekiq
|
|
328
350
|
json Sidekiq::Stats.new.queues
|
329
351
|
end
|
330
352
|
|
331
|
-
########
|
332
|
-
# Filtering
|
333
|
-
|
334
|
-
get "/filter/metrics" do
|
335
|
-
redirect "#{root_path}metrics"
|
336
|
-
end
|
337
|
-
|
338
|
-
post "/filter/metrics" do
|
339
|
-
x = params[:substr]
|
340
|
-
q = Sidekiq::Metrics::Query.new
|
341
|
-
@period = h((params[:period] || "")[0..1])
|
342
|
-
@periods = METRICS_PERIODS
|
343
|
-
minutes = @periods.fetch(@period, @periods.values.first)
|
344
|
-
@query_result = q.top_jobs(minutes: minutes, class_filter: Regexp.new(Regexp.escape(x), Regexp::IGNORECASE))
|
345
|
-
|
346
|
-
erb :metrics
|
347
|
-
end
|
348
|
-
|
349
|
-
get "/filter/retries" do
|
350
|
-
x = params[:substr]
|
351
|
-
return redirect "#{root_path}retries" unless x && x != ""
|
352
|
-
|
353
|
-
@retries = search(Sidekiq::RetrySet.new, params[:substr])
|
354
|
-
erb :retries
|
355
|
-
end
|
356
|
-
|
357
|
-
post "/filter/retries" do
|
358
|
-
x = params[:substr]
|
359
|
-
return redirect "#{root_path}retries" unless x && x != ""
|
360
|
-
|
361
|
-
@retries = search(Sidekiq::RetrySet.new, params[:substr])
|
362
|
-
erb :retries
|
363
|
-
end
|
364
|
-
|
365
|
-
get "/filter/scheduled" do
|
366
|
-
x = params[:substr]
|
367
|
-
return redirect "#{root_path}scheduled" unless x && x != ""
|
368
|
-
|
369
|
-
@scheduled = search(Sidekiq::ScheduledSet.new, params[:substr])
|
370
|
-
erb :scheduled
|
371
|
-
end
|
372
|
-
|
373
|
-
post "/filter/scheduled" do
|
374
|
-
x = params[:substr]
|
375
|
-
return redirect "#{root_path}scheduled" unless x && x != ""
|
376
|
-
|
377
|
-
@scheduled = search(Sidekiq::ScheduledSet.new, params[:substr])
|
378
|
-
erb :scheduled
|
379
|
-
end
|
380
|
-
|
381
|
-
get "/filter/dead" do
|
382
|
-
x = params[:substr]
|
383
|
-
return redirect "#{root_path}morgue" unless x && x != ""
|
384
|
-
|
385
|
-
@dead = search(Sidekiq::DeadSet.new, params[:substr])
|
386
|
-
erb :morgue
|
387
|
-
end
|
388
|
-
|
389
|
-
post "/filter/dead" do
|
390
|
-
x = params[:substr]
|
391
|
-
return redirect "#{root_path}morgue" unless x && x != ""
|
392
|
-
|
393
|
-
@dead = search(Sidekiq::DeadSet.new, params[:substr])
|
394
|
-
erb :morgue
|
395
|
-
end
|
396
|
-
|
397
353
|
post "/change_locale" do
|
398
354
|
locale = params["locale"]
|
399
355
|
|
@@ -428,13 +384,18 @@ module Sidekiq
|
|
428
384
|
Rack::CONTENT_TYPE => "text/html",
|
429
385
|
Rack::CACHE_CONTROL => "private, no-store",
|
430
386
|
Web::CONTENT_LANGUAGE => action.locale,
|
431
|
-
Web::CONTENT_SECURITY_POLICY =>
|
387
|
+
Web::CONTENT_SECURITY_POLICY => process_csp(env, CSP_HEADER_TEMPLATE),
|
388
|
+
Web::X_CONTENT_TYPE_OPTIONS => "nosniff"
|
432
389
|
}
|
433
390
|
# we'll let Rack calculate Content-Length for us.
|
434
391
|
[200, headers, [resp]]
|
435
392
|
end
|
436
393
|
end
|
437
394
|
|
395
|
+
def process_csp(env, input)
|
396
|
+
input.gsub("!placeholder!", env[:csp_nonce])
|
397
|
+
end
|
398
|
+
|
438
399
|
def self.helpers(mod = nil, &block)
|
439
400
|
if block
|
440
401
|
WebAction.class_eval(&block)
|
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -6,14 +6,57 @@ require "yaml"
|
|
6
6
|
require "cgi"
|
7
7
|
|
8
8
|
module Sidekiq
|
9
|
-
#
|
9
|
+
# These methods are available to pages within the Web UI and UI extensions.
|
10
|
+
# They are not public APIs for applications to use.
|
10
11
|
module WebHelpers
|
12
|
+
def style_tag(location, **kwargs)
|
13
|
+
global = location.match?(/:\/\//)
|
14
|
+
location = root_path + location if !global && !location.start_with?(root_path)
|
15
|
+
attrs = {
|
16
|
+
type: "text/css",
|
17
|
+
media: "screen",
|
18
|
+
rel: "stylesheet",
|
19
|
+
nonce: csp_nonce,
|
20
|
+
href: location
|
21
|
+
}
|
22
|
+
html_tag(:link, attrs.merge(kwargs))
|
23
|
+
end
|
24
|
+
|
25
|
+
def script_tag(location, **kwargs)
|
26
|
+
global = location.match?(/:\/\//)
|
27
|
+
location = root_path + location if !global && !location.start_with?(root_path)
|
28
|
+
attrs = {
|
29
|
+
type: "text/javascript",
|
30
|
+
nonce: csp_nonce,
|
31
|
+
src: location
|
32
|
+
}
|
33
|
+
html_tag(:script, attrs.merge(kwargs)) {}
|
34
|
+
end
|
35
|
+
|
36
|
+
# NB: keys and values are not escaped; do not allow user input
|
37
|
+
# in the attributes
|
38
|
+
private def html_tag(tagname, attrs)
|
39
|
+
s = +"<#{tagname}"
|
40
|
+
attrs.each_pair do |k, v|
|
41
|
+
next unless v
|
42
|
+
s << " #{k}=\"#{v}\""
|
43
|
+
end
|
44
|
+
if block_given?
|
45
|
+
s << ">"
|
46
|
+
yield s
|
47
|
+
s << "</#{tagname}>"
|
48
|
+
else
|
49
|
+
s << " />"
|
50
|
+
end
|
51
|
+
s
|
52
|
+
end
|
53
|
+
|
11
54
|
def strings(lang)
|
12
|
-
|
55
|
+
@@strings ||= {}
|
13
56
|
|
14
57
|
# Allow sidekiq-web extensions to add locale paths
|
15
58
|
# so extensions can be localized
|
16
|
-
|
59
|
+
@@strings[lang] ||= settings.locales.each_with_object({}) do |path, global|
|
17
60
|
find_locale_files(lang).each do |file|
|
18
61
|
strs = YAML.safe_load(File.read(file))
|
19
62
|
global.merge!(strs[lang])
|
@@ -34,19 +77,19 @@ module Sidekiq
|
|
34
77
|
end
|
35
78
|
|
36
79
|
def clear_caches
|
37
|
-
|
38
|
-
|
39
|
-
|
80
|
+
@@strings = nil
|
81
|
+
@@locale_files = nil
|
82
|
+
@@available_locales = nil
|
40
83
|
end
|
41
84
|
|
42
85
|
def locale_files
|
43
|
-
|
86
|
+
@@locale_files ||= settings.locales.flat_map { |path|
|
44
87
|
Dir["#{path}/*.yml"]
|
45
88
|
}
|
46
89
|
end
|
47
90
|
|
48
91
|
def available_locales
|
49
|
-
|
92
|
+
@@available_locales ||= Set.new(locale_files.map { |path| File.basename(path, ".yml") })
|
50
93
|
end
|
51
94
|
|
52
95
|
def find_locale_files(lang)
|
@@ -68,7 +111,7 @@ module Sidekiq
|
|
68
111
|
if within.nil?
|
69
112
|
::Rack::Utils.escape_html(jid)
|
70
113
|
else
|
71
|
-
"<a href='#{root_path}
|
114
|
+
"<a href='#{root_path}#{within}?substr=#{jid}'>#{::Rack::Utils.escape_html(jid)}</a>"
|
72
115
|
end
|
73
116
|
end
|
74
117
|
|
@@ -122,10 +165,9 @@ module Sidekiq
|
|
122
165
|
# Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
|
123
166
|
def locale
|
124
167
|
# session[:locale] is set via the locale selector from the footer
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
@locale ||= begin
|
168
|
+
@locale ||= if (l = session&.fetch(:locale, nil)) && available_locales.include?(l)
|
169
|
+
l
|
170
|
+
else
|
129
171
|
matched_locale = user_preferred_languages.map { |preferred|
|
130
172
|
preferred_language = preferred.split("-", 2).first
|
131
173
|
|
@@ -142,7 +184,8 @@ module Sidekiq
|
|
142
184
|
|
143
185
|
# sidekiq/sidekiq#3243
|
144
186
|
def unfiltered?
|
145
|
-
|
187
|
+
s = url_params("substr")
|
188
|
+
yield unless s && s.size > 0
|
146
189
|
end
|
147
190
|
|
148
191
|
def get_locale
|
@@ -267,6 +310,10 @@ module Sidekiq
|
|
267
310
|
"<input type='hidden' name='authenticity_token' value='#{env[:csrf_token]}'/>"
|
268
311
|
end
|
269
312
|
|
313
|
+
def csp_nonce
|
314
|
+
env[:csp_nonce]
|
315
|
+
end
|
316
|
+
|
270
317
|
def to_display(arg)
|
271
318
|
arg.inspect
|
272
319
|
rescue
|
@@ -310,7 +357,7 @@ module Sidekiq
|
|
310
357
|
end
|
311
358
|
|
312
359
|
def h(text)
|
313
|
-
::Rack::Utils.escape_html(text)
|
360
|
+
::Rack::Utils.escape_html(text.to_s)
|
314
361
|
rescue ArgumentError => e
|
315
362
|
raise unless e.message.eql?("invalid byte sequence in UTF-8")
|
316
363
|
text.encode!("UTF-16", "UTF-8", invalid: :replace, replace: "").encode!("UTF-8", "UTF-16")
|
data/lib/sidekiq/web/router.rb
CHANGED
@@ -39,10 +39,13 @@ module Sidekiq
|
|
39
39
|
route(DELETE, path, &block)
|
40
40
|
end
|
41
41
|
|
42
|
-
def route(
|
42
|
+
def route(*methods, path, &block)
|
43
43
|
@routes ||= {GET => [], POST => [], PUT => [], PATCH => [], DELETE => [], HEAD => []}
|
44
44
|
|
45
|
-
|
45
|
+
methods.each do |method|
|
46
|
+
method = method.to_s.upcase
|
47
|
+
@routes[method] << WebRoute.new(method, path, block)
|
48
|
+
end
|
46
49
|
end
|
47
50
|
|
48
51
|
def match(env)
|
data/lib/sidekiq/web.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "erb"
|
4
|
+
require "securerandom"
|
4
5
|
|
5
6
|
require "sidekiq"
|
6
7
|
require "sidekiq/api"
|
@@ -39,14 +40,21 @@ module Sidekiq
|
|
39
40
|
CONTENT_SECURITY_POLICY = "Content-Security-Policy"
|
40
41
|
LOCATION = "Location"
|
41
42
|
X_CASCADE = "X-Cascade"
|
43
|
+
X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options"
|
42
44
|
else
|
43
45
|
CONTENT_LANGUAGE = "content-language"
|
44
46
|
CONTENT_SECURITY_POLICY = "content-security-policy"
|
45
47
|
LOCATION = "location"
|
46
48
|
X_CASCADE = "x-cascade"
|
49
|
+
X_CONTENT_TYPE_OPTIONS = "x-content-type-options"
|
47
50
|
end
|
48
51
|
|
49
52
|
class << self
|
53
|
+
# Forward compatibility with 8.0
|
54
|
+
def configure
|
55
|
+
yield self
|
56
|
+
end
|
57
|
+
|
50
58
|
def settings
|
51
59
|
self
|
52
60
|
end
|
@@ -114,6 +122,7 @@ module Sidekiq
|
|
114
122
|
end
|
115
123
|
|
116
124
|
def call(env)
|
125
|
+
env[:csp_nonce] = SecureRandom.base64(16)
|
117
126
|
app.call(env)
|
118
127
|
end
|
119
128
|
|
@@ -138,7 +147,50 @@ module Sidekiq
|
|
138
147
|
send(:"#{attribute}=", value)
|
139
148
|
end
|
140
149
|
|
141
|
-
|
150
|
+
# Register a class as a Sidekiq Web UI extension. The class should
|
151
|
+
# provide one or more tabs which map to an index route. Options:
|
152
|
+
#
|
153
|
+
# @param extension [Class] Class which contains the HTTP actions, required
|
154
|
+
# @param name [String] the name of the extension, used to namespace assets
|
155
|
+
# @param tab [String | Array] labels(s) of the UI tabs
|
156
|
+
# @param index [String | Array] index route(s) for each tab
|
157
|
+
# @param root_dir [String] directory location to find assets, locales and views, typically `web/` within the gemfile
|
158
|
+
# @param asset_paths [Array] one or more directories under {root}/assets/{name} to be publicly served, e.g. ["js", "css", "img"]
|
159
|
+
# @param cache_for [Integer] amount of time to cache assets, default one day
|
160
|
+
#
|
161
|
+
# TODO name, tab and index will be mandatory in 8.0
|
162
|
+
#
|
163
|
+
# Web extensions will have a root `web/` directory with `locales/`, `assets/`
|
164
|
+
# and `views/` subdirectories.
|
165
|
+
def self.register(extension, name: nil, tab: nil, index: nil, root_dir: nil, cache_for: 86400, asset_paths: nil)
|
166
|
+
tab = Array(tab)
|
167
|
+
index = Array(index)
|
168
|
+
tab.zip(index).each do |tab, index|
|
169
|
+
tabs[tab] = index
|
170
|
+
end
|
171
|
+
if root_dir
|
172
|
+
locdir = File.join(root_dir, "locales")
|
173
|
+
locales << locdir if File.directory?(locdir)
|
174
|
+
|
175
|
+
if asset_paths && name
|
176
|
+
# if you have {root}/assets/{name}/js/scripts.js
|
177
|
+
# and {root}/assets/{name}/css/styles.css
|
178
|
+
# you would pass in:
|
179
|
+
# asset_paths: ["js", "css"]
|
180
|
+
# See script_tag and style_tag in web/helpers.rb
|
181
|
+
assdir = File.join(root_dir, "assets")
|
182
|
+
assurls = Array(asset_paths).map { |x| "/#{name}/#{x}" }
|
183
|
+
assetprops = {
|
184
|
+
urls: assurls,
|
185
|
+
root: assdir,
|
186
|
+
cascade: true
|
187
|
+
}
|
188
|
+
assetprops[:header_rules] = [[:all, {Rack::CACHE_CONTROL => "private, max-age=#{cache_for.to_i}"}]] if cache_for
|
189
|
+
middlewares << [[Rack::Static, assetprops], nil]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
yield self if block_given?
|
142
194
|
extension.registered(WebApplication)
|
143
195
|
end
|
144
196
|
|
@@ -166,7 +218,7 @@ module Sidekiq
|
|
166
218
|
Sidekiq::WebApplication.helpers WebHelpers
|
167
219
|
Sidekiq::WebApplication.helpers Sidekiq::Paginator
|
168
220
|
|
169
|
-
Sidekiq::WebAction.class_eval <<-RUBY,
|
221
|
+
Sidekiq::WebAction.class_eval <<-RUBY, Web::LAYOUT, -1 # standard:disable Style/EvalWithLocation
|
170
222
|
def _render
|
171
223
|
#{ERB.new(File.read(Web::LAYOUT)).src}
|
172
224
|
end
|
data/lib/sidekiq.rb
CHANGED
@@ -32,6 +32,7 @@ require "sidekiq/logger"
|
|
32
32
|
require "sidekiq/client"
|
33
33
|
require "sidekiq/transaction_aware_client"
|
34
34
|
require "sidekiq/job"
|
35
|
+
require "sidekiq/iterable_job"
|
35
36
|
require "sidekiq/worker_compatibility_alias"
|
36
37
|
require "sidekiq/redis_client_adapter"
|
37
38
|
|
@@ -101,18 +102,19 @@ module Sidekiq
|
|
101
102
|
def self.freeze!
|
102
103
|
@frozen = true
|
103
104
|
@config_blocks = nil
|
105
|
+
default_configuration.freeze!
|
104
106
|
end
|
105
107
|
|
106
108
|
# Creates a Sidekiq::Config instance that is more tuned for embedding
|
107
109
|
# within an arbitrary Ruby process. Notably it reduces concurrency by
|
108
110
|
# default so there is less contention for CPU time with other threads.
|
109
111
|
#
|
110
|
-
#
|
112
|
+
# instance = Sidekiq.configure_embed do |config|
|
111
113
|
# config.queues = %w[critical default low]
|
112
114
|
# end
|
113
|
-
#
|
115
|
+
# instance.run
|
114
116
|
# sleep 10
|
115
|
-
#
|
117
|
+
# instance.stop
|
116
118
|
#
|
117
119
|
# NB: it is really easy to overload a Ruby process with threads due to the GIL.
|
118
120
|
# I do not recommend setting concurrency higher than 2-3.
|
data/sidekiq.gemspec
CHANGED
@@ -23,8 +23,9 @@ Gem::Specification.new do |gem|
|
|
23
23
|
"rubygems_mfa_required" => "true"
|
24
24
|
}
|
25
25
|
|
26
|
-
gem.add_dependency "redis-client", ">= 0.
|
26
|
+
gem.add_dependency "redis-client", ">= 0.22.2"
|
27
27
|
gem.add_dependency "connection_pool", ">= 2.3.0"
|
28
28
|
gem.add_dependency "rack", ">= 2.2.4"
|
29
|
-
gem.add_dependency "
|
29
|
+
gem.add_dependency "logger"
|
30
|
+
gem.add_dependency "base64"
|
30
31
|
end
|
@@ -34,6 +34,7 @@ function addListeners() {
|
|
34
34
|
addShiftClickListeners()
|
35
35
|
updateFuzzyTimes();
|
36
36
|
updateNumbers();
|
37
|
+
updateProgressBars();
|
37
38
|
setLivePollFromUrl();
|
38
39
|
|
39
40
|
var buttons = document.querySelectorAll(".live-poll");
|
@@ -180,4 +181,8 @@ function showError(error) {
|
|
180
181
|
|
181
182
|
function updateLocale(event) {
|
182
183
|
event.target.form.submit();
|
183
|
-
}
|
184
|
+
}
|
185
|
+
|
186
|
+
function updateProgressBars() {
|
187
|
+
document.querySelectorAll('.progress-bar').forEach(bar => { bar.style.width = bar.dataset.width + "%"})
|
188
|
+
}
|
@@ -83,6 +83,8 @@ class RealtimeChart extends DashboardChart {
|
|
83
83
|
this.chart.data.datasets[1].data.push(failed);
|
84
84
|
this.chart.update();
|
85
85
|
|
86
|
+
updateScreenReaderDashboardValues(processed, failed);
|
87
|
+
|
86
88
|
updateStatsSummary(this.stats.sidekiq);
|
87
89
|
updateRedisStats(this.stats.redis);
|
88
90
|
updateFooterUTCTime(this.stats.server_utc_time);
|
@@ -108,17 +110,27 @@ class RealtimeChart extends DashboardChart {
|
|
108
110
|
}
|
109
111
|
|
110
112
|
renderLegend(dp) {
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
113
|
+
const entry1 = this.legendEntry(dp[0]);
|
114
|
+
const entry2 = this.legendEntry(dp[1]);
|
115
|
+
const time = document.createElement("span");
|
116
|
+
time.classList.add("time");
|
117
|
+
time.innerText = dp[0].label;
|
118
|
+
|
119
|
+
this.legend.replaceChildren(entry1, entry2, time)
|
120
|
+
}
|
121
|
+
|
122
|
+
legendEntry(dp) {
|
123
|
+
const wrapper = document.createElement("span");
|
124
|
+
|
125
|
+
const swatch = document.createElement("span");
|
126
|
+
swatch.classList.add("swatch");
|
127
|
+
swatch.style.backgroundColor = dp.dataset.borderColor;
|
128
|
+
wrapper.appendChild(swatch)
|
129
|
+
|
130
|
+
const label = document.createElement("span");
|
131
|
+
label.innerText = `${dp.dataset.label}: ${dp.formattedValue}`;
|
132
|
+
wrapper.appendChild(label)
|
133
|
+
return wrapper;
|
122
134
|
}
|
123
135
|
|
124
136
|
renderCursor(dp) {
|
@@ -179,4 +191,4 @@ class RealtimeChart extends DashboardChart {
|
|
179
191
|
if (hc != null) {
|
180
192
|
var htc = new DashboardChart(hc, JSON.parse(hc.textContent))
|
181
193
|
window.historyChart = htc
|
182
|
-
}
|
194
|
+
}
|
@@ -28,7 +28,7 @@ var pulseBeacon = function() {
|
|
28
28
|
}
|
29
29
|
|
30
30
|
var setSliderLabel = function(val) {
|
31
|
-
document.getElementById('sldr-text').innerText = Math.round(parseFloat(val) / 1000) + '
|
31
|
+
document.getElementById('sldr-text').innerText = Math.round(parseFloat(val) / 1000) + ' s';
|
32
32
|
}
|
33
33
|
|
34
34
|
var ready = (callback) => {
|
@@ -36,6 +36,12 @@ var ready = (callback) => {
|
|
36
36
|
else document.addEventListener("DOMContentLoaded", callback);
|
37
37
|
}
|
38
38
|
|
39
|
+
var updateScreenReaderDashboardValues = function(processed, failed) {
|
40
|
+
let lastDashboardUpdateSpan = document.getElementById("sr-last-dashboard-update");
|
41
|
+
var updateText = document.getElementById("sr-last-dashboard-update-template").innerText;
|
42
|
+
lastDashboardUpdateSpan.innerText = updateText.replace("PROCESSED_COUNT", processed).replace("FAILED_COUNT", failed);
|
43
|
+
}
|
44
|
+
|
39
45
|
ready(() => {
|
40
46
|
var sldr = document.getElementById('sldr');
|
41
47
|
if (typeof localStorage.sidekiqTimeInterval !== 'undefined') {
|
@@ -72,6 +72,14 @@ h1, h2, h3 {
|
|
72
72
|
line-height: 45px;
|
73
73
|
}
|
74
74
|
|
75
|
+
.progress {
|
76
|
+
margin-bottom: 0;
|
77
|
+
}
|
78
|
+
|
79
|
+
.w-50 {
|
80
|
+
width: 50%;
|
81
|
+
}
|
82
|
+
|
75
83
|
.header-container, .header-container .page-title-container {
|
76
84
|
display: flex;
|
77
85
|
justify-content: space-between;
|
@@ -640,18 +648,23 @@ div.interval-slider input {
|
|
640
648
|
|
641
649
|
@media (min-width: 768px) {
|
642
650
|
.redis-url {
|
643
|
-
max-width:
|
651
|
+
max-width: 160px;
|
652
|
+
}
|
653
|
+
|
654
|
+
.navbar-fixed-bottom .nav {
|
655
|
+
margin-left: -15px;
|
656
|
+
margin-right: -15px;
|
644
657
|
}
|
645
658
|
}
|
646
659
|
|
647
660
|
@media (min-width: 992px) {
|
648
661
|
.redis-url {
|
649
|
-
max-width:
|
662
|
+
max-width: 380px;
|
650
663
|
}
|
651
664
|
}
|
652
665
|
@media (min-width: 1200px) {
|
653
666
|
.redis-url {
|
654
|
-
max-width:
|
667
|
+
max-width: 580px;
|
655
668
|
}
|
656
669
|
}
|
657
670
|
|
data/web/locales/en.yml
CHANGED
@@ -34,6 +34,8 @@ en:
|
|
34
34
|
Jobs: Jobs
|
35
35
|
Kill: Kill
|
36
36
|
KillAll: Kill All
|
37
|
+
Language: Language
|
38
|
+
LastDashboardUpdateTemplateLiteral: "Latest poll: Processed: PROCESSED_COUNT. Failed: FAILED_COUNT."
|
37
39
|
LastRetry: Last Retry
|
38
40
|
Latency: Latency
|
39
41
|
LivePoll: Live Poll
|
@@ -53,6 +55,7 @@ en:
|
|
53
55
|
PeakMemoryUsage: Peak Memory Usage
|
54
56
|
Plugins: Plugins
|
55
57
|
PollingInterval: Polling interval
|
58
|
+
PollingIntervalMilliseconds: Polling interval milliseconds
|
56
59
|
Process: Process
|
57
60
|
Processed: Processed
|
58
61
|
Processes: Processes
|
@@ -95,7 +98,6 @@ en:
|
|
95
98
|
TotalExecutionTime: Total Execution Time
|
96
99
|
AvgExecutionTime: Average Execution Time
|
97
100
|
Context: Context
|
98
|
-
Bucket: Bucket
|
99
101
|
NoJobMetricsFound: No recent job metrics were found
|
100
102
|
Filter: Filter
|
101
103
|
AnyJobContent: Any job content
|
data/web/locales/fr.yml
CHANGED