sidekiq 3.3.3 → 3.3.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4895c316f5c9f592745594ac4984e150fbf5ea62
4
- data.tar.gz: b78f6b9242d8a981a60d3fbe28a08c3c16b839af
3
+ metadata.gz: a6ca011daf81025502d9f9abfdf4e38fafd05d83
4
+ data.tar.gz: 3687865623e0ad65b98fcd7e5e576f540d80df23
5
5
  SHA512:
6
- metadata.gz: 9134b87eee18dc46b1e95d9190ee3a3f0d5b9426f5b6953d9b55aee39d9fd6e2441b9133fa797e137d8cfdc50c6e99fbff1c74195fe065ff3d84b4e8869333fe
7
- data.tar.gz: 23767242d2d5911526cae5b5061d050a363947cc9f21f7dbc336eb6d5605ab470a0733b2ab6979e33b3dd81969bdd282958ce69f8a6a3a4683fdceb71dfcc4a1
6
+ metadata.gz: 87bf6bf803ce2c24d2c424348fe83681c77dafda7206300c13ebda6c9aca5d36d1551642c68a0d2f37558540a64e8bd5aa5cfb6d95baaa7ce1fd08d62000ffdb
7
+ data.tar.gz: ddf4ccef0e503b5e59b874d8ec14ecb6d94721cf674f19385cb0a6e3d180c3605da63b03bb4752fd11c4da60be2d41f63f08c2f2a413e623d15bd929bc9210ff
data/Changes.md CHANGED
@@ -1,3 +1,16 @@
1
+ 3.3.4
2
+ -----------
3
+
4
+ - **Improved ActiveJob integration** - Web UI now shows ActiveJobs in a
5
+ nicer format and job logging shows the actual class name, requires
6
+ Rails 4.2.2+ [#2248, #2259]
7
+ - Add Sidekiq::Process#dump\_threads API to trigger TTIN output [#2247]
8
+ - Web UI polling now uses Ajax to avoid page reload [#2266, davydovanton]
9
+ - Several Web UI styling improvements [davydovanton]
10
+ - Add Tamil, Hindi translations for Web UI [ferdinandrosario, tejasbubane]
11
+ - Fix Web UI to work with country-specific locales [#2243]
12
+ - Handle circular error causes [#2285, eugenk]
13
+
1
14
  3.3.3
2
15
  -----------
3
16
 
@@ -3,6 +3,18 @@ Sidekiq Pro Changelog
3
3
 
4
4
  Please see [http://sidekiq.org/pro](http://sidekiq.org/pro) for more details and how to buy.
5
5
 
6
+ 2.0.2
7
+ -----------
8
+
9
+ - Multiple Web UIs can now run in the same process. [#2267] If you have
10
+ multiple Redis shards, you can mount UIs for all in the same process:
11
+ ```ruby
12
+ POOL1 = ConnectionPool.new { Redis.new(:url => "redis://localhost:6379/0") }
13
+ POOL2 = ConnectionPool.new { Redis.new(:url => "redis://localhost:6378/0") }
14
+
15
+ mount Sidekiq::Pro::Web.with(redis_pool: POOL1) => '/sidekiq1'
16
+ mount Sidekiq::Pro::Web.with(redis_pool: POOL2) => '/sidekiq2'
17
+ ```
6
18
 
7
19
  2.0.1
8
20
  -----------
@@ -30,6 +30,11 @@ module Sidekiq
30
30
  dead_timeout_in_seconds: 180 * 24 * 60 * 60 # 6 months
31
31
  }
32
32
 
33
+ DEFAULT_WORKER_OPTIONS = {
34
+ 'retry' => true,
35
+ 'queue' => 'default'
36
+ }
37
+
33
38
  def self.❨╯°□°❩╯︵┻━┻
34
39
  puts "Calm down, bro"
35
40
  end
@@ -103,7 +108,7 @@ module Sidekiq
103
108
  end
104
109
 
105
110
  def self.default_worker_options
106
- defined?(@default_worker_options) ? @default_worker_options : { 'retry' => true, 'queue' => 'default' }
111
+ defined?(@default_worker_options) ? @default_worker_options : DEFAULT_WORKER_OPTIONS
107
112
  end
108
113
 
109
114
  def self.load_json(string)
@@ -283,7 +283,7 @@ module Sidekiq
283
283
  "#{target}.#{method}"
284
284
  end
285
285
  when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
286
- args[0]
286
+ @item['wrapped'] || args[0]
287
287
  else
288
288
  klass
289
289
  end
@@ -297,7 +297,7 @@ module Sidekiq
297
297
  arg
298
298
  end
299
299
  when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
300
- args[1..-1]
300
+ @item['wrapped'] ? args[0]["arguments"] : []
301
301
  else
302
302
  args
303
303
  end
@@ -384,7 +384,7 @@ module Sidekiq
384
384
  raise "Retry not available on jobs which have not failed" unless item["failed_at"]
385
385
  remove_job do |message|
386
386
  msg = Sidekiq.load_json(message)
387
- msg['retry_count'] = msg['retry_count'] - 1
387
+ msg['retry_count'] -= 1
388
388
  Sidekiq::Client.push(msg)
389
389
  end
390
390
  end
@@ -721,6 +721,10 @@ module Sidekiq
721
721
  signal('TERM')
722
722
  end
723
723
 
724
+ def dump_threads
725
+ signal('TTIN')
726
+ end
727
+
724
728
  private
725
729
 
726
730
  def signal(sig)
@@ -785,13 +789,15 @@ module Sidekiq
785
789
  def size
786
790
  Sidekiq.redis do |conn|
787
791
  procs = conn.smembers('processes')
788
- return 0 if procs.empty?
789
-
790
- conn.pipelined do
791
- procs.each do |key|
792
- conn.hget(key, 'busy')
793
- end
794
- end.map(&:to_i).inject(:+)
792
+ if procs.empty?
793
+ 0
794
+ else
795
+ conn.pipelined do
796
+ procs.each do |key|
797
+ conn.hget(key, 'busy')
798
+ end
799
+ end.map(&:to_i).inject(:+)
800
+ end
795
801
  end
796
802
  end
797
803
  end
@@ -211,19 +211,23 @@ module Sidekiq
211
211
  raise(ArgumentError, "Message args must be an Array") unless item['args'].is_a?(Array)
212
212
  raise(ArgumentError, "Message class must be either a Class or String representation of the class name") unless item['class'].is_a?(Class) || item['class'].is_a?(String)
213
213
 
214
- if item['class'].is_a?(Class)
215
- raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item['class'].ancestors.inspect}") if !item['class'].respond_to?('get_sidekiq_options')
216
- normalized_item = item['class'].get_sidekiq_options.merge(item)
217
- normalized_item['class'] = normalized_item['class'].to_s
214
+ normalized_hash(item['class'.freeze])
215
+ .each{ |key, value| item[key] = value if item[key].nil? }
216
+
217
+ item['class'.freeze] = item['class'.freeze].to_s
218
+ item['queue'.freeze] = item['queue'.freeze].to_s
219
+ item['jid'.freeze] ||= SecureRandom.hex(12)
220
+ item['enqueued_at'.freeze] ||= Time.now.to_f
221
+ item
222
+ end
223
+
224
+ def normalized_hash(item_class)
225
+ if item_class.is_a?(Class)
226
+ raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item_class.ancestors.inspect}") if !item_class.respond_to?('get_sidekiq_options'.freeze)
227
+ item_class.get_sidekiq_options
218
228
  else
219
- normalized_item = Sidekiq.default_worker_options.merge(item)
229
+ Sidekiq.default_worker_options
220
230
  end
221
-
222
- normalized_item['queue'] = normalized_item['queue'].to_s
223
- normalized_item['jid'] ||= SecureRandom.hex(12)
224
- normalized_item['enqueued_at'] ||= Time.now.to_f
225
- normalized_item
226
231
  end
227
-
228
232
  end
229
233
  end
@@ -35,7 +35,7 @@ module Sidekiq
35
35
 
36
36
  begin
37
37
  work = @strategy.retrieve_work
38
- ::Sidekiq.logger.info("Redis is online, #{Time.now.to_f - @down.to_f} sec downtime") if @down
38
+ ::Sidekiq.logger.info("Redis is online, #{Time.now - @down} sec downtime") if @down
39
39
  @down = nil
40
40
 
41
41
  if work
@@ -4,7 +4,11 @@ module Sidekiq
4
4
  class Logging
5
5
 
6
6
  def call(worker, item, queue)
7
- Sidekiq::Logging.with_context("#{worker.class.to_s} JID-#{item['jid']}#{" BID-#{item['bid']}" if item['bid']}") do
7
+ # If we're using a wrapper class, like ActiveJob, use the "wrapped"
8
+ # attribute to expose the underlying thing.
9
+ klass = item['wrapped'] || worker.class.to_s
10
+
11
+ Sidekiq::Logging.with_context("#{klass} JID-#{item['jid']}#{" BID-#{item['bid']}" if item['bid']}") do
8
12
  begin
9
13
  start = Time.now
10
14
  logger.info { "start" }
@@ -18,7 +22,7 @@ module Sidekiq
18
22
  end
19
23
 
20
24
  def elapsed(start)
21
- (Time.now - start).to_f.round(3)
25
+ (Time.now - start).round(3)
22
26
  end
23
27
 
24
28
  def logger
@@ -188,12 +188,16 @@ module Sidekiq
188
188
  end
189
189
  end
190
190
 
191
- def exception_caused_by_shutdown?(e)
191
+ def exception_caused_by_shutdown?(e, checked_causes = [])
192
192
  # In Ruby 2.1.0 only, check if exception is a result of shutdown.
193
193
  return false unless defined?(e.cause)
194
194
 
195
+ # Handle circular causes
196
+ checked_causes << e.object_id
197
+ return false if checked_causes.include?(e.cause.object_id)
198
+
195
199
  e.cause.instance_of?(Sidekiq::Shutdown) ||
196
- exception_caused_by_shutdown?(e.cause)
200
+ exception_caused_by_shutdown?(e.cause, checked_causes)
197
201
  end
198
202
 
199
203
  end
@@ -1,5 +1,6 @@
1
1
  module Sidekiq
2
2
  module Paginator
3
+
3
4
  def page(key, pageidx=1, page_size=25, opts=nil)
4
5
  current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
5
6
  pageidx = current_page - 1
@@ -22,19 +23,20 @@ module Sidekiq
22
23
  conn.zrange(key, starting, ending, :with_scores => true)
23
24
  end
24
25
  end
26
+ [current_page, total_size, items]
25
27
  when 'list'
26
28
  total_size, items = conn.multi do
27
29
  conn.llen(key)
28
30
  conn.lrange(key, starting, ending)
29
31
  end
32
+ [current_page, total_size, items]
30
33
  when 'none'
31
- return [1, 0, []]
34
+ [1, 0, []]
32
35
  else
33
36
  raise "can't page a #{type}"
34
37
  end
35
38
  end
36
-
37
- [current_page, total_size, items]
38
39
  end
40
+
39
41
  end
40
42
  end
@@ -7,10 +7,7 @@ module Sidekiq
7
7
  class << self
8
8
 
9
9
  def create(options={})
10
- url = options[:url] || determine_redis_provider
11
- if url
12
- options[:url] = url
13
- end
10
+ options[:url] ||= determine_redis_provider
14
11
 
15
12
  # need a connection for Fetcher and Retry
16
13
  size = options[:size] || (Sidekiq.server? ? (Sidekiq.options[:concurrency] + 2) : 5)
@@ -1,3 +1,3 @@
1
1
  module Sidekiq
2
- VERSION = "3.3.3"
2
+ VERSION = "3.3.4"
3
3
  end
@@ -3,30 +3,77 @@ require 'uri'
3
3
  module Sidekiq
4
4
  # This is not a public API
5
5
  module WebHelpers
6
- def strings
7
- @@strings ||= begin
6
+ def strings(lang)
7
+ @@strings ||= {}
8
+ @@strings[lang] ||= begin
8
9
  # Allow sidekiq-web extensions to add locale paths
9
10
  # so extensions can be localized
10
- settings.locales.each_with_object({}) do |path,global|
11
- Dir["#{path}/*.yml"].each_with_object(global) do |file,hash|
11
+ settings.locales.each_with_object({}) do |path, global|
12
+ find_locale_files(lang).each do |file|
12
13
  strs = YAML.load(File.open(file))
13
- hash.deep_merge!(strs)
14
+ global.deep_merge!(strs[lang])
14
15
  end
15
16
  end
16
17
  end
17
18
  end
18
19
 
20
+ def locale_files
21
+ @@locale_files = settings.locales.flat_map do |path|
22
+ Dir["#{path}/*.yml"]
23
+ end
24
+ end
25
+
26
+ def find_locale_files(lang)
27
+ locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
28
+ end
29
+
19
30
  # This is a hook for a Sidekiq Pro feature. Please don't touch.
20
31
  def filtering(*)
21
32
  end
22
33
 
34
+ # This view helper provide ability display you html code in
35
+ # to head of page. Example:
36
+ #
37
+ # <% add_to_head do %>
38
+ # <link rel="stylesheet" .../>
39
+ # <meta .../>
40
+ # <% end %>
41
+ #
42
+ def add_to_head(&block)
43
+ @head_html ||= []
44
+ @head_html << block if block_given?
45
+ end
46
+
47
+ def display_custom_head
48
+ return unless @head_html
49
+ @head_html.map { |block| capture(&block) }.join
50
+ end
51
+
52
+ # Simple capture method for erb templates. The origin was
53
+ # capture method from sinatra-contrib library.
54
+ def capture(&block)
55
+ block.call
56
+ eval('', block.binding)
57
+ end
58
+
59
+ # Given a browser request Accept-Language header like
60
+ # "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2", this function
61
+ # will return "fr" since that's the first code with a matching
62
+ # locale in web/locales
23
63
  def locale
24
- lang = (request.env["HTTP_ACCEPT_LANGUAGE"] || 'en').split(',')[0].downcase
25
- strings[lang] ? lang : 'en'
64
+ @locale ||= begin
65
+ locale = 'en'.freeze
66
+ languages = request.env['HTTP_ACCEPT_LANGUAGE'.freeze] || 'en'.freeze
67
+ languages.downcase.split(','.freeze).each do |lang|
68
+ lang = lang.split(';'.freeze)[0]
69
+ break locale = lang if find_locale_files(lang).any?
70
+ end
71
+ locale
72
+ end
26
73
  end
27
74
 
28
75
  def get_locale
29
- strings[locale]
76
+ strings(locale)
30
77
  end
31
78
 
32
79
  def t(msg, options={})
@@ -129,7 +176,7 @@ module Sidekiq
129
176
  RETRY_JOB_KEYS = Set.new(%w(
130
177
  queue class args retry_count retried_at failed_at
131
178
  jid error_message error_class backtrace
132
- error_backtrace enqueued_at retry
179
+ error_backtrace enqueued_at retry wrapped
133
180
  ))
134
181
 
135
182
  def retry_extra_items(retry_job)
@@ -64,7 +64,7 @@ module Sidekiq
64
64
  # can be true, false or an integer number of lines to save, default *false*
65
65
  # :pool - use the given Redis connection pool to push this type of job to a given shard.
66
66
  def sidekiq_options(opts={})
67
- self.sidekiq_options_hash = get_sidekiq_options.merge((opts || {}).stringify_keys)
67
+ self.sidekiq_options_hash = get_sidekiq_options.merge(opts.stringify_keys)
68
68
  end
69
69
 
70
70
  def sidekiq_retry_in(&block)
@@ -328,6 +328,46 @@ class TestRetry < Sidekiq::Test
328
328
  File.read(@tmp_log_path), 'Log entry missing for sidekiq_retry_in')
329
329
  end
330
330
  end
331
+
332
+ describe 'handles errors withouth cause' do
333
+ before do
334
+ @error = nil
335
+ begin
336
+ raise ::StandardError, 'Error'
337
+ rescue ::StandardError => e
338
+ @error = e
339
+ end
340
+ end
341
+
342
+ it "does not recurse infinitely checking if it's a shutdown" do
343
+ assert(!Sidekiq::Middleware::Server::RetryJobs.new.send(
344
+ :exception_caused_by_shutdown?, @error))
345
+ end
346
+ end
347
+
348
+ describe 'handles errors with circular causes' do
349
+ before do
350
+ @error = nil
351
+ begin
352
+ begin
353
+ raise ::StandardError, 'Error 1'
354
+ rescue ::StandardError => e1
355
+ begin
356
+ raise ::StandardError, 'Error 2'
357
+ rescue ::StandardError => e2
358
+ raise e1
359
+ end
360
+ end
361
+ rescue ::StandardError => e
362
+ @error = e
363
+ end
364
+ end
365
+
366
+ it "does not recurse infinitely checking if it's a shutdown" do
367
+ assert(!Sidekiq::Middleware::Server::RetryJobs.new.send(
368
+ :exception_caused_by_shutdown?, @error))
369
+ end
370
+ end
331
371
  end
332
372
 
333
373
  end
@@ -1,3 +1,4 @@
1
+ # encoding: utf-8
1
2
  require_relative 'helper'
2
3
  require 'sidekiq'
3
4
  require 'sidekiq/web'
@@ -29,6 +30,18 @@ class TestWeb < Sidekiq::Test
29
30
  end
30
31
  end
31
32
 
33
+ it 'can show text with any locales' do
34
+ rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'ru,en'}
35
+ get '/', {}, rackenv
36
+ assert_match(/Панель управления/, last_response.body)
37
+ rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'es,en'}
38
+ get '/', {}, rackenv
39
+ assert_match(/Panel de Control/, last_response.body)
40
+ rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'en-us'}
41
+ get '/', {}, rackenv
42
+ assert_match(/Dashboard/, last_response.body)
43
+ end
44
+
32
45
  describe 'busy' do
33
46
 
34
47
  it 'can display workers' do
@@ -0,0 +1,46 @@
1
+ require_relative 'helper'
2
+ require 'sidekiq'
3
+ require 'sidekiq/web_helpers'
4
+
5
+ class TestWebHelpers < Sidekiq::Test
6
+
7
+ class Helpers
8
+ include Sidekiq::WebHelpers
9
+
10
+ def initialize(params={})
11
+ @thehash = default.merge(params)
12
+ end
13
+
14
+ def request
15
+ self
16
+ end
17
+
18
+ def settings
19
+ self
20
+ end
21
+
22
+ def locales
23
+ ['web/locales']
24
+ end
25
+
26
+ def env
27
+ @thehash
28
+ end
29
+
30
+ def default
31
+ {
32
+ }
33
+ end
34
+ end
35
+
36
+ def test_locale_determination
37
+ obj = Helpers.new
38
+ assert_equal 'en', obj.locale
39
+
40
+ obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2')
41
+ assert_equal 'fr', obj.locale
42
+
43
+ obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2')
44
+ assert_equal 'zh-cn', obj.locale
45
+ end
46
+ end
@@ -18,6 +18,15 @@
18
18
  */
19
19
  (function(e){function n(){var t=r(this);if(!isNaN(t.datetime)){e(this).text(i(t.datetime))}return this}function r(n){n=e(n);if(!n.data("timeago")){n.data("timeago",{datetime:t.datetime(n)});var r=e.trim(n.text());if(r.length>0&&!(t.isTime(n)&&n.attr("title"))){n.attr("title",r)}}return n.data("timeago")}function i(e){return t.inWords(s(e))}function s(e){return(new Date).getTime()-e.getTime()}e.timeago=function(t){if(t instanceof Date){return i(t)}else if(typeof t==="string"){return i(e.timeago.parse(t))}else if(typeof t==="number"){return i(new Date(t))}else{return i(e.timeago.datetime(t))}};var t=e.timeago;e.extend(e.timeago,{settings:{refreshMillis:6e4,allowFuture:false,strings:{prefixAgo:null,prefixFromNow:null,suffixAgo:"ago",suffixFromNow:"from now",seconds:"less than a minute",minute:"about a minute",minutes:"%d minutes",hour:"about an hour",hours:"about %d hours",day:"a day",days:"%d days",month:"about a month",months:"%d months",year:"about a year",years:"%d years",wordSeparator:" ",numbers:[]}},inWords:function(t){function l(r,i){var s=e.isFunction(r)?r(i,t):r;var o=n.numbers&&n.numbers[i]||i;return s.replace(/%d/i,o)}var n=this.settings.strings;var r=n.prefixAgo;var i=n.suffixAgo;if(this.settings.allowFuture){if(t<0){r=n.prefixFromNow;i=n.suffixFromNow}}var s=Math.abs(t)/1e3;var o=s/60;var u=o/60;var a=u/24;var f=a/365;var c=s<45&&l(n.seconds,Math.round(s))||s<90&&l(n.minute,1)||o<45&&l(n.minutes,Math.round(o))||o<90&&l(n.hour,1)||u<24&&l(n.hours,Math.round(u))||u<42&&l(n.day,1)||a<30&&l(n.days,Math.round(a))||a<45&&l(n.month,1)||a<365&&l(n.months,Math.round(a/30))||f<1.5&&l(n.year,1)||l(n.years,Math.round(f));var h=n.wordSeparator===undefined?" ":n.wordSeparator;return e.trim([r,c,i].join(h))},parse:function(t){var n=e.trim(t);n=n.replace(/\.\d+/,"");n=n.replace(/-/,"/").replace(/-/,"/");n=n.replace(/T/," ").replace(/Z/," UTC");n=n.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2");return new Date(n)},datetime:function(n){var r=t.isTime(n)?e(n).attr("datetime"):e(n).attr("title");return t.parse(r)},isTime:function(t){return e(t).get(0).tagName.toLowerCase()==="time"}});e.fn.timeago=function(){var e=this;e.each(n);var r=t.settings;if(r.refreshMillis>0){setInterval(function(){e.each(n)},r.refreshMillis)}return e};document.createElement("abbr");document.createElement("time")})(jQuery)
20
20
 
21
+ /* ========================================================================
22
+ * Bootstrap: dropdown.js v3.3.4
23
+ * http://getbootstrap.com/javascript/#dropdowns
24
+ * ========================================================================
25
+ * Copyright 2011-2015 Twitter, Inc.
26
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
27
+ * ======================================================================== */
28
+ +function(a){"use strict";function e(d){d&&3===d.which||(a(b).remove(),a(c).each(function(){var b=a(this),c=f(b),e={relatedTarget:this};c.hasClass("open")&&(d&&"click"==d.type&&/input|textarea/i.test(d.target.tagName)&&a.contains(c[0],d.target)||(c.trigger(d=a.Event("hide.bs.dropdown",e)),d.isDefaultPrevented()||(b.attr("aria-expanded","false"),c.removeClass("open").trigger("hidden.bs.dropdown",e))))}))}function f(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function g(b){return this.each(function(){var c=a(this),e=c.data("bs.dropdown");e||c.data("bs.dropdown",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var b=".dropdown-backdrop",c='[data-toggle="dropdown"]',d=function(b){a(b).on("click.bs.dropdown",this.toggle)};d.VERSION="3.3.4",d.prototype.toggle=function(b){var c=a(this);if(!c.is(".disabled, :disabled")){var d=f(c),g=d.hasClass("open");if(e(),!g){"ontouchstart"in document.documentElement&&!d.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",e);var h={relatedTarget:this};if(d.trigger(b=a.Event("show.bs.dropdown",h)),b.isDefaultPrevented())return;c.trigger("focus").attr("aria-expanded","true"),d.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},d.prototype.keydown=function(b){if(/(38|40|27|32)/.test(b.which)&&!/input|textarea/i.test(b.target.tagName)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var e=f(d),g=e.hasClass("open");if(!g&&27!=b.which||g&&27==b.which)return 27==b.which&&e.find(c).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find('[role="menu"]'+h+', [role="listbox"]'+h);if(i.length){var j=i.index(b.target);38==b.which&&j>0&&j--,40==b.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=g,a.fn.dropdown.Constructor=d,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",e).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",c,d.prototype.toggle).on("keydown.bs.dropdown.data-api",c,d.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="menu"]',d.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="listbox"]',d.prototype.keydown)}(jQuery);
29
+
21
30
  Sidekiq = {};
22
31
 
23
32
  $(function() {
@@ -42,3 +51,31 @@ $(function() {
42
51
  $($(this).attr('data-target')).toggle();
43
52
  });
44
53
  });
54
+
55
+ function updatePage(url) {
56
+ setInterval(function () {
57
+ $.ajax({
58
+ url: url,
59
+ dataType: 'html'
60
+ }).done(function (data) {
61
+ var $page = $(data).filter('#page')
62
+ $('#page').replaceWith($page)
63
+ })
64
+ }, parseInt(localStorage.timeInterval) || 2000);
65
+ }
66
+
67
+ $(function() {
68
+ 'use strict';
69
+
70
+ var $navbar = $('.navbar-default')
71
+ , staticContentWidth = 0
72
+
73
+ $('[data-navbar="static"]').each(function () {
74
+ staticContentWidth += $(this).width()
75
+ });
76
+
77
+ if ($navbar.width() < staticContentWidth) {
78
+ $('[data-navbar="custom-tab"]').hide()
79
+ $('[data-navbar="dropdown"]').show()
80
+ }
81
+ });
@@ -101,11 +101,6 @@ header.row .pagination {
101
101
  .poll-wrapper {
102
102
  margin: 9px;
103
103
  }
104
- .nav #live-poll {
105
- height: 25px;
106
- line-height: 25px;
107
- width: 195px;
108
- }
109
104
  #live-poll.active {
110
105
  background-color: #009300;
111
106
  }
@@ -208,6 +203,53 @@ table .table-checkbox label {
208
203
  color: #585454;
209
204
  }
210
205
 
206
+ @media (max-width: 768px) {
207
+ .navbar .navbar-header .navbar-livereload {
208
+ border: none;
209
+ margin: 9px 10px 0;
210
+ padding: 0;
211
+ }
212
+
213
+ .navbar .navbar-collapse {
214
+ max-height: 400px;
215
+ }
216
+
217
+ .navbar .navbar-collapse .navbar-livereload {
218
+ display: none;
219
+ }
220
+
221
+ .navbar.navbar-fixed-top ul {
222
+ margin-right: -15px!important;
223
+ }
224
+
225
+ .navbar .nav a {
226
+ text-align: center;
227
+ }
228
+ }
229
+
230
+ @media (width: 768px) {
231
+ .navbar .navbar-collapse .navbar-livereload {
232
+ display: block;
233
+ margin-top: 5px;
234
+ }
235
+
236
+ .navbar .poll-wrapper {
237
+ margin: 4px 4px 0 0;
238
+ }
239
+
240
+ .navbar .dropdown-menu {
241
+ min-width: 120px;
242
+ }
243
+
244
+ .navbar .dropdown-menu a {
245
+ text-align: left;
246
+ }
247
+ }
248
+
249
+ .nav .dropdown {
250
+ display: none;
251
+ }
252
+
211
253
  .navbar-footer .navbar ul.nav {
212
254
  text-align: center;
213
255
  float: none;
@@ -581,6 +623,10 @@ div.interval-slider input {
581
623
  }
582
624
  }
583
625
 
626
+ .chart {
627
+ margin: 0;
628
+ }
629
+
584
630
  .history-heading {
585
631
  padding-right: 15px;
586
632
  }
@@ -600,24 +646,17 @@ div.interval-slider input {
600
646
  text-decoration: none;
601
647
  }
602
648
 
603
- @media (max-width: 768px) {
649
+ @media (max-width: 767px) {
604
650
  .navbar .navbar-brand {
605
651
  float: none;
606
652
  display: block;
607
653
  }
608
654
 
609
- .navbar ul.nav li a {
610
- padding-left: 8px;
611
- padding-right: 8px;
612
- }
613
-
614
655
  .navbar.navbar-fixed-top ul {
615
- float: none;
616
656
  margin-right: 0;
617
657
  }
618
658
 
619
659
  .navbar.navbar-fixed-top li {
620
- float: none;
621
660
  margin-right: 0;
622
661
  }
623
662
 
@@ -644,14 +683,20 @@ div.interval-slider input {
644
683
  .navbar-text {
645
684
  float:none;
646
685
  line-height: 30px;
686
+ margin: 15px auto;
647
687
  }
648
688
  }
649
689
 
650
- @media (max-width: 979px) {
690
+ @media (max-width: 767px) {
651
691
  .navbar-fixed-top, .navbar-fixed-bottom {
652
692
  margin: 0 -20px;
653
693
  }
654
694
 
695
+ .navbar ul.nav li a {
696
+ padding-left: 8px;
697
+ padding-right: 8px;
698
+ }
699
+
655
700
  .admin #page {
656
701
  padding-top: 10px;
657
702
  }
@@ -686,7 +731,7 @@ div.interval-slider input {
686
731
  .container {
687
732
  padding: 0;
688
733
  }
689
- @media (max-width: 979px) {
734
+ @media (max-width: 767px) {
690
735
  .navbar-fixed-top, .navbar-fixed-bottom {
691
736
  position: relative;
692
737
  top: auto;
@@ -73,3 +73,4 @@ en: # <---- change this to your locale code
73
73
  StopAll: Stop All
74
74
  QuietAll: Quiet All
75
75
  PollingInterval: Polling interval
76
+ Plugins: Plugins
@@ -0,0 +1,75 @@
1
+ # elements like %{queue} are variables and should not be translated
2
+ hi:
3
+ Dashboard: डैशबोर्ड
4
+ Status: स्थिती
5
+ Time: समय
6
+ Namespace: नामस्थान
7
+ Realtime: रिअल टाईम
8
+ History: वृत्तान्त
9
+ Busy: व्यस्थ
10
+ Processed: कार्रवाई कृत
11
+ Failed: असफल
12
+ Scheduled: परिगणित
13
+ Retries: पुनर्प्रयास
14
+ Enqueued: कतारबद्ध
15
+ Worker: वर्कर
16
+ LivePoll: लाईव सर्वेक्षण
17
+ StopPolling: सर्वेक्षण रोको
18
+ Queue: कतार
19
+ Class: क्लास
20
+ Job: कार्य
21
+ Arguments: अर्गुमेन्ट्स्
22
+ Extras: अतिरिक्त
23
+ Started: शुरु हुआ
24
+ ShowAll: सब दिखाएं
25
+ CurrentMessagesInQueue: <span class='title'>%{queue}</span> कतार मे वर्तमान कार्य
26
+ Delete: हटाओ
27
+ AddToQueue: कतार मे जोड़ें
28
+ AreYouSureDeleteJob: क्या आप इस कार्य को हटाना चाहते है?
29
+ AreYouSureDeleteQueue: क्या आप %{queue} कतार को हटाना चाहते है?
30
+ Queues: कतारे
31
+ Size: आकार
32
+ Actions: कार्रवाई
33
+ NextRetry: अगला पुन:प्रयास
34
+ RetryCount: पुन:प्रयास संख्या
35
+ RetryNow: पुन:प्रयास करे
36
+ Kill: नष्ट करे
37
+ LastRetry: अंतिम पुन:प्रयास
38
+ OriginallyFailed: पहिले से विफल
39
+ AreYouSure: क्या आपको यकीन है?
40
+ DeleteAll: सब हटाओ
41
+ RetryAll: सब पुन:प्रयास करे
42
+ NoRetriesFound: कोई पुनर्प्रयास नही पाए गए
43
+ Error: एरर
44
+ ErrorClass: एरर क्लास
45
+ ErrorMessage: एरर संदेश
46
+ ErrorBacktrace: एरर बैकट्रेस
47
+ GoBack: ← पीछे
48
+ NoScheduledFound: कोई परिगणित कार्य नही पाए गए
49
+ When: कब
50
+ ScheduledJobs: परिगणित कार्य
51
+ idle: निष्क्रिय
52
+ active: सक्रिय
53
+ Version: वर्जन
54
+ Connections: कनेक्श्न
55
+ MemoryUsage: मेमरी उपयोग
56
+ PeakMemoryUsage: अधिकतम मेमरी उपयोग
57
+ Uptime: उपरिकाल (दिवस)
58
+ OneWeek: १ सप्ताह
59
+ OneMonth: १ महीना
60
+ ThreeMonths: ३ महीने
61
+ SixMonths: ६ महीने
62
+ Failures: असफलता
63
+ DeadJobs: निष्प्राण कार्य
64
+ NoDeadJobsFound: कोई निष्प्राण कार्य नही पाए गए
65
+ Dead: निष्प्राण
66
+ Processes: प्रोसेसेस्
67
+ Thread: थ्रेड
68
+ Threads: थ्रेड्स्
69
+ Jobs: कार्य
70
+ Paused: थमे हुए
71
+ Stop: रोको
72
+ Quiet: शांत करो
73
+ StopAll: सब रोको
74
+ QuietAll: सब शांत करो
75
+ PollingInterval: सर्वेक्षण अंतराल
@@ -2,7 +2,7 @@ ru:
2
2
  Dashboard: Панель управления
3
3
  Status: Статус
4
4
  Time: Время
5
- Namespace: Пространоство имен
5
+ Namespace: Пространство имен
6
6
  Realtime: Сейчас
7
7
  History: История
8
8
  Busy: Занят
@@ -72,3 +72,4 @@ ru:
72
72
  StopAll: Остановить все
73
73
  QuietAll: Отдыхать всем
74
74
  PollingInterval: Интервал опроса
75
+ Plugins: Плагины
@@ -0,0 +1,75 @@
1
+ # elements like %{queue} are variables and should not be translated
2
+ ta: # <---- change this to your locale code
3
+ Dashboard: டாஷ்போர்டு
4
+ Status: நிலைமை
5
+ Time: நேரம்
6
+ Namespace: பெயர்வெளி
7
+ Realtime: நேரலை
8
+ History: வரலாறு
9
+ Busy: பணிமிகுதி
10
+ Processed: நிறையுற்றது
11
+ Failed: தோல்வி
12
+ Scheduled: திட்டமிடப்பட்ட
13
+ Retries: மீண்டும் முயற்சிக்க,
14
+ Enqueued: வரிசைப்படுத்தப்பட்டவை
15
+ Worker: பணியாளர்
16
+ LivePoll: நேரடி கணிப்பு
17
+ StopPolling: நிறுத்து வாக்குப்பதிவு
18
+ Queue: வரிசை
19
+ Class: வகுப்பு
20
+ Job: வேலை
21
+ Arguments: வாதங்கள்,
22
+ Extras: உபரி
23
+ Started: தொடங்குதல்
24
+ ShowAll: அனைத்து காட்டு
25
+ CurrentMessagesInQueue: தற்போதைய வேலைகள் <span class='title'>%{queue}</span>
26
+ Delete: நீக்கு
27
+ AddToQueue: வரிசையில் சேர்
28
+ AreYouSureDeleteJob: நீ இந்த வேலையை நீக்க வேண்டும் என்று உறுதியாக இருக்கிறீர்களா?
29
+ AreYouSureDeleteQueue: நீங்கள் %{queue} வரிசையில் நீக்க வேண்டும் என்பதில் உறுதியாக இருக்கிறீர்களா?
30
+ Queues: வரிசை
31
+ Size: அளவு
32
+ Actions: செயல்கள்
33
+ NextRetry: அடுத்த, மீண்டும் முயற்சிக்கவும்
34
+ RetryCount: கணிப்பீடு, மீண்டும் முயற்சிக்கவும்
35
+ RetryNow: இப்போது மீண்டும் முயற்சி செய்க
36
+ Kill: கொல்
37
+ LastRetry: கடைசியாக, மீண்டும் முயற்சிக்கவும்
38
+ OriginallyFailed: முதலில் தோல்வி
39
+ AreYouSure: நீங்கள் உறுதியாக இருக்கிறீர்களா?
40
+ DeleteAll: அனைத்து நீக்கு
41
+ RetryAll: அனைத்து, மீண்டும் முயற்சிக்கவும்
42
+ NoRetriesFound: இல்லை மீண்டும் காணப்படவில்லை
43
+ Error: பிழை
44
+ ErrorClass: பிழை வகுப்பு
45
+ ErrorMessage: பிழை செய்தி
46
+ ErrorBacktrace: பிழை பின்தேடுலை
47
+ GoBack: பின்புறம்
48
+ NoScheduledFound: திட்டமிட்ட வேலைகள் காணப்படவில்லை
49
+ When: எப்பொழுது?
50
+ ScheduledJobs: திட்டமிட்ட வேலைகள்
51
+ idle: முடங்கு நேரம்
52
+ active: செயலில்
53
+ Version: பதிப்பு
54
+ Connections: இணைப்புகள்
55
+ MemoryUsage: நினைவக பயன்பாடு
56
+ PeakMemoryUsage: உச்ச நினைவக பயன்பாடு
57
+ Uptime: இயக்க நேரம் (நாட்கள்)
58
+ OneWeek: 1 வாரம்
59
+ OneMonth: 1 மாதம்
60
+ ThreeMonths: 3 மாதங்கள்
61
+ SixMonths: 6 மாதங்கள்
62
+ Failures: தோல்விகள்
63
+ DeadJobs: டெட் வேலைகள்
64
+ NoDeadJobsFound: இறந்த வேலை எதுவும் இல்லை
65
+ Dead: இறந்துபோன
66
+ Processes: செயல்முறைகள்
67
+ Thread: நூல்
68
+ Threads: நூல்கள்
69
+ Jobs: வேலை வாய்ப்புகள்
70
+ Paused: தற்காலிக பணிநிறுத்தம்
71
+ Stop: நிறுத்து
72
+ Quiet: அமைதியான
73
+ StopAll: நிறுத்து அனைத்து
74
+ QuietAll: அமைதியான அனைத்து
75
+ PollingInterval: வாக்குப்பதிவு இடைவெளி
@@ -1,34 +1,67 @@
1
1
  <div class="navbar navbar-default navbar-fixed-top">
2
- <div class="navbar-inner">
3
- <div class="container-fluid text-center">
4
- <a class="navbar-brand" href="<%= root_path %>">
5
- <%= Sidekiq::NAME %>
6
- <%= erb :_status %>
7
- </a>
8
- <ul class="nav navbar-nav">
9
- <% Sidekiq::Web.default_tabs.each do |title, url| %>
10
- <% if url == '' %>
11
- <li class="<%= current_path == url ? 'active' : '' %>">
12
- <a href="<%= root_path %><%= url %>"><%= t(title) %></a>
13
- </li>
14
- <% else %>
15
- <li class="<%= current_path.start_with?(url) ? 'active' : '' %>">
16
- <a href="<%= root_path %><%= url %>"><%= t(title) %></a>
17
- </li>
18
- <% end %>
2
+ <div class="container-fluid">
3
+ <div class="navbar-header" data-navbar="static">
4
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-menu">
5
+ <span class="icon-bar"></span>
6
+ <span class="icon-bar"></span>
7
+ <span class="icon-bar"></span>
8
+ </button>
9
+ <div class="navbar-toggle collapsed navbar-livereload">
10
+ <%= erb :_poll %>
11
+ <% if Sidekiq::Web.app_url %>
12
+ <a class="btn btn-inverse" href="<%= Sidekiq::Web.app_url %>">Back to App</a>
13
+ <% end %>
14
+ </div>
15
+ <a class="navbar-brand" href="<%= root_path %>">
16
+ <%= Sidekiq::NAME %>
17
+ <%= erb :_status %>
18
+ </a>
19
+ </div>
20
+
21
+ <div class="collapse navbar-collapse" id="navbar-menu">
22
+ <ul class="nav navbar-nav" data-navbar="static">
23
+ <% Sidekiq::Web.default_tabs.each do |title, url| %>
24
+ <% if url == '' %>
25
+ <li class="<%= current_path == url ? 'active' : '' %>">
26
+ <a href="<%= root_path %><%= url %>"><%= t(title) %></a>
27
+ </li>
28
+ <% else %>
29
+ <li class="<%= current_path.start_with?(url) ? 'active' : '' %>">
30
+ <a href="<%= root_path %><%= url %>"><%= t(title) %></a>
31
+ </li>
19
32
  <% end %>
20
- <% Sidekiq::Web.custom_tabs.each do |title, url| %>
21
- <li class="<%= current_path.start_with?(url) ? 'active' : '' %>">
33
+ <% end %>
34
+
35
+ <li class="dropdown" data-navbar="dropdown">
36
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
37
+ <%= t('Plugins') %> <span class="caret"></span>
38
+ </a>
39
+ <ul class="dropdown-menu" role="menu">
40
+ <% Sidekiq::Web.custom_tabs.each do |title, url| %>
41
+ <li>
22
42
  <a href="<%= root_path %><%= url %>"><%= t(title) %></a>
23
43
  </li>
24
- <% end %>
25
- </ul>
26
- </div>
27
- <div class="poll-wrapper pull-right">
28
- <%= erb :_poll %>
29
- <% if Sidekiq::Web.app_url %>
30
- <a class="btn btn-inverse" href="<%= Sidekiq::Web.app_url %>">Back to App</a>
44
+ <% end %>
45
+ </ul>
46
+ </li>
47
+
48
+ <% Sidekiq::Web.custom_tabs.each do |title, url| %>
49
+ <li class="<%= current_path.start_with?(url) ? 'active' : '' %>" data-navbar="custom-tab">
50
+ <a href="<%= root_path %><%= url %>"><%= t(title) %></a>
51
+ </li>
31
52
  <% end %>
53
+ </ul>
54
+ <ul class="nav navbar-nav navbar-right navbar-livereload" data-navbar="static">
55
+ <li>
56
+ <div class="poll-wrapper pull-right">
57
+ <%= erb :_poll %>
58
+ <% if Sidekiq::Web.app_url %>
59
+ <a class="btn btn-inverse" href="<%= Sidekiq::Web.app_url %>">Back to App</a>
60
+ <% end %>
61
+ </div>
62
+ </li>
63
+ </ul>
32
64
  </div>
33
65
  </div>
34
66
  </div>
67
+ </div>
@@ -1,10 +1,10 @@
1
1
  <% if current_path != '' %>
2
2
  <% if params[:poll] %>
3
3
  <script>
4
- setInterval("window.location.reload(true)", parseInt(localStorage.timeInterval) || 2000);
4
+ updatePage('<%= root_path + current_path %>')
5
5
  </script>
6
- <a id="live-poll" class="btn btn-primary active" href="<%= root_path %><%= current_path %>"><%= t('StopPolling') %></a>
6
+ <a id="live-poll" class="btn btn-primary active" href="<%= root_path + current_path %>"><%= t('StopPolling') %></a>
7
7
  <% else %>
8
- <a id="live-poll" class="btn btn-primary" href="<%= root_path %><%= current_path %>?<%= qparams(poll: true) %>"><%= t('LivePoll') %></a>
8
+ <a id="live-poll" class="btn btn-primary" href="<%= root_path + current_path %>?<%= qparams(poll: true) %>"><%= t('LivePoll') %></a>
9
9
  <% end %>
10
10
  <% end %>
@@ -15,12 +15,12 @@
15
15
  </div>
16
16
  </div>
17
17
 
18
- <div class="row">
18
+ <div class="row chart">
19
19
  <div id="realtime" data-processed-label="<%= t('Processed') %>" data-failed-label="<%= t('Failed') %>"></div>
20
20
  <div id="realtime-legend"></div>
21
21
  </div>
22
22
 
23
- <div class="row">
23
+ <div class="row chart">
24
24
  <h5>
25
25
  <span class="history-heading"><%= t('History') %></span>
26
26
  <a href="<%= root_path %>?days=7" class="history-graph <%= "active" if params[:days] == "7" %>"><%= t('OneWeek') %></a>
@@ -8,6 +8,7 @@
8
8
  <script type="text/javascript" src="<%= root_path %>javascripts/application.js"></script>
9
9
  <script type="text/javascript" src="<%= root_path %>javascripts/locales/jquery.timeago.<%= locale %>.js"></script>
10
10
  <meta name="google" content="notranslate" />
11
+ <%= display_custom_head %>
11
12
  </head>
12
13
  <body class="admin">
13
14
  <%= erb :_nav %>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.3
4
+ version: 3.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-19 00:00:00.000000000 Z
11
+ date: 2015-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -234,6 +234,7 @@ files:
234
234
  - test/test_testing_fake.rb
235
235
  - test/test_testing_inline.rb
236
236
  - test/test_web.rb
237
+ - test/test_web_helpers.rb
237
238
  - test/test_worker_generator.rb
238
239
  - web/assets/images/bootstrap/glyphicons-halflings-white.png
239
240
  - web/assets/images/bootstrap/glyphicons-halflings.png
@@ -297,6 +298,7 @@ files:
297
298
  - web/locales/en.yml
298
299
  - web/locales/es.yml
299
300
  - web/locales/fr.yml
301
+ - web/locales/hi.yml
300
302
  - web/locales/it.yml
301
303
  - web/locales/ja.yml
302
304
  - web/locales/ko.yml
@@ -307,6 +309,7 @@ files:
307
309
  - web/locales/pt.yml
308
310
  - web/locales/ru.yml
309
311
  - web/locales/sv.yml
312
+ - web/locales/ta.yml
310
313
  - web/locales/zh-cn.yml
311
314
  - web/locales/zh-tw.yml
312
315
  - web/views/_footer.erb
@@ -376,4 +379,5 @@ test_files:
376
379
  - test/test_testing_fake.rb
377
380
  - test/test_testing_inline.rb
378
381
  - test/test_web.rb
382
+ - test/test_web_helpers.rb
379
383
  - test/test_worker_generator.rb