sidekiq 7.0.0 → 7.3.0
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 +261 -13
- data/README.md +34 -27
- data/bin/multi_queue_bench +271 -0
- data/bin/sidekiqload +204 -109
- data/bin/sidekiqmon +3 -0
- data/lib/sidekiq/api.rb +151 -23
- data/lib/sidekiq/capsule.rb +20 -0
- data/lib/sidekiq/cli.rb +9 -4
- data/lib/sidekiq/client.rb +40 -24
- data/lib/sidekiq/component.rb +3 -1
- data/lib/sidekiq/config.rb +32 -12
- data/lib/sidekiq/deploy.rb +5 -5
- data/lib/sidekiq/embedded.rb +3 -3
- data/lib/sidekiq/fetch.rb +3 -5
- data/lib/sidekiq/iterable_job.rb +53 -0
- data/lib/sidekiq/job/interrupt_handler.rb +22 -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 +231 -0
- data/lib/sidekiq/job.rb +17 -10
- data/lib/sidekiq/job_logger.rb +24 -11
- data/lib/sidekiq/job_retry.rb +34 -11
- data/lib/sidekiq/job_util.rb +51 -15
- data/lib/sidekiq/launcher.rb +38 -22
- data/lib/sidekiq/logger.rb +1 -1
- data/lib/sidekiq/metrics/query.rb +6 -3
- data/lib/sidekiq/metrics/shared.rb +4 -4
- data/lib/sidekiq/metrics/tracking.rb +9 -3
- data/lib/sidekiq/middleware/chain.rb +12 -9
- data/lib/sidekiq/middleware/current_attributes.rb +70 -17
- data/lib/sidekiq/monitor.rb +17 -4
- data/lib/sidekiq/paginator.rb +4 -4
- data/lib/sidekiq/processor.rb +41 -27
- data/lib/sidekiq/rails.rb +18 -8
- data/lib/sidekiq/redis_client_adapter.rb +31 -35
- data/lib/sidekiq/redis_connection.rb +29 -7
- data/lib/sidekiq/scheduled.rb +4 -4
- data/lib/sidekiq/testing.rb +27 -8
- data/lib/sidekiq/transaction_aware_client.rb +7 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +10 -4
- data/lib/sidekiq/web/application.rb +113 -16
- data/lib/sidekiq/web/csrf_protection.rb +9 -6
- data/lib/sidekiq/web/helpers.rb +104 -33
- data/lib/sidekiq/web.rb +63 -2
- data/lib/sidekiq.rb +2 -1
- data/sidekiq.gemspec +8 -29
- data/web/assets/javascripts/application.js +45 -0
- data/web/assets/javascripts/dashboard-charts.js +38 -12
- data/web/assets/javascripts/dashboard.js +8 -10
- data/web/assets/javascripts/metrics.js +64 -2
- data/web/assets/stylesheets/application-dark.css +4 -0
- data/web/assets/stylesheets/application-rtl.css +10 -0
- data/web/assets/stylesheets/application.css +38 -4
- data/web/locales/da.yml +11 -4
- data/web/locales/en.yml +2 -0
- data/web/locales/fr.yml +14 -0
- data/web/locales/gd.yml +99 -0
- data/web/locales/ja.yml +3 -1
- data/web/locales/pt-br.yml +20 -0
- data/web/locales/tr.yml +101 -0
- data/web/locales/zh-cn.yml +20 -19
- data/web/views/_footer.erb +14 -2
- data/web/views/_job_info.erb +18 -2
- data/web/views/_metrics_period_select.erb +12 -0
- data/web/views/_paging.erb +2 -0
- data/web/views/_poll_link.erb +1 -1
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +46 -35
- data/web/views/dashboard.erb +25 -35
- data/web/views/filtering.erb +7 -0
- data/web/views/layout.erb +6 -6
- data/web/views/metrics.erb +42 -31
- data/web/views/metrics_for_job.erb +41 -51
- data/web/views/morgue.erb +5 -9
- data/web/views/queue.erb +10 -14
- data/web/views/queues.erb +9 -3
- data/web/views/retries.erb +5 -9
- data/web/views/scheduled.erb +12 -13
- metadata +37 -32
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -6,8 +6,51 @@ 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
|
|
@@ -15,12 +58,16 @@ module Sidekiq
|
|
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
|
-
strs = YAML.safe_load(File.
|
61
|
+
strs = YAML.safe_load(File.read(file))
|
19
62
|
global.merge!(strs[lang])
|
20
63
|
end
|
21
64
|
end
|
22
65
|
end
|
23
66
|
|
67
|
+
def to_json(x)
|
68
|
+
Sidekiq.dump_json(x)
|
69
|
+
end
|
70
|
+
|
24
71
|
def singularize(str, count)
|
25
72
|
if count == 1 && str.respond_to?(:singularize) # rails
|
26
73
|
str.singularize
|
@@ -42,15 +89,36 @@ module Sidekiq
|
|
42
89
|
end
|
43
90
|
|
44
91
|
def available_locales
|
45
|
-
@available_locales ||= locale_files.map { |path| File.basename(path, ".yml") }
|
92
|
+
@available_locales ||= Set.new(locale_files.map { |path| File.basename(path, ".yml") })
|
46
93
|
end
|
47
94
|
|
48
95
|
def find_locale_files(lang)
|
49
96
|
locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
|
50
97
|
end
|
51
98
|
|
52
|
-
|
53
|
-
|
99
|
+
def search(jobset, substr)
|
100
|
+
resultset = jobset.scan(substr).to_a
|
101
|
+
@current_page = 1
|
102
|
+
@count = @total_size = resultset.size
|
103
|
+
resultset
|
104
|
+
end
|
105
|
+
|
106
|
+
def filtering(which)
|
107
|
+
erb(:filtering, locals: {which: which})
|
108
|
+
end
|
109
|
+
|
110
|
+
def filter_link(jid, within = "retries")
|
111
|
+
if within.nil?
|
112
|
+
::Rack::Utils.escape_html(jid)
|
113
|
+
else
|
114
|
+
"<a href='#{root_path}filter/#{within}?substr=#{jid}'>#{::Rack::Utils.escape_html(jid)}</a>"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def display_tags(job, within = "retries")
|
119
|
+
job.tags.map { |tag|
|
120
|
+
"<span class='label label-info jobtag'>#{filter_link(tag, within)}</span>"
|
121
|
+
}.join(" ")
|
54
122
|
end
|
55
123
|
|
56
124
|
# This view helper provide ability display you html code in
|
@@ -96,7 +164,10 @@ module Sidekiq
|
|
96
164
|
#
|
97
165
|
# Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
|
98
166
|
def locale
|
99
|
-
|
167
|
+
# session[:locale] is set via the locale selector from the footer
|
168
|
+
@locale ||= if (l = session&.fetch(:locale, nil)) && available_locales.include?(l)
|
169
|
+
l
|
170
|
+
else
|
100
171
|
matched_locale = user_preferred_languages.map { |preferred|
|
101
172
|
preferred_language = preferred.split("-", 2).first
|
102
173
|
|
@@ -111,14 +182,7 @@ module Sidekiq
|
|
111
182
|
end
|
112
183
|
end
|
113
184
|
|
114
|
-
#
|
115
|
-
def display_tags(job, within = nil)
|
116
|
-
job.tags.map { |tag|
|
117
|
-
"<span class='label label-info jobtag'>#{::Rack::Utils.escape_html(tag)}</span>"
|
118
|
-
}.join(" ")
|
119
|
-
end
|
120
|
-
|
121
|
-
# mperham/sidekiq#3243
|
185
|
+
# sidekiq/sidekiq#3243
|
122
186
|
def unfiltered?
|
123
187
|
yield unless env["PATH_INFO"].start_with?("/filter/")
|
124
188
|
end
|
@@ -137,7 +201,7 @@ module Sidekiq
|
|
137
201
|
end
|
138
202
|
|
139
203
|
def sort_direction_label
|
140
|
-
params[:direction] == "asc" ? "↑" : "↓"
|
204
|
+
(params[:direction] == "asc") ? "↑" : "↓"
|
141
205
|
end
|
142
206
|
|
143
207
|
def workset
|
@@ -161,13 +225,21 @@ module Sidekiq
|
|
161
225
|
end
|
162
226
|
end
|
163
227
|
|
228
|
+
def busy_weights(capsule_weights)
|
229
|
+
# backwards compat with 7.0.0, remove in 7.1
|
230
|
+
cw = [capsule_weights].flatten
|
231
|
+
cw.map { |hash|
|
232
|
+
hash.map { |name, weight| (weight > 0) ? +name << ": " << weight.to_s : name }.join(", ")
|
233
|
+
}.join("; ")
|
234
|
+
end
|
235
|
+
|
164
236
|
def stats
|
165
237
|
@stats ||= Sidekiq::Stats.new
|
166
238
|
end
|
167
239
|
|
168
240
|
def redis_url
|
169
241
|
Sidekiq.redis do |conn|
|
170
|
-
conn.
|
242
|
+
conn.config.server_url
|
171
243
|
end
|
172
244
|
end
|
173
245
|
|
@@ -184,7 +256,7 @@ module Sidekiq
|
|
184
256
|
end
|
185
257
|
|
186
258
|
def current_status
|
187
|
-
workset.size == 0 ? "idle" : "active"
|
259
|
+
(workset.size == 0) ? "idle" : "active"
|
188
260
|
end
|
189
261
|
|
190
262
|
def relative_time(time)
|
@@ -217,7 +289,7 @@ module Sidekiq
|
|
217
289
|
end
|
218
290
|
|
219
291
|
def truncate(text, truncate_after_chars = 2000)
|
220
|
-
truncate_after_chars && text.size > truncate_after_chars ? "#{text[0..truncate_after_chars]}..." : text
|
292
|
+
(truncate_after_chars && text.size > truncate_after_chars) ? "#{text[0..truncate_after_chars]}..." : text
|
221
293
|
end
|
222
294
|
|
223
295
|
def display_args(args, truncate_after_chars = 2000)
|
@@ -237,6 +309,10 @@ module Sidekiq
|
|
237
309
|
"<input type='hidden' name='authenticity_token' value='#{env[:csrf_token]}'/>"
|
238
310
|
end
|
239
311
|
|
312
|
+
def csp_nonce
|
313
|
+
env[:csp_nonce]
|
314
|
+
end
|
315
|
+
|
240
316
|
def to_display(arg)
|
241
317
|
arg.inspect
|
242
318
|
rescue
|
@@ -270,27 +346,17 @@ module Sidekiq
|
|
270
346
|
elsif rss_kb < 10_000_000
|
271
347
|
"#{number_with_delimiter((rss_kb / 1024.0).to_i)} MB"
|
272
348
|
else
|
273
|
-
"#{number_with_delimiter((rss_kb / (1024.0 * 1024.0))
|
349
|
+
"#{number_with_delimiter((rss_kb / (1024.0 * 1024.0)), precision: 1)} GB"
|
274
350
|
end
|
275
351
|
end
|
276
352
|
|
277
|
-
def number_with_delimiter(number)
|
278
|
-
|
279
|
-
|
280
|
-
begin
|
281
|
-
Float(number)
|
282
|
-
rescue ArgumentError, TypeError
|
283
|
-
return number
|
284
|
-
end
|
285
|
-
|
286
|
-
options = {delimiter: ",", separator: "."}
|
287
|
-
parts = number.to_s.to_str.split(".")
|
288
|
-
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
|
289
|
-
parts.join(options[:separator])
|
353
|
+
def number_with_delimiter(number, options = {})
|
354
|
+
precision = options[:precision] || 0
|
355
|
+
%(<span data-nwp="#{precision}">#{number.round(precision)}</span>)
|
290
356
|
end
|
291
357
|
|
292
358
|
def h(text)
|
293
|
-
::Rack::Utils.escape_html(text)
|
359
|
+
::Rack::Utils.escape_html(text.to_s)
|
294
360
|
rescue ArgumentError => e
|
295
361
|
raise unless e.message.eql?("invalid byte sequence in UTF-8")
|
296
362
|
text.encode!("UTF-16", "UTF-8", invalid: :replace, replace: "").encode!("UTF-8", "UTF-16")
|
@@ -323,6 +389,11 @@ module Sidekiq
|
|
323
389
|
Time.now.utc.strftime("%H:%M:%S UTC")
|
324
390
|
end
|
325
391
|
|
392
|
+
def pollable?
|
393
|
+
# there's no point to refreshing the metrics pages every N seconds
|
394
|
+
!(current_path == "" || current_path.index("metrics"))
|
395
|
+
end
|
396
|
+
|
326
397
|
def retry_or_delete_or_kill(job, params)
|
327
398
|
if params["retry"]
|
328
399
|
job.retry
|
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"
|
@@ -34,6 +35,18 @@ module Sidekiq
|
|
34
35
|
"Metrics" => "metrics"
|
35
36
|
}
|
36
37
|
|
38
|
+
if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
|
39
|
+
CONTENT_LANGUAGE = "Content-Language"
|
40
|
+
CONTENT_SECURITY_POLICY = "Content-Security-Policy"
|
41
|
+
LOCATION = "Location"
|
42
|
+
X_CASCADE = "X-Cascade"
|
43
|
+
else
|
44
|
+
CONTENT_LANGUAGE = "content-language"
|
45
|
+
CONTENT_SECURITY_POLICY = "content-security-policy"
|
46
|
+
LOCATION = "location"
|
47
|
+
X_CASCADE = "x-cascade"
|
48
|
+
end
|
49
|
+
|
37
50
|
class << self
|
38
51
|
def settings
|
39
52
|
self
|
@@ -48,6 +61,10 @@ module Sidekiq
|
|
48
61
|
end
|
49
62
|
alias_method :tabs, :custom_tabs
|
50
63
|
|
64
|
+
def custom_job_info_rows
|
65
|
+
@custom_job_info_rows ||= []
|
66
|
+
end
|
67
|
+
|
51
68
|
def locales
|
52
69
|
@locales ||= LOCALES
|
53
70
|
end
|
@@ -98,6 +115,7 @@ module Sidekiq
|
|
98
115
|
end
|
99
116
|
|
100
117
|
def call(env)
|
118
|
+
env[:csp_nonce] = SecureRandom.base64(16)
|
101
119
|
app.call(env)
|
102
120
|
end
|
103
121
|
|
@@ -122,7 +140,50 @@ module Sidekiq
|
|
122
140
|
send(:"#{attribute}=", value)
|
123
141
|
end
|
124
142
|
|
125
|
-
|
143
|
+
# Register a class as a Sidekiq Web UI extension. The class should
|
144
|
+
# provide one or more tabs which map to an index route. Options:
|
145
|
+
#
|
146
|
+
# @param extension [Class] Class which contains the HTTP actions, required
|
147
|
+
# @param name [String] the name of the extension, used to namespace assets
|
148
|
+
# @param tab [String | Array] labels(s) of the UI tabs
|
149
|
+
# @param index [String | Array] index route(s) for each tab
|
150
|
+
# @param root_dir [String] directory location to find assets, locales and views, typically `web/` within the gemfile
|
151
|
+
# @param asset_paths [Array] one or more directories under {root}/assets/{name} to be publicly served, e.g. ["js", "css", "img"]
|
152
|
+
# @param cache_for [Integer] amount of time to cache assets, default one day
|
153
|
+
#
|
154
|
+
# TODO name, tab and index will be mandatory in 8.0
|
155
|
+
#
|
156
|
+
# Web extensions will have a root `web/` directory with `locales/`, `assets/`
|
157
|
+
# and `views/` subdirectories.
|
158
|
+
def self.register(extension, name: nil, tab: nil, index: nil, root_dir: nil, cache_for: 86400, asset_paths: nil)
|
159
|
+
tab = Array(tab)
|
160
|
+
index = Array(index)
|
161
|
+
tab.zip(index).each do |tab, index|
|
162
|
+
tabs[tab] = index
|
163
|
+
end
|
164
|
+
if root_dir
|
165
|
+
locdir = File.join(root_dir, "locales")
|
166
|
+
locales << locdir if File.directory?(locdir)
|
167
|
+
|
168
|
+
if asset_paths && name
|
169
|
+
# if you have {root}/assets/{name}/js/scripts.js
|
170
|
+
# and {root}/assets/{name}/css/styles.css
|
171
|
+
# you would pass in:
|
172
|
+
# asset_paths: ["js", "css"]
|
173
|
+
# See script_tag and style_tag in web/helpers.rb
|
174
|
+
assdir = File.join(root_dir, "assets")
|
175
|
+
assurls = Array(asset_paths).map { |x| "/#{name}/#{x}" }
|
176
|
+
assetprops = {
|
177
|
+
urls: assurls,
|
178
|
+
root: assdir,
|
179
|
+
cascade: true
|
180
|
+
}
|
181
|
+
assetprops[:header_rules] = [[:all, {Rack::CACHE_CONTROL => "private, max-age=#{cache_for.to_i}"}]] if cache_for
|
182
|
+
middlewares << [[Rack::Static, assetprops], nil]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
yield self if block_given?
|
126
187
|
extension.registered(WebApplication)
|
127
188
|
end
|
128
189
|
|
@@ -133,7 +194,7 @@ module Sidekiq
|
|
133
194
|
m = middlewares
|
134
195
|
|
135
196
|
rules = []
|
136
|
-
rules = [[:all, {
|
197
|
+
rules = [[:all, {Rack::CACHE_CONTROL => "private, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
|
137
198
|
|
138
199
|
::Rack::Builder.new do
|
139
200
|
use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
|
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
|
|
@@ -112,7 +113,7 @@ module Sidekiq
|
|
112
113
|
# end
|
113
114
|
# inst.run
|
114
115
|
# sleep 10
|
115
|
-
# inst.
|
116
|
+
# inst.stop
|
116
117
|
#
|
117
118
|
# NB: it is really easy to overload a Ruby process with threads due to the GIL.
|
118
119
|
# I do not recommend setting concurrency higher than 2-3.
|
data/sidekiq.gemspec
CHANGED
@@ -2,7 +2,7 @@ require_relative "lib/sidekiq/version"
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |gem|
|
4
4
|
gem.authors = ["Mike Perham"]
|
5
|
-
gem.email = ["
|
5
|
+
gem.email = ["info@contribsys.com"]
|
6
6
|
gem.summary = "Simple, efficient background processing for Ruby"
|
7
7
|
gem.description = "Simple, efficient background processing for Ruby."
|
8
8
|
gem.homepage = "https://sidekiq.org"
|
@@ -16,37 +16,16 @@ Gem::Specification.new do |gem|
|
|
16
16
|
|
17
17
|
gem.metadata = {
|
18
18
|
"homepage_uri" => "https://sidekiq.org",
|
19
|
-
"bug_tracker_uri" => "https://github.com/
|
20
|
-
"documentation_uri" => "https://github.com/
|
21
|
-
"changelog_uri" => "https://github.com/
|
22
|
-
"source_code_uri" => "https://github.com/
|
19
|
+
"bug_tracker_uri" => "https://github.com/sidekiq/sidekiq/issues",
|
20
|
+
"documentation_uri" => "https://github.com/sidekiq/sidekiq/wiki",
|
21
|
+
"changelog_uri" => "https://github.com/sidekiq/sidekiq/blob/main/Changes.md",
|
22
|
+
"source_code_uri" => "https://github.com/sidekiq/sidekiq",
|
23
|
+
"rubygems_mfa_required" => "true"
|
23
24
|
}
|
24
25
|
|
25
|
-
gem.add_dependency "redis-client", ">= 0.
|
26
|
+
gem.add_dependency "redis-client", ">= 0.22.2"
|
26
27
|
gem.add_dependency "connection_pool", ">= 2.3.0"
|
27
28
|
gem.add_dependency "rack", ">= 2.2.4"
|
28
29
|
gem.add_dependency "concurrent-ruby", "< 2"
|
29
|
-
gem.
|
30
|
-
|
31
|
-
####################################################
|
32
|
-
|
33
|
-
|
34
|
-
█████████ █████ ██████████ ██████████ █████ ████ █████ ██████ ██████████ █████
|
35
|
-
███░░░░░███░░███ ░░███░░░░███ ░░███░░░░░█░░███ ███░ ░░███ ███░░░░███ ░███░░░░███ ███░░░███
|
36
|
-
░███ ░░░ ░███ ░███ ░░███ ░███ █ ░ ░███ ███ ░███ ███ ░░███ ░░░ ███ ███ ░░███
|
37
|
-
░░█████████ ░███ ░███ ░███ ░██████ ░███████ ░███ ░███ ░███ ███ ░███ ░███
|
38
|
-
░░░░░░░░███ ░███ ░███ ░███ ░███░░█ ░███░░███ ░███ ░███ ██░███ ███ ░███ ░███
|
39
|
-
███ ░███ ░███ ░███ ███ ░███ ░ █ ░███ ░░███ ░███ ░░███ ░░████ ███ ░░███ ███
|
40
|
-
░░█████████ █████ ██████████ ██████████ █████ ░░████ █████ ░░░██████░██ ███ ██ ░░░█████░
|
41
|
-
░░░░░░░░░ ░░░░░ ░░░░░░░░░░ ░░░░░░░░░░ ░░░░░ ░░░░ ░░░░░ ░░░░░░ ░░ ░░░ ░░ ░░░░░░
|
42
|
-
|
43
|
-
|
44
|
-
WARNING: This is a beta release, expect breakage!
|
45
|
-
|
46
|
-
1. Use `gem 'sidekiq', '<7'` in your Gemfile if you don't want to be a beta tester.
|
47
|
-
2. Read the release notes at https://github.com/mperham/sidekiq/blob/main/docs/7.0-Upgrade.md
|
48
|
-
3. Search for open/closed issues at https://github.com/mperham/sidekiq/issues/
|
49
|
-
|
50
|
-
####################################################
|
51
|
-
EOM
|
30
|
+
gem.add_dependency "logger"
|
52
31
|
end
|
@@ -31,7 +31,10 @@ function addListeners() {
|
|
31
31
|
node.addEventListener("click", addDataToggleListeners)
|
32
32
|
})
|
33
33
|
|
34
|
+
addShiftClickListeners()
|
34
35
|
updateFuzzyTimes();
|
36
|
+
updateNumbers();
|
37
|
+
updateProgressBars();
|
35
38
|
setLivePollFromUrl();
|
36
39
|
|
37
40
|
var buttons = document.querySelectorAll(".live-poll");
|
@@ -45,6 +48,8 @@ function addListeners() {
|
|
45
48
|
scheduleLivePoll();
|
46
49
|
}
|
47
50
|
}
|
51
|
+
|
52
|
+
document.getElementById("locale-select").addEventListener("change", updateLocale);
|
48
53
|
}
|
49
54
|
|
50
55
|
function addPollingListeners(_event) {
|
@@ -71,6 +76,23 @@ function addDataToggleListeners(event) {
|
|
71
76
|
}
|
72
77
|
}
|
73
78
|
|
79
|
+
function addShiftClickListeners() {
|
80
|
+
let checkboxes = Array.from(document.querySelectorAll(".shift_clickable"));
|
81
|
+
let lastChecked = null;
|
82
|
+
checkboxes.forEach(checkbox => {
|
83
|
+
checkbox.addEventListener("click", (e) => {
|
84
|
+
if (e.shiftKey && lastChecked) {
|
85
|
+
let myIndex = checkboxes.indexOf(checkbox);
|
86
|
+
let lastIndex = checkboxes.indexOf(lastChecked);
|
87
|
+
let [min, max] = [myIndex, lastIndex].sort();
|
88
|
+
let newState = checkbox.checked;
|
89
|
+
checkboxes.slice(min, max).forEach(c => c.checked = newState);
|
90
|
+
}
|
91
|
+
lastChecked = checkbox;
|
92
|
+
});
|
93
|
+
});
|
94
|
+
}
|
95
|
+
|
74
96
|
function updateFuzzyTimes() {
|
75
97
|
var locale = document.body.getAttribute("data-locale");
|
76
98
|
var parts = locale.split('-');
|
@@ -84,6 +106,20 @@ function updateFuzzyTimes() {
|
|
84
106
|
t.cancel();
|
85
107
|
}
|
86
108
|
|
109
|
+
function updateNumbers() {
|
110
|
+
document.querySelectorAll("[data-nwp]").forEach(node => {
|
111
|
+
let number = parseFloat(node.textContent);
|
112
|
+
let precision = parseInt(node.dataset["nwp"] || 0);
|
113
|
+
if (typeof number === "number") {
|
114
|
+
let formatted = number.toLocaleString(undefined, {
|
115
|
+
minimumFractionDigits: precision,
|
116
|
+
maximumFractionDigits: precision,
|
117
|
+
});
|
118
|
+
node.textContent = formatted;
|
119
|
+
}
|
120
|
+
});
|
121
|
+
}
|
122
|
+
|
87
123
|
function setLivePollFromUrl() {
|
88
124
|
var url_params = new URL(window.location.href).searchParams
|
89
125
|
|
@@ -122,6 +158,7 @@ function checkResponse(resp) {
|
|
122
158
|
|
123
159
|
function scheduleLivePoll() {
|
124
160
|
let ti = parseInt(localStorage.sidekiqTimeInterval) || 5000;
|
161
|
+
if (ti < 2000) { ti = 2000 }
|
125
162
|
livePollTimer = setTimeout(livePollCallback, ti);
|
126
163
|
}
|
127
164
|
|
@@ -141,3 +178,11 @@ function replacePage(text) {
|
|
141
178
|
function showError(error) {
|
142
179
|
console.error(error)
|
143
180
|
}
|
181
|
+
|
182
|
+
function updateLocale(event) {
|
183
|
+
event.target.form.submit();
|
184
|
+
}
|
185
|
+
|
186
|
+
function updateProgressBars() {
|
187
|
+
document.querySelectorAll('.progress-bar').forEach(bar => { bar.style.width = bar.dataset.width + "%"})
|
188
|
+
}
|
@@ -57,7 +57,9 @@ class DashboardChart extends BaseChart {
|
|
57
57
|
class RealtimeChart extends DashboardChart {
|
58
58
|
constructor(el, options) {
|
59
59
|
super(el, options);
|
60
|
-
|
60
|
+
let d = parseInt(localStorage.sidekiqTimeInterval) || 5000;
|
61
|
+
if (d < 2000) { d = 2000; }
|
62
|
+
this.delay = d
|
61
63
|
this.startPolling();
|
62
64
|
document.addEventListener("interval:update", this.handleUpdate.bind(this));
|
63
65
|
}
|
@@ -84,6 +86,7 @@ class RealtimeChart extends DashboardChart {
|
|
84
86
|
updateStatsSummary(this.stats.sidekiq);
|
85
87
|
updateRedisStats(this.stats.redis);
|
86
88
|
updateFooterUTCTime(this.stats.server_utc_time);
|
89
|
+
updateNumbers();
|
87
90
|
pulseBeacon();
|
88
91
|
|
89
92
|
this.stats = stats;
|
@@ -105,17 +108,27 @@ class RealtimeChart extends DashboardChart {
|
|
105
108
|
}
|
106
109
|
|
107
110
|
renderLegend(dp) {
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
111
|
+
const entry1 = this.legendEntry(dp[0]);
|
112
|
+
const entry2 = this.legendEntry(dp[1]);
|
113
|
+
const time = document.createElement("span");
|
114
|
+
time.classList.add("time");
|
115
|
+
time.innerText = dp[0].label;
|
116
|
+
|
117
|
+
this.legend.replaceChildren(entry1, entry2, time)
|
118
|
+
}
|
119
|
+
|
120
|
+
legendEntry(dp) {
|
121
|
+
const wrapper = document.createElement("span");
|
122
|
+
|
123
|
+
const swatch = document.createElement("span");
|
124
|
+
swatch.classList.add("swatch");
|
125
|
+
swatch.style.backgroundColor = dp.dataset.borderColor;
|
126
|
+
wrapper.appendChild(swatch)
|
127
|
+
|
128
|
+
const label = document.createElement("span");
|
129
|
+
label.innerText = `${dp.dataset.label}: ${dp.formattedValue}`;
|
130
|
+
wrapper.appendChild(label)
|
131
|
+
return wrapper;
|
119
132
|
}
|
120
133
|
|
121
134
|
renderCursor(dp) {
|
@@ -164,3 +177,16 @@ class RealtimeChart extends DashboardChart {
|
|
164
177
|
};
|
165
178
|
}
|
166
179
|
}
|
180
|
+
|
181
|
+
var rc = document.getElementById("realtime-chart")
|
182
|
+
if (rc != null) {
|
183
|
+
var rtc = new RealtimeChart(rc, JSON.parse(rc.textContent))
|
184
|
+
rtc.registerLegend(document.getElementById("realtime-legend"))
|
185
|
+
window.realtimeChart = rtc
|
186
|
+
}
|
187
|
+
|
188
|
+
var hc = document.getElementById("history-chart")
|
189
|
+
if (hc != null) {
|
190
|
+
var htc = new DashboardChart(hc, JSON.parse(hc.textContent))
|
191
|
+
window.historyChart = htc
|
192
|
+
}
|
@@ -1,15 +1,13 @@
|
|
1
1
|
Sidekiq = {};
|
2
2
|
|
3
|
-
var nf = new Intl.NumberFormat();
|
4
|
-
|
5
3
|
var updateStatsSummary = function(data) {
|
6
|
-
document.getElementById("txtProcessed").innerText =
|
7
|
-
document.getElementById("txtFailed").innerText =
|
8
|
-
document.getElementById("txtBusy").innerText =
|
9
|
-
document.getElementById("txtScheduled").innerText =
|
10
|
-
document.getElementById("txtRetries").innerText =
|
11
|
-
document.getElementById("txtEnqueued").innerText =
|
12
|
-
document.getElementById("txtDead").innerText =
|
4
|
+
document.getElementById("txtProcessed").innerText = data.processed;
|
5
|
+
document.getElementById("txtFailed").innerText = data.failed;
|
6
|
+
document.getElementById("txtBusy").innerText = data.busy;
|
7
|
+
document.getElementById("txtScheduled").innerText = data.scheduled;
|
8
|
+
document.getElementById("txtRetries").innerText = data.retries;
|
9
|
+
document.getElementById("txtEnqueued").innerText = data.enqueued;
|
10
|
+
document.getElementById("txtDead").innerText = data.dead;
|
13
11
|
}
|
14
12
|
|
15
13
|
var updateRedisStats = function(data) {
|
@@ -30,7 +28,7 @@ var pulseBeacon = function() {
|
|
30
28
|
}
|
31
29
|
|
32
30
|
var setSliderLabel = function(val) {
|
33
|
-
document.getElementById('sldr-text').innerText = Math.round(parseFloat(val) / 1000) + '
|
31
|
+
document.getElementById('sldr-text').innerText = Math.round(parseFloat(val) / 1000) + ' s';
|
34
32
|
}
|
35
33
|
|
36
34
|
var ready = (callback) => {
|