sidekiq_status 1.0.0

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.
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)