sidekiq_status 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3-p194-perf@sidekiq_status --create
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ # - jruby-19mode # JRuby in 1.9 mode
6
+ # - rbx-19mode
7
+ # uncomment this line if your project needs to run something other than `rake`:
8
+ # script: bundle exec rspec spec
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ -m markdown
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sidekiq_status.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Artem Ignatyev
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # SidekiqStatus
2
+
3
+ [![Build Status](https://secure.travis-ci.org/cryo28/sidekiq_status.png)](http://travis-ci.org/cryo28/sidekiq_status)
4
+ [![Dependency Status](https://gemnasium.com/cryo28/sidekiq_status.png)](https://gemnasium.com/cryo28/sidekiq_status)
5
+
6
+ Sidekiq extension to track job execution statuses and returning job results back to the client in a convenient manner
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'sidekiq_status', :git => 'git@github.com:cryo28/sidekiq_status.git'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ ## Usage
21
+
22
+ ### Basic
23
+
24
+ Create a status-friendly worker by include SidekiqStatus::Worker module having #perform method with Sidekiq worker-compatible signature:
25
+
26
+ ```ruby
27
+ class MyWorker
28
+ include SidekiqStatus::Worker
29
+
30
+ def perform(arg1, arg2)
31
+ # do something
32
+ end
33
+ end
34
+ ```
35
+
36
+ Now you can enqueue some jobs for this worker
37
+
38
+ ```ruby
39
+ jid = MyWorker.perform_async('val_for_arg1', 'val_for_arg2')
40
+ ```
41
+
42
+ If a job is rejected by some Client middleware, #perform_async returns false (as it doesn with ordinary Sidekiq worker).
43
+
44
+ Now, you can easily track the status of the job execution:
45
+
46
+ ```ruby
47
+ status_container = SidekiqStatus::Container.load(jid)
48
+ status_container.status # => 'waiting'
49
+ ```
50
+
51
+ When a jobs is scheduled its status is *waiting*. As soon sidekiq worker begins job execution its status is changed to *working*.
52
+ If the job successfully finishes (i.e. doesn't raise an unhandled exception) its status is *complete*. Otherwise its status is *failed*.
53
+
54
+ ### Communication from Worker to Client
55
+
56
+ *SidekiqStatus::Container* has some attributes and *SidekiqStatus::Worker* module extends your Worker class with a few methods which allow Worker to leave
57
+ some info for the subsequent fetch by a Client. For example you can notify client of the worker progress via *at* and *total=* methods
58
+
59
+ ```ruby
60
+ class MyWorker
61
+ include SidekiqStatus::Worker
62
+
63
+ def perform(arg1, arg2)
64
+ objects = Array.new(200) { 'some_object_to_process' }
65
+ self.total= objects.count
66
+ objects.each_with_index do |object, index|
67
+ at(index, "Processing object #{index}")
68
+ object.process!
69
+ end
70
+ end
71
+ end
72
+ ```
73
+
74
+ Lets presume a client refreshes container at the middle of job execution (when it's processing the object number 50):
75
+
76
+ ```ruby
77
+ container = SidekiqStatus::Container.load(jid) # or container.reload
78
+
79
+ container.status # => 'working'
80
+ container.at # => 50
81
+ container.total # => 200
82
+ container.pct_complete # => 25
83
+ container.message # => 'Processing object #{50}'
84
+ ```
85
+
86
+ Also, a job can leave for the client any custom payload. The only requirement is json-serializeability
87
+
88
+ ```ruby
89
+ class MyWorker
90
+ include SidekiqStatus::Worker
91
+
92
+ def perform(arg1, arg2)
93
+ objects = Array.new(5) { |i| i }
94
+ self.total= objects.count
95
+ result = objects.inject([]) do |accum, object|
96
+ accum << "result #{object}"
97
+ accum
98
+ end
99
+
100
+ self.payload= result
101
+ end
102
+ end
103
+ ```
104
+
105
+
106
+ Then a client can fetch the result payload
107
+
108
+ ```ruby
109
+ container = SidekiqStatus::Container.load(jid)
110
+ container.status # => 'complete'
111
+ container.payload # => ["result 0", "result 1", "result 2", "result 3", "result 4"]
112
+ ```
113
+
114
+ SidekiqStatus stores all container attributes in a separate redis key until it's explicitly deleted via container.delete method
115
+ or until redis key expires (see SidekiqStatus::Container.ttl class_attribute).
116
+
117
+ ### Job kill
118
+
119
+ Any job which is waiting or working can be killed. A working job is killed at the moment of container access.
120
+
121
+ ```ruby
122
+ container = SidekiqStatus::Container.load(jid)
123
+ container.status # => 'working'
124
+ container.killable? # => true
125
+ container.should_kill # => false
126
+
127
+ container.request_kill
128
+
129
+ container.status # => 'working'
130
+ container.killable? # => false
131
+ container.should_kill # => true
132
+
133
+ sleep(1)
134
+
135
+ container.reload
136
+ container.status # => 'killed'
137
+ ```
138
+
139
+ ### Sidekiq web integration
140
+
141
+ SidekiqStatus also provides an extension to Sidekiq web interface with /statuses page where you can track and kill jobs
142
+ and clean status containers.
143
+
144
+ 1. Setup Sidekiq web interface according to Sidekiq documentation
145
+ 2. Add "require 'sidekiq_status/web'" beneath "require 'sidekiq/web'"
146
+
147
+ ## Contributing
148
+
149
+ 1. Fork it
150
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
151
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
152
+ 4. Push to the branch (`git push origin my-new-feature`)
153
+ 5. Create new Pull Request
154
+
155
+
156
+ ## Copyright
157
+
158
+ SidekiqStatus © 2012 by Artem Ignatyev. SidekiqStatus is licensed under the MIT license
159
+
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+ # -*- encoding : utf-8 -*-
3
+
4
+ require "bundler/gem_tasks"
5
+
6
+ require 'rspec/core/rake_task'
7
+ spec = RSpec::Core::RakeTask.new
8
+
9
+ task :default => :spec
@@ -0,0 +1,25 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ module SidekiqStatus
4
+ class ClientMiddleware
5
+ def call(worker, item, queue)
6
+ return yield unless worker < SidekiqStatus::Worker
7
+
8
+ jid = item['jid']
9
+ args = item['args']
10
+ item['args'] = [jid]
11
+
12
+ SidekiqStatus::Container.create(
13
+ 'jid' => jid,
14
+ 'worker' => worker.name,
15
+ 'queue' => queue,
16
+ 'args' => args
17
+ )
18
+
19
+ yield
20
+ rescue Exception => exc
21
+ SidekiqStatus::Container.load(jid).delete rescue nil
22
+ raise exc
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,354 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ # Sidekiq extension to track job execution statuses and returning job results back to the client in a convenient manner
4
+ module SidekiqStatus
5
+ # SidekiqStatus job container. Contains all job attributes, redis storage/retrieval logic,
6
+ # some syntactical sugar, such as status predicates and some attribute writers
7
+ # Doesn't hook into Sidekiq worker
8
+ class Container
9
+ # Exception raised if SidekiqStatus job being loaded is not found in Redis
10
+ class StatusNotFound < RuntimeError;
11
+ end
12
+
13
+ # Possible SidekiqStatus job statuses
14
+ STATUS_NAMES = %w(waiting working complete failed killed).freeze
15
+
16
+ # A list of statuses jobs in which are not considered pending
17
+ FINISHED_STATUS_NAMES = %w(complete failed killed).freeze
18
+
19
+ # Redis SortedSet key containing requests to kill {SidekiqStatus} jobs
20
+ KILL_KEY = 'sidekiq_status_kill'.freeze
21
+
22
+ # Redis SortedSet key to track existing {SidekiqStatus} jobs
23
+ STATUSES_KEY = 'sidekiq_statuses'.freeze
24
+
25
+ class_attribute :ttl
26
+ self.ttl = 60*60*24*30 # 30 days
27
+
28
+ # Default attribute values (assigned to a newly created container if not explicitly defined)
29
+ DEFAULTS = {
30
+ 'args' => [],
31
+ 'worker' => 'SidekiqStatus::Worker',
32
+ 'queue' => '',
33
+ 'status' => 'waiting',
34
+ 'at' => 0,
35
+ 'total' => 100,
36
+ 'message' => nil,
37
+ 'payload' => {}
38
+ }.freeze
39
+
40
+ attr_reader :jid, :args, :worker, :queue
41
+ attr_reader :status, :at, :total, :message, :last_updated_at
42
+ attr_accessor :payload
43
+
44
+ # @param [#to_s] jid SidekiqStatus job id
45
+ # @return [String] redis key to store/fetch {SidekiqStatus::Container} for the given job
46
+ def self.status_key(jid)
47
+ "sidekiq_status:#{jid}"
48
+ end
49
+
50
+ # @return [String] Redis SortedSet key to track existing {SidekiqStatus} jobs
51
+ def self.statuses_key
52
+ STATUSES_KEY
53
+ end
54
+
55
+ # @return [String] Redis SortedSet key containing requests to kill {SidekiqStatus} jobs
56
+ def self.kill_key
57
+ KILL_KEY
58
+ end
59
+
60
+ # Delete all {SidekiqStatus} jobs which are in given status
61
+ #
62
+ # @param [String,Array<String>,nil] status_names List of status names. If nil - delete jobs in any status
63
+ def self.delete(status_names = nil)
64
+ status_names ||= STATUS_NAMES
65
+ status_names = [status_names] unless status_names.is_a?(Array)
66
+
67
+ self.statuses.select { |container| status_names.include?(container.status) }.map(&:delete)
68
+ end
69
+
70
+
71
+ # Retrieve {SidekiqStatus} job identifiers
72
+ # It's possible to perform some pagination by specifying range boundaries
73
+ #
74
+ # @param [Integer] start
75
+ # @param [Integer] stop
76
+ # @return [Array<[String,jid]>] Array of hash-like arrays of job id => last_updated_at (unixtime) pairs
77
+ # @see *Redis#zrange* for details on return values format
78
+ def self.status_jids(start = 0, stop = -1)
79
+ Sidekiq.redis do |conn|
80
+ conn.zrange(self.statuses_key, start, stop, :with_scores => true)
81
+ end
82
+ end
83
+
84
+ # Retrieve {SidekiqStatus} jobs
85
+ # It's possible to perform some pagination by specifying range boundaries
86
+ #
87
+ # @param [Integer] start
88
+ # @param [Integer] stop
89
+ # @return [Array<SidekiqStatus::Container>]
90
+ def self.statuses(start = 0, stop = -1)
91
+ jids = status_jids(start, stop)
92
+ jids = Hash[jids].keys
93
+ load_multi(jids)
94
+ end
95
+
96
+ # @return [Integer] Known {SidekiqStatus} jobs amount
97
+ def self.size
98
+ Sidekiq.redis do |conn|
99
+ conn.zcard(self.statuses_key)
100
+ end
101
+ end
102
+
103
+ # Create (initialize, generate unique jid and save) a new {SidekiqStatus} job with given arguments.
104
+ #
105
+ # @overload
106
+ # @param [String] jid job identifier
107
+ # @overload
108
+ # @param [Hash] data
109
+ # @option data [String] jid (SecureRandom.hex(12)) optional job id to create status container for
110
+ # @option data [Array] args job arguments
111
+ # @option data [String] worker job worker class name
112
+ # @option data [String] queue job queue
113
+ #
114
+ # @return [SidekiqStatus::Container]
115
+ def self.create(data = {})
116
+ jid = data.delete('jid') if data.is_a?(Hash)
117
+ jid ||= SecureRandom.hex(12)
118
+
119
+ new(jid, data).tap(&:save)
120
+ end
121
+
122
+ # Load {SidekiqStatus::Container} by job identifier
123
+ #
124
+ # @param [String] jid job identifier
125
+ # @raise [StatusNotFound] if there's no info about {SidekiqStatus} job with given *jid*
126
+ # @return [SidekiqStatus::Container]
127
+ def self.load(jid)
128
+ data = load_data(jid)
129
+ new(jid, data)
130
+ end
131
+
132
+ # Load a list of {SidekiqStatus::Container SidekiqStatus jobs} from Redis
133
+ #
134
+ # @param [Array<String>] jids A list of job identifiers to load
135
+ # @return [Array<SidekiqStatus::Container>>]
136
+ def self.load_multi(jids)
137
+ data = load_data_multi(jids)
138
+ data.map do |jid, data|
139
+ new(jid, data)
140
+ end
141
+ end
142
+
143
+ # Load {SidekiqStatus::Container SidekiqStatus job} {SidekiqStatus::Container#dump serialized data} from Redis
144
+ #
145
+ # @param [String] jid job identifier
146
+ # @raise [StatusNotFound] if there's no info about {SidekiqStatus} job with given *jid*
147
+ # @return [Hash] Job container data (as parsed json, but container is not yet initialized)
148
+ def self.load_data(jid)
149
+ load_data_multi([jid])[jid] or raise StatusNotFound.new(jid.to_s)
150
+ end
151
+
152
+ # Load multiple {SidekiqStatus::Container SidekiqStatus job} {SidekiqStatus::Container#dump serialized data} from Redis
153
+ #
154
+ # As this method is the most frequently used one, it also contains expire job clean up logic
155
+ #
156
+ # @param [Array<#to_s>] jids a list of job identifiers to load data for
157
+ # @return [Hash{String => Hash}] A hash of job-id to deserialized data pairs
158
+ def self.load_data_multi(jids)
159
+ keys = jids.map { |jid| status_key(jid) }
160
+
161
+ return {} if keys.empty?
162
+
163
+ threshold = Time.now - self.ttl
164
+
165
+ data = Sidekiq.redis do |conn|
166
+ conn.multi do
167
+ conn.mget(*keys)
168
+
169
+ conn.zremrangebyscore(kill_key, 0, threshold.to_i) # Clean up expired unprocessed kill requests
170
+ conn.zremrangebyscore(statuses_key, 0, threshold.to_i) # Clean up expired statuses from statuses sorted set
171
+ end
172
+ end
173
+
174
+ data = data.first.map do |json|
175
+ json ? Sidekiq.load_json(json) : nil
176
+ end
177
+
178
+ Hash[jids.zip(data)]
179
+ end
180
+
181
+ # Initialize a new {SidekiqStatus::Container} with given unique job identifier and attribute data
182
+ #
183
+ # @param [String] jid
184
+ # @param [Hash] data
185
+ def initialize(jid, data = {})
186
+ @jid = jid
187
+ load(data)
188
+ end
189
+
190
+ # Reload current container data from JSON (in case they've changed)
191
+ def reload
192
+ data = self.class.load_data(jid)
193
+ load(data)
194
+ self
195
+ end
196
+
197
+ # @return [String] redis key to store current {SidekiqStatus::Container container}
198
+ # {SidekiqStatus::Container#dump data}
199
+ def status_key
200
+ self.class.status_key(jid)
201
+ end
202
+
203
+ # Save current container attribute values to redis
204
+ def save
205
+ data = dump
206
+ data = Sidekiq.dump_json(data)
207
+
208
+ Sidekiq.redis do |conn|
209
+ conn.multi do
210
+ conn.setex(status_key, self.ttl, data)
211
+ conn.zadd(self.class.statuses_key, Time.now.to_f.to_s, self.jid)
212
+ end
213
+ end
214
+ end
215
+
216
+ # Delete current container data from redis
217
+ def delete
218
+ Sidekiq.redis do |conn|
219
+ conn.multi do
220
+ conn.del(status_key)
221
+
222
+ conn.zrem(self.class.kill_key, self.jid)
223
+ conn.zrem(self.class.statuses_key, self.jid)
224
+ end
225
+ end
226
+ end
227
+
228
+ # Request kill for the {SidekiqStatus::Worker SidekiqStatus job}
229
+ # which parameters are tracked by the current {SidekiqStatus::Container}
230
+ def request_kill
231
+ Sidekiq.redis do |conn|
232
+ conn.zadd(self.class.kill_key, Time.now.to_f.to_s, self.jid)
233
+ end
234
+ end
235
+
236
+ # @return [Boolean] if job kill is requested
237
+ def kill_requested?
238
+ Sidekiq.redis do |conn|
239
+ conn.zrank(self.class.kill_key, self.jid)
240
+ end
241
+ end
242
+
243
+ # Reflect the fact that a job has been killed in redis
244
+ def kill
245
+ self.status = 'killed'
246
+
247
+ Sidekiq.redis do |conn|
248
+ conn.multi do
249
+ save
250
+ conn.zrem(self.class.kill_key, self.jid)
251
+ end
252
+ end
253
+ end
254
+
255
+ # @return [Boolean] can the current job be killed
256
+ def killable?
257
+ !kill_requested? && %w(waiting working).include?(self.status)
258
+ end
259
+
260
+ # @return [Integer] Job progress in percents (reported solely by {SidekiqStatus::Worker job})
261
+ def pct_complete
262
+ (at.to_f / total * 100).round
263
+ end
264
+
265
+ # @param [Fixnum] at Report the progress of a job which is tracked by the current {SidekiqStatus::Container}
266
+ def at=(at)
267
+ raise ArgumentError, "at=#{at.inspect} is not a scalar number" unless at.is_a?(Numeric)
268
+ @at = at
269
+ @total = @at if @total < @at
270
+ end
271
+
272
+ # Report the estimated upper limit of {SidekiqStatus::Container#at= job items}
273
+ #
274
+ # @param [Fixnum] total
275
+ def total=(total)
276
+ raise ArgumentError, "total=#{total.inspect} is not a scalar number" unless total.is_a?(Numeric)
277
+ @total = total
278
+ end
279
+
280
+ # Report current job execution status
281
+ #
282
+ # @param [String] status Current job {SidekiqStatus::STATUS_NAMES status}
283
+ def status=(status)
284
+ raise ArgumentError, "invalid status #{status.inspect}" unless STATUS_NAMES.include?(status)
285
+ @status = status
286
+ end
287
+
288
+ # Report side message for the client code
289
+ #
290
+ # @param [String] message
291
+ def message=(message)
292
+ @message = message && message.to_s
293
+ end
294
+
295
+ # Assign multiple values to {SidekiqStatus::Container} attributes at once
296
+ #
297
+ # @param [Hash{#to_s => #to_json}] attrs Attribute=>value pairs
298
+ def attributes=(attrs = {})
299
+ attrs.each do |attr_name, value|
300
+ setter = "#{attr_name}="
301
+ send(setter, value)
302
+ end
303
+ end
304
+
305
+ # Assign multiple values to {SidekiqStatus::Container} attributes at once and save to redis
306
+ # @param [Hash{#to_s => #to_json}] attrs Attribute=>value pairs
307
+ def update_attributes(attrs = {})
308
+ self.attributes = attrs
309
+ save
310
+ end
311
+
312
+ STATUS_NAMES.each do |status_name|
313
+ define_method("#{status_name}?") do
314
+ status == status_name
315
+ end
316
+ end
317
+
318
+ protected
319
+
320
+ # Merge-in given data to the current container
321
+ #
322
+ # @private
323
+ # @param [Hash] data
324
+ def load(data)
325
+ data = DEFAULTS.merge(data)
326
+
327
+ @args, @worker, @queue = data.values_at('args', 'worker', 'queue')
328
+ @status, @at, @total, @message = data.values_at('status', 'at', 'total', 'message')
329
+ @payload = data['payload']
330
+ @last_updated_at = data['last_updated_at'] && Time.at(data['last_updated_at'].to_i)
331
+ end
332
+
333
+ # Dump current container attribute values to json-serializable hash
334
+ #
335
+ # @private
336
+ # @return [Hash] Data for subsequent json-serialization
337
+ def dump
338
+ {
339
+ 'args' => self.args,
340
+ 'worker' => self.worker,
341
+ 'queue' => self.queue,
342
+
343
+ 'status' => self.status,
344
+ 'at' => self.at,
345
+ 'total' => self.total,
346
+ 'message' => self.message,
347
+
348
+ 'payload' => self.payload,
349
+ 'last_updated_at' => Time.now.to_i
350
+ }
351
+ end
352
+ end
353
+ end
354
+
@@ -0,0 +1,5 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module SidekiqStatus
3
+ # SidekiqStatus version
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,65 @@
1
+ module SidekiqStatus
2
+ # Hook into *Sidekiq::Web* Sinatra app which adds a new "/statuses" page
3
+ module Web
4
+ # A ruby module hook to treat the included code as if it was defined inside Sidekiq::Web.
5
+ # Thus it extends Sidekiq::Web Sinatra application
6
+ #
7
+ # @param [Sidekiq::Web] target
8
+ def self.extend_object(target)
9
+ target.class_eval do
10
+ # Calls the given block for every possible template file in views,
11
+ # named name.ext, where ext is registered on engine.
12
+ def find_template(views, name, engine, &block)
13
+ super(File.expand_path('../../../web/views', __FILE__), name, engine, &block)
14
+ super
15
+ end
16
+
17
+ tabs << 'Statuses'
18
+
19
+ get '/statuses' do
20
+ @count = (params[:count] || 25).to_i
21
+
22
+ @current_page = (params[:page] || 1).to_i
23
+ @current_page = 1 unless @current_page > 0
24
+
25
+ @total_size = SidekiqStatus::Container.size
26
+
27
+ pageidx = @current_page - 1
28
+ @statuses = SidekiqStatus::Container.statuses(pageidx * @count, (pageidx + 1) * @count)
29
+
30
+ slim :statuses
31
+ end
32
+
33
+ get '/statuses/:jid' do
34
+ @status = SidekiqStatus::Container.load(params[:jid])
35
+ slim :status
36
+ end
37
+
38
+ get '/statuses/:jid/kill' do
39
+ SidekiqStatus::Container.load(params[:jid]).request_kill
40
+ redirect to(:statuses)
41
+ end
42
+
43
+ get '/statuses/delete/all' do
44
+ SidekiqStatus::Container.delete
45
+ redirect to(:statuses)
46
+ end
47
+
48
+ get '/statuses/delete/complete' do
49
+ SidekiqStatus::Container.delete('complete')
50
+ redirect to(:statuses)
51
+ end
52
+
53
+ get '/statuses/delete/finished' do
54
+ SidekiqStatus::Container.delete(SidekiqStatus::Container::FINISHED_STATUS_NAMES)
55
+ redirect to(:statuses)
56
+ end
57
+
58
+
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ require 'sidekiq/web'
65
+ Sidekiq::Web.extend(SidekiqStatus::Web)