sidekiq 6.2.2 → 8.1.5
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 +726 -11
- data/LICENSE.txt +9 -0
- data/README.md +70 -39
- data/bin/kiq +17 -0
- data/bin/lint-herb +13 -0
- data/bin/multi_queue_bench +271 -0
- data/bin/sidekiq +4 -9
- data/bin/sidekiqload +214 -115
- data/bin/sidekiqmon +4 -1
- data/bin/webload +69 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +124 -0
- data/lib/generators/sidekiq/job_generator.rb +71 -0
- data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +3 -3
- data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
- data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
- data/lib/sidekiq/api.rb +729 -264
- data/lib/sidekiq/capsule.rb +135 -0
- data/lib/sidekiq/cli.rb +124 -100
- data/lib/sidekiq/client.rb +153 -106
- data/lib/sidekiq/component.rb +132 -0
- data/lib/sidekiq/config.rb +320 -0
- data/lib/sidekiq/deploy.rb +64 -0
- data/lib/sidekiq/embedded.rb +64 -0
- data/lib/sidekiq/fetch.rb +27 -26
- data/lib/sidekiq/iterable_job.rb +56 -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 +322 -0
- data/lib/sidekiq/job.rb +397 -5
- data/lib/sidekiq/job_logger.rb +23 -32
- data/lib/sidekiq/job_retry.rb +141 -68
- data/lib/sidekiq/job_util.rb +113 -0
- data/lib/sidekiq/launcher.rb +122 -98
- data/lib/sidekiq/loader.rb +57 -0
- data/lib/sidekiq/logger.rb +27 -106
- data/lib/sidekiq/manager.rb +41 -43
- data/lib/sidekiq/metrics/query.rb +184 -0
- data/lib/sidekiq/metrics/shared.rb +109 -0
- data/lib/sidekiq/metrics/tracking.rb +153 -0
- data/lib/sidekiq/middleware/chain.rb +96 -51
- data/lib/sidekiq/middleware/current_attributes.rb +120 -0
- data/lib/sidekiq/middleware/i18n.rb +8 -4
- data/lib/sidekiq/middleware/modules.rb +23 -0
- data/lib/sidekiq/monitor.rb +16 -6
- data/lib/sidekiq/paginator.rb +37 -10
- data/lib/sidekiq/processor.rb +105 -87
- data/lib/sidekiq/profiler.rb +73 -0
- data/lib/sidekiq/rails.rb +49 -36
- data/lib/sidekiq/redis_client_adapter.rb +117 -0
- data/lib/sidekiq/redis_connection.rb +55 -86
- data/lib/sidekiq/ring_buffer.rb +32 -0
- data/lib/sidekiq/scheduled.rb +106 -50
- data/lib/sidekiq/systemd.rb +2 -0
- data/lib/sidekiq/test_api.rb +331 -0
- data/lib/sidekiq/testing/inline.rb +2 -30
- data/lib/sidekiq/testing.rb +2 -342
- data/lib/sidekiq/transaction_aware_client.rb +59 -0
- data/lib/sidekiq/tui/controls.rb +53 -0
- data/lib/sidekiq/tui/filtering.rb +53 -0
- data/lib/sidekiq/tui/tabs/base_tab.rb +204 -0
- data/lib/sidekiq/tui/tabs/busy.rb +118 -0
- data/lib/sidekiq/tui/tabs/dead.rb +19 -0
- data/lib/sidekiq/tui/tabs/home.rb +144 -0
- data/lib/sidekiq/tui/tabs/metrics.rb +131 -0
- data/lib/sidekiq/tui/tabs/queues.rb +95 -0
- data/lib/sidekiq/tui/tabs/retries.rb +19 -0
- data/lib/sidekiq/tui/tabs/scheduled.rb +19 -0
- data/lib/sidekiq/tui/tabs/set_tab.rb +96 -0
- data/lib/sidekiq/tui/tabs.rb +15 -0
- data/lib/sidekiq/tui.rb +382 -0
- data/lib/sidekiq/version.rb +6 -1
- data/lib/sidekiq/web/action.rb +149 -64
- data/lib/sidekiq/web/application.rb +376 -268
- data/lib/sidekiq/web/config.rb +117 -0
- data/lib/sidekiq/web/helpers.rb +213 -87
- data/lib/sidekiq/web/router.rb +61 -74
- data/lib/sidekiq/web.rb +71 -100
- data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
- data/lib/sidekiq.rb +95 -196
- data/sidekiq.gemspec +14 -11
- data/web/assets/images/logo.png +0 -0
- data/web/assets/images/status.png +0 -0
- data/web/assets/javascripts/application.js +171 -57
- data/web/assets/javascripts/base-charts.js +120 -0
- data/web/assets/javascripts/chart.min.js +13 -0
- data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
- data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
- data/web/assets/javascripts/dashboard-charts.js +194 -0
- data/web/assets/javascripts/dashboard.js +41 -274
- data/web/assets/javascripts/metrics.js +280 -0
- data/web/assets/stylesheets/style.css +776 -0
- data/web/locales/ar.yml +72 -70
- data/web/locales/cs.yml +64 -62
- data/web/locales/da.yml +62 -53
- data/web/locales/de.yml +67 -65
- data/web/locales/el.yml +45 -24
- data/web/locales/en.yml +93 -69
- data/web/locales/es.yml +91 -68
- data/web/locales/fa.yml +67 -65
- data/web/locales/fr.yml +82 -67
- data/web/locales/gd.yml +110 -0
- data/web/locales/he.yml +67 -64
- data/web/locales/hi.yml +61 -59
- data/web/locales/it.yml +94 -54
- data/web/locales/ja.yml +74 -68
- data/web/locales/ko.yml +54 -52
- data/web/locales/lt.yml +68 -66
- data/web/locales/nb.yml +63 -61
- data/web/locales/nl.yml +54 -52
- data/web/locales/pl.yml +47 -45
- data/web/locales/{pt-br.yml → pt-BR.yml} +85 -56
- data/web/locales/pt.yml +53 -51
- data/web/locales/ru.yml +69 -66
- data/web/locales/sv.yml +55 -53
- data/web/locales/ta.yml +62 -60
- data/web/locales/tr.yml +102 -0
- data/web/locales/uk.yml +87 -61
- data/web/locales/ur.yml +66 -64
- data/web/locales/vi.yml +69 -67
- data/web/locales/zh-CN.yml +107 -0
- data/web/locales/{zh-tw.yml → zh-TW.yml} +44 -9
- data/web/views/_footer.html.erb +32 -0
- data/web/views/_job_info.html.erb +115 -0
- data/web/views/_metrics_period_select.html.erb +15 -0
- data/web/views/_nav.html.erb +45 -0
- data/web/views/_paging.html.erb +26 -0
- data/web/views/_poll_link.html.erb +4 -0
- data/web/views/_summary.html.erb +40 -0
- data/web/views/busy.html.erb +151 -0
- data/web/views/dashboard.html.erb +104 -0
- data/web/views/dead.html.erb +38 -0
- data/web/views/filtering.html.erb +6 -0
- data/web/views/layout.html.erb +26 -0
- data/web/views/metrics.html.erb +85 -0
- data/web/views/metrics_for_job.html.erb +58 -0
- data/web/views/morgue.html.erb +69 -0
- data/web/views/profiles.html.erb +43 -0
- data/web/views/queue.html.erb +57 -0
- data/web/views/queues.html.erb +46 -0
- data/web/views/retries.html.erb +77 -0
- data/web/views/retry.html.erb +39 -0
- data/web/views/scheduled.html.erb +64 -0
- data/web/views/{scheduled_job_info.erb → scheduled_job_info.html.erb} +3 -3
- metadata +130 -61
- data/LICENSE +0 -9
- data/lib/generators/sidekiq/worker_generator.rb +0 -57
- data/lib/sidekiq/delay.rb +0 -41
- data/lib/sidekiq/exception_handler.rb +0 -27
- data/lib/sidekiq/extensions/action_mailer.rb +0 -48
- data/lib/sidekiq/extensions/active_record.rb +0 -43
- data/lib/sidekiq/extensions/class_methods.rb +0 -43
- data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
- data/lib/sidekiq/util.rb +0 -95
- data/lib/sidekiq/web/csrf_protection.rb +0 -180
- data/lib/sidekiq/worker.rb +0 -244
- data/web/assets/stylesheets/application-dark.css +0 -147
- data/web/assets/stylesheets/application-rtl.css +0 -246
- data/web/assets/stylesheets/application.css +0 -1053
- data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
- data/web/assets/stylesheets/bootstrap.css +0 -5
- data/web/locales/zh-cn.yml +0 -68
- data/web/views/_footer.erb +0 -20
- data/web/views/_job_info.erb +0 -89
- data/web/views/_nav.erb +0 -52
- data/web/views/_paging.erb +0 -23
- data/web/views/_poll_link.erb +0 -7
- data/web/views/_status.erb +0 -4
- data/web/views/_summary.erb +0 -40
- data/web/views/busy.erb +0 -132
- data/web/views/dashboard.erb +0 -83
- data/web/views/dead.erb +0 -34
- data/web/views/layout.erb +0 -42
- data/web/views/morgue.erb +0 -78
- data/web/views/queue.erb +0 -55
- data/web/views/queues.erb +0 -38
- data/web/views/retries.erb +0 -83
- data/web/views/retry.erb +0 -34
- data/web/views/scheduled.erb +0 -57
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidekiq
|
|
4
|
+
class Web
|
|
5
|
+
##
|
|
6
|
+
# Configure the Sidekiq::Web instance in this process:
|
|
7
|
+
#
|
|
8
|
+
# require "sidekiq/web"
|
|
9
|
+
# Sidekiq::Web.configure do |config|
|
|
10
|
+
# config.register(MyExtension, name: "myext", tab: "TabName", index: "tabpage/")
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# This should go in your `config/routes.rb` or similar. It
|
|
14
|
+
# does not belong in your initializer since Web should not be
|
|
15
|
+
# loaded in some processes (like an actual Sidekiq process).
|
|
16
|
+
# See `examples/webui-ext` for a sample web extension.
|
|
17
|
+
class Config
|
|
18
|
+
extend Forwardable
|
|
19
|
+
|
|
20
|
+
OPTIONS = {
|
|
21
|
+
# By default we support direct uploads to p.f.c since the UI is a JS SPA
|
|
22
|
+
# and very difficult for us to vendor or provide ourselves. If you are worried
|
|
23
|
+
# about data security and wish to self-host, you can change these URLs.
|
|
24
|
+
profile_view_url: "https://profiler.firefox.com/public/%s",
|
|
25
|
+
profile_store_url: "https://api.profiler.firefox.com/compressed-store"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Allows users to add custom rows to all of the Job
|
|
30
|
+
# tables, e.g. Retries, Dead, Scheduled, with custom
|
|
31
|
+
# links to other systems, see _job_info.erb and test
|
|
32
|
+
# in web_test.rb
|
|
33
|
+
#
|
|
34
|
+
# Sidekiq::Web.configure do |cfg|
|
|
35
|
+
# cfg.custom_job_info_rows << JobLogLink.new
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# class JobLogLink
|
|
39
|
+
# def add_pair(job)
|
|
40
|
+
# yield "External Logs", "<a href='https://example.com/logs/#{job.jid}'>Logs for #{job.jid}</a>"
|
|
41
|
+
# end
|
|
42
|
+
# end
|
|
43
|
+
attr_accessor :custom_job_info_rows
|
|
44
|
+
|
|
45
|
+
attr_reader :tabs
|
|
46
|
+
attr_reader :locales
|
|
47
|
+
attr_reader :views
|
|
48
|
+
attr_reader :middlewares
|
|
49
|
+
|
|
50
|
+
# Adds the "Back to App" link in the header
|
|
51
|
+
attr_accessor :app_url
|
|
52
|
+
attr_accessor :assets_path
|
|
53
|
+
|
|
54
|
+
def initialize
|
|
55
|
+
@options = OPTIONS.dup
|
|
56
|
+
@locales = LOCALES
|
|
57
|
+
@views = VIEWS
|
|
58
|
+
@assets_path = ASSETS
|
|
59
|
+
@tabs = DEFAULT_TABS.dup
|
|
60
|
+
@middlewares = []
|
|
61
|
+
@custom_job_info_rows = []
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def_delegators :@options, :[], :[]=, :fetch, :key?, :has_key?, :merge!, :dig
|
|
65
|
+
|
|
66
|
+
def use(*args, &block)
|
|
67
|
+
middlewares << [args, block]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Register a class as a Sidekiq Web UI extension. The class should
|
|
71
|
+
# provide one or more tabs which map to an index route. Options:
|
|
72
|
+
#
|
|
73
|
+
# @param extclass [Class] Class which contains the HTTP actions, required
|
|
74
|
+
# @param name [String] the name of the extension, used to namespace assets
|
|
75
|
+
# @param tab [String | Array] labels(s) of the UI tabs
|
|
76
|
+
# @param index [String | Array] index route(s) for each tab
|
|
77
|
+
# @param root_dir [String] directory location to find assets, locales and views, typically `web/` within the gemfile
|
|
78
|
+
# @param asset_paths [Array] one or more directories under {root}/assets/{name} to be publicly served, e.g. ["js", "css", "img"]
|
|
79
|
+
# @param cache_for [Integer] amount of time to cache assets, default one day
|
|
80
|
+
#
|
|
81
|
+
# Web extensions will have a root `web/` directory with `locales/`, `assets/`
|
|
82
|
+
# and `views/` subdirectories.
|
|
83
|
+
def register_extension(extclass, name:, tab:, index:, root_dir: nil, cache_for: 86400, asset_paths: nil)
|
|
84
|
+
tab = Array(tab)
|
|
85
|
+
index = Array(index)
|
|
86
|
+
tab.zip(index).each do |tab, index|
|
|
87
|
+
tabs[tab] = index
|
|
88
|
+
end
|
|
89
|
+
if root_dir
|
|
90
|
+
locdir = File.join(root_dir, "locales")
|
|
91
|
+
locales << locdir if File.directory?(locdir)
|
|
92
|
+
|
|
93
|
+
if asset_paths && name
|
|
94
|
+
# if you have {root}/assets/{name}/js/scripts.js
|
|
95
|
+
# and {root}/assets/{name}/css/styles.css
|
|
96
|
+
# you would pass in:
|
|
97
|
+
# asset_paths: ["js", "css"]
|
|
98
|
+
# See script_tag and style_tag in web/helpers.rb
|
|
99
|
+
assdir = File.join(root_dir, "assets")
|
|
100
|
+
assurls = Array(asset_paths).map { |x| "/#{name}/#{x}" }
|
|
101
|
+
assetprops = {
|
|
102
|
+
urls: assurls,
|
|
103
|
+
root: assdir,
|
|
104
|
+
cascade: true
|
|
105
|
+
}
|
|
106
|
+
assetprops[:header_rules] = [[:all, {"cache-control" => "private, max-age=#{cache_for.to_i}"}]] if cache_for
|
|
107
|
+
middlewares << [[Rack::Static, assetprops], nil]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
yield self if block_given?
|
|
112
|
+
extclass.registered(Web::Application)
|
|
113
|
+
end
|
|
114
|
+
alias_method :register, :register_extension
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
data/lib/sidekiq/web/helpers.rb
CHANGED
|
@@ -1,26 +1,118 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "uri"
|
|
4
|
-
require "
|
|
5
|
-
require "yaml"
|
|
6
|
-
require "cgi"
|
|
4
|
+
require "cgi/escape"
|
|
7
5
|
|
|
8
6
|
module Sidekiq
|
|
9
|
-
#
|
|
7
|
+
# These methods are available to pages within the Web UI and UI extensions.
|
|
8
|
+
# They are not public APIs for applications to use.
|
|
10
9
|
module WebHelpers
|
|
10
|
+
def store_name
|
|
11
|
+
hash = redis_info
|
|
12
|
+
return "Dragonfly" if hash.has_key?("dragonfly_version")
|
|
13
|
+
return "Valkey" if hash.has_key?("valkey_version")
|
|
14
|
+
"Redis"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def store_version
|
|
18
|
+
hash = redis_info
|
|
19
|
+
return hash["dragonfly_version"] if hash.has_key?("dragonfly_version")
|
|
20
|
+
return hash["valkey_version"] if hash.has_key?("valkey_version")
|
|
21
|
+
hash["redis_version"]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def style_tag(location, **kwargs)
|
|
25
|
+
global = location.match?(/:\/\//)
|
|
26
|
+
location = root_path + location if !global && !location.start_with?(root_path)
|
|
27
|
+
attrs = {
|
|
28
|
+
type: "text/css",
|
|
29
|
+
media: "screen",
|
|
30
|
+
rel: "stylesheet",
|
|
31
|
+
nonce: csp_nonce,
|
|
32
|
+
href: location
|
|
33
|
+
}
|
|
34
|
+
add_to_head do
|
|
35
|
+
html_tag(:link, attrs.merge(kwargs))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def script_tag(location, **kwargs)
|
|
40
|
+
global = location.match?(/:\/\//)
|
|
41
|
+
location = root_path + location if !global && !location.start_with?(root_path)
|
|
42
|
+
attrs = {
|
|
43
|
+
type: "text/javascript",
|
|
44
|
+
nonce: csp_nonce,
|
|
45
|
+
src: location
|
|
46
|
+
}
|
|
47
|
+
html_tag(:script, attrs.merge(kwargs)) {}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# NB: keys and values are not escaped; do not allow user input
|
|
51
|
+
# in the attributes
|
|
52
|
+
private def html_tag(tagname, attrs)
|
|
53
|
+
s = "<#{tagname}"
|
|
54
|
+
attrs.each_pair do |k, v|
|
|
55
|
+
next unless v
|
|
56
|
+
s << " #{k}=\"#{v}\""
|
|
57
|
+
end
|
|
58
|
+
if block_given?
|
|
59
|
+
s << ">"
|
|
60
|
+
yield s
|
|
61
|
+
s << "</#{tagname}>"
|
|
62
|
+
else
|
|
63
|
+
s << " />"
|
|
64
|
+
end
|
|
65
|
+
s
|
|
66
|
+
end
|
|
67
|
+
|
|
11
68
|
def strings(lang)
|
|
12
|
-
|
|
69
|
+
@@strings ||= {}
|
|
13
70
|
|
|
14
71
|
# Allow sidekiq-web extensions to add locale paths
|
|
15
72
|
# so extensions can be localized
|
|
16
|
-
|
|
73
|
+
@@strings[lang] ||= config.locales.each_with_object({}) do |path, global|
|
|
17
74
|
find_locale_files(lang).each do |file|
|
|
18
|
-
strs =
|
|
75
|
+
strs = parse_yaml_new(file)
|
|
19
76
|
global.merge!(strs[lang])
|
|
20
77
|
end
|
|
21
78
|
end
|
|
22
79
|
end
|
|
23
80
|
|
|
81
|
+
# TODO Remove
|
|
82
|
+
def parse_yaml_old(path)
|
|
83
|
+
require "yaml"
|
|
84
|
+
YAML.safe_load_file(path)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def parse_yaml_new(path)
|
|
88
|
+
locale = nil
|
|
89
|
+
map = {}
|
|
90
|
+
IO.readlines(path, chomp: true).each do |line|
|
|
91
|
+
case line
|
|
92
|
+
when /\A\s*\#.*/
|
|
93
|
+
# line comment
|
|
94
|
+
when !locale && /\A([a-zA-Z\-_]+):/
|
|
95
|
+
locale = $1
|
|
96
|
+
map[locale] = {}
|
|
97
|
+
when /\A\s+(\w+):\s+(.+)\z/
|
|
98
|
+
# A few values have double quotes to include special characters in YAML.
|
|
99
|
+
# Strip them off manually as our greedy match will include them.
|
|
100
|
+
key = $1
|
|
101
|
+
s = $2
|
|
102
|
+
s = s[1..] if s[0] == "\""
|
|
103
|
+
s = s[0..-2] if s[-1] == "\""
|
|
104
|
+
map[locale][key] = s
|
|
105
|
+
else
|
|
106
|
+
raise ArgumentError, "unable to parse #{path}: #{line}"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
map
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def to_json(x)
|
|
113
|
+
Sidekiq.dump_json(x)
|
|
114
|
+
end
|
|
115
|
+
|
|
24
116
|
def singularize(str, count)
|
|
25
117
|
if count == 1 && str.respond_to?(:singularize) # rails
|
|
26
118
|
str.singularize
|
|
@@ -30,27 +122,52 @@ module Sidekiq
|
|
|
30
122
|
end
|
|
31
123
|
|
|
32
124
|
def clear_caches
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
125
|
+
@@strings = nil
|
|
126
|
+
@@locale_files = nil
|
|
127
|
+
@@available_locales = nil
|
|
36
128
|
end
|
|
37
129
|
|
|
38
130
|
def locale_files
|
|
39
|
-
|
|
131
|
+
@@locale_files ||= config.locales.flat_map { |path|
|
|
40
132
|
Dir["#{path}/*.yml"]
|
|
41
133
|
}
|
|
42
134
|
end
|
|
43
135
|
|
|
44
136
|
def available_locales
|
|
45
|
-
|
|
137
|
+
@@available_locales ||= Set.new(locale_files.map { |path| File.basename(path, ".yml") })
|
|
46
138
|
end
|
|
47
139
|
|
|
48
140
|
def find_locale_files(lang)
|
|
49
141
|
locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
|
|
50
142
|
end
|
|
51
143
|
|
|
52
|
-
|
|
53
|
-
|
|
144
|
+
def language_name(locale)
|
|
145
|
+
strings(locale).fetch("LanguageName", locale)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def search(jobset, substr)
|
|
149
|
+
resultset = jobset.scan(substr).to_a
|
|
150
|
+
@current_page = 1
|
|
151
|
+
@count = @total_size = resultset.size
|
|
152
|
+
resultset
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def filtering(which, placeholder_key: "AnyJobContent", label_key: "Filter")
|
|
156
|
+
erb(:filtering, locals: {which:, placeholder_key:, label_key:})
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def filter_link(jid, within = "retries")
|
|
160
|
+
if within.nil?
|
|
161
|
+
::Rack::Utils.escape_html(jid)
|
|
162
|
+
else
|
|
163
|
+
"<a href='#{root_path}#{within}?substr=#{jid}'>#{::Rack::Utils.escape_html(jid)}</a>"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def display_tags(job, within = "retries")
|
|
168
|
+
job.tags.map { |tag|
|
|
169
|
+
"<span class='label label-info jobtag jobtag-#{Rack::Utils.escape_html(tag)}'>#{filter_link(tag, within)}</span>"
|
|
170
|
+
}.join(" ")
|
|
54
171
|
end
|
|
55
172
|
|
|
56
173
|
# This view helper provide ability display you html code in
|
|
@@ -70,17 +187,6 @@ module Sidekiq
|
|
|
70
187
|
@head_html.join if defined?(@head_html)
|
|
71
188
|
end
|
|
72
189
|
|
|
73
|
-
def poll_path
|
|
74
|
-
if current_path != "" && params["poll"]
|
|
75
|
-
path = root_path + current_path
|
|
76
|
-
query_string = to_query_string(params.slice(*params.keys - %w[page poll]))
|
|
77
|
-
path += "?#{query_string}" unless query_string.empty?
|
|
78
|
-
path
|
|
79
|
-
else
|
|
80
|
-
""
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
190
|
def text_direction
|
|
85
191
|
get_locale["TextDirection"] || "ltr"
|
|
86
192
|
end
|
|
@@ -89,10 +195,13 @@ module Sidekiq
|
|
|
89
195
|
text_direction == "rtl"
|
|
90
196
|
end
|
|
91
197
|
|
|
92
|
-
# See https://www.
|
|
198
|
+
# See https://www.rfc-editor.org/rfc/rfc9110.html#section-12.5.4
|
|
199
|
+
# Returns an array of language tags ordered by their quality value
|
|
200
|
+
#
|
|
201
|
+
# Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
|
|
93
202
|
def user_preferred_languages
|
|
94
203
|
languages = env["HTTP_ACCEPT_LANGUAGE"]
|
|
95
|
-
languages.to_s.
|
|
204
|
+
languages.to_s.gsub(/\s+/, "").split(",").map { |language|
|
|
96
205
|
locale, quality = language.split(";q=", 2)
|
|
97
206
|
locale = nil if locale == "*" # Ignore wildcards
|
|
98
207
|
quality = quality ? quality.to_f : 1.0
|
|
@@ -104,34 +213,39 @@ module Sidekiq
|
|
|
104
213
|
|
|
105
214
|
# Given an Accept-Language header like "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2"
|
|
106
215
|
# this method will try to best match the available locales to the user's preferred languages.
|
|
107
|
-
#
|
|
108
|
-
# Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
|
|
109
216
|
def locale
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
217
|
+
# session[:locale] is set via the locale selector from the footer
|
|
218
|
+
@locale ||= if (l = session&.fetch(:locale, nil)) && available_locales.include?(l)
|
|
219
|
+
l
|
|
220
|
+
else
|
|
221
|
+
matched_locale = nil
|
|
222
|
+
# Attempt to find a case-insensitive exact match first
|
|
223
|
+
user_preferred_languages.each do |preferred|
|
|
224
|
+
# We only care about the language and primary subtag
|
|
225
|
+
# "en-GB-oxendict" becomes "en-GB"
|
|
226
|
+
language_tag = preferred.split("-")[0..1].join("-")
|
|
227
|
+
matched_locale = available_locales.find { |available_locale| available_locale.casecmp?(language_tag) }
|
|
228
|
+
break if matched_locale
|
|
229
|
+
end
|
|
113
230
|
|
|
114
|
-
|
|
115
|
-
preferred_language == available.split("-", 2).first
|
|
116
|
-
}
|
|
231
|
+
return matched_locale if matched_locale
|
|
117
232
|
|
|
118
|
-
|
|
119
|
-
|
|
233
|
+
# Find the first base language match
|
|
234
|
+
# "en-US,es-MX;q=0.9" matches "en"
|
|
235
|
+
user_preferred_languages.each do |preferred|
|
|
236
|
+
base_language = preferred.split("-", 2).first
|
|
237
|
+
matched_locale = available_locales.find { |available_locale| available_locale.casecmp?(base_language) }
|
|
238
|
+
break if matched_locale
|
|
239
|
+
end
|
|
120
240
|
|
|
121
241
|
matched_locale || "en"
|
|
122
242
|
end
|
|
123
243
|
end
|
|
124
244
|
|
|
125
|
-
#
|
|
126
|
-
def display_tags(job, within = nil)
|
|
127
|
-
job.tags.map { |tag|
|
|
128
|
-
"<span class='label label-info jobtag'>#{::Rack::Utils.escape_html(tag)}</span>"
|
|
129
|
-
}.join(" ")
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# mperham/sidekiq#3243
|
|
245
|
+
# sidekiq/sidekiq#3243
|
|
133
246
|
def unfiltered?
|
|
134
|
-
|
|
247
|
+
s = url_params("substr")
|
|
248
|
+
yield unless s && s.size > 0
|
|
135
249
|
end
|
|
136
250
|
|
|
137
251
|
def get_locale
|
|
@@ -148,33 +262,42 @@ module Sidekiq
|
|
|
148
262
|
end
|
|
149
263
|
|
|
150
264
|
def sort_direction_label
|
|
151
|
-
|
|
265
|
+
(url_params("direction") == "asc") ? "↑" : "↓"
|
|
152
266
|
end
|
|
153
267
|
|
|
154
|
-
def
|
|
155
|
-
@
|
|
268
|
+
def workset
|
|
269
|
+
@work ||= Sidekiq::WorkSet.new
|
|
156
270
|
end
|
|
157
271
|
|
|
158
272
|
def processes
|
|
159
273
|
@processes ||= Sidekiq::ProcessSet.new
|
|
160
274
|
end
|
|
161
275
|
|
|
276
|
+
# Sorts processes by hostname following the natural sort order
|
|
277
|
+
def sorted_processes
|
|
278
|
+
@sorted_processes ||= begin
|
|
279
|
+
return processes unless processes.all? { |p| p["hostname"] }
|
|
280
|
+
|
|
281
|
+
processes.to_a.sort_by do |process|
|
|
282
|
+
# Kudos to `shurikk` on StackOverflow
|
|
283
|
+
# https://stackoverflow.com/a/15170063/575547
|
|
284
|
+
process["hostname"].split(/(\d+)/).map { |a| /\d+/.match?(a) ? a.to_i : a }
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
162
289
|
def stats
|
|
163
290
|
@stats ||= Sidekiq::Stats.new
|
|
164
291
|
end
|
|
165
292
|
|
|
166
|
-
def
|
|
293
|
+
def redis_url
|
|
167
294
|
Sidekiq.redis do |conn|
|
|
168
|
-
conn.
|
|
295
|
+
conn.config.server_url
|
|
169
296
|
end
|
|
170
297
|
end
|
|
171
298
|
|
|
172
|
-
def namespace
|
|
173
|
-
@ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
|
|
174
|
-
end
|
|
175
|
-
|
|
176
299
|
def redis_info
|
|
177
|
-
Sidekiq.redis_info
|
|
300
|
+
@info ||= Sidekiq.default_configuration.redis_info
|
|
178
301
|
end
|
|
179
302
|
|
|
180
303
|
def root_path
|
|
@@ -186,7 +309,7 @@ module Sidekiq
|
|
|
186
309
|
end
|
|
187
310
|
|
|
188
311
|
def current_status
|
|
189
|
-
|
|
312
|
+
(workset.size == 0) ? "idle" : "active"
|
|
190
313
|
end
|
|
191
314
|
|
|
192
315
|
def relative_time(time)
|
|
@@ -194,32 +317,43 @@ module Sidekiq
|
|
|
194
317
|
%(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
|
|
195
318
|
end
|
|
196
319
|
|
|
320
|
+
def queue_names_by_capsule(pro)
|
|
321
|
+
cap = pro.capsules
|
|
322
|
+
if cap
|
|
323
|
+
cap.map { |k, v| v["weights"].keys.join(", ") }.join("; ")
|
|
324
|
+
else
|
|
325
|
+
# DEPRECATED Backwards compatibility with older processes.
|
|
326
|
+
# 'capsules' element added in v8.0.9
|
|
327
|
+
pro.queues.join(", ")
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
197
331
|
def job_params(job, score)
|
|
198
332
|
"#{score}-#{job["jid"]}"
|
|
199
333
|
end
|
|
200
334
|
|
|
201
|
-
def
|
|
202
|
-
score, jid =
|
|
335
|
+
def parse_key(key)
|
|
336
|
+
score, jid = key.split("-", 2)
|
|
203
337
|
[score.to_f, jid]
|
|
204
338
|
end
|
|
205
339
|
|
|
206
|
-
SAFE_QPARAMS = %w[page
|
|
340
|
+
SAFE_QPARAMS = %w[page direction]
|
|
207
341
|
|
|
208
342
|
# Merge options with current params, filter safe params, and stringify to query string
|
|
209
343
|
def qparams(options)
|
|
210
344
|
stringified_options = options.transform_keys(&:to_s)
|
|
211
345
|
|
|
212
|
-
to_query_string(params.merge(stringified_options))
|
|
346
|
+
to_query_string(request.params.merge(stringified_options))
|
|
213
347
|
end
|
|
214
348
|
|
|
215
|
-
def to_query_string(
|
|
216
|
-
|
|
349
|
+
def to_query_string(hash)
|
|
350
|
+
hash.map { |key, value|
|
|
217
351
|
SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
|
|
218
352
|
}.compact.join("&")
|
|
219
353
|
end
|
|
220
354
|
|
|
221
355
|
def truncate(text, truncate_after_chars = 2000)
|
|
222
|
-
truncate_after_chars && text.size > truncate_after_chars ? "#{text[0..truncate_after_chars]}..." : text
|
|
356
|
+
(truncate_after_chars && text.size > truncate_after_chars) ? "#{text[0..truncate_after_chars]}..." : text
|
|
223
357
|
end
|
|
224
358
|
|
|
225
359
|
def display_args(args, truncate_after_chars = 2000)
|
|
@@ -236,7 +370,11 @@ module Sidekiq
|
|
|
236
370
|
end
|
|
237
371
|
|
|
238
372
|
def csrf_tag
|
|
239
|
-
"
|
|
373
|
+
""
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def csp_nonce
|
|
377
|
+
env[:csp_nonce]
|
|
240
378
|
end
|
|
241
379
|
|
|
242
380
|
def to_display(arg)
|
|
@@ -253,7 +391,7 @@ module Sidekiq
|
|
|
253
391
|
queue class args retry_count retried_at failed_at
|
|
254
392
|
jid error_message error_class backtrace
|
|
255
393
|
error_backtrace enqueued_at retry wrapped
|
|
256
|
-
created_at tags
|
|
394
|
+
created_at tags display_class
|
|
257
395
|
])
|
|
258
396
|
|
|
259
397
|
def retry_extra_items(retry_job)
|
|
@@ -272,27 +410,17 @@ module Sidekiq
|
|
|
272
410
|
elsif rss_kb < 10_000_000
|
|
273
411
|
"#{number_with_delimiter((rss_kb / 1024.0).to_i)} MB"
|
|
274
412
|
else
|
|
275
|
-
"#{number_with_delimiter(
|
|
413
|
+
"#{number_with_delimiter(rss_kb / (1024.0 * 1024.0), precision: 1)} GB"
|
|
276
414
|
end
|
|
277
415
|
end
|
|
278
416
|
|
|
279
|
-
def number_with_delimiter(number)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
begin
|
|
283
|
-
Float(number)
|
|
284
|
-
rescue ArgumentError, TypeError
|
|
285
|
-
return number
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
options = {delimiter: ",", separator: "."}
|
|
289
|
-
parts = number.to_s.to_str.split(".")
|
|
290
|
-
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
|
|
291
|
-
parts.join(options[:separator])
|
|
417
|
+
def number_with_delimiter(number, options = {})
|
|
418
|
+
precision = options[:precision] || 0
|
|
419
|
+
%(<span data-nwp="#{precision}">#{number.round(precision)}</span>)
|
|
292
420
|
end
|
|
293
421
|
|
|
294
422
|
def h(text)
|
|
295
|
-
::Rack::Utils.escape_html(text)
|
|
423
|
+
::Rack::Utils.escape_html(text.to_s)
|
|
296
424
|
rescue ArgumentError => e
|
|
297
425
|
raise unless e.message.eql?("invalid byte sequence in UTF-8")
|
|
298
426
|
text.encode!("UTF-16", "UTF-8", invalid: :replace, replace: "").encode!("UTF-8", "UTF-16")
|
|
@@ -312,7 +440,7 @@ module Sidekiq
|
|
|
312
440
|
end
|
|
313
441
|
|
|
314
442
|
def environment_title_prefix
|
|
315
|
-
environment = Sidekiq.
|
|
443
|
+
environment = Sidekiq.default_configuration[:environment] || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
|
316
444
|
|
|
317
445
|
"[#{environment.upcase}] " unless environment == "production"
|
|
318
446
|
end
|
|
@@ -325,11 +453,9 @@ module Sidekiq
|
|
|
325
453
|
Time.now.utc.strftime("%H:%M:%S UTC")
|
|
326
454
|
end
|
|
327
455
|
|
|
328
|
-
def
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
"#{redis_connection}#{namespace_suffix}"
|
|
332
|
-
end
|
|
456
|
+
def pollable?
|
|
457
|
+
# there's no point to refreshing the metrics pages every N seconds
|
|
458
|
+
!(current_path == "" || current_path.index("metrics"))
|
|
333
459
|
end
|
|
334
460
|
|
|
335
461
|
def retry_or_delete_or_kill(job, params)
|