sidekiq 3.4.1 → 7.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (235) hide show
  1. checksums.yaml +5 -5
  2. data/Changes.md +1118 -4
  3. data/LICENSE.txt +9 -0
  4. data/README.md +55 -47
  5. data/bin/multi_queue_bench +271 -0
  6. data/bin/sidekiq +26 -3
  7. data/bin/sidekiqload +247 -0
  8. data/bin/sidekiqmon +11 -0
  9. data/lib/generators/sidekiq/job_generator.rb +57 -0
  10. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  11. data/lib/generators/sidekiq/templates/job_spec.rb.erb +6 -0
  12. data/lib/generators/sidekiq/templates/job_test.rb.erb +8 -0
  13. data/lib/sidekiq/api.rb +714 -312
  14. data/lib/sidekiq/capsule.rb +130 -0
  15. data/lib/sidekiq/cli.rb +275 -241
  16. data/lib/sidekiq/client.rb +141 -110
  17. data/lib/sidekiq/component.rb +68 -0
  18. data/lib/sidekiq/config.rb +291 -0
  19. data/lib/sidekiq/deploy.rb +62 -0
  20. data/lib/sidekiq/embedded.rb +61 -0
  21. data/lib/sidekiq/fetch.rb +53 -121
  22. data/lib/sidekiq/iterable_job.rb +53 -0
  23. data/lib/sidekiq/job/interrupt_handler.rb +22 -0
  24. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  25. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  26. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  27. data/lib/sidekiq/job/iterable.rb +231 -0
  28. data/lib/sidekiq/job.rb +385 -0
  29. data/lib/sidekiq/job_logger.rb +64 -0
  30. data/lib/sidekiq/job_retry.rb +305 -0
  31. data/lib/sidekiq/job_util.rb +107 -0
  32. data/lib/sidekiq/launcher.rb +241 -66
  33. data/lib/sidekiq/logger.rb +131 -0
  34. data/lib/sidekiq/manager.rb +91 -192
  35. data/lib/sidekiq/metrics/query.rb +156 -0
  36. data/lib/sidekiq/metrics/shared.rb +95 -0
  37. data/lib/sidekiq/metrics/tracking.rb +140 -0
  38. data/lib/sidekiq/middleware/chain.rb +114 -56
  39. data/lib/sidekiq/middleware/current_attributes.rb +111 -0
  40. data/lib/sidekiq/middleware/i18n.rb +8 -7
  41. data/lib/sidekiq/middleware/modules.rb +21 -0
  42. data/lib/sidekiq/monitor.rb +146 -0
  43. data/lib/sidekiq/paginator.rb +29 -16
  44. data/lib/sidekiq/processor.rb +248 -112
  45. data/lib/sidekiq/rails.rb +61 -27
  46. data/lib/sidekiq/redis_client_adapter.rb +114 -0
  47. data/lib/sidekiq/redis_connection.rb +68 -48
  48. data/lib/sidekiq/ring_buffer.rb +29 -0
  49. data/lib/sidekiq/scheduled.rb +173 -52
  50. data/lib/sidekiq/sd_notify.rb +149 -0
  51. data/lib/sidekiq/systemd.rb +24 -0
  52. data/lib/sidekiq/testing/inline.rb +7 -5
  53. data/lib/sidekiq/testing.rb +206 -65
  54. data/lib/sidekiq/transaction_aware_client.rb +51 -0
  55. data/lib/sidekiq/version.rb +4 -1
  56. data/lib/sidekiq/web/action.rb +99 -0
  57. data/lib/sidekiq/web/application.rb +479 -0
  58. data/lib/sidekiq/web/csrf_protection.rb +183 -0
  59. data/lib/sidekiq/web/helpers.rb +415 -0
  60. data/lib/sidekiq/web/router.rb +104 -0
  61. data/lib/sidekiq/web.rb +158 -200
  62. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  63. data/lib/sidekiq.rb +100 -132
  64. data/sidekiq.gemspec +27 -23
  65. data/web/assets/images/apple-touch-icon.png +0 -0
  66. data/web/assets/images/favicon.ico +0 -0
  67. data/web/assets/javascripts/application.js +177 -72
  68. data/web/assets/javascripts/base-charts.js +106 -0
  69. data/web/assets/javascripts/chart.min.js +13 -0
  70. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  71. data/web/assets/javascripts/dashboard-charts.js +192 -0
  72. data/web/assets/javascripts/dashboard.js +37 -286
  73. data/web/assets/javascripts/metrics.js +298 -0
  74. data/web/assets/stylesheets/application-dark.css +147 -0
  75. data/web/assets/stylesheets/application-rtl.css +163 -0
  76. data/web/assets/stylesheets/application.css +228 -247
  77. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  78. data/web/assets/stylesheets/bootstrap.css +4 -8
  79. data/web/locales/ar.yml +87 -0
  80. data/web/locales/cs.yml +62 -52
  81. data/web/locales/da.yml +60 -53
  82. data/web/locales/de.yml +65 -53
  83. data/web/locales/el.yml +43 -24
  84. data/web/locales/en.yml +86 -61
  85. data/web/locales/es.yml +70 -53
  86. data/web/locales/fa.yml +80 -0
  87. data/web/locales/fr.yml +86 -56
  88. data/web/locales/gd.yml +99 -0
  89. data/web/locales/he.yml +80 -0
  90. data/web/locales/hi.yml +59 -59
  91. data/web/locales/it.yml +53 -53
  92. data/web/locales/ja.yml +78 -56
  93. data/web/locales/ko.yml +52 -52
  94. data/web/locales/lt.yml +83 -0
  95. data/web/locales/{no.yml → nb.yml} +62 -54
  96. data/web/locales/nl.yml +52 -52
  97. data/web/locales/pl.yml +45 -45
  98. data/web/locales/pt-br.yml +83 -55
  99. data/web/locales/pt.yml +51 -51
  100. data/web/locales/ru.yml +68 -60
  101. data/web/locales/sv.yml +53 -53
  102. data/web/locales/ta.yml +60 -60
  103. data/web/locales/tr.yml +101 -0
  104. data/web/locales/uk.yml +77 -0
  105. data/web/locales/ur.yml +80 -0
  106. data/web/locales/vi.yml +83 -0
  107. data/web/locales/zh-cn.yml +43 -16
  108. data/web/locales/zh-tw.yml +42 -8
  109. data/web/views/_footer.erb +22 -9
  110. data/web/views/_job_info.erb +27 -6
  111. data/web/views/_metrics_period_select.erb +12 -0
  112. data/web/views/_nav.erb +8 -22
  113. data/web/views/_paging.erb +3 -1
  114. data/web/views/_poll_link.erb +4 -0
  115. data/web/views/_summary.erb +7 -7
  116. data/web/views/busy.erb +91 -31
  117. data/web/views/dashboard.erb +52 -22
  118. data/web/views/dead.erb +5 -4
  119. data/web/views/filtering.erb +7 -0
  120. data/web/views/layout.erb +19 -7
  121. data/web/views/metrics.erb +91 -0
  122. data/web/views/metrics_for_job.erb +59 -0
  123. data/web/views/morgue.erb +26 -20
  124. data/web/views/queue.erb +36 -25
  125. data/web/views/queues.erb +24 -7
  126. data/web/views/retries.erb +29 -21
  127. data/web/views/retry.erb +6 -5
  128. data/web/views/scheduled.erb +20 -17
  129. data/web/views/scheduled_job_info.erb +2 -1
  130. metadata +101 -232
  131. data/.gitignore +0 -12
  132. data/.travis.yml +0 -16
  133. data/3.0-Upgrade.md +0 -70
  134. data/COMM-LICENSE +0 -85
  135. data/Contributing.md +0 -32
  136. data/Gemfile +0 -22
  137. data/LICENSE +0 -9
  138. data/Pro-2.0-Upgrade.md +0 -138
  139. data/Pro-Changes.md +0 -412
  140. data/Rakefile +0 -9
  141. data/bin/sidekiqctl +0 -93
  142. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +0 -6
  143. data/lib/generators/sidekiq/templates/worker_test.rb.erb +0 -8
  144. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  145. data/lib/sidekiq/actor.rb +0 -39
  146. data/lib/sidekiq/core_ext.rb +0 -105
  147. data/lib/sidekiq/exception_handler.rb +0 -30
  148. data/lib/sidekiq/extensions/action_mailer.rb +0 -56
  149. data/lib/sidekiq/extensions/active_record.rb +0 -39
  150. data/lib/sidekiq/extensions/class_methods.rb +0 -39
  151. data/lib/sidekiq/extensions/generic_proxy.rb +0 -24
  152. data/lib/sidekiq/logging.rb +0 -104
  153. data/lib/sidekiq/middleware/server/active_record.rb +0 -13
  154. data/lib/sidekiq/middleware/server/logging.rb +0 -35
  155. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -206
  156. data/lib/sidekiq/util.rb +0 -55
  157. data/lib/sidekiq/web_helpers.rb +0 -234
  158. data/lib/sidekiq/worker.rb +0 -89
  159. data/test/config.yml +0 -9
  160. data/test/env_based_config.yml +0 -11
  161. data/test/fake_env.rb +0 -0
  162. data/test/fixtures/en.yml +0 -2
  163. data/test/helper.rb +0 -39
  164. data/test/test_api.rb +0 -494
  165. data/test/test_cli.rb +0 -365
  166. data/test/test_client.rb +0 -269
  167. data/test/test_exception_handler.rb +0 -55
  168. data/test/test_extensions.rb +0 -120
  169. data/test/test_fetch.rb +0 -104
  170. data/test/test_logging.rb +0 -34
  171. data/test/test_manager.rb +0 -164
  172. data/test/test_middleware.rb +0 -159
  173. data/test/test_processor.rb +0 -166
  174. data/test/test_redis_connection.rb +0 -127
  175. data/test/test_retry.rb +0 -373
  176. data/test/test_scheduled.rb +0 -120
  177. data/test/test_scheduling.rb +0 -71
  178. data/test/test_sidekiq.rb +0 -69
  179. data/test/test_testing.rb +0 -82
  180. data/test/test_testing_fake.rb +0 -271
  181. data/test/test_testing_inline.rb +0 -93
  182. data/test/test_web.rb +0 -594
  183. data/test/test_web_helpers.rb +0 -52
  184. data/test/test_worker_generator.rb +0 -17
  185. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  186. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  187. data/web/assets/images/status/active.png +0 -0
  188. data/web/assets/images/status/idle.png +0 -0
  189. data/web/assets/javascripts/locales/README.md +0 -27
  190. data/web/assets/javascripts/locales/jquery.timeago.ar.js +0 -96
  191. data/web/assets/javascripts/locales/jquery.timeago.bg.js +0 -18
  192. data/web/assets/javascripts/locales/jquery.timeago.bs.js +0 -49
  193. data/web/assets/javascripts/locales/jquery.timeago.ca.js +0 -18
  194. data/web/assets/javascripts/locales/jquery.timeago.cs.js +0 -18
  195. data/web/assets/javascripts/locales/jquery.timeago.cy.js +0 -20
  196. data/web/assets/javascripts/locales/jquery.timeago.da.js +0 -18
  197. data/web/assets/javascripts/locales/jquery.timeago.de.js +0 -18
  198. data/web/assets/javascripts/locales/jquery.timeago.el.js +0 -18
  199. data/web/assets/javascripts/locales/jquery.timeago.en-short.js +0 -20
  200. data/web/assets/javascripts/locales/jquery.timeago.en.js +0 -20
  201. data/web/assets/javascripts/locales/jquery.timeago.es.js +0 -18
  202. data/web/assets/javascripts/locales/jquery.timeago.et.js +0 -18
  203. data/web/assets/javascripts/locales/jquery.timeago.fa.js +0 -22
  204. data/web/assets/javascripts/locales/jquery.timeago.fi.js +0 -28
  205. data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +0 -16
  206. data/web/assets/javascripts/locales/jquery.timeago.fr.js +0 -17
  207. data/web/assets/javascripts/locales/jquery.timeago.he.js +0 -18
  208. data/web/assets/javascripts/locales/jquery.timeago.hr.js +0 -49
  209. data/web/assets/javascripts/locales/jquery.timeago.hu.js +0 -18
  210. data/web/assets/javascripts/locales/jquery.timeago.hy.js +0 -18
  211. data/web/assets/javascripts/locales/jquery.timeago.id.js +0 -18
  212. data/web/assets/javascripts/locales/jquery.timeago.it.js +0 -16
  213. data/web/assets/javascripts/locales/jquery.timeago.ja.js +0 -19
  214. data/web/assets/javascripts/locales/jquery.timeago.ko.js +0 -17
  215. data/web/assets/javascripts/locales/jquery.timeago.lt.js +0 -20
  216. data/web/assets/javascripts/locales/jquery.timeago.mk.js +0 -20
  217. data/web/assets/javascripts/locales/jquery.timeago.nl.js +0 -20
  218. data/web/assets/javascripts/locales/jquery.timeago.no.js +0 -18
  219. data/web/assets/javascripts/locales/jquery.timeago.pl.js +0 -31
  220. data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +0 -16
  221. data/web/assets/javascripts/locales/jquery.timeago.pt.js +0 -16
  222. data/web/assets/javascripts/locales/jquery.timeago.ro.js +0 -18
  223. data/web/assets/javascripts/locales/jquery.timeago.rs.js +0 -49
  224. data/web/assets/javascripts/locales/jquery.timeago.ru.js +0 -34
  225. data/web/assets/javascripts/locales/jquery.timeago.sk.js +0 -18
  226. data/web/assets/javascripts/locales/jquery.timeago.sl.js +0 -44
  227. data/web/assets/javascripts/locales/jquery.timeago.sv.js +0 -18
  228. data/web/assets/javascripts/locales/jquery.timeago.th.js +0 -20
  229. data/web/assets/javascripts/locales/jquery.timeago.tr.js +0 -16
  230. data/web/assets/javascripts/locales/jquery.timeago.uk.js +0 -34
  231. data/web/assets/javascripts/locales/jquery.timeago.uz.js +0 -19
  232. data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +0 -20
  233. data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +0 -20
  234. data/web/views/_poll.erb +0 -10
  235. /data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
@@ -0,0 +1,99 @@
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, {Rack::CONTENT_TYPE => "text/plain"}, [res.to_s]]
19
+ end
20
+
21
+ def redirect(location)
22
+ throw :halt, [302, {Web::LOCATION => "#{request.base_url}#{location}"}, []]
23
+ end
24
+
25
+ def reload_page
26
+ current_location = request.referer.gsub(request.base_url, "")
27
+ redirect current_location
28
+ end
29
+
30
+ def params
31
+ indifferent_hash = Hash.new { |hash, key| hash[key.to_s] if Symbol === key }
32
+
33
+ indifferent_hash.merge! request.params
34
+ route_params.each { |k, v| indifferent_hash[k.to_s] = v }
35
+
36
+ indifferent_hash
37
+ end
38
+
39
+ def route_params
40
+ env[WebRouter::ROUTE_PARAMS]
41
+ end
42
+
43
+ def session
44
+ env[RACK_SESSION]
45
+ end
46
+
47
+ def erb(content, options = {})
48
+ if content.is_a? Symbol
49
+ unless respond_to?(:"_erb_#{content}")
50
+ views = options[:views] || Web.settings.views
51
+ src = ERB.new(File.read("#{views}/#{content}.erb")).src
52
+ WebAction.class_eval <<-RUBY, __FILE__, __LINE__ + 1
53
+ def _erb_#{content}
54
+ #{src}
55
+ end
56
+ RUBY
57
+ end
58
+ end
59
+
60
+ if @_erb
61
+ _erb(content, options[:locals])
62
+ else
63
+ @_erb = true
64
+ content = _erb(content, options[:locals])
65
+
66
+ _render { content }
67
+ end
68
+ end
69
+
70
+ def render(engine, content, options = {})
71
+ raise "Only erb templates are supported" if engine != :erb
72
+
73
+ erb(content, options)
74
+ end
75
+
76
+ def json(payload)
77
+ [200, {Rack::CONTENT_TYPE => "application/json", Rack::CACHE_CONTROL => "private, no-store"}, [Sidekiq.dump_json(payload)]]
78
+ end
79
+
80
+ def initialize(env, block)
81
+ @_erb = false
82
+ @env = env
83
+ @block = block
84
+ @files ||= {}
85
+ end
86
+
87
+ private
88
+
89
+ def _erb(file, locals)
90
+ locals&.each { |k, v| define_singleton_method(k) { v } unless singleton_methods.include? k }
91
+
92
+ if file.is_a?(String)
93
+ ERB.new(file).result(binding)
94
+ else
95
+ send(:"_erb_#{file}")
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,479 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ class WebApplication
5
+ extend WebRouter
6
+
7
+ REDIS_KEYS = %w[redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human]
8
+ CSP_HEADER_TEMPLATE = [
9
+ "default-src 'self' https: http:",
10
+ "child-src 'self'",
11
+ "connect-src 'self' https: http: wss: ws:",
12
+ "font-src 'self' https: http:",
13
+ "frame-src 'self'",
14
+ "img-src 'self' https: http: data:",
15
+ "manifest-src 'self'",
16
+ "media-src 'self'",
17
+ "object-src 'none'",
18
+ "script-src 'self' 'nonce-!placeholder!'",
19
+ "style-src 'self' https: http: 'unsafe-inline'", # TODO Nonce in 8.0
20
+ "worker-src 'self'",
21
+ "base-uri 'self'"
22
+ ].join("; ").freeze
23
+ METRICS_PERIODS = {
24
+ "1h" => 60,
25
+ "2h" => 120,
26
+ "4h" => 240,
27
+ "8h" => 480
28
+ }
29
+
30
+ def initialize(klass)
31
+ @klass = klass
32
+ end
33
+
34
+ def settings
35
+ @klass.settings
36
+ end
37
+
38
+ def self.settings
39
+ Sidekiq::Web.settings
40
+ end
41
+
42
+ def self.tabs
43
+ Sidekiq::Web.tabs
44
+ end
45
+
46
+ def self.set(key, val)
47
+ # nothing, backwards compatibility
48
+ end
49
+
50
+ head "/" do
51
+ # HEAD / is the cheapest heartbeat possible,
52
+ # it hits Redis to ensure connectivity and returns
53
+ # the size of the default queue
54
+ Sidekiq.redis { |c| c.llen("queue:default") }.to_s
55
+ end
56
+
57
+ get "/" do
58
+ @redis_info = redis_info.select { |k, v| REDIS_KEYS.include? k }
59
+ days = (params["days"] || 30).to_i
60
+ return halt(401) if days < 1 || days > 180
61
+
62
+ stats_history = Sidekiq::Stats::History.new(days)
63
+ @processed_history = stats_history.processed
64
+ @failed_history = stats_history.failed
65
+
66
+ erb(:dashboard)
67
+ end
68
+
69
+ get "/metrics" do
70
+ q = Sidekiq::Metrics::Query.new
71
+ @period = h((params[:period] || "")[0..1])
72
+ @periods = METRICS_PERIODS
73
+ minutes = @periods.fetch(@period, @periods.values.first)
74
+ @query_result = q.top_jobs(minutes: minutes)
75
+ erb(:metrics)
76
+ end
77
+
78
+ get "/metrics/:name" do
79
+ @name = route_params[:name]
80
+ @period = h((params[:period] || "")[0..1])
81
+ q = Sidekiq::Metrics::Query.new
82
+ @periods = METRICS_PERIODS
83
+ minutes = @periods.fetch(@period, @periods.values.first)
84
+ @query_result = q.for_job(@name, minutes: minutes)
85
+ erb(:metrics_for_job)
86
+ end
87
+
88
+ get "/busy" do
89
+ @count = (params["count"] || 100).to_i
90
+ (@current_page, @total_size, @workset) = page_items(workset, params["page"], @count)
91
+
92
+ erb(:busy)
93
+ end
94
+
95
+ post "/busy" do
96
+ if params["identity"]
97
+ pro = Sidekiq::ProcessSet[params["identity"]]
98
+
99
+ pro.quiet! if params["quiet"]
100
+ pro.stop! if params["stop"]
101
+ else
102
+ processes.each do |pro|
103
+ next if pro.embedded?
104
+
105
+ pro.quiet! if params["quiet"]
106
+ pro.stop! if params["stop"]
107
+ end
108
+ end
109
+
110
+ redirect "#{root_path}busy"
111
+ end
112
+
113
+ get "/queues" do
114
+ @queues = Sidekiq::Queue.all
115
+
116
+ erb(:queues)
117
+ end
118
+
119
+ QUEUE_NAME = /\A[a-z_:.\-0-9]+\z/i
120
+
121
+ get "/queues/:name" do
122
+ @name = route_params[:name]
123
+
124
+ halt(404) if !@name || @name !~ QUEUE_NAME
125
+
126
+ @count = (params["count"] || 25).to_i
127
+ @queue = Sidekiq::Queue.new(@name)
128
+ (@current_page, @total_size, @jobs) = page("queue:#{@name}", params["page"], @count, reverse: params["direction"] == "asc")
129
+ @jobs = @jobs.map { |msg| Sidekiq::JobRecord.new(msg, @name) }
130
+
131
+ erb(:queue)
132
+ end
133
+
134
+ post "/queues/:name" do
135
+ queue = Sidekiq::Queue.new(route_params[:name])
136
+
137
+ if Sidekiq.pro? && params["pause"]
138
+ queue.pause!
139
+ elsif Sidekiq.pro? && params["unpause"]
140
+ queue.unpause!
141
+ else
142
+ queue.clear
143
+ end
144
+
145
+ redirect "#{root_path}queues"
146
+ end
147
+
148
+ post "/queues/:name/delete" do
149
+ name = route_params[:name]
150
+ Sidekiq::JobRecord.new(params["key_val"], name).delete
151
+
152
+ redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
153
+ end
154
+
155
+ get "/morgue" do
156
+ @count = (params["count"] || 25).to_i
157
+ (@current_page, @total_size, @dead) = page("dead", params["page"], @count, reverse: true)
158
+ @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
159
+
160
+ erb(:morgue)
161
+ end
162
+
163
+ get "/morgue/:key" do
164
+ key = route_params[:key]
165
+ halt(404) unless key
166
+
167
+ @dead = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
168
+
169
+ if @dead.nil?
170
+ redirect "#{root_path}morgue"
171
+ else
172
+ erb(:dead)
173
+ end
174
+ end
175
+
176
+ post "/morgue" do
177
+ redirect(request.path) unless params["key"]
178
+
179
+ params["key"].each do |key|
180
+ job = Sidekiq::DeadSet.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}morgue")
185
+ end
186
+
187
+ post "/morgue/all/delete" do
188
+ Sidekiq::DeadSet.new.clear
189
+
190
+ redirect "#{root_path}morgue"
191
+ end
192
+
193
+ post "/morgue/all/retry" do
194
+ Sidekiq::DeadSet.new.retry_all
195
+
196
+ redirect "#{root_path}morgue"
197
+ end
198
+
199
+ post "/morgue/:key" do
200
+ key = route_params[:key]
201
+ halt(404) unless key
202
+
203
+ job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
204
+ retry_or_delete_or_kill job, params if job
205
+
206
+ redirect_with_query("#{root_path}morgue")
207
+ end
208
+
209
+ get "/retries" do
210
+ @count = (params["count"] || 25).to_i
211
+ (@current_page, @total_size, @retries) = page("retry", params["page"], @count)
212
+ @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
213
+
214
+ erb(:retries)
215
+ end
216
+
217
+ get "/retries/:key" do
218
+ @retry = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
219
+
220
+ if @retry.nil?
221
+ redirect "#{root_path}retries"
222
+ else
223
+ erb(:retry)
224
+ end
225
+ end
226
+
227
+ post "/retries" do
228
+ redirect(request.path) unless params["key"]
229
+
230
+ params["key"].each do |key|
231
+ job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first
232
+ retry_or_delete_or_kill job, params if job
233
+ end
234
+
235
+ redirect_with_query("#{root_path}retries")
236
+ end
237
+
238
+ post "/retries/all/delete" do
239
+ Sidekiq::RetrySet.new.clear
240
+
241
+ redirect "#{root_path}retries"
242
+ end
243
+
244
+ post "/retries/all/retry" do
245
+ Sidekiq::RetrySet.new.retry_all
246
+
247
+ redirect "#{root_path}retries"
248
+ end
249
+
250
+ post "/retries/all/kill" do
251
+ Sidekiq::RetrySet.new.kill_all
252
+
253
+ redirect "#{root_path}retries"
254
+ end
255
+
256
+ post "/retries/:key" do
257
+ job = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
258
+
259
+ retry_or_delete_or_kill job, params if job
260
+
261
+ redirect_with_query("#{root_path}retries")
262
+ end
263
+
264
+ get "/scheduled" do
265
+ @count = (params["count"] || 25).to_i
266
+ (@current_page, @total_size, @scheduled) = page("schedule", params["page"], @count)
267
+ @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
268
+
269
+ erb(:scheduled)
270
+ end
271
+
272
+ get "/scheduled/:key" do
273
+ @job = Sidekiq::ScheduledSet.new.fetch(*parse_params(route_params[:key])).first
274
+
275
+ if @job.nil?
276
+ redirect "#{root_path}scheduled"
277
+ else
278
+ erb(:scheduled_job_info)
279
+ end
280
+ end
281
+
282
+ post "/scheduled" do
283
+ redirect(request.path) unless params["key"]
284
+
285
+ params["key"].each do |key|
286
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
287
+ delete_or_add_queue job, params if job
288
+ end
289
+
290
+ redirect_with_query("#{root_path}scheduled")
291
+ end
292
+
293
+ post "/scheduled/:key" do
294
+ key = route_params[:key]
295
+ halt(404) unless key
296
+
297
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
298
+ delete_or_add_queue job, params if job
299
+
300
+ redirect_with_query("#{root_path}scheduled")
301
+ end
302
+
303
+ get "/dashboard/stats" do
304
+ redirect "#{root_path}stats"
305
+ end
306
+
307
+ get "/stats" do
308
+ sidekiq_stats = Sidekiq::Stats.new
309
+ redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
310
+ json(
311
+ sidekiq: {
312
+ processed: sidekiq_stats.processed,
313
+ failed: sidekiq_stats.failed,
314
+ busy: sidekiq_stats.workers_size,
315
+ processes: sidekiq_stats.processes_size,
316
+ enqueued: sidekiq_stats.enqueued,
317
+ scheduled: sidekiq_stats.scheduled_size,
318
+ retries: sidekiq_stats.retry_size,
319
+ dead: sidekiq_stats.dead_size,
320
+ default_latency: sidekiq_stats.default_queue_latency
321
+ },
322
+ redis: redis_stats,
323
+ server_utc_time: server_utc_time
324
+ )
325
+ end
326
+
327
+ get "/stats/queues" do
328
+ json Sidekiq::Stats.new.queues
329
+ end
330
+
331
+ ########
332
+ # Filtering
333
+
334
+ get "/filter/metrics" do
335
+ redirect "#{root_path}metrics"
336
+ end
337
+
338
+ post "/filter/metrics" do
339
+ x = params[:substr]
340
+ q = Sidekiq::Metrics::Query.new
341
+ @period = h((params[:period] || "")[0..1])
342
+ @periods = METRICS_PERIODS
343
+ minutes = @periods.fetch(@period, @periods.values.first)
344
+ @query_result = q.top_jobs(minutes: minutes, class_filter: Regexp.new(Regexp.escape(x), Regexp::IGNORECASE))
345
+
346
+ erb :metrics
347
+ end
348
+
349
+ get "/filter/retries" do
350
+ x = params[:substr]
351
+ return redirect "#{root_path}retries" unless x && x != ""
352
+
353
+ @retries = search(Sidekiq::RetrySet.new, params[:substr])
354
+ erb :retries
355
+ end
356
+
357
+ post "/filter/retries" do
358
+ x = params[:substr]
359
+ return redirect "#{root_path}retries" unless x && x != ""
360
+
361
+ @retries = search(Sidekiq::RetrySet.new, params[:substr])
362
+ erb :retries
363
+ end
364
+
365
+ get "/filter/scheduled" do
366
+ x = params[:substr]
367
+ return redirect "#{root_path}scheduled" unless x && x != ""
368
+
369
+ @scheduled = search(Sidekiq::ScheduledSet.new, params[:substr])
370
+ erb :scheduled
371
+ end
372
+
373
+ post "/filter/scheduled" do
374
+ x = params[:substr]
375
+ return redirect "#{root_path}scheduled" unless x && x != ""
376
+
377
+ @scheduled = search(Sidekiq::ScheduledSet.new, params[:substr])
378
+ erb :scheduled
379
+ end
380
+
381
+ get "/filter/dead" do
382
+ x = params[:substr]
383
+ return redirect "#{root_path}morgue" unless x && x != ""
384
+
385
+ @dead = search(Sidekiq::DeadSet.new, params[:substr])
386
+ erb :morgue
387
+ end
388
+
389
+ post "/filter/dead" do
390
+ x = params[:substr]
391
+ return redirect "#{root_path}morgue" unless x && x != ""
392
+
393
+ @dead = search(Sidekiq::DeadSet.new, params[:substr])
394
+ erb :morgue
395
+ end
396
+
397
+ post "/change_locale" do
398
+ locale = params["locale"]
399
+
400
+ match = available_locales.find { |available|
401
+ locale == available
402
+ }
403
+
404
+ session[:locale] = match if match
405
+
406
+ reload_page
407
+ end
408
+
409
+ def call(env)
410
+ action = self.class.match(env)
411
+ return [404, {Rack::CONTENT_TYPE => "text/plain", Web::X_CASCADE => "pass"}, ["Not Found"]] unless action
412
+
413
+ app = @klass
414
+ resp = catch(:halt) do
415
+ self.class.run_befores(app, action)
416
+ action.instance_exec env, &action.block
417
+ ensure
418
+ self.class.run_afters(app, action)
419
+ end
420
+
421
+ case resp
422
+ when Array
423
+ # redirects go here
424
+ resp
425
+ else
426
+ # rendered content goes here
427
+ headers = {
428
+ Rack::CONTENT_TYPE => "text/html",
429
+ Rack::CACHE_CONTROL => "private, no-store",
430
+ Web::CONTENT_LANGUAGE => action.locale,
431
+ Web::CONTENT_SECURITY_POLICY => process_csp(env, CSP_HEADER_TEMPLATE)
432
+ }
433
+ # we'll let Rack calculate Content-Length for us.
434
+ [200, headers, [resp]]
435
+ end
436
+ end
437
+
438
+ def process_csp(env, input)
439
+ input.gsub("!placeholder!", env[:csp_nonce])
440
+ end
441
+
442
+ def self.helpers(mod = nil, &block)
443
+ if block
444
+ WebAction.class_eval(&block)
445
+ else
446
+ WebAction.send(:include, mod)
447
+ end
448
+ end
449
+
450
+ def self.before(path = nil, &block)
451
+ befores << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
452
+ end
453
+
454
+ def self.after(path = nil, &block)
455
+ afters << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
456
+ end
457
+
458
+ def self.run_befores(app, action)
459
+ run_hooks(befores, app, action)
460
+ end
461
+
462
+ def self.run_afters(app, action)
463
+ run_hooks(afters, app, action)
464
+ end
465
+
466
+ def self.run_hooks(hooks, app, action)
467
+ hooks.select { |p, _| !p || p =~ action.env[WebRouter::PATH_INFO] }
468
+ .each { |_, b| action.instance_exec(action.env, app, &b) }
469
+ end
470
+
471
+ def self.befores
472
+ @befores ||= []
473
+ end
474
+
475
+ def self.afters
476
+ @afters ||= []
477
+ end
478
+ end
479
+ end