From 9ea167db16bf7f4cc9ee94967d0ea0b4dee6a7c5 Mon Sep 17 00:00:00 2001 From: Amadeus Folego Date: Tue, 26 Jul 2016 11:03:17 -0300 Subject: [PATCH] Migrate Sidekiq::Web to a pure Rack application Migrate Sidekiq::Web a pure Rack application to avoid sinatra as dependency. rack-protection is still needed. The application is mounted on top of Rack::Builder, mantaining all of the previous http interface. Rack apps being used: - Rack::File to serve assets - Rack::Session::Cookie, the secret can be configured via Sidekiq::Web.session_secret - Rack::Protection, same as before when using sinatra - Sidekiq::WebApplication, described below. Sidekiq::WebApplication is a very simple rack application composed of a Sidekiq::WebRouter and a Sidekiq::WebAction dispatcher. This terminology was adopted to be able to mantain Sidekiq::Web as a Rack app. The Router is heavily inspired on Rack::Router[0] (and in many parts identical), however not being retrocompatible. The Action is a wrapper to provide convenience, DRY code and maintain the old interface. I tried to mantain most of the old application structures so that customizations and monkey-patches are easily adjustable or even further work be done to enforce retrocompatibility. Testing welcome! 0: https://github.com/pjb3/rack-router --- lib/sidekiq/web.rb | 276 ++++-------------- lib/sidekiq/web/application.rb | 245 ++++++++++++++++ .../{web_helpers.rb => web/helpers.rb} | 6 +- lib/sidekiq/web/router.rb | 173 +++++++++++ myapp/Gemfile | 2 +- sidekiq.gemspec | 2 +- test/test_web.rb | 14 +- web/views/_job_info.erb | 8 +- web/views/_nav.erb | 6 +- web/views/_paging.erb | 10 +- web/views/_poll_js.erb | 2 +- web/views/_poll_link.erb | 2 +- web/views/dashboard.erb | 12 +- web/views/dead.erb | 2 +- web/views/layout.erb | 8 +- web/views/morgue.erb | 2 +- web/views/queue.erb | 4 +- web/views/retries.erb | 2 +- web/views/retry.erb | 2 +- web/views/scheduled.erb | 2 +- web/views/scheduled_job_info.erb | 2 +- 21 files changed, 519 insertions(+), 263 deletions(-) create mode 100644 lib/sidekiq/web/application.rb rename lib/sidekiq/{web_helpers.rb => web/helpers.rb} (96%) create mode 100644 lib/sidekiq/web/router.rb diff --git a/lib/sidekiq/web.rb b/lib/sidekiq/web.rb index 22a4837c..ed454f4b 100644 --- a/lib/sidekiq/web.rb +++ b/lib/sidekiq/web.rb @@ -1,26 +1,30 @@ # frozen_string_literal: true require 'erb' require 'yaml' -require 'sinatra/base' require 'sidekiq' require 'sidekiq/api' require 'sidekiq/paginator' -require 'sidekiq/web_helpers' + +require 'sidekiq/web/router' +require 'sidekiq/web/application' + +require 'rack/protection' + +require 'rack/builder' +require 'rack/static' +require 'rack/session/cookie' module Sidekiq - class Web < Sinatra::Base - include Sidekiq::Paginator + class Web + REQUEST_METHOD = 'REQUEST_METHOD'.freeze + PATH_INFO = 'PATH_INFO'.freeze - enable :sessions - use ::Rack::Protection, :use => :authenticity_token unless ENV['RACK_ENV'] == 'test' - - set :root, File.expand_path(File.dirname(__FILE__) + "/../../web") - set :public_folder, proc { "#{root}/assets" } - set :views, proc { "#{root}/views" } - set :locales, ["#{root}/locales"] - - helpers WebHelpers + ROOT = File.expand_path(File.dirname(__FILE__) + "/../../web") + VIEWS = "#{ROOT}/views" + LOCALES = ["#{ROOT}/locales"] + LAYOUT = "#{VIEWS}/layout.erb" + ASSETS = "#{ROOT}/assets" DEFAULT_TABS = { "Dashboard" => '', @@ -41,226 +45,56 @@ module Sidekiq end alias_method :tabs, :custom_tabs - attr_accessor :app_url + def locales + @locales ||= LOCALES + end + + def session_secret=(secret) + @secret = secret + end + + attr_accessor :app_url, :session_secret + attr_writer :locales end - get "/busy" do - erb :busy - end + def initialize + secret = Web.session_secret - post "/busy" do - if params['identity'] - p = Sidekiq::Process.new('identity' => params['identity']) - p.quiet! if params[:quiet] - p.stop! if params[:stop] - else - processes.each do |pro| - pro.quiet! if params[:quiet] - pro.stop! if params[:stop] + if secret.nil? + # explicitly generating a session secret eagerly to play nice with preforking + begin + require 'securerandom' + secret = SecureRandom.hex(64) + rescue LoadError, NotImplementedError + # SecureRandom raises a NotImplementedError if no random device is available + secret = "%064x" % Kernel.rand(2**256-1) end end - redirect "#{root_path}busy" - end - get "/queues" do - @queues = Sidekiq::Queue.all - erb :queues - end + @app = Rack::Builder.new do + %w(stylesheets javascripts images).each do |asset_dir| + map "/#{asset_dir}" do + run Rack::File.new("#{ASSETS}/#{asset_dir}") + end + end - get "/queues/:name" do - halt 404 unless params[:name] - @count = (params[:count] || 25).to_i - @name = params[:name] - @queue = Sidekiq::Queue.new(@name) - (@current_page, @total_size, @messages) = page("queue:#{@name}", params[:page], @count) - @messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) } - erb :queue - end + use Rack::Session::Cookie, secret: secret + use ::Rack::Protection, use: :authenticity_token unless ENV['RACK_ENV'] == 'test' - post "/queues/:name" do - Sidekiq::Queue.new(params[:name]).clear - redirect "#{root_path}queues" - end - - post "/queues/:name/delete" do - Sidekiq::Job.new(params[:key_val], params[:name]).delete - redirect_with_query("#{root_path}queues/#{params[:name]}") - end - - get '/morgue' do - @count = (params[:count] || 25).to_i - (@current_page, @total_size, @dead) = page("dead", params[:page], @count, reverse: true) - @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) } - erb :morgue - end - - get "/morgue/:key" do - halt 404 unless params['key'] - @dead = Sidekiq::DeadSet.new.fetch(*parse_params(params['key'])).first - redirect "#{root_path}morgue" if @dead.nil? - erb :dead - end - - post '/morgue' do - redirect request.path unless params['key'] - - params['key'].each do |key| - job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first - retry_or_delete_or_kill job, params if job - end - redirect_with_query("#{root_path}morgue") - end - - post "/morgue/all/delete" do - Sidekiq::DeadSet.new.clear - redirect "#{root_path}morgue" - end - - post "/morgue/all/retry" do - Sidekiq::DeadSet.new.retry_all - redirect "#{root_path}morgue" - end - - post "/morgue/:key" do - halt 404 unless params['key'] - job = Sidekiq::DeadSet.new.fetch(*parse_params(params['key'])).first - retry_or_delete_or_kill job, params if job - redirect_with_query("#{root_path}morgue") - end - - - get '/retries' do - @count = (params[:count] || 25).to_i - (@current_page, @total_size, @retries) = page("retry", params[:page], @count) - @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) } - erb :retries - end - - get "/retries/:key" do - @retry = Sidekiq::RetrySet.new.fetch(*parse_params(params['key'])).first - redirect "#{root_path}retries" if @retry.nil? - erb :retry - end - - post '/retries' do - redirect request.path unless params['key'] - - params['key'].each do |key| - job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first - retry_or_delete_or_kill job, params if job - end - redirect_with_query("#{root_path}retries") - end - - post "/retries/all/delete" do - Sidekiq::RetrySet.new.clear - redirect "#{root_path}retries" - end - - post "/retries/all/retry" do - Sidekiq::RetrySet.new.retry_all - redirect "#{root_path}retries" - end - - post "/retries/:key" do - job = Sidekiq::RetrySet.new.fetch(*parse_params(params['key'])).first - retry_or_delete_or_kill job, params if job - redirect_with_query("#{root_path}retries") - end - - get '/scheduled' do - @count = (params[:count] || 25).to_i - (@current_page, @total_size, @scheduled) = page("schedule", params[:page], @count) - @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) } - erb :scheduled - end - - get "/scheduled/:key" do - @job = Sidekiq::ScheduledSet.new.fetch(*parse_params(params['key'])).first - redirect "#{root_path}scheduled" if @job.nil? - erb :scheduled_job_info - end - - post '/scheduled' do - redirect request.path unless params['key'] - - params['key'].each do |key| - job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first - delete_or_add_queue job, params if job - end - redirect_with_query("#{root_path}scheduled") - end - - post "/scheduled/:key" do - halt 404 unless params['key'] - job = Sidekiq::ScheduledSet.new.fetch(*parse_params(params['key'])).first - delete_or_add_queue job, params if job - redirect_with_query("#{root_path}scheduled") - end - - get '/' do - @redis_info = redis_info.select{ |k, v| REDIS_KEYS.include? k } - stats_history = Sidekiq::Stats::History.new((params[:days] || 30).to_i) - @processed_history = stats_history.processed - @failed_history = stats_history.failed - erb :dashboard - end - - REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human) - - get '/dashboard/stats' do - redirect "#{root_path}stats" - end - - get '/stats' do - sidekiq_stats = Sidekiq::Stats.new - redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k } - - content_type :json - Sidekiq.dump_json( - sidekiq: { - processed: sidekiq_stats.processed, - failed: sidekiq_stats.failed, - busy: sidekiq_stats.workers_size, - processes: sidekiq_stats.processes_size, - enqueued: sidekiq_stats.enqueued, - scheduled: sidekiq_stats.scheduled_size, - retries: sidekiq_stats.retry_size, - dead: sidekiq_stats.dead_size, - default_latency: sidekiq_stats.default_queue_latency - }, - redis: redis_stats - ) - end - - get '/stats/queues' do - queue_stats = Sidekiq::Stats::Queues.new - - content_type :json - Sidekiq.dump_json( - queue_stats.lengths - ) - end - - private - - def retry_or_delete_or_kill job, params - if params['retry'] - job.retry - elsif params['delete'] - job.delete - elsif params['kill'] - job.kill + run WebApplication.new end end - def delete_or_add_queue job, params - if params['delete'] - job.delete - elsif params['add_to_queue'] - job.add_to_queue - end + def call(env) + @app.call(env) end + + def self.call(env) + @app ||= new + @app.call(env) + end + + ERB.new(File.read LAYOUT).def_method(WebAction, '_render') end end diff --git a/lib/sidekiq/web/application.rb b/lib/sidekiq/web/application.rb new file mode 100644 index 00000000..1fff12ac --- /dev/null +++ b/lib/sidekiq/web/application.rb @@ -0,0 +1,245 @@ +# frozen_string_literal: true +module Sidekiq + class WebApplication + extend WebRouter + + REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human) + + get "/" do + @redis_info = redis_info.select{ |k, v| REDIS_KEYS.include? k } + stats_history = Sidekiq::Stats::History.new((params['days'] || 30).to_i) + @processed_history = stats_history.processed + @failed_history = stats_history.failed + + render(:dashboard) + end + + get "/busy" do + render(:busy) + end + + post "/busy" do + if params['identity'] + p = Sidekiq::Process.new('identity' => params['identity']) + p.quiet! if params['quiet'] + p.stop! if params['stop'] + else + processes.each do |pro| + pro.quiet! if params['quiet'] + pro.stop! if params['stop'] + end + end + + redirect "busy" + end + + get "/queues" do + @queues = Sidekiq::Queue.all + + render(:queues) + end + + get "/queues/:name" do + @name = route_params[:name] + + next(not_found(env)) unless @name + + @count = (params['count'] || 25).to_i + @queue = Sidekiq::Queue.new(@name) + (@current_page, @total_size, @messages) = page("queue:#{@name}", params['page'], @count) + @messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) } + + render(:queue) + end + + post "/queues/:name" do + Sidekiq::Queue.new(route_params[:name]).clear + + redirect "queues" + end + + post "/queues/:name/delete" do + name = route_params[:name] + Sidekiq::Job.new(params['key_val'], name).delete + + redirect_with_query("queues/#{name}") + end + + get '/morgue' do + @count = (params['count'] || 25).to_i + (@current_page, @total_size, @dead) = page("dead", params['page'], @count, reverse: true) + @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) } + + render(:morgue) + end + + get "/morgue/:key" do + next not_found(env) unless key = route_params[:key] + + @dead = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first + + if @dead.nil? + redirect "morgue" + else + render(:dead) + end + end + + post '/morgue' do + next redirect(request.path) unless params['key'] + + params['key'].each do |key| + job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first + retry_or_delete_or_kill job, params if job + end + + redirect_with_query("morgue") + end + + post "/morgue/all/delete" do + Sidekiq::DeadSet.new.clear + + redirect "morgue" + end + + post "/morgue/all/retry" do + Sidekiq::DeadSet.new.retry_all + + redirect "morgue" + end + + post "/morgue/:key" do + next not_found(env) unless key = route_params[:key] + + job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first + retry_or_delete_or_kill job, params if job + + redirect_with_query("morgue") + end + + get '/retries' do + @count = (params['count'] || 25).to_i + (@current_page, @total_size, @retries) = page("retry", params['page'], @count) + @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) } + + render(:retries) + end + + get "/retries/:key" do + @retry = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first + + if @retry.nil? + redirect "retries" + else + render(:retry) + end + end + + post '/retries' do + next redirect(request.path) unless params['key'] + + params['key'].each do |key| + job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first + retry_or_delete_or_kill job, params if job + end + + redirect_with_query("retries") + end + + post "/retries/all/delete" do + Sidekiq::RetrySet.new.clear + + redirect "retries" + end + + post "/retries/all/retry" do + Sidekiq::RetrySet.new.retry_all + + redirect "retries" + end + + post "/retries/:key" do + job = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first + + retry_or_delete_or_kill job, params if job + + redirect_with_query("retries") + end + + get '/scheduled' do + @count = (params['count'] || 25).to_i + (@current_page, @total_size, @scheduled) = page("schedule", params['page'], @count) + @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) } + + render(:scheduled) + end + + get "/scheduled/:key" do + @job = Sidekiq::ScheduledSet.new.fetch(*parse_params(route_params[:key])).first + + if @job.nil? + redirect "scheduled" + else + render(:scheduled_job_info) + end + end + + post '/scheduled' do + next redirect(request.path) unless params['key'] + + params['key'].each do |key| + job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first + delete_or_add_queue job, params if job + end + + redirect_with_query("scheduled") + end + + post "/scheduled/:key" do + next not_found(env) unless key = route_params[:key] + + job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first + delete_or_add_queue job, params if job + + redirect_with_query("scheduled") + end + + get '/dashboard/stats' do + redirect "stats" + end + + get '/stats' do + sidekiq_stats = Sidekiq::Stats.new + redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k } + + json( + sidekiq: { + processed: sidekiq_stats.processed, + failed: sidekiq_stats.failed, + busy: sidekiq_stats.workers_size, + processes: sidekiq_stats.processes_size, + enqueued: sidekiq_stats.enqueued, + scheduled: sidekiq_stats.scheduled_size, + retries: sidekiq_stats.retry_size, + dead: sidekiq_stats.dead_size, + default_latency: sidekiq_stats.default_queue_latency + }, + redis: redis_stats + ) + end + + get '/stats/queues' do + json Sidekiq::Stats::Queues.new.lengths + end + + def not_found(env) + [404, {}, []] + end + + def call(env) + action = self.class.match(env) || WebAction.new(env, method(:not_found).to_proc) + + action.instance_exec env, &action.app + end + end +end diff --git a/lib/sidekiq/web_helpers.rb b/lib/sidekiq/web/helpers.rb similarity index 96% rename from lib/sidekiq/web_helpers.rb rename to lib/sidekiq/web/helpers.rb index 0f89cdd7..0a15a7e9 100644 --- a/lib/sidekiq/web_helpers.rb +++ b/lib/sidekiq/web/helpers.rb @@ -9,7 +9,7 @@ module Sidekiq @@strings[lang] ||= begin # Allow sidekiq-web extensions to add locale paths # so extensions can be localized - settings.locales.each_with_object({}) do |path, global| + Web.locales.each_with_object({}) do |path, global| find_locale_files(lang).each do |file| strs = YAML.load(File.open(file)) global.deep_merge!(strs[lang]) @@ -24,7 +24,7 @@ module Sidekiq end def locale_files - @@locale_files ||= settings.locales.flat_map do |path| + @@locale_files ||= Web.locales.flat_map do |path| Dir["#{path}/*.yml"] end end @@ -69,7 +69,7 @@ module Sidekiq def locale @locale ||= begin locale = 'en'.freeze - languages = request.env['HTTP_ACCEPT_LANGUAGE'.freeze] || 'en'.freeze + languages = env['HTTP_ACCEPT_LANGUAGE'.freeze] || 'en'.freeze languages.downcase.split(','.freeze).each do |lang| next if lang == '*'.freeze lang = lang.split(';'.freeze)[0] diff --git a/lib/sidekiq/web/router.rb b/lib/sidekiq/web/router.rb new file mode 100644 index 00000000..8a8cca1b --- /dev/null +++ b/lib/sidekiq/web/router.rb @@ -0,0 +1,173 @@ +# frozen_string_literal: true +require 'sidekiq/web/helpers' + +module Sidekiq + module WebRouter + GET = 'GET'.freeze + POST = 'POST'.freeze + HEAD = 'HEAD'.freeze + + ROUTE_PARAMS = 'rack.route_params'.freeze + REQUEST_METHOD = 'REQUEST_METHOD'.freeze + PATH_INFO = 'PATH_INFO'.freeze + + def get(path, &block) + route(GET, path, &block) + end + + def post(path, &block) + route(POST, path, &block) + end + + def route(method, path, &block) + @routes ||= [] + @routes << WebRoute.new(method, path, block) + end + + def match(env) + request_method = env[REQUEST_METHOD] + request_method = GET if request_method == HEAD + @routes.each do |route| + if params = route.match(request_method, env[PATH_INFO]) + env[ROUTE_PARAMS] = params + return WebAction.new(env, route.app) + end + end + + nil + end + end + + class WebAction + include WebHelpers + include Sidekiq::Paginator + + RACK_SESSION = 'rack.session'.freeze + + CONTENT_TYPE = "Content-Type".freeze + LOCATION = "Location".freeze + + TEXT_HTML = "text/html".freeze + APPLICATION_JSON = "application/json".freeze + + attr_accessor :env, :app + + def request + @request ||= Rack::Request.new(env) + end + + def params + request.params + end + + def route_params + env[WebRouter::ROUTE_PARAMS] + end + + def session + env[RACK_SESSION] + end + + def render(file) + output = _render { ERB.new(File.read "#{Web::VIEWS}/#{file}.erb").result(binding) } + + [200, { CONTENT_TYPE => TEXT_HTML }, [output]] + end + + def redirect(location) + [302, { LOCATION => "#{request.base_url}#{root_path}#{location}" }, []] + end + + def json(payload) + [200, { CONTENT_TYPE => APPLICATION_JSON }, [Sidekiq.dump_json(payload)]] + end + + def partial(file, locals = {}) + ERB.new(File.read "#{Web::VIEWS}/_#{file}.erb").result(binding) + end + + def initialize(env, app) + @env = env + @app = app + end + + def retry_or_delete_or_kill(job, params) + if params['retry'] + job.retry + elsif params['delete'] + job.delete + elsif params['kill'] + job.kill + end + end + + def delete_or_add_queue(job, params) + if params['delete'] + job.delete + elsif params['add_to_queue'] + job.add_to_queue + end + end + end + + class WebRoute + attr_accessor :request_method, :pattern, :app, :constraints, :name + + NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^:$\/]+)/.freeze + + def initialize(request_method, pattern, app) + @request_method = request_method + @pattern = pattern + @app = app + end + + def regexp + @regexp ||= compile + end + + def compile + p = if pattern.match(NAMED_SEGMENTS_PATTERN) + pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^$/]+)') + else + pattern + end + + Regexp.new("\\A#{p}\\Z") + end + + def match(request_method, path) + return nil unless request_method == self.request_method + + if path_match = path.match(regexp) + params = Hash[path_match.names.map(&:to_sym).zip(path_match.captures)] + + params if meets_constraints(params) + end + end + + def meets_constraints(params) + if constraints + constraints.each do |param, constraint| + unless params[param].to_s.match(constraint) + return false + end + end + end + + true + end + + def eql?(o) + o.is_a?(self.class) && + o.request_method == request_method && + o.pattern == pattern && + o.app == app && + o.constraints == constraints + end + alias == eql? + + def hash + request_method.hash ^ pattern.hash ^ app.hash ^ constraints.hash + end + end +end diff --git a/myapp/Gemfile b/myapp/Gemfile index cfa4af61..2777d0f8 100644 --- a/myapp/Gemfile +++ b/myapp/Gemfile @@ -18,4 +18,4 @@ gem 'sidekiq', :path => '..' #de gem 'pry-byebug' # sidekiq-web dependencies -gem 'sinatra' +gem 'rack-protection' diff --git a/sidekiq.gemspec b/sidekiq.gemspec index e7d0b71f..b34449e9 100644 --- a/sidekiq.gemspec +++ b/sidekiq.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |gem| gem.add_dependency 'redis', '~> 3.2', '>= 3.2.1' gem.add_dependency 'connection_pool', '~> 2.2', '>= 2.2.0' gem.add_dependency 'concurrent-ruby', '~> 1.0' - gem.add_dependency 'sinatra', '>= 1.4.7' + gem.add_dependency 'rack-protection', '~> 1.5' gem.add_development_dependency 'redis-namespace', '~> 1.5', '>= 1.5.2' gem.add_development_dependency 'minitest', '~> 5.7', '>= 5.7.0' gem.add_development_dependency 'rake', '~> 10.0' diff --git a/test/test_web.rb b/test/test_web.rb index 152b2cce..99b2603c 100644 --- a/test/test_web.rb +++ b/test/test_web.rb @@ -3,7 +3,7 @@ require_relative 'helper' require 'sidekiq/web' require 'rack/test' -require 'tilt/erubis' +#require 'tilt/erubis' class TestWeb < Sidekiq::Test @@ -341,7 +341,6 @@ class TestWeb < Sidekiq::Test assert last_response.body.include?( "<a>hello</a>" ) assert !last_response.body.include?( "hello" ) - # on /queues page params = add_xss_retry # sorry, don't know how to easily make this show up on queues page otherwise. post "/retries/#{job_params(*params)}", 'retry' => 'Retry' @@ -372,21 +371,23 @@ class TestWeb < Sidekiq::Test describe 'custom locales' do before do - Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), "fixtures") + Sidekiq::Web.locales << File.join(File.dirname(__FILE__), "fixtures") Sidekiq::Web.tabs['Custom Tab'] = '/custom' - Sidekiq::Web.get('/custom') do + Sidekiq::WebApplication.get('/custom') do clear_caches # ugly hack since I can't figure out how to access WebHelpers outside of this context - t('translated_text') + + [200, { "Content-Type" => 'text/html' }, [ t('translated_text') ]] end end after do Sidekiq::Web.tabs.delete 'Custom Tab' - Sidekiq::Web.settings.locales.pop + Sidekiq::Web.locales.pop end it 'can show user defined tab with custom locales' do get '/custom' + assert_match(/Changed text/, last_response.body) end end @@ -564,6 +565,7 @@ class TestWeb < Sidekiq::Test Sidekiq.redis do |conn| conn.zadd('retry', score, Sidekiq.dump_json(msg)) end + [msg, score] end diff --git a/web/views/_job_info.erb b/web/views/_job_info.erb index a943a7a2..549a09ab 100644 --- a/web/views/_job_info.erb +++ b/web/views/_job_info.erb @@ -1,3 +1,5 @@ +<% job = locals[:job] %> +

<%= t('Job') %>

@@ -50,7 +52,7 @@ <% end %> - <% if type == :retry %> + <% if locals[:type] == :retry %> <% if job['retry_count'] && job['retry_count'] > 0 %> <%= t('RetryCount') %> @@ -71,13 +73,13 @@ <%= relative_time(job.at) %> <% end %> - <% if type == :scheduled %> + <% if locals[:type] == :scheduled %> <%= t('Scheduled') %> <%= relative_time(job.at) %> <% end %> - <% if type == :dead %> + <% if locals[:type] == :dead %> <%= t('LastRetry') %> <%= relative_time(job.at) %> diff --git a/web/views/_nav.erb b/web/views/_nav.erb index c950b9c4..795457be 100644 --- a/web/views/_nav.erb +++ b/web/views/_nav.erb @@ -7,14 +7,14 @@ <%= Sidekiq::NAME %> - <%= erb :_status %> + <%= partial :status %> @@ -54,7 +54,7 @@