sidekiq 6.1.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 (127) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +71 -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 +1718 -0
  13. data/Ent-2.0-Upgrade.md +37 -0
  14. data/Ent-Changes.md +269 -0
  15. data/Gemfile +24 -0
  16. data/Gemfile.lock +208 -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 +790 -0
  23. data/README.md +94 -0
  24. data/Rakefile +10 -0
  25. data/bin/sidekiq +42 -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 +262 -0
  34. data/lib/sidekiq/api.rb +960 -0
  35. data/lib/sidekiq/cli.rb +401 -0
  36. data/lib/sidekiq/client.rb +263 -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 +43 -0
  41. data/lib/sidekiq/extensions/class_methods.rb +43 -0
  42. data/lib/sidekiq/extensions/generic_proxy.rb +31 -0
  43. data/lib/sidekiq/fetch.rb +82 -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 +206 -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 +50 -0
  55. data/lib/sidekiq/redis_connection.rb +146 -0
  56. data/lib/sidekiq/scheduled.rb +173 -0
  57. data/lib/sidekiq/sd_notify.rb +149 -0
  58. data/lib/sidekiq/systemd.rb +24 -0
  59. data/lib/sidekiq/testing.rb +344 -0
  60. data/lib/sidekiq/testing/inline.rb +30 -0
  61. data/lib/sidekiq/util.rb +67 -0
  62. data/lib/sidekiq/version.rb +5 -0
  63. data/lib/sidekiq/web.rb +213 -0
  64. data/lib/sidekiq/web/action.rb +93 -0
  65. data/lib/sidekiq/web/application.rb +357 -0
  66. data/lib/sidekiq/web/csrf_protection.rb +153 -0
  67. data/lib/sidekiq/web/helpers.rb +333 -0
  68. data/lib/sidekiq/web/router.rb +101 -0
  69. data/lib/sidekiq/worker.rb +244 -0
  70. data/sidekiq.gemspec +20 -0
  71. data/web/assets/images/favicon.ico +0 -0
  72. data/web/assets/images/logo.png +0 -0
  73. data/web/assets/images/status.png +0 -0
  74. data/web/assets/javascripts/application.js +95 -0
  75. data/web/assets/javascripts/dashboard.js +296 -0
  76. data/web/assets/stylesheets/application-dark.css +133 -0
  77. data/web/assets/stylesheets/application-rtl.css +246 -0
  78. data/web/assets/stylesheets/application.css +1158 -0
  79. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  80. data/web/assets/stylesheets/bootstrap.css +5 -0
  81. data/web/locales/ar.yml +81 -0
  82. data/web/locales/cs.yml +78 -0
  83. data/web/locales/da.yml +68 -0
  84. data/web/locales/de.yml +81 -0
  85. data/web/locales/el.yml +68 -0
  86. data/web/locales/en.yml +83 -0
  87. data/web/locales/es.yml +70 -0
  88. data/web/locales/fa.yml +80 -0
  89. data/web/locales/fr.yml +78 -0
  90. data/web/locales/he.yml +79 -0
  91. data/web/locales/hi.yml +75 -0
  92. data/web/locales/it.yml +69 -0
  93. data/web/locales/ja.yml +83 -0
  94. data/web/locales/ko.yml +68 -0
  95. data/web/locales/lt.yml +83 -0
  96. data/web/locales/nb.yml +77 -0
  97. data/web/locales/nl.yml +68 -0
  98. data/web/locales/pl.yml +59 -0
  99. data/web/locales/pt-br.yml +68 -0
  100. data/web/locales/pt.yml +67 -0
  101. data/web/locales/ru.yml +78 -0
  102. data/web/locales/sv.yml +68 -0
  103. data/web/locales/ta.yml +75 -0
  104. data/web/locales/uk.yml +76 -0
  105. data/web/locales/ur.yml +80 -0
  106. data/web/locales/vi.yml +83 -0
  107. data/web/locales/zh-cn.yml +68 -0
  108. data/web/locales/zh-tw.yml +68 -0
  109. data/web/views/_footer.erb +20 -0
  110. data/web/views/_job_info.erb +89 -0
  111. data/web/views/_nav.erb +52 -0
  112. data/web/views/_paging.erb +23 -0
  113. data/web/views/_poll_link.erb +7 -0
  114. data/web/views/_status.erb +4 -0
  115. data/web/views/_summary.erb +40 -0
  116. data/web/views/busy.erb +101 -0
  117. data/web/views/dashboard.erb +75 -0
  118. data/web/views/dead.erb +34 -0
  119. data/web/views/layout.erb +41 -0
  120. data/web/views/morgue.erb +78 -0
  121. data/web/views/queue.erb +55 -0
  122. data/web/views/queues.erb +38 -0
  123. data/web/views/retries.erb +83 -0
  124. data/web/views/retry.erb +34 -0
  125. data/web/views/scheduled.erb +57 -0
  126. data/web/views/scheduled_job_info.erb +8 -0
  127. metadata +212 -0
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ VERSION = "6.1.0"
5
+ end
@@ -0,0 +1,213 @@
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
+ require "sidekiq/web/csrf_protection"
14
+
15
+ require "rack/content_length"
16
+
17
+ require "rack/builder"
18
+ require "rack/file"
19
+ require "rack/session/cookie"
20
+
21
+ module Sidekiq
22
+ class Web
23
+ ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../web")
24
+ VIEWS = "#{ROOT}/views"
25
+ LOCALES = ["#{ROOT}/locales"]
26
+ LAYOUT = "#{VIEWS}/layout.erb"
27
+ ASSETS = "#{ROOT}/assets"
28
+
29
+ DEFAULT_TABS = {
30
+ "Dashboard" => "",
31
+ "Busy" => "busy",
32
+ "Queues" => "queues",
33
+ "Retries" => "retries",
34
+ "Scheduled" => "scheduled",
35
+ "Dead" => "morgue"
36
+ }
37
+
38
+ class << self
39
+ def settings
40
+ self
41
+ end
42
+
43
+ def middlewares
44
+ @middlewares ||= []
45
+ end
46
+
47
+ def use(*middleware_args, &block)
48
+ middlewares << [middleware_args, block]
49
+ end
50
+
51
+ def default_tabs
52
+ DEFAULT_TABS
53
+ end
54
+
55
+ def custom_tabs
56
+ @custom_tabs ||= {}
57
+ end
58
+ alias_method :tabs, :custom_tabs
59
+
60
+ def locales
61
+ @locales ||= LOCALES
62
+ end
63
+
64
+ def views
65
+ @views ||= VIEWS
66
+ end
67
+
68
+ def enable(*opts)
69
+ opts.each { |key| set(key, true) }
70
+ end
71
+
72
+ def disable(*opts)
73
+ opts.each { |key| set(key, false) }
74
+ end
75
+
76
+ # Helper for the Sinatra syntax: Sidekiq::Web.set(:session_secret, Rails.application.secrets...)
77
+ def set(attribute, value)
78
+ send(:"#{attribute}=", value)
79
+ end
80
+
81
+ attr_accessor :app_url, :session_secret, :redis_pool, :sessions
82
+ attr_writer :locales, :views
83
+ end
84
+
85
+ def self.inherited(child)
86
+ child.app_url = app_url
87
+ child.session_secret = session_secret
88
+ child.redis_pool = redis_pool
89
+ child.sessions = sessions
90
+ end
91
+
92
+ def settings
93
+ self.class.settings
94
+ end
95
+
96
+ def use(*middleware_args, &block)
97
+ middlewares << [middleware_args, block]
98
+ end
99
+
100
+ def middlewares
101
+ @middlewares ||= Web.middlewares.dup
102
+ end
103
+
104
+ def call(env)
105
+ app.call(env)
106
+ end
107
+
108
+ def self.call(env)
109
+ @app ||= new
110
+ @app.call(env)
111
+ end
112
+
113
+ def app
114
+ @app ||= build
115
+ end
116
+
117
+ def enable(*opts)
118
+ opts.each { |key| set(key, true) }
119
+ end
120
+
121
+ def disable(*opts)
122
+ opts.each { |key| set(key, false) }
123
+ end
124
+
125
+ def set(attribute, value)
126
+ send(:"#{attribute}=", value)
127
+ end
128
+
129
+ # Default values
130
+ set :sessions, true
131
+
132
+ attr_writer :sessions
133
+
134
+ def sessions
135
+ unless instance_variable_defined?("@sessions")
136
+ @sessions = self.class.sessions
137
+ @sessions = @sessions.to_hash.dup if @sessions.respond_to?(:to_hash)
138
+ end
139
+
140
+ @sessions
141
+ end
142
+
143
+ def self.register(extension)
144
+ extension.registered(WebApplication)
145
+ end
146
+
147
+ private
148
+
149
+ def using?(middleware)
150
+ middlewares.any? do |(m, _)|
151
+ m.is_a?(Array) && (m[0] == middleware || m[0].is_a?(middleware))
152
+ end
153
+ end
154
+
155
+ def build_sessions
156
+ middlewares = self.middlewares
157
+
158
+ s = sessions
159
+
160
+ # turn on CSRF protection if sessions are enabled and this is not the test env
161
+ if s && !using?(CsrfProtection) && ENV["RACK_ENV"] != "test"
162
+ middlewares.unshift [[CsrfProtection], nil]
163
+ end
164
+
165
+ if s && !using?(::Rack::Session::Cookie)
166
+ unless (secret = Web.session_secret)
167
+ require "securerandom"
168
+ secret = SecureRandom.hex(64)
169
+ end
170
+
171
+ options = {secret: secret}
172
+ options = options.merge(s.to_hash) if s.respond_to? :to_hash
173
+
174
+ middlewares.unshift [[::Rack::Session::Cookie, options], nil]
175
+ end
176
+
177
+ # Since Sidekiq::WebApplication no longer calculates its own
178
+ # Content-Length response header, we must ensure that the Rack middleware
179
+ # that does this is loaded
180
+ unless using? ::Rack::ContentLength
181
+ middlewares.unshift [[::Rack::ContentLength], nil]
182
+ end
183
+ end
184
+
185
+ def build
186
+ build_sessions
187
+
188
+ middlewares = self.middlewares
189
+ klass = self.class
190
+
191
+ ::Rack::Builder.new do
192
+ %w[stylesheets javascripts images].each do |asset_dir|
193
+ map "/#{asset_dir}" do
194
+ run ::Rack::File.new("#{ASSETS}/#{asset_dir}", {"Cache-Control" => "public, max-age=86400"})
195
+ end
196
+ end
197
+
198
+ middlewares.each { |middleware, block| use(*middleware, &block) }
199
+
200
+ run WebApplication.new(klass)
201
+ end
202
+ end
203
+ end
204
+
205
+ Sidekiq::WebApplication.helpers WebHelpers
206
+ Sidekiq::WebApplication.helpers Sidekiq::Paginator
207
+
208
+ Sidekiq::WebAction.class_eval <<-RUBY, __FILE__, __LINE__ + 1
209
+ def _render
210
+ #{ERB.new(File.read(Web::LAYOUT)).src}
211
+ end
212
+ RUBY
213
+ 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,357 @@
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
+ # redirects go here
304
+ resp
305
+ else
306
+ # rendered content goes here
307
+ headers = {
308
+ "Content-Type" => "text/html",
309
+ "Cache-Control" => "no-cache",
310
+ "Content-Language" => action.locale,
311
+ "Content-Security-Policy" => CSP_HEADER
312
+ }
313
+ # we'll let Rack calculate Content-Length for us.
314
+ [200, headers, [resp]]
315
+ end
316
+
317
+ resp
318
+ end
319
+
320
+ def self.helpers(mod = nil, &block)
321
+ if block_given?
322
+ WebAction.class_eval(&block)
323
+ else
324
+ WebAction.send(:include, mod)
325
+ end
326
+ end
327
+
328
+ def self.before(path = nil, &block)
329
+ befores << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
330
+ end
331
+
332
+ def self.after(path = nil, &block)
333
+ afters << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
334
+ end
335
+
336
+ def self.run_befores(app, action)
337
+ run_hooks(befores, app, action)
338
+ end
339
+
340
+ def self.run_afters(app, action)
341
+ run_hooks(afters, app, action)
342
+ end
343
+
344
+ def self.run_hooks(hooks, app, action)
345
+ hooks.select { |p, _| !p || p =~ action.env[WebRouter::PATH_INFO] }
346
+ .each { |_, b| action.instance_exec(action.env, app, &b) }
347
+ end
348
+
349
+ def self.befores
350
+ @befores ||= []
351
+ end
352
+
353
+ def self.afters
354
+ @afters ||= []
355
+ end
356
+ end
357
+ end