sidekiq 6.0.0

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 (121) 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 +13 -0
  6. data/.standard.yml +20 -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/6.0-Upgrade.md +70 -0
  11. data/COMM-LICENSE +97 -0
  12. data/Changes.md +1570 -0
  13. data/Ent-2.0-Upgrade.md +37 -0
  14. data/Ent-Changes.md +250 -0
  15. data/Gemfile +24 -0
  16. data/Gemfile.lock +196 -0
  17. data/LICENSE +9 -0
  18. data/Pro-2.0-Upgrade.md +138 -0
  19. data/Pro-3.0-Upgrade.md +44 -0
  20. data/Pro-4.0-Upgrade.md +35 -0
  21. data/Pro-5.0-Upgrade.md +25 -0
  22. data/Pro-Changes.md +768 -0
  23. data/README.md +95 -0
  24. data/Rakefile +10 -0
  25. data/bin/sidekiq +18 -0
  26. data/bin/sidekiqload +153 -0
  27. data/bin/sidekiqmon +9 -0
  28. data/code_of_conduct.md +50 -0
  29. data/lib/generators/sidekiq/templates/worker.rb.erb +9 -0
  30. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +6 -0
  31. data/lib/generators/sidekiq/templates/worker_test.rb.erb +8 -0
  32. data/lib/generators/sidekiq/worker_generator.rb +47 -0
  33. data/lib/sidekiq.rb +248 -0
  34. data/lib/sidekiq/api.rb +927 -0
  35. data/lib/sidekiq/cli.rb +380 -0
  36. data/lib/sidekiq/client.rb +242 -0
  37. data/lib/sidekiq/delay.rb +41 -0
  38. data/lib/sidekiq/exception_handler.rb +27 -0
  39. data/lib/sidekiq/extensions/action_mailer.rb +47 -0
  40. data/lib/sidekiq/extensions/active_record.rb +42 -0
  41. data/lib/sidekiq/extensions/class_methods.rb +42 -0
  42. data/lib/sidekiq/extensions/generic_proxy.rb +31 -0
  43. data/lib/sidekiq/fetch.rb +80 -0
  44. data/lib/sidekiq/job_logger.rb +55 -0
  45. data/lib/sidekiq/job_retry.rb +249 -0
  46. data/lib/sidekiq/launcher.rb +181 -0
  47. data/lib/sidekiq/logger.rb +69 -0
  48. data/lib/sidekiq/manager.rb +135 -0
  49. data/lib/sidekiq/middleware/chain.rb +151 -0
  50. data/lib/sidekiq/middleware/i18n.rb +40 -0
  51. data/lib/sidekiq/monitor.rb +148 -0
  52. data/lib/sidekiq/paginator.rb +42 -0
  53. data/lib/sidekiq/processor.rb +282 -0
  54. data/lib/sidekiq/rails.rb +52 -0
  55. data/lib/sidekiq/redis_connection.rb +138 -0
  56. data/lib/sidekiq/scheduled.rb +172 -0
  57. data/lib/sidekiq/testing.rb +332 -0
  58. data/lib/sidekiq/testing/inline.rb +30 -0
  59. data/lib/sidekiq/util.rb +69 -0
  60. data/lib/sidekiq/version.rb +5 -0
  61. data/lib/sidekiq/web.rb +205 -0
  62. data/lib/sidekiq/web/action.rb +93 -0
  63. data/lib/sidekiq/web/application.rb +356 -0
  64. data/lib/sidekiq/web/helpers.rb +324 -0
  65. data/lib/sidekiq/web/router.rb +103 -0
  66. data/lib/sidekiq/worker.rb +247 -0
  67. data/sidekiq.gemspec +21 -0
  68. data/web/assets/images/favicon.ico +0 -0
  69. data/web/assets/images/logo.png +0 -0
  70. data/web/assets/images/status.png +0 -0
  71. data/web/assets/javascripts/application.js +92 -0
  72. data/web/assets/javascripts/dashboard.js +296 -0
  73. data/web/assets/stylesheets/application-rtl.css +246 -0
  74. data/web/assets/stylesheets/application.css +1144 -0
  75. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  76. data/web/assets/stylesheets/bootstrap.css +5 -0
  77. data/web/locales/ar.yml +81 -0
  78. data/web/locales/cs.yml +78 -0
  79. data/web/locales/da.yml +68 -0
  80. data/web/locales/de.yml +69 -0
  81. data/web/locales/el.yml +68 -0
  82. data/web/locales/en.yml +81 -0
  83. data/web/locales/es.yml +70 -0
  84. data/web/locales/fa.yml +80 -0
  85. data/web/locales/fr.yml +78 -0
  86. data/web/locales/he.yml +79 -0
  87. data/web/locales/hi.yml +75 -0
  88. data/web/locales/it.yml +69 -0
  89. data/web/locales/ja.yml +81 -0
  90. data/web/locales/ko.yml +68 -0
  91. data/web/locales/nb.yml +77 -0
  92. data/web/locales/nl.yml +68 -0
  93. data/web/locales/pl.yml +59 -0
  94. data/web/locales/pt-br.yml +68 -0
  95. data/web/locales/pt.yml +67 -0
  96. data/web/locales/ru.yml +78 -0
  97. data/web/locales/sv.yml +68 -0
  98. data/web/locales/ta.yml +75 -0
  99. data/web/locales/uk.yml +76 -0
  100. data/web/locales/ur.yml +80 -0
  101. data/web/locales/zh-cn.yml +68 -0
  102. data/web/locales/zh-tw.yml +68 -0
  103. data/web/views/_footer.erb +20 -0
  104. data/web/views/_job_info.erb +88 -0
  105. data/web/views/_nav.erb +52 -0
  106. data/web/views/_paging.erb +23 -0
  107. data/web/views/_poll_link.erb +7 -0
  108. data/web/views/_status.erb +4 -0
  109. data/web/views/_summary.erb +40 -0
  110. data/web/views/busy.erb +98 -0
  111. data/web/views/dashboard.erb +75 -0
  112. data/web/views/dead.erb +34 -0
  113. data/web/views/layout.erb +40 -0
  114. data/web/views/morgue.erb +75 -0
  115. data/web/views/queue.erb +46 -0
  116. data/web/views/queues.erb +30 -0
  117. data/web/views/retries.erb +80 -0
  118. data/web/views/retry.erb +34 -0
  119. data/web/views/scheduled.erb +54 -0
  120. data/web/views/scheduled_job_info.erb +8 -0
  121. metadata +220 -0
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/testing"
4
+
5
+ ##
6
+ # The Sidekiq inline infrastructure overrides perform_async so that it
7
+ # actually calls perform instead. This allows workers to be run inline in a
8
+ # testing environment.
9
+ #
10
+ # This is similar to `Resque.inline = true` functionality.
11
+ #
12
+ # Example:
13
+ #
14
+ # require 'sidekiq/testing/inline'
15
+ #
16
+ # $external_variable = 0
17
+ #
18
+ # class ExternalWorker
19
+ # include Sidekiq::Worker
20
+ #
21
+ # def perform
22
+ # $external_variable = 1
23
+ # end
24
+ # end
25
+ #
26
+ # assert_equal 0, $external_variable
27
+ # ExternalWorker.perform_async
28
+ # assert_equal 1, $external_variable
29
+ #
30
+ Sidekiq::Testing.inline!
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "securerandom"
5
+ require "sidekiq/exception_handler"
6
+
7
+ module Sidekiq
8
+ ##
9
+ # This module is part of Sidekiq core and not intended for extensions.
10
+ #
11
+ module Util
12
+ include ExceptionHandler
13
+
14
+ EXPIRY = 60 * 60 * 24
15
+
16
+ def watchdog(last_words)
17
+ yield
18
+ rescue Exception => ex
19
+ handle_exception(ex, {context: last_words})
20
+ raise ex
21
+ end
22
+
23
+ def safe_thread(name, &block)
24
+ Thread.new do
25
+ Thread.current.name = name
26
+ watchdog(name, &block)
27
+ end
28
+ end
29
+
30
+ def logger
31
+ Sidekiq.logger
32
+ end
33
+
34
+ def redis(&block)
35
+ Sidekiq.redis(&block)
36
+ end
37
+
38
+ def tid
39
+ Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
40
+ end
41
+
42
+ def hostname
43
+ ENV["DYNO"] || Socket.gethostname
44
+ end
45
+
46
+ def process_nonce
47
+ @@process_nonce ||= SecureRandom.hex(6)
48
+ end
49
+
50
+ def identity
51
+ @@identity ||= "#{hostname}:#{::Process.pid}:#{process_nonce}"
52
+ end
53
+
54
+ def fire_event(event, options = {})
55
+ reverse = options[:reverse]
56
+ reraise = options[:reraise]
57
+
58
+ arr = Sidekiq.options[:lifecycle_events][event]
59
+ arr.reverse! if reverse
60
+ arr.each do |block|
61
+ block.call
62
+ rescue => ex
63
+ handle_exception(ex, {context: "Exception during Sidekiq lifecycle event.", event: event})
64
+ raise ex if reraise
65
+ end
66
+ arr.clear
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ VERSION = "6.0.0"
5
+ end
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ require "sidekiq"
6
+ require "sidekiq/api"
7
+ require "sidekiq/paginator"
8
+ require "sidekiq/web/helpers"
9
+
10
+ require "sidekiq/web/router"
11
+ require "sidekiq/web/action"
12
+ require "sidekiq/web/application"
13
+
14
+ require "rack/protection"
15
+
16
+ require "rack/builder"
17
+ require "rack/file"
18
+ require "rack/session/cookie"
19
+
20
+ module Sidekiq
21
+ class Web
22
+ ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../web")
23
+ VIEWS = "#{ROOT}/views"
24
+ LOCALES = ["#{ROOT}/locales"]
25
+ LAYOUT = "#{VIEWS}/layout.erb"
26
+ ASSETS = "#{ROOT}/assets"
27
+
28
+ DEFAULT_TABS = {
29
+ "Dashboard" => "",
30
+ "Busy" => "busy",
31
+ "Queues" => "queues",
32
+ "Retries" => "retries",
33
+ "Scheduled" => "scheduled",
34
+ "Dead" => "morgue",
35
+ }
36
+
37
+ class << self
38
+ def settings
39
+ self
40
+ end
41
+
42
+ def middlewares
43
+ @middlewares ||= []
44
+ end
45
+
46
+ def use(*middleware_args, &block)
47
+ middlewares << [middleware_args, block]
48
+ end
49
+
50
+ def default_tabs
51
+ DEFAULT_TABS
52
+ end
53
+
54
+ def custom_tabs
55
+ @custom_tabs ||= {}
56
+ end
57
+ alias_method :tabs, :custom_tabs
58
+
59
+ def locales
60
+ @locales ||= LOCALES
61
+ end
62
+
63
+ def views
64
+ @views ||= VIEWS
65
+ end
66
+
67
+ def enable(*opts)
68
+ opts.each { |key| set(key, true) }
69
+ end
70
+
71
+ def disable(*opts)
72
+ opts.each { |key| set(key, false) }
73
+ end
74
+
75
+ # Helper for the Sinatra syntax: Sidekiq::Web.set(:session_secret, Rails.application.secrets...)
76
+ def set(attribute, value)
77
+ send(:"#{attribute}=", value)
78
+ end
79
+
80
+ attr_accessor :app_url, :session_secret, :redis_pool, :sessions
81
+ attr_writer :locales, :views
82
+ end
83
+
84
+ def self.inherited(child)
85
+ child.app_url = app_url
86
+ child.session_secret = session_secret
87
+ child.redis_pool = redis_pool
88
+ child.sessions = sessions
89
+ end
90
+
91
+ def settings
92
+ self.class.settings
93
+ end
94
+
95
+ def use(*middleware_args, &block)
96
+ middlewares << [middleware_args, block]
97
+ end
98
+
99
+ def middlewares
100
+ @middlewares ||= Web.middlewares.dup
101
+ end
102
+
103
+ def call(env)
104
+ app.call(env)
105
+ end
106
+
107
+ def self.call(env)
108
+ @app ||= new
109
+ @app.call(env)
110
+ end
111
+
112
+ def app
113
+ @app ||= build
114
+ end
115
+
116
+ def enable(*opts)
117
+ opts.each { |key| set(key, true) }
118
+ end
119
+
120
+ def disable(*opts)
121
+ opts.each { |key| set(key, false) }
122
+ end
123
+
124
+ def set(attribute, value)
125
+ send(:"#{attribute}=", value)
126
+ end
127
+
128
+ # Default values
129
+ set :sessions, true
130
+
131
+ attr_writer :sessions
132
+
133
+ def sessions
134
+ unless instance_variable_defined?("@sessions")
135
+ @sessions = self.class.sessions
136
+ @sessions = @sessions.to_hash.dup if @sessions.respond_to?(:to_hash)
137
+ end
138
+
139
+ @sessions
140
+ end
141
+
142
+ def self.register(extension)
143
+ extension.registered(WebApplication)
144
+ end
145
+
146
+ private
147
+
148
+ def using?(middleware)
149
+ middlewares.any? do |(m, _)|
150
+ m.is_a?(Array) && (m[0] == middleware || m[0].is_a?(middleware))
151
+ end
152
+ end
153
+
154
+ def build_sessions
155
+ middlewares = self.middlewares
156
+
157
+ unless using?(::Rack::Protection) || ENV["RACK_ENV"] == "test"
158
+ middlewares.unshift [[::Rack::Protection, {use: :authenticity_token}], nil]
159
+ end
160
+
161
+ s = sessions
162
+ return unless s
163
+
164
+ unless using? ::Rack::Session::Cookie
165
+ unless (secret = Web.session_secret)
166
+ require "securerandom"
167
+ secret = SecureRandom.hex(64)
168
+ end
169
+
170
+ options = {secret: secret}
171
+ options = options.merge(s.to_hash) if s.respond_to? :to_hash
172
+
173
+ middlewares.unshift [[::Rack::Session::Cookie, options], nil]
174
+ end
175
+ end
176
+
177
+ def build
178
+ build_sessions
179
+
180
+ middlewares = self.middlewares
181
+ klass = self.class
182
+
183
+ ::Rack::Builder.new do
184
+ %w[stylesheets javascripts images].each do |asset_dir|
185
+ map "/#{asset_dir}" do
186
+ run ::Rack::File.new("#{ASSETS}/#{asset_dir}", {"Cache-Control" => "public, max-age=86400"})
187
+ end
188
+ end
189
+
190
+ middlewares.each { |middleware, block| use(*middleware, &block) }
191
+
192
+ run WebApplication.new(klass)
193
+ end
194
+ end
195
+ end
196
+
197
+ Sidekiq::WebApplication.helpers WebHelpers
198
+ Sidekiq::WebApplication.helpers Sidekiq::Paginator
199
+
200
+ Sidekiq::WebAction.class_eval <<-RUBY, __FILE__, __LINE__ + 1
201
+ def _render
202
+ #{ERB.new(File.read(Web::LAYOUT)).src}
203
+ end
204
+ RUBY
205
+ end
@@ -0,0 +1,93 @@
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.is_a? Symbol
44
+ unless respond_to?(:"_erb_#{content}")
45
+ src = ERB.new(File.read("#{Web.settings.views}/#{content}.erb")).src
46
+ WebAction.class_eval <<-RUBY, __FILE__, __LINE__ + 1
47
+ def _erb_#{content}
48
+ #{src}
49
+ end
50
+ RUBY
51
+ end
52
+ end
53
+
54
+ if @_erb
55
+ _erb(content, options[:locals])
56
+ else
57
+ @_erb = true
58
+ content = _erb(content, options[:locals])
59
+
60
+ _render { content }
61
+ end
62
+ end
63
+
64
+ def render(engine, content, options = {})
65
+ raise "Only erb templates are supported" if engine != :erb
66
+
67
+ erb(content, options)
68
+ end
69
+
70
+ def json(payload)
71
+ [200, {"Content-Type" => "application/json", "Cache-Control" => "no-cache"}, [Sidekiq.dump_json(payload)]]
72
+ end
73
+
74
+ def initialize(env, block)
75
+ @_erb = false
76
+ @env = env
77
+ @block = block
78
+ @files ||= {}
79
+ end
80
+
81
+ private
82
+
83
+ def _erb(file, locals)
84
+ locals&.each { |k, v| define_singleton_method(k) { v } unless singleton_methods.include? k }
85
+
86
+ if file.is_a?(String)
87
+ ERB.new(file).result(binding)
88
+ else
89
+ send(:"_erb_#{file}")
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,356 @@
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
+ key = route_params[:key]
116
+ halt(404) unless key
117
+
118
+ @dead = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
119
+
120
+ if @dead.nil?
121
+ redirect "#{root_path}morgue"
122
+ else
123
+ erb(:dead)
124
+ end
125
+ end
126
+
127
+ post "/morgue" do
128
+ redirect(request.path) unless params["key"]
129
+
130
+ params["key"].each do |key|
131
+ job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
132
+ retry_or_delete_or_kill job, params if job
133
+ end
134
+
135
+ redirect_with_query("#{root_path}morgue")
136
+ end
137
+
138
+ post "/morgue/all/delete" do
139
+ Sidekiq::DeadSet.new.clear
140
+
141
+ redirect "#{root_path}morgue"
142
+ end
143
+
144
+ post "/morgue/all/retry" do
145
+ Sidekiq::DeadSet.new.retry_all
146
+
147
+ redirect "#{root_path}morgue"
148
+ end
149
+
150
+ post "/morgue/:key" do
151
+ key = route_params[:key]
152
+ halt(404) unless key
153
+
154
+ job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
155
+ retry_or_delete_or_kill job, params if job
156
+
157
+ redirect_with_query("#{root_path}morgue")
158
+ end
159
+
160
+ get "/retries" do
161
+ @count = (params["count"] || 25).to_i
162
+ (@current_page, @total_size, @retries) = page("retry", params["page"], @count)
163
+ @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
164
+
165
+ erb(:retries)
166
+ end
167
+
168
+ get "/retries/:key" do
169
+ @retry = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
170
+
171
+ if @retry.nil?
172
+ redirect "#{root_path}retries"
173
+ else
174
+ erb(:retry)
175
+ end
176
+ end
177
+
178
+ post "/retries" do
179
+ redirect(request.path) unless params["key"]
180
+
181
+ params["key"].each do |key|
182
+ job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first
183
+ retry_or_delete_or_kill job, params if job
184
+ end
185
+
186
+ redirect_with_query("#{root_path}retries")
187
+ end
188
+
189
+ post "/retries/all/delete" do
190
+ Sidekiq::RetrySet.new.clear
191
+
192
+ redirect "#{root_path}retries"
193
+ end
194
+
195
+ post "/retries/all/retry" do
196
+ Sidekiq::RetrySet.new.retry_all
197
+
198
+ redirect "#{root_path}retries"
199
+ end
200
+
201
+ post "/retries/all/kill" do
202
+ Sidekiq::RetrySet.new.kill_all
203
+
204
+ redirect "#{root_path}retries"
205
+ end
206
+
207
+ post "/retries/:key" do
208
+ job = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
209
+
210
+ retry_or_delete_or_kill job, params if job
211
+
212
+ redirect_with_query("#{root_path}retries")
213
+ end
214
+
215
+ get "/scheduled" do
216
+ @count = (params["count"] || 25).to_i
217
+ (@current_page, @total_size, @scheduled) = page("schedule", params["page"], @count)
218
+ @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
219
+
220
+ erb(:scheduled)
221
+ end
222
+
223
+ get "/scheduled/:key" do
224
+ @job = Sidekiq::ScheduledSet.new.fetch(*parse_params(route_params[:key])).first
225
+
226
+ if @job.nil?
227
+ redirect "#{root_path}scheduled"
228
+ else
229
+ erb(:scheduled_job_info)
230
+ end
231
+ end
232
+
233
+ post "/scheduled" do
234
+ redirect(request.path) unless params["key"]
235
+
236
+ params["key"].each do |key|
237
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
238
+ delete_or_add_queue job, params if job
239
+ end
240
+
241
+ redirect_with_query("#{root_path}scheduled")
242
+ end
243
+
244
+ post "/scheduled/:key" do
245
+ key = route_params[:key]
246
+ halt(404) unless key
247
+
248
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
249
+ delete_or_add_queue job, params if job
250
+
251
+ redirect_with_query("#{root_path}scheduled")
252
+ end
253
+
254
+ get "/dashboard/stats" do
255
+ redirect "#{root_path}stats"
256
+ end
257
+
258
+ get "/stats" do
259
+ sidekiq_stats = Sidekiq::Stats.new
260
+ redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
261
+ json(
262
+ sidekiq: {
263
+ processed: sidekiq_stats.processed,
264
+ failed: sidekiq_stats.failed,
265
+ busy: sidekiq_stats.workers_size,
266
+ processes: sidekiq_stats.processes_size,
267
+ enqueued: sidekiq_stats.enqueued,
268
+ scheduled: sidekiq_stats.scheduled_size,
269
+ retries: sidekiq_stats.retry_size,
270
+ dead: sidekiq_stats.dead_size,
271
+ default_latency: sidekiq_stats.default_queue_latency,
272
+ },
273
+ redis: redis_stats,
274
+ server_utc_time: server_utc_time
275
+ )
276
+ end
277
+
278
+ get "/stats/queues" do
279
+ json Sidekiq::Stats::Queues.new.lengths
280
+ end
281
+
282
+ def call(env)
283
+ action = self.class.match(env)
284
+ return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found"]] unless action
285
+
286
+ resp = catch(:halt) {
287
+ app = @klass
288
+ self.class.run_befores(app, action)
289
+ begin
290
+ resp = action.instance_exec env, &action.block
291
+ ensure
292
+ self.class.run_afters(app, action)
293
+ end
294
+
295
+ resp
296
+ }
297
+
298
+ resp = case resp
299
+ when Array
300
+ resp
301
+ else
302
+ headers = {
303
+ "Content-Type" => "text/html",
304
+ "Cache-Control" => "no-cache",
305
+ "Content-Language" => action.locale,
306
+ "Content-Security-Policy" => CSP_HEADER,
307
+ }
308
+
309
+ [200, headers, [resp]]
310
+ end
311
+
312
+ resp[1] = resp[1].dup
313
+
314
+ resp[1][CONTENT_LENGTH] = resp[2].inject(0) { |l, p| l + p.bytesize }.to_s
315
+
316
+ resp
317
+ end
318
+
319
+ def self.helpers(mod = nil, &block)
320
+ if block_given?
321
+ WebAction.class_eval(&block)
322
+ else
323
+ WebAction.send(:include, mod)
324
+ end
325
+ end
326
+
327
+ def self.before(path = nil, &block)
328
+ befores << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
329
+ end
330
+
331
+ def self.after(path = nil, &block)
332
+ afters << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
333
+ end
334
+
335
+ def self.run_befores(app, action)
336
+ run_hooks(befores, app, action)
337
+ end
338
+
339
+ def self.run_afters(app, action)
340
+ run_hooks(afters, app, action)
341
+ end
342
+
343
+ def self.run_hooks(hooks, app, action)
344
+ hooks.select { |p, _| !p || p =~ action.env[WebRouter::PATH_INFO] }
345
+ .each { |_, b| action.instance_exec(action.env, app, &b) }
346
+ end
347
+
348
+ def self.befores
349
+ @befores ||= []
350
+ end
351
+
352
+ def self.afters
353
+ @afters ||= []
354
+ end
355
+ end
356
+ end