sidekiq 5.2.8

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.

Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +61 -0
  3. data/.github/contributing.md +32 -0
  4. data/.github/issue_template.md +11 -0
  5. data/.gitignore +15 -0
  6. data/.travis.yml +11 -0
  7. data/3.0-Upgrade.md +70 -0
  8. data/4.0-Upgrade.md +53 -0
  9. data/5.0-Upgrade.md +56 -0
  10. data/COMM-LICENSE +97 -0
  11. data/Changes.md +1542 -0
  12. data/Ent-Changes.md +238 -0
  13. data/Gemfile +23 -0
  14. data/LICENSE +9 -0
  15. data/Pro-2.0-Upgrade.md +138 -0
  16. data/Pro-3.0-Upgrade.md +44 -0
  17. data/Pro-4.0-Upgrade.md +35 -0
  18. data/Pro-Changes.md +759 -0
  19. data/README.md +109 -0
  20. data/Rakefile +9 -0
  21. data/bin/sidekiq +18 -0
  22. data/bin/sidekiqctl +20 -0
  23. data/bin/sidekiqload +149 -0
  24. data/code_of_conduct.md +50 -0
  25. data/lib/generators/sidekiq/templates/worker.rb.erb +9 -0
  26. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +6 -0
  27. data/lib/generators/sidekiq/templates/worker_test.rb.erb +8 -0
  28. data/lib/generators/sidekiq/worker_generator.rb +49 -0
  29. data/lib/sidekiq.rb +237 -0
  30. data/lib/sidekiq/api.rb +940 -0
  31. data/lib/sidekiq/cli.rb +445 -0
  32. data/lib/sidekiq/client.rb +243 -0
  33. data/lib/sidekiq/core_ext.rb +1 -0
  34. data/lib/sidekiq/ctl.rb +221 -0
  35. data/lib/sidekiq/delay.rb +42 -0
  36. data/lib/sidekiq/exception_handler.rb +29 -0
  37. data/lib/sidekiq/extensions/action_mailer.rb +57 -0
  38. data/lib/sidekiq/extensions/active_record.rb +40 -0
  39. data/lib/sidekiq/extensions/class_methods.rb +40 -0
  40. data/lib/sidekiq/extensions/generic_proxy.rb +31 -0
  41. data/lib/sidekiq/fetch.rb +81 -0
  42. data/lib/sidekiq/job_logger.rb +25 -0
  43. data/lib/sidekiq/job_retry.rb +262 -0
  44. data/lib/sidekiq/launcher.rb +173 -0
  45. data/lib/sidekiq/logging.rb +122 -0
  46. data/lib/sidekiq/manager.rb +137 -0
  47. data/lib/sidekiq/middleware/chain.rb +150 -0
  48. data/lib/sidekiq/middleware/i18n.rb +42 -0
  49. data/lib/sidekiq/middleware/server/active_record.rb +23 -0
  50. data/lib/sidekiq/paginator.rb +43 -0
  51. data/lib/sidekiq/processor.rb +279 -0
  52. data/lib/sidekiq/rails.rb +58 -0
  53. data/lib/sidekiq/redis_connection.rb +144 -0
  54. data/lib/sidekiq/scheduled.rb +174 -0
  55. data/lib/sidekiq/testing.rb +333 -0
  56. data/lib/sidekiq/testing/inline.rb +29 -0
  57. data/lib/sidekiq/util.rb +66 -0
  58. data/lib/sidekiq/version.rb +4 -0
  59. data/lib/sidekiq/web.rb +213 -0
  60. data/lib/sidekiq/web/action.rb +89 -0
  61. data/lib/sidekiq/web/application.rb +353 -0
  62. data/lib/sidekiq/web/helpers.rb +325 -0
  63. data/lib/sidekiq/web/router.rb +100 -0
  64. data/lib/sidekiq/worker.rb +220 -0
  65. data/sidekiq.gemspec +21 -0
  66. data/web/assets/images/favicon.ico +0 -0
  67. data/web/assets/images/logo.png +0 -0
  68. data/web/assets/images/status.png +0 -0
  69. data/web/assets/javascripts/application.js +92 -0
  70. data/web/assets/javascripts/dashboard.js +315 -0
  71. data/web/assets/stylesheets/application-rtl.css +246 -0
  72. data/web/assets/stylesheets/application.css +1144 -0
  73. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  74. data/web/assets/stylesheets/bootstrap.css +5 -0
  75. data/web/locales/ar.yml +81 -0
  76. data/web/locales/cs.yml +78 -0
  77. data/web/locales/da.yml +68 -0
  78. data/web/locales/de.yml +69 -0
  79. data/web/locales/el.yml +68 -0
  80. data/web/locales/en.yml +81 -0
  81. data/web/locales/es.yml +70 -0
  82. data/web/locales/fa.yml +80 -0
  83. data/web/locales/fr.yml +78 -0
  84. data/web/locales/he.yml +79 -0
  85. data/web/locales/hi.yml +75 -0
  86. data/web/locales/it.yml +69 -0
  87. data/web/locales/ja.yml +80 -0
  88. data/web/locales/ko.yml +68 -0
  89. data/web/locales/nb.yml +77 -0
  90. data/web/locales/nl.yml +68 -0
  91. data/web/locales/pl.yml +59 -0
  92. data/web/locales/pt-br.yml +68 -0
  93. data/web/locales/pt.yml +67 -0
  94. data/web/locales/ru.yml +78 -0
  95. data/web/locales/sv.yml +68 -0
  96. data/web/locales/ta.yml +75 -0
  97. data/web/locales/uk.yml +76 -0
  98. data/web/locales/ur.yml +80 -0
  99. data/web/locales/zh-cn.yml +68 -0
  100. data/web/locales/zh-tw.yml +68 -0
  101. data/web/views/_footer.erb +20 -0
  102. data/web/views/_job_info.erb +88 -0
  103. data/web/views/_nav.erb +52 -0
  104. data/web/views/_paging.erb +23 -0
  105. data/web/views/_poll_link.erb +7 -0
  106. data/web/views/_status.erb +4 -0
  107. data/web/views/_summary.erb +40 -0
  108. data/web/views/busy.erb +98 -0
  109. data/web/views/dashboard.erb +75 -0
  110. data/web/views/dead.erb +34 -0
  111. data/web/views/layout.erb +40 -0
  112. data/web/views/morgue.erb +75 -0
  113. data/web/views/queue.erb +46 -0
  114. data/web/views/queues.erb +30 -0
  115. data/web/views/retries.erb +80 -0
  116. data/web/views/retry.erb +34 -0
  117. data/web/views/scheduled.erb +54 -0
  118. data/web/views/scheduled_job_info.erb +8 -0
  119. metadata +230 -0
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ require 'sidekiq/testing'
3
+
4
+ ##
5
+ # The Sidekiq inline infrastructure overrides perform_async so that it
6
+ # actually calls perform instead. This allows workers to be run inline in a
7
+ # testing environment.
8
+ #
9
+ # This is similar to `Resque.inline = true` functionality.
10
+ #
11
+ # Example:
12
+ #
13
+ # require 'sidekiq/testing/inline'
14
+ #
15
+ # $external_variable = 0
16
+ #
17
+ # class ExternalWorker
18
+ # include Sidekiq::Worker
19
+ #
20
+ # def perform
21
+ # $external_variable = 1
22
+ # end
23
+ # end
24
+ #
25
+ # assert_equal 0, $external_variable
26
+ # ExternalWorker.perform_async
27
+ # assert_equal 1, $external_variable
28
+ #
29
+ Sidekiq::Testing.inline!
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ require 'socket'
3
+ require 'securerandom'
4
+ require 'sidekiq/exception_handler'
5
+
6
+ module Sidekiq
7
+ ##
8
+ # This module is part of Sidekiq core and not intended for extensions.
9
+ #
10
+ module Util
11
+ include ExceptionHandler
12
+
13
+ EXPIRY = 60 * 60 * 24
14
+
15
+ def watchdog(last_words)
16
+ yield
17
+ rescue Exception => ex
18
+ handle_exception(ex, { context: last_words })
19
+ raise ex
20
+ end
21
+
22
+ def safe_thread(name, &block)
23
+ Thread.new do
24
+ Thread.current['sidekiq_label'] = name
25
+ watchdog(name, &block)
26
+ end
27
+ end
28
+
29
+ def logger
30
+ Sidekiq.logger
31
+ end
32
+
33
+ def redis(&block)
34
+ Sidekiq.redis(&block)
35
+ end
36
+
37
+ def hostname
38
+ ENV['DYNO'] || Socket.gethostname
39
+ end
40
+
41
+ def process_nonce
42
+ @@process_nonce ||= SecureRandom.hex(6)
43
+ end
44
+
45
+ def identity
46
+ @@identity ||= "#{hostname}:#{$$}:#{process_nonce}"
47
+ end
48
+
49
+ def fire_event(event, options={})
50
+ reverse = options[:reverse]
51
+ reraise = options[:reraise]
52
+
53
+ arr = Sidekiq.options[:lifecycle_events][event]
54
+ arr.reverse! if reverse
55
+ arr.each do |block|
56
+ begin
57
+ block.call
58
+ rescue => ex
59
+ handle_exception(ex, { context: "Exception during Sidekiq lifecycle event.", event: event })
60
+ raise ex if reraise
61
+ end
62
+ end
63
+ arr.clear
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module Sidekiq
3
+ VERSION = "5.2.8"
4
+ end
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+ require 'erb'
3
+
4
+ require 'sidekiq'
5
+ require 'sidekiq/api'
6
+ require 'sidekiq/paginator'
7
+ require 'sidekiq/web/helpers'
8
+
9
+ require 'sidekiq/web/router'
10
+ require 'sidekiq/web/action'
11
+ require 'sidekiq/web/application'
12
+
13
+ require 'rack/protection'
14
+
15
+ require 'rack/builder'
16
+ require 'rack/file'
17
+ require 'rack/session/cookie'
18
+
19
+ module Sidekiq
20
+ class Web
21
+ ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../web")
22
+ VIEWS = "#{ROOT}/views"
23
+ LOCALES = ["#{ROOT}/locales"]
24
+ LAYOUT = "#{VIEWS}/layout.erb"
25
+ ASSETS = "#{ROOT}/assets"
26
+
27
+ DEFAULT_TABS = {
28
+ "Dashboard" => '',
29
+ "Busy" => 'busy',
30
+ "Queues" => 'queues',
31
+ "Retries" => 'retries',
32
+ "Scheduled" => 'scheduled',
33
+ "Dead" => 'morgue',
34
+ }
35
+
36
+ class << self
37
+ def settings
38
+ self
39
+ end
40
+
41
+ def middlewares
42
+ @middlewares ||= []
43
+ end
44
+
45
+ def use(*middleware_args, &block)
46
+ middlewares << [middleware_args, block]
47
+ end
48
+
49
+ def default_tabs
50
+ DEFAULT_TABS
51
+ end
52
+
53
+ def custom_tabs
54
+ @custom_tabs ||= {}
55
+ end
56
+ alias_method :tabs, :custom_tabs
57
+
58
+ def locales
59
+ @locales ||= LOCALES
60
+ end
61
+
62
+ def views
63
+ @views ||= VIEWS
64
+ end
65
+
66
+ def enable(*opts)
67
+ opts.each {|key| set(key, true) }
68
+ end
69
+
70
+ def disable(*opts)
71
+ opts.each {|key| set(key, false) }
72
+ end
73
+
74
+ # Helper for the Sinatra syntax: Sidekiq::Web.set(:session_secret, Rails.application.secrets...)
75
+ def set(attribute, value)
76
+ send(:"#{attribute}=", value)
77
+ end
78
+
79
+ attr_accessor :app_url, :session_secret, :redis_pool, :sessions
80
+ attr_writer :locales, :views
81
+ end
82
+
83
+ def self.inherited(child)
84
+ child.app_url = self.app_url
85
+ child.session_secret = self.session_secret
86
+ child.redis_pool = self.redis_pool
87
+ child.sessions = self.sessions
88
+ end
89
+
90
+ def settings
91
+ self.class.settings
92
+ end
93
+
94
+ def use(*middleware_args, &block)
95
+ middlewares << [middleware_args, block]
96
+ end
97
+
98
+ def middlewares
99
+ @middlewares ||= Web.middlewares.dup
100
+ end
101
+
102
+ def call(env)
103
+ app.call(env)
104
+ end
105
+
106
+ def self.call(env)
107
+ @app ||= new
108
+ @app.call(env)
109
+ end
110
+
111
+ def app
112
+ @app ||= build
113
+ end
114
+
115
+ def enable(*opts)
116
+ opts.each {|key| set(key, true) }
117
+ end
118
+
119
+ def disable(*opts)
120
+ opts.each {|key| set(key, false) }
121
+ end
122
+
123
+ def set(attribute, value)
124
+ send(:"#{attribute}=", value)
125
+ end
126
+
127
+ # Default values
128
+ set :sessions, true
129
+
130
+ attr_writer :sessions
131
+
132
+ def sessions
133
+ unless instance_variable_defined?("@sessions")
134
+ @sessions = self.class.sessions
135
+ @sessions = @sessions.to_hash.dup if @sessions.respond_to?(:to_hash)
136
+ end
137
+
138
+ @sessions
139
+ end
140
+
141
+ def self.register(extension)
142
+ extension.registered(WebApplication)
143
+ end
144
+
145
+ private
146
+
147
+ def using?(middleware)
148
+ middlewares.any? do |(m,_)|
149
+ m.kind_of?(Array) && (m[0] == middleware || m[0].kind_of?(middleware))
150
+ end
151
+ end
152
+
153
+ def build_sessions
154
+ middlewares = self.middlewares
155
+
156
+ unless using?(::Rack::Protection) || ENV['RACK_ENV'] == 'test'
157
+ middlewares.unshift [[::Rack::Protection, { use: :authenticity_token }], nil]
158
+ end
159
+
160
+ s = sessions
161
+ return unless s
162
+
163
+ unless using? ::Rack::Session::Cookie
164
+ unless secret = Web.session_secret
165
+ require 'securerandom'
166
+ secret = SecureRandom.hex(64)
167
+ end
168
+
169
+ options = { secret: secret }
170
+ options = options.merge(s.to_hash) if s.respond_to? :to_hash
171
+
172
+ middlewares.unshift [[::Rack::Session::Cookie, options], nil]
173
+ end
174
+ end
175
+
176
+ def build
177
+ build_sessions
178
+
179
+ middlewares = self.middlewares
180
+ klass = self.class
181
+
182
+ ::Rack::Builder.new do
183
+ %w(stylesheets javascripts images).each do |asset_dir|
184
+ map "/#{asset_dir}" do
185
+ run ::Rack::File.new("#{ASSETS}/#{asset_dir}", { 'Cache-Control' => 'public, max-age=86400' })
186
+ end
187
+ end
188
+
189
+ middlewares.each {|middleware, block| use(*middleware, &block) }
190
+
191
+ run WebApplication.new(klass)
192
+ end
193
+ end
194
+ end
195
+
196
+ Sidekiq::WebApplication.helpers WebHelpers
197
+ Sidekiq::WebApplication.helpers Sidekiq::Paginator
198
+
199
+ Sidekiq::WebAction.class_eval "def _render\n#{ERB.new(File.read(Web::LAYOUT)).src}\nend"
200
+ end
201
+
202
+ if defined?(::ActionDispatch::Request::Session) &&
203
+ !::ActionDispatch::Request::Session.method_defined?(:each)
204
+ # mperham/sidekiq#2460
205
+ # Rack apps can't reuse the Rails session store without
206
+ # this monkeypatch, fixed in Rails 5.
207
+ class ActionDispatch::Request::Session
208
+ def each(&block)
209
+ hash = self.to_hash
210
+ hash.each(&block)
211
+ end
212
+ end
213
+ end
@@ -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