ultra-fast-tool 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. checksums.yaml +7 -0
  2. data/sidekiq-8.1.6/Changes.md +2515 -0
  3. data/sidekiq-8.1.6/LICENSE.txt +9 -0
  4. data/sidekiq-8.1.6/README.md +124 -0
  5. data/sidekiq-8.1.6/bin/kiq +17 -0
  6. data/sidekiq-8.1.6/bin/lint-herb +13 -0
  7. data/sidekiq-8.1.6/bin/multi_queue_bench +271 -0
  8. data/sidekiq-8.1.6/bin/sidekiq +37 -0
  9. data/sidekiq-8.1.6/bin/sidekiqload +256 -0
  10. data/sidekiq-8.1.6/bin/sidekiqmon +11 -0
  11. data/sidekiq-8.1.6/bin/webload +69 -0
  12. data/sidekiq-8.1.6/lib/active_job/queue_adapters/sidekiq_adapter.rb +124 -0
  13. data/sidekiq-8.1.6/lib/generators/sidekiq/job_generator.rb +71 -0
  14. data/sidekiq-8.1.6/lib/generators/sidekiq/templates/job.rb.erb +9 -0
  15. data/sidekiq-8.1.6/lib/generators/sidekiq/templates/job_spec.rb.erb +6 -0
  16. data/sidekiq-8.1.6/lib/generators/sidekiq/templates/job_test.rb.erb +8 -0
  17. data/sidekiq-8.1.6/lib/sidekiq/api.rb +1466 -0
  18. data/sidekiq-8.1.6/lib/sidekiq/capsule.rb +135 -0
  19. data/sidekiq-8.1.6/lib/sidekiq/cli.rb +433 -0
  20. data/sidekiq-8.1.6/lib/sidekiq/client.rb +306 -0
  21. data/sidekiq-8.1.6/lib/sidekiq/component.rb +132 -0
  22. data/sidekiq-8.1.6/lib/sidekiq/config.rb +316 -0
  23. data/sidekiq-8.1.6/lib/sidekiq/deploy.rb +64 -0
  24. data/sidekiq-8.1.6/lib/sidekiq/embedded.rb +64 -0
  25. data/sidekiq-8.1.6/lib/sidekiq/fetch.rb +89 -0
  26. data/sidekiq-8.1.6/lib/sidekiq/iterable_job.rb +56 -0
  27. data/sidekiq-8.1.6/lib/sidekiq/job/interrupt_handler.rb +24 -0
  28. data/sidekiq-8.1.6/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  29. data/sidekiq-8.1.6/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  30. data/sidekiq-8.1.6/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  31. data/sidekiq-8.1.6/lib/sidekiq/job/iterable.rb +322 -0
  32. data/sidekiq-8.1.6/lib/sidekiq/job.rb +400 -0
  33. data/sidekiq-8.1.6/lib/sidekiq/job_logger.rb +54 -0
  34. data/sidekiq-8.1.6/lib/sidekiq/job_retry.rb +332 -0
  35. data/sidekiq-8.1.6/lib/sidekiq/job_util.rb +113 -0
  36. data/sidekiq-8.1.6/lib/sidekiq/launcher.rb +283 -0
  37. data/sidekiq-8.1.6/lib/sidekiq/loader.rb +57 -0
  38. data/sidekiq-8.1.6/lib/sidekiq/logger.rb +87 -0
  39. data/sidekiq-8.1.6/lib/sidekiq/manager.rb +134 -0
  40. data/sidekiq-8.1.6/lib/sidekiq/metrics/query.rb +184 -0
  41. data/sidekiq-8.1.6/lib/sidekiq/metrics/shared.rb +109 -0
  42. data/sidekiq-8.1.6/lib/sidekiq/metrics/tracking.rb +153 -0
  43. data/sidekiq-8.1.6/lib/sidekiq/middleware/chain.rb +207 -0
  44. data/sidekiq-8.1.6/lib/sidekiq/middleware/current_attributes.rb +120 -0
  45. data/sidekiq-8.1.6/lib/sidekiq/middleware/i18n.rb +44 -0
  46. data/sidekiq-8.1.6/lib/sidekiq/middleware/modules.rb +23 -0
  47. data/sidekiq-8.1.6/lib/sidekiq/monitor.rb +143 -0
  48. data/sidekiq-8.1.6/lib/sidekiq/paginator.rb +74 -0
  49. data/sidekiq-8.1.6/lib/sidekiq/processor.rb +298 -0
  50. data/sidekiq-8.1.6/lib/sidekiq/profiler.rb +73 -0
  51. data/sidekiq-8.1.6/lib/sidekiq/rails.rb +63 -0
  52. data/sidekiq-8.1.6/lib/sidekiq/redis_client_adapter.rb +117 -0
  53. data/sidekiq-8.1.6/lib/sidekiq/redis_connection.rb +115 -0
  54. data/sidekiq-8.1.6/lib/sidekiq/ring_buffer.rb +32 -0
  55. data/sidekiq-8.1.6/lib/sidekiq/scheduled.rb +235 -0
  56. data/sidekiq-8.1.6/lib/sidekiq/sd_notify.rb +149 -0
  57. data/sidekiq-8.1.6/lib/sidekiq/systemd.rb +26 -0
  58. data/sidekiq-8.1.6/lib/sidekiq/test_api.rb +331 -0
  59. data/sidekiq-8.1.6/lib/sidekiq/testing/inline.rb +2 -0
  60. data/sidekiq-8.1.6/lib/sidekiq/testing.rb +3 -0
  61. data/sidekiq-8.1.6/lib/sidekiq/transaction_aware_client.rb +59 -0
  62. data/sidekiq-8.1.6/lib/sidekiq/tui/controls.rb +53 -0
  63. data/sidekiq-8.1.6/lib/sidekiq/tui/filtering.rb +53 -0
  64. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/base_tab.rb +204 -0
  65. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/busy.rb +118 -0
  66. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/dead.rb +19 -0
  67. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/home.rb +144 -0
  68. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/metrics.rb +131 -0
  69. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/queues.rb +95 -0
  70. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/retries.rb +19 -0
  71. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/scheduled.rb +19 -0
  72. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/set_tab.rb +96 -0
  73. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs.rb +15 -0
  74. data/sidekiq-8.1.6/lib/sidekiq/tui.rb +382 -0
  75. data/sidekiq-8.1.6/lib/sidekiq/version.rb +10 -0
  76. data/sidekiq-8.1.6/lib/sidekiq/web/action.rb +178 -0
  77. data/sidekiq-8.1.6/lib/sidekiq/web/application.rb +480 -0
  78. data/sidekiq-8.1.6/lib/sidekiq/web/config.rb +117 -0
  79. data/sidekiq-8.1.6/lib/sidekiq/web/helpers.rb +474 -0
  80. data/sidekiq-8.1.6/lib/sidekiq/web/router.rb +91 -0
  81. data/sidekiq-8.1.6/lib/sidekiq/web.rb +140 -0
  82. data/sidekiq-8.1.6/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  83. data/sidekiq-8.1.6/lib/sidekiq.rb +161 -0
  84. data/sidekiq-8.1.6/sidekiq.gemspec +31 -0
  85. data/sidekiq-8.1.6/web/assets/images/apple-touch-icon.png +0 -0
  86. data/sidekiq-8.1.6/web/assets/images/favicon.ico +0 -0
  87. data/sidekiq-8.1.6/web/assets/images/logo.png +0 -0
  88. data/sidekiq-8.1.6/web/assets/images/status.png +0 -0
  89. data/sidekiq-8.1.6/web/assets/javascripts/application.js +204 -0
  90. data/sidekiq-8.1.6/web/assets/javascripts/base-charts.js +120 -0
  91. data/sidekiq-8.1.6/web/assets/javascripts/chart.min.js +13 -0
  92. data/sidekiq-8.1.6/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  93. data/sidekiq-8.1.6/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  94. data/sidekiq-8.1.6/web/assets/javascripts/dashboard-charts.js +194 -0
  95. data/sidekiq-8.1.6/web/assets/javascripts/dashboard.js +63 -0
  96. data/sidekiq-8.1.6/web/assets/javascripts/metrics.js +280 -0
  97. data/sidekiq-8.1.6/web/assets/stylesheets/style.css +776 -0
  98. data/sidekiq-8.1.6/web/locales/ar.yml +89 -0
  99. data/sidekiq-8.1.6/web/locales/cs.yml +80 -0
  100. data/sidekiq-8.1.6/web/locales/da.yml +77 -0
  101. data/sidekiq-8.1.6/web/locales/de.yml +83 -0
  102. data/sidekiq-8.1.6/web/locales/el.yml +89 -0
  103. data/sidekiq-8.1.6/web/locales/en.yml +110 -0
  104. data/sidekiq-8.1.6/web/locales/es.yml +109 -0
  105. data/sidekiq-8.1.6/web/locales/fa.yml +82 -0
  106. data/sidekiq-8.1.6/web/locales/fr.yml +100 -0
  107. data/sidekiq-8.1.6/web/locales/gd.yml +110 -0
  108. data/sidekiq-8.1.6/web/locales/he.yml +82 -0
  109. data/sidekiq-8.1.6/web/locales/hi.yml +77 -0
  110. data/sidekiq-8.1.6/web/locales/it.yml +109 -0
  111. data/sidekiq-8.1.6/web/locales/ja.yml +92 -0
  112. data/sidekiq-8.1.6/web/locales/ko.yml +70 -0
  113. data/sidekiq-8.1.6/web/locales/lt.yml +85 -0
  114. data/sidekiq-8.1.6/web/locales/nb.yml +79 -0
  115. data/sidekiq-8.1.6/web/locales/nl.yml +70 -0
  116. data/sidekiq-8.1.6/web/locales/pl.yml +61 -0
  117. data/sidekiq-8.1.6/web/locales/pt-BR.yml +97 -0
  118. data/sidekiq-8.1.6/web/locales/pt.yml +69 -0
  119. data/sidekiq-8.1.6/web/locales/ru.yml +85 -0
  120. data/sidekiq-8.1.6/web/locales/sv.yml +70 -0
  121. data/sidekiq-8.1.6/web/locales/ta.yml +77 -0
  122. data/sidekiq-8.1.6/web/locales/tr.yml +102 -0
  123. data/sidekiq-8.1.6/web/locales/uk.yml +102 -0
  124. data/sidekiq-8.1.6/web/locales/ur.yml +82 -0
  125. data/sidekiq-8.1.6/web/locales/vi.yml +85 -0
  126. data/sidekiq-8.1.6/web/locales/zh-CN.yml +107 -0
  127. data/sidekiq-8.1.6/web/locales/zh-TW.yml +103 -0
  128. data/sidekiq-8.1.6/web/views/_footer.html.erb +32 -0
  129. data/sidekiq-8.1.6/web/views/_job_info.html.erb +115 -0
  130. data/sidekiq-8.1.6/web/views/_metrics_period_select.html.erb +15 -0
  131. data/sidekiq-8.1.6/web/views/_nav.html.erb +45 -0
  132. data/sidekiq-8.1.6/web/views/_paging.html.erb +26 -0
  133. data/sidekiq-8.1.6/web/views/_poll_link.html.erb +4 -0
  134. data/sidekiq-8.1.6/web/views/_summary.html.erb +40 -0
  135. data/sidekiq-8.1.6/web/views/busy.html.erb +155 -0
  136. data/sidekiq-8.1.6/web/views/dashboard.html.erb +104 -0
  137. data/sidekiq-8.1.6/web/views/dead.html.erb +38 -0
  138. data/sidekiq-8.1.6/web/views/filtering.html.erb +6 -0
  139. data/sidekiq-8.1.6/web/views/layout.html.erb +26 -0
  140. data/sidekiq-8.1.6/web/views/metrics.html.erb +85 -0
  141. data/sidekiq-8.1.6/web/views/metrics_for_job.html.erb +58 -0
  142. data/sidekiq-8.1.6/web/views/morgue.html.erb +69 -0
  143. data/sidekiq-8.1.6/web/views/profiles.html.erb +43 -0
  144. data/sidekiq-8.1.6/web/views/queue.html.erb +57 -0
  145. data/sidekiq-8.1.6/web/views/queues.html.erb +46 -0
  146. data/sidekiq-8.1.6/web/views/retries.html.erb +77 -0
  147. data/sidekiq-8.1.6/web/views/retry.html.erb +39 -0
  148. data/sidekiq-8.1.6/web/views/scheduled.html.erb +64 -0
  149. data/sidekiq-8.1.6/web/views/scheduled_job_info.html.erb +8 -0
  150. data/ultra-fast-tool.gemspec +11 -0
  151. metadata +189 -0
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module Sidekiq
6
+ class Web
7
+ ##
8
+ # These instance methods are available to all executing ERB
9
+ # templates.
10
+ class Action
11
+ attr_accessor :env, :block
12
+
13
+ def initialize(env, block)
14
+ @_erb = false
15
+ @env = env
16
+ @block = block
17
+ end
18
+
19
+ def config
20
+ env[:web_config]
21
+ end
22
+
23
+ def request
24
+ @request ||= ::Rack::Request.new(env)
25
+ end
26
+
27
+ def halt(res, msg = nil)
28
+ throw :halt, [res, {"content-type" => "text/plain"}, [msg || res.to_s]]
29
+ end
30
+
31
+ # external redirect
32
+ def redirect_to(url)
33
+ throw :halt, [302, {"location" => url}, []]
34
+ end
35
+
36
+ def header(key, value)
37
+ env["response_headers"][key] = value.to_s
38
+ end
39
+
40
+ # internal redirect
41
+ def redirect(location)
42
+ throw :halt, [302, {"location" => "#{request.base_url}#{location}"}, []]
43
+ end
44
+
45
+ def reload_page
46
+ current_location = request.referer.gsub(request.base_url, "")
47
+ redirect current_location
48
+ end
49
+
50
+ # stuff after ? or form input
51
+ # uses String keys, no Symbols!
52
+ def url_params(key)
53
+ warn { "URL parameter `#{key}` should be accessed via String, not Symbol (at #{caller(3..3).first})" } if key.is_a?(Symbol)
54
+ request.params[key.to_s]
55
+ end
56
+
57
+ # variables embedded in path, `/metrics/:name`
58
+ # uses Symbol keys, no Strings!
59
+ def route_params(key)
60
+ warn { "Route parameter `#{key}` should be accessed via Symbol, not String (at #{caller(3..3).first})" } if key.is_a?(String)
61
+ env["rack.route_params"][key.to_sym]
62
+ end
63
+
64
+ def params
65
+ warn { "Direct access to Rack parameters is discouraged, use `url_params` or `route_params` (at #{caller(3..3).first})" }
66
+ request.params
67
+ end
68
+
69
+ def session
70
+ env["rack.session"] || fail(<<~EOM)
71
+ Sidekiq::Web needs a valid Rack session. If this is a Rails app, make
72
+ sure you mount Sidekiq::Web *inside* your application routes:
73
+
74
+
75
+ Rails.application.routes.draw do
76
+ mount Sidekiq::Web => "/sidekiq"
77
+ ....
78
+ end
79
+
80
+
81
+ If this is a Rails app in API mode, you need to enable sessions.
82
+
83
+ https://guides.rubyonrails.org/api_app.html#using-session-middlewares
84
+
85
+ If this is a bare Rack app, use a session middleware before Sidekiq::Web:
86
+
87
+ # first, use IRB to create a shared secret key for sessions and commit it
88
+ require 'securerandom'; File.open(".session.key", "w") {|f| f.write(SecureRandom.hex(32)) }
89
+
90
+ # now use the secret with a session cookie middleware
91
+ use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400
92
+ run Sidekiq::Web
93
+
94
+ EOM
95
+ end
96
+
97
+ def logger
98
+ Sidekiq.logger
99
+ end
100
+
101
+ # flash { "Some message to show on redirect" }
102
+ def flash
103
+ msg = yield
104
+ logger.info msg
105
+ session[:skq_flash] = msg
106
+ end
107
+
108
+ def flash?
109
+ session&.[](:skq_flash)
110
+ end
111
+
112
+ def get_flash
113
+ @flash ||= session.delete(:skq_flash)
114
+ end
115
+
116
+ def erb(content, options = {})
117
+ if content.is_a? Symbol
118
+ unless respond_to?(:"_erb_#{content}")
119
+ views = options[:views] || Web.views
120
+ filename = "#{views}/#{content}.html.erb"
121
+ src = ERB.new(File.read(filename)).src
122
+
123
+ # Need to use lineno less by 1 because erb generates a
124
+ # comment before the source code.
125
+ Action.class_eval <<-RUBY, filename, -1 # standard:disable Style/EvalWithLocation
126
+ def _erb_#{content}
127
+ #{src}
128
+ end
129
+ RUBY
130
+ end
131
+ end
132
+
133
+ if @_erb
134
+ _erb(content, options[:locals])
135
+ else
136
+ @_erb = true
137
+ content = _erb(content, options[:locals])
138
+
139
+ _render { content }
140
+ end
141
+ end
142
+
143
+ def render(engine, content, options = {})
144
+ raise "Only erb templates are supported" if engine != :erb
145
+
146
+ erb(content, options)
147
+ end
148
+
149
+ def json(payload)
150
+ [200,
151
+ {"content-type" => "application/json", "cache-control" => "private, no-store"},
152
+ [Sidekiq.dump_json(payload)]]
153
+ end
154
+
155
+ private
156
+
157
+ def warn
158
+ Sidekiq.logger.warn yield
159
+ end
160
+
161
+ def _erb(file, locals)
162
+ locals&.each { |k, v| define_singleton_method(k) { v } unless singleton_methods.include? k }
163
+
164
+ if file.is_a?(String)
165
+ ERB.new(file).result(binding)
166
+ else
167
+ send(:"_erb_#{file}")
168
+ end
169
+ end
170
+
171
+ class_eval <<-RUBY, ::Sidekiq::Web::LAYOUT, -1 # standard:disable Style/EvalWithLocation
172
+ def _render
173
+ #{ERB.new(File.read(::Sidekiq::Web::LAYOUT)).src}
174
+ end
175
+ RUBY
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,480 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/paginator"
4
+ require "sidekiq/web/helpers"
5
+
6
+ module Sidekiq
7
+ class Web
8
+ class Application
9
+ extend Router
10
+ include Router
11
+
12
+ REDIS_KEYS = %w[redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human]
13
+
14
+ CSP_HEADER_TEMPLATE = [
15
+ "default-src 'self' https: http:",
16
+ "child-src 'self'",
17
+ "connect-src 'self' https: http: wss: ws:",
18
+ "font-src 'none'",
19
+ "frame-src 'self'",
20
+ "img-src 'self' https: http: data:",
21
+ "manifest-src 'self'",
22
+ "media-src 'self'",
23
+ "object-src 'none'",
24
+ "script-src 'self' 'nonce-!placeholder!'",
25
+ "style-src 'self' 'nonce-!placeholder!'",
26
+ "worker-src 'self'",
27
+ "base-uri 'self'"
28
+ ].join("; ").freeze
29
+
30
+ METRICS_PERIODS = {
31
+ "1h" => {minutes: 60},
32
+ "2h" => {minutes: 120},
33
+ "4h" => {minutes: 240},
34
+ "8h" => {minutes: 480},
35
+ "24h" => {hours: 24},
36
+ "48h" => {hours: 48},
37
+ "72h" => {hours: 72}
38
+ }
39
+
40
+ def initialize(inst)
41
+ @app = inst
42
+ end
43
+
44
+ head "/" do
45
+ # HEAD / is the cheapest heartbeat possible,
46
+ # it hits Redis to ensure connectivity
47
+ _ = Sidekiq.redis { |c| c.llen("queue:default") }
48
+ ""
49
+ end
50
+
51
+ get "/" do
52
+ @redis_info = redis_info.slice(*REDIS_KEYS)
53
+ days = (url_params("days") || 30).to_i
54
+ return halt(401) if days < 1 || days > 180
55
+
56
+ stats_history = Sidekiq::Stats::History.new(days)
57
+ @processed_history = stats_history.processed
58
+ @failed_history = stats_history.failed
59
+
60
+ erb(:dashboard)
61
+ end
62
+
63
+ get "/metrics" do
64
+ x = url_params("substr")
65
+ class_filter = (x.nil? || x == "") ? nil : Regexp.new(Regexp.escape(x), Regexp::IGNORECASE)
66
+
67
+ q = Sidekiq::Metrics::Query.new
68
+ @period = h(url_params("period") || "1h")
69
+ @periods = METRICS_PERIODS
70
+ args = @periods.fetch(@period, @periods.values.first)
71
+ @query_result = q.top_jobs(**args.merge(class_filter: class_filter))
72
+
73
+ header "refresh", 60 if @period == "1h"
74
+ erb(:metrics)
75
+ end
76
+
77
+ get "/metrics/:name" do
78
+ @name = route_params(:name)
79
+ @period = h(url_params("period") || "1h")
80
+ # Periods larger than 8 hours are not supported for histogram chart
81
+ @period = "8h" if @period.to_i > 8
82
+ @periods = METRICS_PERIODS.reject { |k, v| k.to_i > 8 }
83
+ args = @periods.fetch(@period, @periods.values.first)
84
+ q = Sidekiq::Metrics::Query.new
85
+ @query_result = q.for_job(@name, **args)
86
+
87
+ header "refresh", 60 if @period == "1h"
88
+ erb(:metrics_for_job)
89
+ end
90
+
91
+ get "/busy" do
92
+ @count = (url_params("count") || 100).to_i
93
+
94
+ # Use /busy?only=(jobs|processes) to limit page data
95
+ only = url_params("only")
96
+ halt 401, "Invalid value for only" unless only.nil? || only == "jobs" || only == "processes"
97
+
98
+ @show_jobs = (only != "processes")
99
+ @show_processes = (only != "jobs")
100
+
101
+ if @show_jobs
102
+ (@current_page, @total_size, @workset) = page_items(workset, url_params("page"), @count)
103
+ @iterable_states = Sidekiq::IterableJobQuery.new(workset.map { |_, _, work| work.job.jid })
104
+ end
105
+
106
+ erb(:busy)
107
+ end
108
+
109
+ post "/busy" do
110
+ if url_params("identity")
111
+ pro = Sidekiq::ProcessSet[url_params("identity")]
112
+
113
+ pro&.quiet! if url_params("quiet")
114
+ pro&.stop! if url_params("stop")
115
+ else
116
+ processes.each do |pro|
117
+ next if pro.embedded?
118
+
119
+ pro.quiet! if url_params("quiet")
120
+ pro.stop! if url_params("stop")
121
+ end
122
+ end
123
+
124
+ redirect "#{root_path}busy"
125
+ end
126
+
127
+ get "/queues" do
128
+ @queues = Sidekiq::Queue.all
129
+
130
+ erb(:queues)
131
+ end
132
+
133
+ QUEUE_NAME = /\A[a-z_:.\-0-9]+\z/i
134
+
135
+ get "/queues/:name" do
136
+ @name = route_params(:name)
137
+
138
+ halt(404) if !@name || @name !~ QUEUE_NAME
139
+
140
+ @count = (url_params("count") || 25).to_i
141
+ @queue = Sidekiq::Queue.new(@name)
142
+ (@current_page, @total_size, @jobs) = page("queue:#{@name}", url_params("page"), @count, reverse: url_params("direction") == "asc")
143
+ @jobs = @jobs.map { |msg| Sidekiq::JobRecord.new(msg, @name) }
144
+
145
+ erb(:queue)
146
+ end
147
+
148
+ post "/queues/:name" do
149
+ queue = Sidekiq::Queue.new(route_params(:name))
150
+
151
+ if Sidekiq.pro? && url_params("pause")
152
+ queue.pause!
153
+ elsif Sidekiq.pro? && url_params("unpause")
154
+ queue.unpause!
155
+ else
156
+ queue.clear
157
+ end
158
+
159
+ redirect "#{root_path}queues"
160
+ end
161
+
162
+ post "/queues/:name/delete" do
163
+ name = route_params(:name)
164
+ Sidekiq::JobRecord.new(url_params("key_val"), name).delete
165
+
166
+ redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
167
+ end
168
+
169
+ get "/morgue" do
170
+ x = url_params("substr")
171
+
172
+ if x && x != ""
173
+ @dead = search(Sidekiq::DeadSet.new, x)
174
+ else
175
+ @count = (url_params("count") || 25).to_i
176
+ (@current_page, @total_size, @dead) = page("dead", url_params("page"), @count, reverse: true)
177
+ @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
178
+ end
179
+
180
+ erb(:morgue)
181
+ end
182
+
183
+ get "/morgue/:key" do
184
+ key = route_params(:key)
185
+ halt(404) unless key
186
+
187
+ @dead = Sidekiq::DeadSet.new.fetch(*parse_key(key)).first
188
+
189
+ if @dead.nil?
190
+ redirect "#{root_path}morgue"
191
+ else
192
+ erb(:dead)
193
+ end
194
+ end
195
+
196
+ post "/morgue" do
197
+ redirect(request.path) unless url_params("key")
198
+
199
+ url_params("key").each do |key|
200
+ job = Sidekiq::DeadSet.new.fetch(*parse_key(key)).first
201
+ retry_or_delete_or_kill job, request.params if job
202
+ end
203
+
204
+ redirect_with_query("#{root_path}morgue")
205
+ end
206
+
207
+ post "/morgue/all/delete" do
208
+ Sidekiq::DeadSet.new.clear
209
+
210
+ redirect "#{root_path}morgue"
211
+ end
212
+
213
+ post "/morgue/all/retry" do
214
+ Sidekiq::DeadSet.new.retry_all
215
+
216
+ redirect "#{root_path}morgue"
217
+ end
218
+
219
+ post "/morgue/:key" do
220
+ key = route_params(:key)
221
+ halt(404) unless key
222
+
223
+ job = Sidekiq::DeadSet.new.fetch(*parse_key(key)).first
224
+ retry_or_delete_or_kill job, request.params if job
225
+
226
+ redirect_with_query("#{root_path}morgue")
227
+ end
228
+
229
+ get "/retries" do
230
+ x = url_params("substr")
231
+
232
+ if x && x != ""
233
+ @retries = search(Sidekiq::RetrySet.new, x)
234
+ else
235
+ @count = (url_params("count") || 25).to_i
236
+ (@current_page, @total_size, @retries) = page("retry", url_params("page"), @count)
237
+ @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
238
+ end
239
+
240
+ @iterable_states = Sidekiq::IterableJobQuery.new(@retries.map(&:jid))
241
+
242
+ erb(:retries)
243
+ end
244
+
245
+ get "/retries/:key" do
246
+ @retry = Sidekiq::RetrySet.new.fetch(*parse_key(route_params(:key))).first
247
+
248
+ if @retry.nil?
249
+ redirect "#{root_path}retries"
250
+ else
251
+ erb(:retry)
252
+ end
253
+ end
254
+
255
+ post "/retries" do
256
+ redirect(request.path) unless url_params("key")
257
+
258
+ url_params("key").each do |key|
259
+ job = Sidekiq::RetrySet.new.fetch(*parse_key(key)).first
260
+ retry_or_delete_or_kill job, request.params if job
261
+ end
262
+
263
+ redirect_with_query("#{root_path}retries")
264
+ end
265
+
266
+ post "/retries/all/delete" do
267
+ Sidekiq::RetrySet.new.clear
268
+ redirect "#{root_path}retries"
269
+ end
270
+
271
+ post "/retries/all/retry" do
272
+ Sidekiq::RetrySet.new.retry_all
273
+ redirect "#{root_path}retries"
274
+ end
275
+
276
+ post "/retries/all/kill" do
277
+ Sidekiq::RetrySet.new.kill_all
278
+ redirect "#{root_path}retries"
279
+ end
280
+
281
+ post "/retries/:key" do
282
+ job = Sidekiq::RetrySet.new.fetch(*parse_key(route_params(:key))).first
283
+
284
+ retry_or_delete_or_kill job, request.params if job
285
+
286
+ redirect_with_query("#{root_path}retries")
287
+ end
288
+
289
+ get "/scheduled" do
290
+ x = url_params("substr")
291
+
292
+ if x && x != ""
293
+ @scheduled = search(Sidekiq::ScheduledSet.new, x)
294
+ else
295
+ @count = (url_params("count") || 25).to_i
296
+ (@current_page, @total_size, @scheduled) = page("schedule", url_params("page"), @count)
297
+ @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
298
+ end
299
+
300
+ erb(:scheduled)
301
+ end
302
+
303
+ get "/scheduled/:key" do
304
+ @job = Sidekiq::ScheduledSet.new.fetch(*parse_key(route_params(:key))).first
305
+
306
+ if @job.nil?
307
+ redirect "#{root_path}scheduled"
308
+ else
309
+ erb(:scheduled_job_info)
310
+ end
311
+ end
312
+
313
+ post "/scheduled" do
314
+ redirect(request.path) unless url_params("key")
315
+
316
+ url_params("key").each do |key|
317
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_key(key)).first
318
+ delete_or_add_queue job, request.params if job
319
+ end
320
+
321
+ redirect_with_query("#{root_path}scheduled")
322
+ end
323
+
324
+ post "/scheduled/:key" do
325
+ key = route_params(:key)
326
+ halt(404) unless key
327
+
328
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_key(key)).first
329
+ delete_or_add_queue job, request.params if job
330
+
331
+ redirect_with_query("#{root_path}scheduled")
332
+ end
333
+
334
+ post "/scheduled/all/delete" do
335
+ Sidekiq::ScheduledSet.new.clear
336
+ redirect "#{root_path}scheduled"
337
+ end
338
+
339
+ post "/scheduled/all/add_to_queue" do
340
+ Sidekiq::ScheduledSet.new.each(&:add_to_queue)
341
+ redirect "#{root_path}scheduled"
342
+ end
343
+
344
+ get "/dashboard/stats" do
345
+ redirect "#{root_path}stats"
346
+ end
347
+
348
+ get "/stats" do
349
+ sidekiq_stats = Sidekiq::Stats.new
350
+ redis_stats = redis_info.slice(*REDIS_KEYS)
351
+ redis_stats["store_name"] = store_name
352
+ redis_stats["store_version"] = store_version
353
+ json(
354
+ sidekiq: {
355
+ processed: sidekiq_stats.processed,
356
+ failed: sidekiq_stats.failed,
357
+ busy: sidekiq_stats.workers_size,
358
+ processes: sidekiq_stats.processes_size,
359
+ enqueued: sidekiq_stats.enqueued,
360
+ scheduled: sidekiq_stats.scheduled_size,
361
+ retries: sidekiq_stats.retry_size,
362
+ dead: sidekiq_stats.dead_size,
363
+ default_latency: sidekiq_stats.default_queue_latency
364
+ },
365
+ redis: redis_stats,
366
+ server_utc_time: server_utc_time
367
+ )
368
+ end
369
+
370
+ get "/stats/queues" do
371
+ json Sidekiq::Stats.new.queues
372
+ end
373
+
374
+ get "/profiles" do
375
+ erb(:profiles)
376
+ end
377
+
378
+ get "/profiles/:key" do
379
+ store = config[:profile_store_url]
380
+ return redirect_to "#{root_path}profiles" unless store
381
+
382
+ key = route_params(:key)
383
+ sid = Sidekiq.redis { |c| c.hget(key, "sid") }
384
+
385
+ unless sid
386
+ require "net/http"
387
+ data = Sidekiq.redis { |c| c.hget(key, "data") }
388
+
389
+ store_uri = URI(store)
390
+ http = Net::HTTP.new(store_uri.host, store_uri.port)
391
+ http.use_ssl = store_uri.scheme == "https"
392
+ request = Net::HTTP::Post.new(store_uri.request_uri)
393
+ request.body = data
394
+ request["Accept"] = "application/vnd.firefox-profiler+json;version=1.0"
395
+ request["User-Agent"] = "Sidekiq #{Sidekiq::VERSION} job profiler"
396
+
397
+ resp = http.request(request)
398
+ # https://raw.githubusercontent.com/firefox-devtools/profiler-server/master/tools/decode_jwt_payload.py
399
+ rawjson = resp.body.split(".")[1].unpack1("m")
400
+ sid = Sidekiq.load_json(rawjson)["profileToken"]
401
+ Sidekiq.redis { |c| c.hset(key, "sid", sid) }
402
+ end
403
+ url = config[:profile_view_url] % sid
404
+ redirect_to url
405
+ end
406
+
407
+ get "/profiles/:key/data" do
408
+ key = route_params(:key)
409
+ data = Sidekiq.redis { |c| c.hget(key, "data") }
410
+
411
+ [200, {
412
+ "content-type" => "application/json",
413
+ "content-encoding" => "gzip",
414
+ # allow Firefox Profiler's XHR to fetch this profile data
415
+ "access-control-allow-origin" => "*"
416
+ }, [data]]
417
+ end
418
+
419
+ post "/change_locale" do
420
+ locale = url_params("locale")
421
+
422
+ match = available_locales.find { |available|
423
+ locale == available
424
+ }
425
+
426
+ session[:locale] = match if match
427
+
428
+ reload_page
429
+ end
430
+
431
+ def redis(&)
432
+ Thread.current[:sidekiq_redis_pool].with(&)
433
+ end
434
+
435
+ def call(env)
436
+ action = match(env)
437
+ return [404, {"content-type" => "text/plain", "x-cascade" => "pass"}, ["Not Found"]] unless action
438
+
439
+ headers = {
440
+ "content-type" => "text/html",
441
+ "cache-control" => "private, no-store",
442
+ "content-language" => action.locale,
443
+ "content-security-policy" => process_csp(env, CSP_HEADER_TEMPLATE),
444
+ "x-content-type-options" => "nosniff"
445
+ }
446
+ env["response_headers"] = headers
447
+ resp = catch(:halt) do
448
+ Thread.current[:sidekiq_redis_pool] = env[:redis_pool]
449
+ action.instance_exec env, &action.block
450
+ ensure
451
+ Thread.current[:sidekiq_redis_pool] = nil
452
+ end
453
+
454
+ case resp
455
+ when Array
456
+ # redirects go here
457
+ resp
458
+ else
459
+ # rendered content goes here
460
+ # we'll let Rack calculate Content-Length for us.
461
+ [200, env["response_headers"], [resp]]
462
+ end
463
+ end
464
+
465
+ def process_csp(env, input)
466
+ input.gsub("!placeholder!", env[:csp_nonce])
467
+ end
468
+
469
+ # Used by extensions to add helper methods accessible to
470
+ # any defined endpoints in Application. Careful with generic
471
+ # method naming as there's no namespacing so collisions are
472
+ # possible.
473
+ def self.helpers(mod)
474
+ Sidekiq::Web::Action.send(:include, mod)
475
+ end
476
+ helpers WebHelpers
477
+ helpers Paginator
478
+ end
479
+ end
480
+ end