1
0
Fork 0
mirror of https://github.com/mperham/sidekiq.git synced 2022-11-09 13:52:34 -05:00

merge master

This commit is contained in:
Mike Perham 2016-08-24 09:44:46 -07:00
commit 0f166de976
20 changed files with 731 additions and 239 deletions

View file

@ -1,10 +1,15 @@
# Sidekiq Changes # Sidekiq Changes
HEAD 4.2.0
----------- -----------
- Enable development-mode code reloading. With Rails 5.0+, you don't need - Enable development-mode code reloading. **With Rails 5.0+, you don't need
to restart Sidekiq to pick up your Sidekiq::Worker changes anymore! [#2457] to restart Sidekiq to pick up your Sidekiq::Worker changes anymore!** [#2457]
- **Remove Sinatra dependency**. Sidekiq's Web UI now uses Rack directly.
Thank you to Sidekiq's newest committer, **badosu**, for writing the code
and doing a lot of testing to ensure compatibility with many different
3rd party plugins. If your Web UI works with 4.1.4 but fails with
4.2.0, please open an issue. [#3075]
- Allow tuning of concurrency with the `RAILS_MAX_THREADS` env var. [#2985] - Allow tuning of concurrency with the `RAILS_MAX_THREADS` env var. [#2985]
This is the same var used by Puma so you can tune all of your systems This is the same var used by Puma so you can tune all of your systems
the same way: the same way:

5
examples/web/Gemfile Normal file
View file

@ -0,0 +1,5 @@
source 'https://rubygems.org'
gem 'sidekiq', path: '../../'
gem 'thin'
gem 'pry'

14
examples/web/config.ru Normal file
View file

@ -0,0 +1,14 @@
require 'sidekiq/web'
require 'redis'
$redis = Redis.new
class SinatraWorker
include Sidekiq::Worker
def perform(msg="lulz you forgot a msg!")
$redis.lpush("sinkiq-example-messages", msg)
end
end
run Sidekiq::Web

View file

@ -1,26 +1,28 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'erb' require 'erb'
require 'yaml'
require 'sinatra/base'
require 'sidekiq' require 'sidekiq'
require 'sidekiq/api' require 'sidekiq/api'
require 'sidekiq/paginator' require 'sidekiq/paginator'
require 'sidekiq/web_helpers' require 'sidekiq/web/helpers'
require 'sidekiq/web/router'
require 'sidekiq/web/action'
require 'sidekiq/web/application'
require 'rack/protection'
require 'rack/builder'
require 'rack/file'
require 'rack/session/cookie'
module Sidekiq module Sidekiq
class Web < Sinatra::Base class Web
include Sidekiq::Paginator ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../web")
VIEWS = "#{ROOT}/views".freeze
enable :sessions LOCALES = ["#{ROOT}/locales".freeze]
use ::Rack::Protection, :use => :authenticity_token unless ENV['RACK_ENV'] == 'test' LAYOUT = "#{VIEWS}/layout.erb".freeze
ASSETS = "#{ROOT}/assets".freeze
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
DEFAULT_TABS = { DEFAULT_TABS = {
"Dashboard" => '', "Dashboard" => '',
@ -32,6 +34,18 @@ module Sidekiq
} }
class << self class << self
def settings
self
end
def middlewares
@middlewares ||= []
end
def use(*middleware_args, &block)
middlewares << [middleware_args, block]
end
def default_tabs def default_tabs
DEFAULT_TABS DEFAULT_TABS
end end
@ -41,227 +55,94 @@ module Sidekiq
end end
alias_method :tabs, :custom_tabs alias_method :tabs, :custom_tabs
attr_accessor :app_url def locales
end @locales ||= LOCALES
get "/busy" do
erb :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 end
redirect "#{root_path}busy"
end
get "/queues" do def views
@queues = Sidekiq::Queue.all @views ||= VIEWS
erb :queues
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
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 end
redirect_with_query("#{root_path}morgue")
end
post "/morgue/all/delete" do def session_secret=(secret)
Sidekiq::DeadSet.new.clear @session_secret = secret
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 end
redirect_with_query("#{root_path}retries")
attr_accessor :app_url, :session_secret, :redis_pool
attr_writer :locales, :views
end end
post "/retries/all/delete" do def settings
Sidekiq::RetrySet.new.clear self.class.settings
redirect "#{root_path}retries"
end end
post "/retries/all/retry" do def use(*middleware_args, &block)
Sidekiq::RetrySet.new.retry_all middlewares << [middleware_args, block]
redirect "#{root_path}retries"
end end
post "/retries/:key" do def middlewares
job = Sidekiq::RetrySet.new.fetch(*parse_params(params['key'])).first @middlewares ||= Web.middlewares.dup
retry_or_delete_or_kill job, params if job
redirect_with_query("#{root_path}retries")
end end
get '/scheduled' do def call(env)
@count = (params[:count] || 25).to_i app.call(env)
(@current_page, @total_size, @scheduled) = page("schedule", params[:page], @count)
@scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
erb :scheduled
end end
get "/scheduled/:key" do def self.call(env)
@job = Sidekiq::ScheduledSet.new.fetch(*parse_params(params['key'])).first @app ||= new
redirect "#{root_path}scheduled" if @job.nil? @app.call(env)
erb :scheduled_job_info
end end
post '/scheduled' do def app
redirect request.path unless params['key'] @app ||= build
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 end
post "/scheduled/:key" do def self.register(extension)
halt 404 unless params['key'] extension.registered(WebApplication)
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 end
private private
def retry_or_delete_or_kill job, params def using?(middleware)
if params['retry'] middlewares.any? do |(m,_)|
job.retry m.kind_of?(Array) && (m[0] == middleware || m[0].kind_of?(middleware))
elsif params['delete']
job.delete
elsif params['kill']
job.kill
end end
end end
def delete_or_add_queue job, params def build
if params['delete'] middlewares = self.middlewares
job.delete klass = self.class
elsif params['add_to_queue']
job.add_to_queue unless using?(::Rack::Protection) || ENV['RACK_ENV'] == 'test'
middlewares.unshift [[::Rack::Protection, { use: :authenticity_token }], nil]
end
unless using? ::Rack::Session::Cookie
unless secret = Web.session_secret
require 'securerandom'
secret = SecureRandom.hex(64)
end
middlewares.unshift [[::Rack::Session::Cookie, { secret: secret }], nil]
end
::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
middlewares.each {|middleware, block| use *middleware, &block }
run WebApplication.new(klass)
end end
end end
end end
Sidekiq::WebApplication.helpers WebHelpers
Sidekiq::WebApplication.helpers Sidekiq::Paginator
Sidekiq::WebAction.class_eval "def _render\n#{ERB.new(File.read(Web::LAYOUT)).src}\nend"
end end
if defined?(::ActionDispatch::Request::Session) && if defined?(::ActionDispatch::Request::Session) &&

98
lib/sidekiq/web/action.rb Normal file
View file

@ -0,0 +1,98 @@
# frozen_string_literal: true
module Sidekiq
class WebAction
RACK_SESSION = 'rack.session'.freeze
LOCATION = "Location".freeze
CONTENT_TYPE = "Content-Type".freeze
TEXT_HTML = { CONTENT_TYPE => "text/html".freeze }
APPLICATION_JSON = { CONTENT_TYPE => "application/json".freeze }
attr_accessor :env, :block, :type
def settings
Web.settings
end
def request
@request ||= ::Rack::Request.new(env)
end
def halt(res)
throw :halt, res
end
def redirect(location)
throw :halt, [302, { LOCATION => "#{request.base_url}#{location}" }, []]
end
def params
indifferent_hash = Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
indifferent_hash.merge! request.params
route_params.each {|k,v| indifferent_hash[k.to_s] = v }
indifferent_hash
end
def route_params
env[WebRouter::ROUTE_PARAMS]
end
def session
env[RACK_SESSION]
end
def content_type(type)
@type = type
end
def erb(content, options = {})
if content.kind_of? Symbol
unless respond_to?(:"_erb_#{content}")
src = ERB.new(File.read("#{Web.settings.views}/#{content}.erb")).src
WebAction.class_eval("def _erb_#{content}\n#{src}\n end")
end
end
if @_erb
_erb(content, options[:locals])
else
@_erb = true
content = _erb(content, options[:locals])
_render { content }
end
end
def render(engine, content, options = {})
raise "Only erb templates are supported" if engine != :erb
erb(content, options)
end
def json(payload)
[200, APPLICATION_JSON, [Sidekiq.dump_json(payload)]]
end
def initialize(env, block)
@env = env
@block = block
@@files ||= {}
end
private
def _erb(file, locals)
locals.each {|k, v| define_singleton_method(k){ v } } if locals
if file.kind_of?(String)
ERB.new(file).result(binding)
else
send(:"_erb_#{file}")
end
end
end
end

View file

@ -0,0 +1,335 @@
# frozen_string_literal: true
module Sidekiq
class WebApplication
extend WebRouter
CONTENT_LENGTH = "Content-Length".freeze
CONTENT_TYPE = "Content-Type".freeze
REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human)
NOT_FOUND = [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass" }, ["Not Found"]]
def initialize(klass)
@klass = klass
end
def settings
@klass.settings
end
def self.settings
Sidekiq::Web.settings
end
def self.tabs
Sidekiq::Web.tabs
end
def self.set(key, val)
# nothing, backwards compatibility
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
get "/busy" do
erb(: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 "#{root_path}busy"
end
get "/queues" do
@queues = Sidekiq::Queue.all
erb(:queues)
end
get "/queues/:name" do
@name = route_params[:name]
halt(404) 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) }
erb(:queue)
end
post "/queues/:name" do
Sidekiq::Queue.new(route_params[:name]).clear
redirect "#{root_path}queues"
end
post "/queues/:name/delete" do
name = route_params[:name]
Sidekiq::Job.new(params['key_val'], name).delete
redirect_with_query("#{root_path}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) }
erb(:morgue)
end
get "/morgue/:key" do
halt(404) unless key = route_params[:key]
@dead = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
if @dead.nil?
redirect "#{root_path}morgue"
else
erb(:dead)
end
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 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("#{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(route_params[:key])).first
if @retry.nil?
redirect "#{root_path}retries"
else
erb(:retry)
end
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(route_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(route_params[:key])).first
if @job.nil?
redirect "#{root_path}scheduled"
else
erb(:scheduled_job_info)
end
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 key = route_params[:key]
job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
delete_or_add_queue job, params if job
redirect_with_query("#{root_path}scheduled")
end
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 }
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 call(env)
action = self.class.match(env)
return NOT_FOUND unless action
resp = catch(:halt) do
app = @klass
self.class.run_befores(app, action)
begin
resp = action.instance_exec env, &action.block
ensure
self.class.run_afters(app, action)
end
resp
end
resp = case resp
when Array
resp
when Fixnum
[resp, {}, []]
else
type_header = case action.type
when :json
WebAction::APPLICATION_JSON
when String
{ WebAction::CONTENT_TYPE => action.type }
else
WebAction::TEXT_HTML
end
[200, type_header, [resp]]
end
resp[1][CONTENT_LENGTH] = resp[2].inject(0) { |l, p| l + p.bytesize }.to_s
resp
end
def self.helpers(mod=nil, &block)
if block_given?
WebAction.class_eval(&block)
else
WebAction.send(:include, mod)
end
end
def self.before(path=nil, &block)
befores << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
end
def self.after(path=nil, &block)
afters << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
end
def self.run_befores(app, action)
run_hooks(befores, app, action)
end
def self.run_afters(app, action)
run_hooks(afters, app, action)
end
def self.run_hooks(hooks, app, action)
hooks.select { |p,_| !p || p =~ action.env[WebRouter::PATH_INFO] }.
each {|_,b| action.instance_exec(action.env, app, &b) }
end
def self.befores
@befores ||= []
end
def self.afters
@afters ||= []
end
end
end

View file

@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'uri' require 'uri'
require 'yaml'
module Sidekiq module Sidekiq
# This is not a public API # This is not a public API
@ -45,21 +46,13 @@ module Sidekiq
# <meta .../> # <meta .../>
# <% end %> # <% end %>
# #
def add_to_head(&block) def add_to_head
@head_html ||= [] @head_html ||= []
@head_html << block if block_given? @head_html << yield.dup if block_given?
end end
def display_custom_head def display_custom_head
return unless defined?(@head_html) @head_html.join if @head_html
@head_html.map { |block| capture(&block) }.join
end
# Simple capture method for erb templates. The origin was
# capture method from sinatra-contrib library.
def capture(&block)
block.call
eval('', block.binding)
end end
# Given a browser request Accept-Language header like # Given a browser request Accept-Language header like
@ -69,7 +62,7 @@ module Sidekiq
def locale def locale
@locale ||= begin @locale ||= begin
locale = 'en'.freeze 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| languages.downcase.split(','.freeze).each do |lang|
next if lang == '*'.freeze next if lang == '*'.freeze
lang = lang.split(';'.freeze)[0] lang = lang.split(';'.freeze)[0]
@ -251,5 +244,23 @@ module Sidekiq
"#{redis_connection}#{namespace_suffix}" "#{redis_connection}#{namespace_suffix}"
end end
end 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 end
end end

96
lib/sidekiq/web/router.rb Normal file
View file

@ -0,0 +1,96 @@
# frozen_string_literal: true
require 'rack'
module Sidekiq
module WebRouter
GET = 'GET'.freeze
DELETE = 'DELETE'.freeze
POST = 'POST'.freeze
PUT = 'PUT'.freeze
PATCH = 'PATCH'.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 put(path, &block)
route(PUT, path, &block)
end
def patch(path, &block)
route(PATCH, path, &block)
end
def delete(path, &block)
route(DELETE, path, &block)
end
def route(method, path, &block)
@routes ||= { GET => [], POST => [], PUT => [], PATCH => [], DELETE => [], HEAD => [] }
@routes[method] << WebRoute.new(method, path, block)
@routes[HEAD] << WebRoute.new(method, path, block) if method == GET
end
def match(env)
request_method = env[REQUEST_METHOD]
path_info = ::Rack::Utils.unescape env[PATH_INFO]
@routes[request_method].each do |route|
if params = route.match(request_method, path_info)
env[ROUTE_PARAMS] = params
return WebAction.new(env, route.block)
end
end
nil
end
end
class WebRoute
attr_accessor :request_method, :pattern, :block, :name
NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^\.:$\/]+)/.freeze
def initialize(request_method, pattern, block)
@request_method = request_method
@pattern = pattern
@block = block
end
def matcher
@matcher ||= compile
end
def compile
if pattern.match(NAMED_SEGMENTS_PATTERN)
p = pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^$/]+)')
Regexp.new("\\A#{p}\\Z")
else
pattern
end
end
def match(request_method, path)
case matcher
when String
{} if path == matcher
else
if path_match = path.match(matcher)
params = Hash[path_match.names.map(&:to_sym).zip(path_match.captures)]
end
end
end
end
end

View file

@ -3,7 +3,6 @@ source 'https://rubygems.org'
gem 'pry' gem 'pry'
gem 'sidekiq', :path => '..' gem 'sidekiq', :path => '..'
gem 'rails', '5.0.0' gem 'rails', '5.0.0'
gem 'sinatra', github: 'sinatra/sinatra'
platforms :ruby do platforms :ruby do
gem 'sqlite3' gem 'sqlite3'

View file

@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
gem.add_dependency 'redis', '~> 3.2', '>= 3.2.1' gem.add_dependency 'redis', '~> 3.2', '>= 3.2.1'
gem.add_dependency 'connection_pool', '~> 2.2', '>= 2.2.0' gem.add_dependency 'connection_pool', '~> 2.2', '>= 2.2.0'
gem.add_dependency 'concurrent-ruby', '~> 1.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 'redis-namespace', '~> 1.5', '>= 1.5.2'
gem.add_development_dependency 'minitest', '~> 5.7', '>= 5.7.0' gem.add_development_dependency 'minitest', '~> 5.7', '>= 5.7.0'
gem.add_development_dependency 'rake', '~> 10.0' gem.add_development_dependency 'rake', '~> 10.0'

View file

@ -3,10 +3,8 @@
require_relative 'helper' require_relative 'helper'
require 'sidekiq/web' require 'sidekiq/web'
require 'rack/test' require 'rack/test'
require 'tilt/erubis'
class TestWeb < Sidekiq::Test class TestWeb < Sidekiq::Test
describe 'sidekiq web' do describe 'sidekiq web' do
include Rack::Test::Methods include Rack::Test::Methods
@ -341,7 +339,6 @@ class TestWeb < Sidekiq::Test
assert last_response.body.include?( "&lt;a&gt;hello&lt;&#x2F;a&gt;" ) assert last_response.body.include?( "&lt;a&gt;hello&lt;&#x2F;a&gt;" )
assert !last_response.body.include?( "<a>hello</a>" ) assert !last_response.body.include?( "<a>hello</a>" )
# on /queues page # on /queues page
params = add_xss_retry # sorry, don't know how to easily make this show up on queues page otherwise. 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' post "/retries/#{job_params(*params)}", 'retry' => 'Retry'
@ -374,7 +371,7 @@ class TestWeb < Sidekiq::Test
before do before do
Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), "fixtures") Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), "fixtures")
Sidekiq::Web.tabs['Custom Tab'] = '/custom' 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 clear_caches # ugly hack since I can't figure out how to access WebHelpers outside of this context
t('translated_text') t('translated_text')
end end
@ -387,6 +384,7 @@ class TestWeb < Sidekiq::Test
it 'can show user defined tab with custom locales' do it 'can show user defined tab with custom locales' do
get '/custom' get '/custom'
assert_match(/Changed text/, last_response.body) assert_match(/Changed text/, last_response.body)
end end
end end
@ -564,6 +562,7 @@ class TestWeb < Sidekiq::Test
Sidekiq.redis do |conn| Sidekiq.redis do |conn|
conn.zadd('retry', score, Sidekiq.dump_json(msg)) conn.zadd('retry', score, Sidekiq.dump_json(msg))
end end
[msg, score] [msg, score]
end end
@ -596,6 +595,7 @@ class TestWeb < Sidekiq::Test
Sidekiq.redis do |conn| Sidekiq.redis do |conn|
conn.zadd('retry', score, Sidekiq.dump_json(msg)) conn.zadd('retry', score, Sidekiq.dump_json(msg))
end end
[msg, score] [msg, score]
end end
@ -611,4 +611,51 @@ class TestWeb < Sidekiq::Test
end end
end end
end end
describe 'sidekiq web with basic auth' do
include Rack::Test::Methods
def app
app = Sidekiq::Web.new
app.use(Rack::Auth::Basic) { |user, pass| user == "a" && pass == "b" }
app
end
it 'requires basic authentication' do
get '/'
assert_equal 401, last_response.status
refute_nil last_response.header["WWW-Authenticate"]
end
it 'authenticates successfuly' do
basic_authorize 'a', 'b'
get '/'
assert_equal 200, last_response.status
end
end
describe 'sidekiq web with custom session' do
include Rack::Test::Methods
def app
app = Sidekiq::Web.new
app.use Rack::Session::Cookie, secret: 'v3rys3cr31', host: 'nicehost.org'
app
end
it 'requires basic authentication' do
get '/'
session_options = last_request.env['rack.session'].options
assert_equal 'v3rys3cr31', session_options[:secret]
assert_equal 'nicehost.org', session_options[:host]
end
end
end end

View file

@ -58,14 +58,14 @@
</div> </div>
<% end %> <% end %>
<% if @redis_info.fetch("used_memory_human", nil) %> <% if @redis_info.fetch("used_memory_human", nil) %>
<div class="stat"> <div class="stat">
<h3 class="used_memory_human"><%= @redis_info.fetch("used_memory_human") %></h3> <h3 class="used_memory_human"><%= @redis_info.fetch("used_memory_human") %></h3>
<p><%= t('MemoryUsage') %></p> <p><%= t('MemoryUsage') %></p>
</div> </div>
<% end %> <% end %>
<% if @redis_info.fetch("used_memory_peak_human", nil) %> <% if @redis_info.fetch("used_memory_peak_human", nil) %>
<div class="stat"> <div class="stat">
<h3 class="used_memory_peak_human"><%= @redis_info.fetch("used_memory_peak_human") %></h3> <h3 class="used_memory_peak_human"><%= @redis_info.fetch("used_memory_peak_human") %></h3>
<p><%= t('PeakMemoryUsage') %></p> <p><%= t('PeakMemoryUsage') %></p>

View file

@ -1,4 +1,4 @@
<%= erb :_job_info, :locals => {:job => @dead, :type => :dead} %> <%= erb :_job_info, locals: { job: @dead, type: :dead } %>
<h3><%= t('Error') %></h3> <h3><%= t('Error') %></h3>
<div class="table_container"> <div class="table_container">

View file

@ -2,6 +2,7 @@
<html> <html>
<head> <head>
<title><%= environment_title_prefix %><%= Sidekiq::NAME %></title> <title><%= environment_title_prefix %><%= Sidekiq::NAME %></title>
<meta charset="utf8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link href="<%= root_path %>stylesheets/bootstrap.css" media="screen" rel="stylesheet" type="text/css" /> <link href="<%= root_path %>stylesheets/bootstrap.css" media="screen" rel="stylesheet" type="text/css" />
<link href="<%= root_path %>stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" /> <link href="<%= root_path %>stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
@ -26,7 +27,7 @@
</div> </div>
</div> </div>
</div> </div>
<%= erb :_footer %> <%= erb :_footer %>
<%= erb :_poll_js %> <%= erb :_poll_js %>
</body> </body>
</html> </html>

View file

@ -4,7 +4,7 @@
</div> </div>
<% if @dead.size > 0 && @total_size > @count %> <% if @dead.size > 0 && @total_size > @count %>
<div class="col-sm-4"> <div class="col-sm-4">
<%= erb :_paging, :locals => { :url => "#{root_path}morgue" } %> <%= erb :_paging, locals: { url: "#{root_path}morgue" } %>
</div> </div>
<% end %> <% end %>
<%= filtering('dead') %> <%= filtering('dead') %>

View file

@ -8,7 +8,7 @@
</h3> </h3>
</div> </div>
<div class="col-sm-4 pull-right"> <div class="col-sm-4 pull-right">
<%= erb :_paging, :locals => { :url => "#{root_path}queues/#{@name}" } %> <%= erb :_paging, locals: { url: "#{root_path}queues/#{@name}" } %>
</div> </div>
</header> </header>
<div class="table_container"> <div class="table_container">
@ -42,4 +42,4 @@
<% end %> <% end %>
</table> </table>
</div> </div>
<%= erb :_paging, :locals => { :url => "#{root_path}queues/#{@name}" } %> <%= erb :_paging, locals: { url: "#{root_path}queues/#{@name}" } %>

View file

@ -4,7 +4,7 @@
</div> </div>
<% if @retries.size > 0 && @total_size > @count %> <% if @retries.size > 0 && @total_size > @count %>
<div class="col-sm-4"> <div class="col-sm-4">
<%= erb :_paging, :locals => { :url => "#{root_path}retries" } %> <%= erb :_paging, locals: { url: "#{root_path}retries" } %>
</div> </div>
<% end %> <% end %>
<%= filtering('retries') %> <%= filtering('retries') %>

View file

@ -1,4 +1,4 @@
<%= erb :_job_info, :locals => {:job => @retry, :type => :retry} %> <%= erb :_job_info, locals: { job: @retry, type: :retry } %>
<h3><%= t('Error') %></h3> <h3><%= t('Error') %></h3>
<div class="table_container"> <div class="table_container">

View file

@ -4,7 +4,7 @@
</div> </div>
<% if @scheduled.size > 0 && @total_size > @count %> <% if @scheduled.size > 0 && @total_size > @count %>
<div class="col-sm-4"> <div class="col-sm-4">
<%= erb :_paging, :locals => { :url => "#{root_path}scheduled" } %> <%= erb :_paging, locals: { url: "#{root_path}scheduled" } %>
</div> </div>
<% end %> <% end %>
<%= filtering('scheduled') %> <%= filtering('scheduled') %>

View file

@ -1,4 +1,4 @@
<%= erb :_job_info, :locals => {:job => @job, :type => :scheduled} %> <%= erb :_job_info, locals: { job: @job, type: :scheduled } %>
<form class="form-horizontal" action="<%= root_path %>scheduled/<%= job_params(@job, @job.score) %>" method="post"> <form class="form-horizontal" action="<%= root_path %>scheduled/<%= job_params(@job, @job.score) %>" method="post">
<%= csrf_tag %> <%= csrf_tag %>