sidekiq 6.0.4

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 (122) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +82 -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 +72 -0
  11. data/COMM-LICENSE +97 -0
  12. data/Changes.md +1666 -0
  13. data/Ent-2.0-Upgrade.md +37 -0
  14. data/Ent-Changes.md +256 -0
  15. data/Gemfile +24 -0
  16. data/Gemfile.lock +199 -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 +776 -0
  23. data/README.md +97 -0
  24. data/Rakefile +10 -0
  25. data/bin/sidekiq +18 -0
  26. data/bin/sidekiqload +157 -0
  27. data/bin/sidekiqmon +8 -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 +57 -0
  33. data/lib/sidekiq.rb +260 -0
  34. data/lib/sidekiq/api.rb +960 -0
  35. data/lib/sidekiq/cli.rb +387 -0
  36. data/lib/sidekiq/client.rb +256 -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 +63 -0
  45. data/lib/sidekiq/job_retry.rb +262 -0
  46. data/lib/sidekiq/launcher.rb +179 -0
  47. data/lib/sidekiq/logger.rb +165 -0
  48. data/lib/sidekiq/manager.rb +135 -0
  49. data/lib/sidekiq/middleware/chain.rb +160 -0
  50. data/lib/sidekiq/middleware/i18n.rb +40 -0
  51. data/lib/sidekiq/monitor.rb +133 -0
  52. data/lib/sidekiq/paginator.rb +47 -0
  53. data/lib/sidekiq/processor.rb +280 -0
  54. data/lib/sidekiq/rails.rb +52 -0
  55. data/lib/sidekiq/redis_connection.rb +141 -0
  56. data/lib/sidekiq/scheduled.rb +173 -0
  57. data/lib/sidekiq/testing.rb +344 -0
  58. data/lib/sidekiq/testing/inline.rb +30 -0
  59. data/lib/sidekiq/util.rb +67 -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 +359 -0
  64. data/lib/sidekiq/web/helpers.rb +336 -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-dark.css +125 -0
  74. data/web/assets/stylesheets/application-rtl.css +246 -0
  75. data/web/assets/stylesheets/application.css +1153 -0
  76. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  77. data/web/assets/stylesheets/bootstrap.css +5 -0
  78. data/web/locales/ar.yml +81 -0
  79. data/web/locales/cs.yml +78 -0
  80. data/web/locales/da.yml +68 -0
  81. data/web/locales/de.yml +81 -0
  82. data/web/locales/el.yml +68 -0
  83. data/web/locales/en.yml +83 -0
  84. data/web/locales/es.yml +70 -0
  85. data/web/locales/fa.yml +80 -0
  86. data/web/locales/fr.yml +78 -0
  87. data/web/locales/he.yml +79 -0
  88. data/web/locales/hi.yml +75 -0
  89. data/web/locales/it.yml +69 -0
  90. data/web/locales/ja.yml +81 -0
  91. data/web/locales/ko.yml +68 -0
  92. data/web/locales/nb.yml +77 -0
  93. data/web/locales/nl.yml +68 -0
  94. data/web/locales/pl.yml +59 -0
  95. data/web/locales/pt-br.yml +68 -0
  96. data/web/locales/pt.yml +67 -0
  97. data/web/locales/ru.yml +78 -0
  98. data/web/locales/sv.yml +68 -0
  99. data/web/locales/ta.yml +75 -0
  100. data/web/locales/uk.yml +76 -0
  101. data/web/locales/ur.yml +80 -0
  102. data/web/locales/zh-cn.yml +68 -0
  103. data/web/locales/zh-tw.yml +68 -0
  104. data/web/views/_footer.erb +20 -0
  105. data/web/views/_job_info.erb +89 -0
  106. data/web/views/_nav.erb +52 -0
  107. data/web/views/_paging.erb +23 -0
  108. data/web/views/_poll_link.erb +7 -0
  109. data/web/views/_status.erb +4 -0
  110. data/web/views/_summary.erb +40 -0
  111. data/web/views/busy.erb +101 -0
  112. data/web/views/dashboard.erb +75 -0
  113. data/web/views/dead.erb +34 -0
  114. data/web/views/layout.erb +41 -0
  115. data/web/views/morgue.erb +78 -0
  116. data/web/views/queue.erb +55 -0
  117. data/web/views/queues.erb +38 -0
  118. data/web/views/retries.erb +83 -0
  119. data/web/views/retry.erb +34 -0
  120. data/web/views/scheduled.erb +57 -0
  121. data/web/views/scheduled_job_info.erb +8 -0
  122. metadata +221 -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,67 @@
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
+ def watchdog(last_words)
15
+ yield
16
+ rescue Exception => ex
17
+ handle_exception(ex, {context: last_words})
18
+ raise ex
19
+ end
20
+
21
+ def safe_thread(name, &block)
22
+ Thread.new do
23
+ Thread.current.name = name
24
+ watchdog(name, &block)
25
+ end
26
+ end
27
+
28
+ def logger
29
+ Sidekiq.logger
30
+ end
31
+
32
+ def redis(&block)
33
+ Sidekiq.redis(&block)
34
+ end
35
+
36
+ def tid
37
+ Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
38
+ end
39
+
40
+ def hostname
41
+ ENV["DYNO"] || Socket.gethostname
42
+ end
43
+
44
+ def process_nonce
45
+ @@process_nonce ||= SecureRandom.hex(6)
46
+ end
47
+
48
+ def identity
49
+ @@identity ||= "#{hostname}:#{::Process.pid}:#{process_nonce}"
50
+ end
51
+
52
+ def fire_event(event, options = {})
53
+ reverse = options[:reverse]
54
+ reraise = options[:reraise]
55
+
56
+ arr = Sidekiq.options[:lifecycle_events][event]
57
+ arr.reverse! if reverse
58
+ arr.each do |block|
59
+ block.call
60
+ rescue => ex
61
+ handle_exception(ex, {context: "Exception during Sidekiq lifecycle event.", event: event})
62
+ raise ex if reraise
63
+ end
64
+ arr.clear
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ VERSION = "6.0.4"
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,359 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ class WebApplication
5
+ extend WebRouter
6
+
7
+ CONTENT_LENGTH = "Content-Length"
8
+ REDIS_KEYS = %w[redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human]
9
+ CSP_HEADER = [
10
+ "default-src 'self' https: http:",
11
+ "child-src 'self'",
12
+ "connect-src 'self' https: http: wss: ws:",
13
+ "font-src 'self' https: http:",
14
+ "frame-src 'self'",
15
+ "img-src 'self' https: http: data:",
16
+ "manifest-src 'self'",
17
+ "media-src 'self'",
18
+ "object-src 'none'",
19
+ "script-src 'self' https: http: 'unsafe-inline'",
20
+ "style-src 'self' https: http: 'unsafe-inline'",
21
+ "worker-src 'self'",
22
+ "base-uri 'self'",
23
+ ].join("; ").freeze
24
+
25
+ def initialize(klass)
26
+ @klass = klass
27
+ end
28
+
29
+ def settings
30
+ @klass.settings
31
+ end
32
+
33
+ def self.settings
34
+ Sidekiq::Web.settings
35
+ end
36
+
37
+ def self.tabs
38
+ Sidekiq::Web.tabs
39
+ end
40
+
41
+ def self.set(key, val)
42
+ # nothing, backwards compatibility
43
+ end
44
+
45
+ get "/" do
46
+ @redis_info = redis_info.select { |k, v| REDIS_KEYS.include? k }
47
+ stats_history = Sidekiq::Stats::History.new((params["days"] || 30).to_i)
48
+ @processed_history = stats_history.processed
49
+ @failed_history = stats_history.failed
50
+
51
+ erb(:dashboard)
52
+ end
53
+
54
+ get "/busy" do
55
+ erb(:busy)
56
+ end
57
+
58
+ post "/busy" do
59
+ if params["identity"]
60
+ p = Sidekiq::Process.new("identity" => params["identity"])
61
+ p.quiet! if params["quiet"]
62
+ p.stop! if params["stop"]
63
+ else
64
+ processes.each do |pro|
65
+ pro.quiet! if params["quiet"]
66
+ pro.stop! if params["stop"]
67
+ end
68
+ end
69
+
70
+ redirect "#{root_path}busy"
71
+ end
72
+
73
+ get "/queues" do
74
+ @queues = Sidekiq::Queue.all
75
+
76
+ erb(:queues)
77
+ end
78
+
79
+ get "/queues/:name" do
80
+ @name = route_params[:name]
81
+
82
+ halt(404) unless @name
83
+
84
+ @count = (params["count"] || 25).to_i
85
+ @queue = Sidekiq::Queue.new(@name)
86
+ (@current_page, @total_size, @messages) = page("queue:#{@name}", params["page"], @count, reverse: params["direction"] == "asc")
87
+ @messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) }
88
+
89
+ erb(:queue)
90
+ end
91
+
92
+ post "/queues/:name" do
93
+ queue = Sidekiq::Queue.new(route_params[:name])
94
+
95
+ if Sidekiq.pro? && params["pause"]
96
+ queue.pause!
97
+ elsif Sidekiq.pro? && params["unpause"]
98
+ queue.unpause!
99
+ else
100
+ queue.clear
101
+ end
102
+
103
+ redirect "#{root_path}queues"
104
+ end
105
+
106
+ post "/queues/:name/delete" do
107
+ name = route_params[:name]
108
+ Sidekiq::Job.new(params["key_val"], name).delete
109
+
110
+ redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
111
+ end
112
+
113
+ get "/morgue" do
114
+ @count = (params["count"] || 25).to_i
115
+ (@current_page, @total_size, @dead) = page("dead", params["page"], @count, reverse: true)
116
+ @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
117
+
118
+ erb(:morgue)
119
+ end
120
+
121
+ get "/morgue/:key" do
122
+ key = route_params[:key]
123
+ halt(404) unless key
124
+
125
+ @dead = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
126
+
127
+ if @dead.nil?
128
+ redirect "#{root_path}morgue"
129
+ else
130
+ erb(:dead)
131
+ end
132
+ end
133
+
134
+ post "/morgue" do
135
+ redirect(request.path) unless params["key"]
136
+
137
+ params["key"].each do |key|
138
+ job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
139
+ retry_or_delete_or_kill job, params if job
140
+ end
141
+
142
+ redirect_with_query("#{root_path}morgue")
143
+ end
144
+
145
+ post "/morgue/all/delete" do
146
+ Sidekiq::DeadSet.new.clear
147
+
148
+ redirect "#{root_path}morgue"
149
+ end
150
+
151
+ post "/morgue/all/retry" do
152
+ Sidekiq::DeadSet.new.retry_all
153
+
154
+ redirect "#{root_path}morgue"
155
+ end
156
+
157
+ post "/morgue/:key" do
158
+ key = route_params[:key]
159
+ halt(404) unless key
160
+
161
+ job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
162
+ retry_or_delete_or_kill job, params if job
163
+
164
+ redirect_with_query("#{root_path}morgue")
165
+ end
166
+
167
+ get "/retries" do
168
+ @count = (params["count"] || 25).to_i
169
+ (@current_page, @total_size, @retries) = page("retry", params["page"], @count)
170
+ @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
171
+
172
+ erb(:retries)
173
+ end
174
+
175
+ get "/retries/:key" do
176
+ @retry = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
177
+
178
+ if @retry.nil?
179
+ redirect "#{root_path}retries"
180
+ else
181
+ erb(:retry)
182
+ end
183
+ end
184
+
185
+ post "/retries" do
186
+ redirect(request.path) unless params["key"]
187
+
188
+ params["key"].each do |key|
189
+ job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first
190
+ retry_or_delete_or_kill job, params if job
191
+ end
192
+
193
+ redirect_with_query("#{root_path}retries")
194
+ end
195
+
196
+ post "/retries/all/delete" do
197
+ Sidekiq::RetrySet.new.clear
198
+
199
+ redirect "#{root_path}retries"
200
+ end
201
+
202
+ post "/retries/all/retry" do
203
+ Sidekiq::RetrySet.new.retry_all
204
+
205
+ redirect "#{root_path}retries"
206
+ end
207
+
208
+ post "/retries/all/kill" do
209
+ Sidekiq::RetrySet.new.kill_all
210
+
211
+ redirect "#{root_path}retries"
212
+ end
213
+
214
+ post "/retries/:key" do
215
+ job = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
216
+
217
+ retry_or_delete_or_kill job, params if job
218
+
219
+ redirect_with_query("#{root_path}retries")
220
+ end
221
+
222
+ get "/scheduled" do
223
+ @count = (params["count"] || 25).to_i
224
+ (@current_page, @total_size, @scheduled) = page("schedule", params["page"], @count)
225
+ @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
226
+
227
+ erb(:scheduled)
228
+ end
229
+
230
+ get "/scheduled/:key" do
231
+ @job = Sidekiq::ScheduledSet.new.fetch(*parse_params(route_params[:key])).first
232
+
233
+ if @job.nil?
234
+ redirect "#{root_path}scheduled"
235
+ else
236
+ erb(:scheduled_job_info)
237
+ end
238
+ end
239
+
240
+ post "/scheduled" do
241
+ redirect(request.path) unless params["key"]
242
+
243
+ params["key"].each do |key|
244
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
245
+ delete_or_add_queue job, params if job
246
+ end
247
+
248
+ redirect_with_query("#{root_path}scheduled")
249
+ end
250
+
251
+ post "/scheduled/:key" do
252
+ key = route_params[:key]
253
+ halt(404) unless key
254
+
255
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
256
+ delete_or_add_queue job, params if job
257
+
258
+ redirect_with_query("#{root_path}scheduled")
259
+ end
260
+
261
+ get "/dashboard/stats" do
262
+ redirect "#{root_path}stats"
263
+ end
264
+
265
+ get "/stats" do
266
+ sidekiq_stats = Sidekiq::Stats.new
267
+ redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
268
+ json(
269
+ sidekiq: {
270
+ processed: sidekiq_stats.processed,
271
+ failed: sidekiq_stats.failed,
272
+ busy: sidekiq_stats.workers_size,
273
+ processes: sidekiq_stats.processes_size,
274
+ enqueued: sidekiq_stats.enqueued,
275
+ scheduled: sidekiq_stats.scheduled_size,
276
+ retries: sidekiq_stats.retry_size,
277
+ dead: sidekiq_stats.dead_size,
278
+ default_latency: sidekiq_stats.default_queue_latency,
279
+ },
280
+ redis: redis_stats,
281
+ server_utc_time: server_utc_time
282
+ )
283
+ end
284
+
285
+ get "/stats/queues" do
286
+ json Sidekiq::Stats::Queues.new.lengths
287
+ end
288
+
289
+ def call(env)
290
+ action = self.class.match(env)
291
+ return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found"]] unless action
292
+
293
+ app = @klass
294
+ resp = catch(:halt) do # rubocop:disable Standard/SemanticBlocks
295
+ self.class.run_befores(app, action)
296
+ action.instance_exec env, &action.block
297
+ ensure
298
+ self.class.run_afters(app, action)
299
+ end
300
+
301
+ resp = case resp
302
+ when Array
303
+ resp
304
+ else
305
+ headers = {
306
+ "Content-Type" => "text/html",
307
+ "Cache-Control" => "no-cache",
308
+ "Content-Language" => action.locale,
309
+ "Content-Security-Policy" => CSP_HEADER,
310
+ }
311
+
312
+ [200, headers, [resp]]
313
+ end
314
+
315
+ resp[1] = resp[1].dup
316
+
317
+ resp[1][CONTENT_LENGTH] = resp[2].sum(&:bytesize).to_s
318
+
319
+ resp
320
+ end
321
+
322
+ def self.helpers(mod = nil, &block)
323
+ if block_given?
324
+ WebAction.class_eval(&block)
325
+ else
326
+ WebAction.send(:include, mod)
327
+ end
328
+ end
329
+
330
+ def self.before(path = nil, &block)
331
+ befores << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
332
+ end
333
+
334
+ def self.after(path = nil, &block)
335
+ afters << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
336
+ end
337
+
338
+ def self.run_befores(app, action)
339
+ run_hooks(befores, app, action)
340
+ end
341
+
342
+ def self.run_afters(app, action)
343
+ run_hooks(afters, app, action)
344
+ end
345
+
346
+ def self.run_hooks(hooks, app, action)
347
+ hooks.select { |p, _| !p || p =~ action.env[WebRouter::PATH_INFO] }
348
+ .each { |_, b| action.instance_exec(action.env, app, &b) }
349
+ end
350
+
351
+ def self.befores
352
+ @befores ||= []
353
+ end
354
+
355
+ def self.afters
356
+ @afters ||= []
357
+ end
358
+ end
359
+ end