sidekiq 3.5.4 → 5.2.7
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.circleci/config.yml +61 -0
- data/{Contributing.md → .github/contributing.md} +0 -0
- data/.github/issue_template.md +11 -0
- data/.gitignore +3 -0
- data/.travis.yml +5 -10
- data/4.0-Upgrade.md +53 -0
- data/5.0-Upgrade.md +56 -0
- data/COMM-LICENSE +13 -11
- data/Changes.md +376 -1
- data/Ent-Changes.md +201 -2
- data/Gemfile +14 -18
- data/LICENSE +1 -1
- data/Pro-3.0-Upgrade.md +44 -0
- data/Pro-4.0-Upgrade.md +35 -0
- data/Pro-Changes.md +307 -2
- data/README.md +34 -22
- data/Rakefile +3 -3
- data/bin/sidekiq +0 -1
- data/bin/sidekiqctl +13 -86
- data/bin/sidekiqload +23 -27
- data/code_of_conduct.md +50 -0
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +3 -3
- data/lib/generators/sidekiq/templates/worker_test.rb.erb +6 -6
- data/lib/sidekiq.rb +72 -25
- data/lib/sidekiq/api.rb +206 -73
- data/lib/sidekiq/cli.rb +145 -101
- data/lib/sidekiq/client.rb +42 -36
- data/lib/sidekiq/core_ext.rb +1 -105
- data/lib/sidekiq/ctl.rb +221 -0
- data/lib/sidekiq/delay.rb +42 -0
- data/lib/sidekiq/exception_handler.rb +4 -5
- data/lib/sidekiq/extensions/action_mailer.rb +1 -0
- data/lib/sidekiq/extensions/active_record.rb +1 -0
- data/lib/sidekiq/extensions/class_methods.rb +1 -0
- data/lib/sidekiq/extensions/generic_proxy.rb +8 -1
- data/lib/sidekiq/fetch.rb +36 -111
- data/lib/sidekiq/job_logger.rb +25 -0
- data/lib/sidekiq/job_retry.rb +262 -0
- data/lib/sidekiq/launcher.rb +129 -55
- data/lib/sidekiq/logging.rb +21 -3
- data/lib/sidekiq/manager.rb +83 -182
- data/lib/sidekiq/middleware/chain.rb +1 -0
- data/lib/sidekiq/middleware/i18n.rb +1 -0
- data/lib/sidekiq/middleware/server/active_record.rb +10 -0
- data/lib/sidekiq/paginator.rb +1 -0
- data/lib/sidekiq/processor.rb +221 -103
- data/lib/sidekiq/rails.rb +47 -27
- data/lib/sidekiq/redis_connection.rb +74 -7
- data/lib/sidekiq/scheduled.rb +87 -28
- data/lib/sidekiq/testing.rb +150 -19
- data/lib/sidekiq/testing/inline.rb +1 -0
- data/lib/sidekiq/util.rb +15 -17
- data/lib/sidekiq/version.rb +2 -1
- data/lib/sidekiq/web.rb +120 -184
- data/lib/sidekiq/web/action.rb +89 -0
- data/lib/sidekiq/web/application.rb +353 -0
- data/lib/sidekiq/{web_helpers.rb → web/helpers.rb} +123 -47
- data/lib/sidekiq/web/router.rb +100 -0
- data/lib/sidekiq/worker.rb +135 -18
- data/sidekiq.gemspec +8 -14
- data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
- data/web/assets/javascripts/application.js +24 -20
- data/web/assets/javascripts/dashboard.js +33 -18
- data/web/assets/stylesheets/application-rtl.css +246 -0
- data/web/assets/stylesheets/application.css +401 -7
- data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
- data/web/assets/stylesheets/bootstrap.css +4 -8
- data/web/locales/ar.yml +81 -0
- data/web/locales/cs.yml +11 -1
- data/web/locales/de.yml +1 -1
- data/web/locales/en.yml +4 -0
- data/web/locales/es.yml +4 -3
- data/web/locales/fa.yml +80 -0
- data/web/locales/fr.yml +21 -12
- data/web/locales/he.yml +79 -0
- data/web/locales/ja.yml +24 -13
- data/web/locales/ru.yml +3 -0
- data/web/locales/ur.yml +80 -0
- data/web/views/_footer.erb +7 -9
- data/web/views/_job_info.erb +5 -1
- data/web/views/_nav.erb +5 -19
- data/web/views/_paging.erb +1 -1
- data/web/views/busy.erb +18 -9
- data/web/views/dashboard.erb +5 -5
- data/web/views/dead.erb +1 -1
- data/web/views/layout.erb +13 -5
- data/web/views/morgue.erb +16 -12
- data/web/views/queue.erb +12 -11
- data/web/views/queues.erb +5 -3
- data/web/views/retries.erb +19 -13
- data/web/views/retry.erb +2 -2
- data/web/views/scheduled.erb +4 -4
- data/web/views/scheduled_job_info.erb +1 -1
- metadata +45 -227
- data/lib/sidekiq/actor.rb +0 -39
- data/lib/sidekiq/middleware/server/logging.rb +0 -40
- data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -206
- data/test/config.yml +0 -9
- data/test/env_based_config.yml +0 -11
- data/test/fake_env.rb +0 -0
- data/test/fixtures/en.yml +0 -2
- data/test/helper.rb +0 -49
- data/test/test_api.rb +0 -493
- data/test/test_cli.rb +0 -335
- data/test/test_client.rb +0 -194
- data/test/test_exception_handler.rb +0 -55
- data/test/test_extensions.rb +0 -126
- data/test/test_fetch.rb +0 -104
- data/test/test_logging.rb +0 -34
- data/test/test_manager.rb +0 -168
- data/test/test_middleware.rb +0 -159
- data/test/test_processor.rb +0 -237
- data/test/test_rails.rb +0 -21
- data/test/test_redis_connection.rb +0 -126
- data/test/test_retry.rb +0 -325
- data/test/test_scheduled.rb +0 -114
- data/test/test_scheduling.rb +0 -49
- data/test/test_sidekiq.rb +0 -99
- data/test/test_testing.rb +0 -142
- data/test/test_testing_fake.rb +0 -268
- data/test/test_testing_inline.rb +0 -93
- data/test/test_util.rb +0 -16
- data/test/test_web.rb +0 -608
- data/test/test_web_helpers.rb +0 -53
- data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
- data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
- data/web/assets/images/status/active.png +0 -0
- data/web/assets/images/status/idle.png +0 -0
- data/web/assets/javascripts/locales/README.md +0 -27
- data/web/assets/javascripts/locales/jquery.timeago.ar.js +0 -96
- data/web/assets/javascripts/locales/jquery.timeago.bg.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.bs.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.ca.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.cs.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.cy.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.da.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.de.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.el.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.en-short.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.en.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.es.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.et.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.fa.js +0 -22
- data/web/assets/javascripts/locales/jquery.timeago.fi.js +0 -28
- data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.fr.js +0 -17
- data/web/assets/javascripts/locales/jquery.timeago.he.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.hr.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.hu.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.hy.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.id.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.it.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.ja.js +0 -19
- data/web/assets/javascripts/locales/jquery.timeago.ko.js +0 -17
- data/web/assets/javascripts/locales/jquery.timeago.lt.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.mk.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.nl.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.no.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.pl.js +0 -31
- data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.pt.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.ro.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.rs.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.ru.js +0 -34
- data/web/assets/javascripts/locales/jquery.timeago.sk.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.sl.js +0 -44
- data/web/assets/javascripts/locales/jquery.timeago.sv.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.th.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.tr.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.uk.js +0 -34
- data/web/assets/javascripts/locales/jquery.timeago.uz.js +0 -19
- data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +0 -20
- data/web/views/_poll_js.erb +0 -5
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
class WebAction
|
5
|
+
RACK_SESSION = 'rack.session'
|
6
|
+
|
7
|
+
attr_accessor :env, :block, :type
|
8
|
+
|
9
|
+
def settings
|
10
|
+
Web.settings
|
11
|
+
end
|
12
|
+
|
13
|
+
def request
|
14
|
+
@request ||= ::Rack::Request.new(env)
|
15
|
+
end
|
16
|
+
|
17
|
+
def halt(res)
|
18
|
+
throw :halt, res
|
19
|
+
end
|
20
|
+
|
21
|
+
def redirect(location)
|
22
|
+
throw :halt, [302, { "Location" => "#{request.base_url}#{location}" }, []]
|
23
|
+
end
|
24
|
+
|
25
|
+
def params
|
26
|
+
indifferent_hash = Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
27
|
+
|
28
|
+
indifferent_hash.merge! request.params
|
29
|
+
route_params.each {|k,v| indifferent_hash[k.to_s] = v }
|
30
|
+
|
31
|
+
indifferent_hash
|
32
|
+
end
|
33
|
+
|
34
|
+
def route_params
|
35
|
+
env[WebRouter::ROUTE_PARAMS]
|
36
|
+
end
|
37
|
+
|
38
|
+
def session
|
39
|
+
env[RACK_SESSION]
|
40
|
+
end
|
41
|
+
|
42
|
+
def erb(content, options = {})
|
43
|
+
if content.kind_of? Symbol
|
44
|
+
unless respond_to?(:"_erb_#{content}")
|
45
|
+
src = ERB.new(File.read("#{Web.settings.views}/#{content}.erb")).src
|
46
|
+
WebAction.class_eval("def _erb_#{content}\n#{src}\n end")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
if @_erb
|
51
|
+
_erb(content, options[:locals])
|
52
|
+
else
|
53
|
+
@_erb = true
|
54
|
+
content = _erb(content, options[:locals])
|
55
|
+
|
56
|
+
_render { content }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def render(engine, content, options = {})
|
61
|
+
raise "Only erb templates are supported" if engine != :erb
|
62
|
+
|
63
|
+
erb(content, options)
|
64
|
+
end
|
65
|
+
|
66
|
+
def json(payload)
|
67
|
+
[200, { "Content-Type" => "application/json", "Cache-Control" => "no-cache" }, [Sidekiq.dump_json(payload)]]
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize(env, block)
|
71
|
+
@_erb = false
|
72
|
+
@env = env
|
73
|
+
@block = block
|
74
|
+
@@files ||= {}
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def _erb(file, locals)
|
80
|
+
locals.each {|k, v| define_singleton_method(k){ v } unless (singleton_methods.include? k)} if locals
|
81
|
+
|
82
|
+
if file.kind_of?(String)
|
83
|
+
ERB.new(file).result(binding)
|
84
|
+
else
|
85
|
+
send(:"_erb_#{file}")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,353 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
class WebApplication
|
5
|
+
extend WebRouter
|
6
|
+
|
7
|
+
CONTENT_LENGTH = "Content-Length"
|
8
|
+
CONTENT_TYPE = "Content-Type"
|
9
|
+
REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human)
|
10
|
+
CSP_HEADER = [
|
11
|
+
"default-src 'self' https: http:",
|
12
|
+
"child-src 'self'",
|
13
|
+
"connect-src 'self' https: http: wss: ws:",
|
14
|
+
"font-src 'self' https: http:",
|
15
|
+
"frame-src 'self'",
|
16
|
+
"img-src 'self' https: http: data:",
|
17
|
+
"manifest-src 'self'",
|
18
|
+
"media-src 'self'",
|
19
|
+
"object-src 'none'",
|
20
|
+
"script-src 'self' https: http: 'unsafe-inline'",
|
21
|
+
"style-src 'self' https: http: 'unsafe-inline'",
|
22
|
+
"worker-src 'self'",
|
23
|
+
"base-uri 'self'"
|
24
|
+
].join('; ').freeze
|
25
|
+
|
26
|
+
def initialize(klass)
|
27
|
+
@klass = klass
|
28
|
+
end
|
29
|
+
|
30
|
+
def settings
|
31
|
+
@klass.settings
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.settings
|
35
|
+
Sidekiq::Web.settings
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.tabs
|
39
|
+
Sidekiq::Web.tabs
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.set(key, val)
|
43
|
+
# nothing, backwards compatibility
|
44
|
+
end
|
45
|
+
|
46
|
+
get "/" do
|
47
|
+
@redis_info = redis_info.select{ |k, v| REDIS_KEYS.include? k }
|
48
|
+
stats_history = Sidekiq::Stats::History.new((params['days'] || 30).to_i)
|
49
|
+
@processed_history = stats_history.processed
|
50
|
+
@failed_history = stats_history.failed
|
51
|
+
|
52
|
+
erb(:dashboard)
|
53
|
+
end
|
54
|
+
|
55
|
+
get "/busy" do
|
56
|
+
erb(:busy)
|
57
|
+
end
|
58
|
+
|
59
|
+
post "/busy" do
|
60
|
+
if params['identity']
|
61
|
+
p = Sidekiq::Process.new('identity' => params['identity'])
|
62
|
+
p.quiet! if params['quiet']
|
63
|
+
p.stop! if params['stop']
|
64
|
+
else
|
65
|
+
processes.each do |pro|
|
66
|
+
pro.quiet! if params['quiet']
|
67
|
+
pro.stop! if params['stop']
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
redirect "#{root_path}busy"
|
72
|
+
end
|
73
|
+
|
74
|
+
get "/queues" do
|
75
|
+
@queues = Sidekiq::Queue.all
|
76
|
+
|
77
|
+
erb(:queues)
|
78
|
+
end
|
79
|
+
|
80
|
+
get "/queues/:name" do
|
81
|
+
@name = route_params[:name]
|
82
|
+
|
83
|
+
halt(404) unless @name
|
84
|
+
|
85
|
+
@count = (params['count'] || 25).to_i
|
86
|
+
@queue = Sidekiq::Queue.new(@name)
|
87
|
+
(@current_page, @total_size, @messages) = page("queue:#{@name}", params['page'], @count)
|
88
|
+
@messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) }
|
89
|
+
|
90
|
+
erb(:queue)
|
91
|
+
end
|
92
|
+
|
93
|
+
post "/queues/:name" do
|
94
|
+
Sidekiq::Queue.new(route_params[:name]).clear
|
95
|
+
|
96
|
+
redirect "#{root_path}queues"
|
97
|
+
end
|
98
|
+
|
99
|
+
post "/queues/:name/delete" do
|
100
|
+
name = route_params[:name]
|
101
|
+
Sidekiq::Job.new(params['key_val'], name).delete
|
102
|
+
|
103
|
+
redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
|
104
|
+
end
|
105
|
+
|
106
|
+
get '/morgue' do
|
107
|
+
@count = (params['count'] || 25).to_i
|
108
|
+
(@current_page, @total_size, @dead) = page("dead", params['page'], @count, reverse: true)
|
109
|
+
@dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
110
|
+
|
111
|
+
erb(:morgue)
|
112
|
+
end
|
113
|
+
|
114
|
+
get "/morgue/:key" do
|
115
|
+
halt(404) unless key = route_params[:key]
|
116
|
+
|
117
|
+
@dead = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
|
118
|
+
|
119
|
+
if @dead.nil?
|
120
|
+
redirect "#{root_path}morgue"
|
121
|
+
else
|
122
|
+
erb(:dead)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
post '/morgue' do
|
127
|
+
redirect(request.path) unless params['key']
|
128
|
+
|
129
|
+
params['key'].each do |key|
|
130
|
+
job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
|
131
|
+
retry_or_delete_or_kill job, params if job
|
132
|
+
end
|
133
|
+
|
134
|
+
redirect_with_query("#{root_path}morgue")
|
135
|
+
end
|
136
|
+
|
137
|
+
post "/morgue/all/delete" do
|
138
|
+
Sidekiq::DeadSet.new.clear
|
139
|
+
|
140
|
+
redirect "#{root_path}morgue"
|
141
|
+
end
|
142
|
+
|
143
|
+
post "/morgue/all/retry" do
|
144
|
+
Sidekiq::DeadSet.new.retry_all
|
145
|
+
|
146
|
+
redirect "#{root_path}morgue"
|
147
|
+
end
|
148
|
+
|
149
|
+
post "/morgue/:key" do
|
150
|
+
halt(404) unless key = route_params[:key]
|
151
|
+
|
152
|
+
job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
|
153
|
+
retry_or_delete_or_kill job, params if job
|
154
|
+
|
155
|
+
redirect_with_query("#{root_path}morgue")
|
156
|
+
end
|
157
|
+
|
158
|
+
get '/retries' do
|
159
|
+
@count = (params['count'] || 25).to_i
|
160
|
+
(@current_page, @total_size, @retries) = page("retry", params['page'], @count)
|
161
|
+
@retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
162
|
+
|
163
|
+
erb(:retries)
|
164
|
+
end
|
165
|
+
|
166
|
+
get "/retries/:key" do
|
167
|
+
@retry = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
|
168
|
+
|
169
|
+
if @retry.nil?
|
170
|
+
redirect "#{root_path}retries"
|
171
|
+
else
|
172
|
+
erb(:retry)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
post '/retries' do
|
177
|
+
redirect(request.path) unless params['key']
|
178
|
+
|
179
|
+
params['key'].each do |key|
|
180
|
+
job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first
|
181
|
+
retry_or_delete_or_kill job, params if job
|
182
|
+
end
|
183
|
+
|
184
|
+
redirect_with_query("#{root_path}retries")
|
185
|
+
end
|
186
|
+
|
187
|
+
post "/retries/all/delete" do
|
188
|
+
Sidekiq::RetrySet.new.clear
|
189
|
+
|
190
|
+
redirect "#{root_path}retries"
|
191
|
+
end
|
192
|
+
|
193
|
+
post "/retries/all/retry" do
|
194
|
+
Sidekiq::RetrySet.new.retry_all
|
195
|
+
|
196
|
+
redirect "#{root_path}retries"
|
197
|
+
end
|
198
|
+
|
199
|
+
post "/retries/all/kill" do
|
200
|
+
Sidekiq::RetrySet.new.kill_all
|
201
|
+
|
202
|
+
redirect "#{root_path}retries"
|
203
|
+
end
|
204
|
+
|
205
|
+
post "/retries/:key" do
|
206
|
+
job = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
|
207
|
+
|
208
|
+
retry_or_delete_or_kill job, params if job
|
209
|
+
|
210
|
+
redirect_with_query("#{root_path}retries")
|
211
|
+
end
|
212
|
+
|
213
|
+
get '/scheduled' do
|
214
|
+
@count = (params['count'] || 25).to_i
|
215
|
+
(@current_page, @total_size, @scheduled) = page("schedule", params['page'], @count)
|
216
|
+
@scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
217
|
+
|
218
|
+
erb(:scheduled)
|
219
|
+
end
|
220
|
+
|
221
|
+
get "/scheduled/:key" do
|
222
|
+
@job = Sidekiq::ScheduledSet.new.fetch(*parse_params(route_params[:key])).first
|
223
|
+
|
224
|
+
if @job.nil?
|
225
|
+
redirect "#{root_path}scheduled"
|
226
|
+
else
|
227
|
+
erb(:scheduled_job_info)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
post '/scheduled' do
|
232
|
+
redirect(request.path) unless params['key']
|
233
|
+
|
234
|
+
params['key'].each do |key|
|
235
|
+
job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
|
236
|
+
delete_or_add_queue job, params if job
|
237
|
+
end
|
238
|
+
|
239
|
+
redirect_with_query("#{root_path}scheduled")
|
240
|
+
end
|
241
|
+
|
242
|
+
post "/scheduled/:key" do
|
243
|
+
halt(404) unless key = route_params[:key]
|
244
|
+
|
245
|
+
job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
|
246
|
+
delete_or_add_queue job, params if job
|
247
|
+
|
248
|
+
redirect_with_query("#{root_path}scheduled")
|
249
|
+
end
|
250
|
+
|
251
|
+
get '/dashboard/stats' do
|
252
|
+
redirect "#{root_path}stats"
|
253
|
+
end
|
254
|
+
|
255
|
+
get '/stats' do
|
256
|
+
sidekiq_stats = Sidekiq::Stats.new
|
257
|
+
redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
|
258
|
+
json(
|
259
|
+
sidekiq: {
|
260
|
+
processed: sidekiq_stats.processed,
|
261
|
+
failed: sidekiq_stats.failed,
|
262
|
+
busy: sidekiq_stats.workers_size,
|
263
|
+
processes: sidekiq_stats.processes_size,
|
264
|
+
enqueued: sidekiq_stats.enqueued,
|
265
|
+
scheduled: sidekiq_stats.scheduled_size,
|
266
|
+
retries: sidekiq_stats.retry_size,
|
267
|
+
dead: sidekiq_stats.dead_size,
|
268
|
+
default_latency: sidekiq_stats.default_queue_latency
|
269
|
+
},
|
270
|
+
redis: redis_stats,
|
271
|
+
server_utc_time: server_utc_time
|
272
|
+
)
|
273
|
+
end
|
274
|
+
|
275
|
+
get '/stats/queues' do
|
276
|
+
json Sidekiq::Stats::Queues.new.lengths
|
277
|
+
end
|
278
|
+
|
279
|
+
def call(env)
|
280
|
+
action = self.class.match(env)
|
281
|
+
return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass" }, ["Not Found"]] unless action
|
282
|
+
|
283
|
+
resp = catch(:halt) do
|
284
|
+
app = @klass
|
285
|
+
self.class.run_befores(app, action)
|
286
|
+
begin
|
287
|
+
resp = action.instance_exec env, &action.block
|
288
|
+
ensure
|
289
|
+
self.class.run_afters(app, action)
|
290
|
+
end
|
291
|
+
|
292
|
+
resp
|
293
|
+
end
|
294
|
+
|
295
|
+
resp = case resp
|
296
|
+
when Array
|
297
|
+
resp
|
298
|
+
else
|
299
|
+
headers = {
|
300
|
+
"Content-Type" => "text/html",
|
301
|
+
"Cache-Control" => "no-cache",
|
302
|
+
"Content-Language" => action.locale,
|
303
|
+
"Content-Security-Policy" => CSP_HEADER
|
304
|
+
}
|
305
|
+
|
306
|
+
[200, headers, [resp]]
|
307
|
+
end
|
308
|
+
|
309
|
+
resp[1] = resp[1].dup
|
310
|
+
|
311
|
+
resp[1][CONTENT_LENGTH] = resp[2].inject(0) { |l, p| l + p.bytesize }.to_s
|
312
|
+
|
313
|
+
resp
|
314
|
+
end
|
315
|
+
|
316
|
+
def self.helpers(mod=nil, &block)
|
317
|
+
if block_given?
|
318
|
+
WebAction.class_eval(&block)
|
319
|
+
else
|
320
|
+
WebAction.send(:include, mod)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def self.before(path=nil, &block)
|
325
|
+
befores << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
|
326
|
+
end
|
327
|
+
|
328
|
+
def self.after(path=nil, &block)
|
329
|
+
afters << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
|
330
|
+
end
|
331
|
+
|
332
|
+
def self.run_befores(app, action)
|
333
|
+
run_hooks(befores, app, action)
|
334
|
+
end
|
335
|
+
|
336
|
+
def self.run_afters(app, action)
|
337
|
+
run_hooks(afters, app, action)
|
338
|
+
end
|
339
|
+
|
340
|
+
def self.run_hooks(hooks, app, action)
|
341
|
+
hooks.select { |p,_| !p || p =~ action.env[WebRouter::PATH_INFO] }.
|
342
|
+
each {|_,b| action.instance_exec(action.env, app, &b) }
|
343
|
+
end
|
344
|
+
|
345
|
+
def self.befores
|
346
|
+
@befores ||= []
|
347
|
+
end
|
348
|
+
|
349
|
+
def self.afters
|
350
|
+
@afters ||= []
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
@@ -1,4 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'uri'
|
3
|
+
require 'set'
|
4
|
+
require 'yaml'
|
5
|
+
require 'cgi'
|
2
6
|
|
3
7
|
module Sidekiq
|
4
8
|
# This is not a public API
|
@@ -11,18 +15,28 @@ module Sidekiq
|
|
11
15
|
settings.locales.each_with_object({}) do |path, global|
|
12
16
|
find_locale_files(lang).each do |file|
|
13
17
|
strs = YAML.load(File.open(file))
|
14
|
-
global.
|
18
|
+
global.merge!(strs[lang])
|
15
19
|
end
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
19
23
|
|
24
|
+
def clear_caches
|
25
|
+
@@strings = nil
|
26
|
+
@@locale_files = nil
|
27
|
+
@@available_locales = nil
|
28
|
+
end
|
29
|
+
|
20
30
|
def locale_files
|
21
|
-
@@locale_files
|
31
|
+
@@locale_files ||= settings.locales.flat_map do |path|
|
22
32
|
Dir["#{path}/*.yml"]
|
23
33
|
end
|
24
34
|
end
|
25
35
|
|
36
|
+
def available_locales
|
37
|
+
@@available_locales ||= locale_files.map { |path| File.basename(path, '.yml') }.uniq
|
38
|
+
end
|
39
|
+
|
26
40
|
def find_locale_files(lang)
|
27
41
|
locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
|
28
42
|
end
|
@@ -39,46 +53,75 @@ module Sidekiq
|
|
39
53
|
# <meta .../>
|
40
54
|
# <% end %>
|
41
55
|
#
|
42
|
-
def add_to_head
|
56
|
+
def add_to_head
|
43
57
|
@head_html ||= []
|
44
|
-
@head_html <<
|
58
|
+
@head_html << yield.dup if block_given?
|
45
59
|
end
|
46
60
|
|
47
61
|
def display_custom_head
|
48
|
-
|
49
|
-
|
62
|
+
@head_html.join if defined?(@head_html)
|
63
|
+
end
|
64
|
+
|
65
|
+
def poll_path
|
66
|
+
if current_path != '' && params['poll']
|
67
|
+
root_path + current_path
|
68
|
+
else
|
69
|
+
""
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def text_direction
|
74
|
+
get_locale['TextDirection'] || 'ltr'
|
75
|
+
end
|
76
|
+
|
77
|
+
def rtl?
|
78
|
+
text_direction == 'rtl'
|
50
79
|
end
|
51
80
|
|
52
|
-
#
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
81
|
+
# See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
|
82
|
+
def user_preferred_languages
|
83
|
+
languages = env['HTTP_ACCEPT_LANGUAGE']
|
84
|
+
languages.to_s.downcase.gsub(/\s+/, '').split(',').map do |language|
|
85
|
+
locale, quality = language.split(';q=', 2)
|
86
|
+
locale = nil if locale == '*' # Ignore wildcards
|
87
|
+
quality = quality ? quality.to_f : 1.0
|
88
|
+
[locale, quality]
|
89
|
+
end.sort do |(_, left), (_, right)|
|
90
|
+
right <=> left
|
91
|
+
end.map(&:first).compact
|
57
92
|
end
|
58
93
|
|
59
|
-
# Given
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
94
|
+
# 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"
|
95
|
+
# this method will try to best match the available locales to the user's preferred languages.
|
96
|
+
#
|
97
|
+
# Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
|
63
98
|
def locale
|
64
99
|
@locale ||= begin
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
100
|
+
matched_locale = user_preferred_languages.map do |preferred|
|
101
|
+
preferred_language = preferred.split('-', 2).first
|
102
|
+
|
103
|
+
lang_group = available_locales.select do |available|
|
104
|
+
preferred_language == available.split('-', 2).first
|
105
|
+
end
|
106
|
+
|
107
|
+
lang_group.find { |lang| lang == preferred } || lang_group.min_by(&:length)
|
108
|
+
end.compact.first
|
109
|
+
|
110
|
+
matched_locale || 'en'
|
73
111
|
end
|
74
112
|
end
|
75
113
|
|
114
|
+
# mperham/sidekiq#3243
|
115
|
+
def unfiltered?
|
116
|
+
yield unless env['PATH_INFO'].start_with?("/filter/")
|
117
|
+
end
|
118
|
+
|
76
119
|
def get_locale
|
77
120
|
strings(locale)
|
78
121
|
end
|
79
122
|
|
80
123
|
def t(msg, options={})
|
81
|
-
string = get_locale[msg] || msg
|
124
|
+
string = get_locale[msg] || strings('en')[msg] || msg
|
82
125
|
if options.empty?
|
83
126
|
string
|
84
127
|
else
|
@@ -104,28 +147,19 @@ module Sidekiq
|
|
104
147
|
end.map { |msg| Sidekiq.load_json(msg) }
|
105
148
|
end
|
106
149
|
|
107
|
-
def location
|
108
|
-
Sidekiq.redis { |conn| conn.client.location }
|
109
|
-
end
|
110
|
-
|
111
150
|
def redis_connection
|
112
|
-
Sidekiq.redis
|
151
|
+
Sidekiq.redis do |conn|
|
152
|
+
c = conn.connection
|
153
|
+
"redis://#{c[:location]}/#{c[:db]}"
|
154
|
+
end
|
113
155
|
end
|
114
156
|
|
115
157
|
def namespace
|
116
|
-
|
158
|
+
@ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
|
117
159
|
end
|
118
160
|
|
119
161
|
def redis_info
|
120
|
-
Sidekiq.
|
121
|
-
# admin commands can't go through redis-namespace starting
|
122
|
-
# in redis-namespace 2.0
|
123
|
-
if conn.respond_to?(:namespace)
|
124
|
-
conn.redis.info
|
125
|
-
else
|
126
|
-
conn.info
|
127
|
-
end
|
128
|
-
end
|
162
|
+
Sidekiq.redis_info
|
129
163
|
end
|
130
164
|
|
131
165
|
def root_path
|
@@ -141,7 +175,8 @@ module Sidekiq
|
|
141
175
|
end
|
142
176
|
|
143
177
|
def relative_time(time)
|
144
|
-
|
178
|
+
stamp = time.getutc.iso8601
|
179
|
+
%{<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>}
|
145
180
|
end
|
146
181
|
|
147
182
|
def job_params(job, score)
|
@@ -149,7 +184,7 @@ module Sidekiq
|
|
149
184
|
end
|
150
185
|
|
151
186
|
def parse_params(params)
|
152
|
-
score, jid = params.split("-")
|
187
|
+
score, jid = params.split("-", 2)
|
153
188
|
[score.to_f, jid]
|
154
189
|
end
|
155
190
|
|
@@ -157,10 +192,14 @@ module Sidekiq
|
|
157
192
|
|
158
193
|
# Merge options with current params, filter safe params, and stringify to query string
|
159
194
|
def qparams(options)
|
160
|
-
|
195
|
+
# stringify
|
196
|
+
options.keys.each do |key|
|
197
|
+
options[key.to_s] = options.delete(key)
|
198
|
+
end
|
199
|
+
|
161
200
|
params.merge(options).map do |key, value|
|
162
|
-
SAFE_QPARAMS.include?(key) ? "#{key}=#{value}" : next
|
163
|
-
end.join("&")
|
201
|
+
SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
|
202
|
+
end.compact.join("&")
|
164
203
|
end
|
165
204
|
|
166
205
|
def truncate(text, truncate_after_chars = 2000)
|
@@ -168,9 +207,16 @@ module Sidekiq
|
|
168
207
|
end
|
169
208
|
|
170
209
|
def display_args(args, truncate_after_chars = 2000)
|
171
|
-
args
|
172
|
-
|
173
|
-
|
210
|
+
return "Invalid job payload, args is nil" if args == nil
|
211
|
+
return "Invalid job payload, args must be an Array, not #{args.class.name}" if !args.is_a?(Array)
|
212
|
+
|
213
|
+
begin
|
214
|
+
args.map do |arg|
|
215
|
+
h(truncate(to_display(arg), truncate_after_chars))
|
216
|
+
end.join(", ")
|
217
|
+
rescue
|
218
|
+
"Illegal job arguments: #{h args.inspect}"
|
219
|
+
end
|
174
220
|
end
|
175
221
|
|
176
222
|
def csrf_tag
|
@@ -193,6 +239,7 @@ module Sidekiq
|
|
193
239
|
queue class args retry_count retried_at failed_at
|
194
240
|
jid error_message error_class backtrace
|
195
241
|
error_backtrace enqueued_at retry wrapped
|
242
|
+
created_at
|
196
243
|
))
|
197
244
|
|
198
245
|
def retry_extra_items(retry_job)
|
@@ -245,5 +292,34 @@ module Sidekiq
|
|
245
292
|
def product_version
|
246
293
|
"Sidekiq v#{Sidekiq::VERSION}"
|
247
294
|
end
|
295
|
+
|
296
|
+
def server_utc_time
|
297
|
+
Time.now.utc.strftime('%H:%M:%S UTC')
|
298
|
+
end
|
299
|
+
|
300
|
+
def redis_connection_and_namespace
|
301
|
+
@redis_connection_and_namespace ||= begin
|
302
|
+
namespace_suffix = namespace == nil ? '' : "##{namespace}"
|
303
|
+
"#{redis_connection}#{namespace_suffix}"
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def retry_or_delete_or_kill(job, params)
|
308
|
+
if params['retry']
|
309
|
+
job.retry
|
310
|
+
elsif params['delete']
|
311
|
+
job.delete
|
312
|
+
elsif params['kill']
|
313
|
+
job.kill
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def delete_or_add_queue(job, params)
|
318
|
+
if params['delete']
|
319
|
+
job.delete
|
320
|
+
elsif params['add_to_queue']
|
321
|
+
job.add_to_queue
|
322
|
+
end
|
323
|
+
end
|
248
324
|
end
|
249
325
|
end
|