require 'sinatra/base' require 'erb' require 'resque' require 'resque/version' require 'time' if defined? Encoding Encoding.default_external = Encoding::UTF_8 end module Resque class Server < Sinatra::Base dir = File.dirname(File.expand_path(__FILE__)) set :views, "#{dir}/server/views" if respond_to? :public_folder set :public_folder, "#{dir}/server/public" else set :public, "#{dir}/server/public" end set :static, true helpers do include Rack::Utils alias_method :h, :escape_html def current_section url_path request.path_info.sub('/','').split('/')[0].downcase end def current_page url_path request.path_info.sub('/','') end def url_path(*path_parts) [ path_prefix, path_parts ].join("/").squeeze('/') end alias_method :u, :url_path def path_prefix request.env['SCRIPT_NAME'] end def class_if_current(path = '') 'class="current"' if current_page[0, path.size] == path end def tab(name) dname = name.to_s.downcase path = url_path(dname) "
#{text}
" end end def show(page, layout = true) response["Cache-Control"] = "max-age=0, private, must-revalidate" begin erb page.to_sym, {:layout => layout}, :resque => Resque rescue Mongo::ConnectionError, Mongo::ConnectionFailure erb :error, {:layout => false}, :error => "Can't connect to MongoDB!" end end def show_for_polling(page) content_type "text/html" @polling = true show(page.to_sym, false).gsub(/\s{1,}/, ' ') end def processes_in(delay_until) return 'Immediately' if delay_until.nil? now = Time.now time = distance_of_time_in_words(now, delay_until) return "Immediately (#{time})" if now > delay_until return time end def enqueued_at(resque_enqueue_timestamp) return 'Unknown' if resque_enqueue_timestamp.nil? now = Time.now time = distance_of_time_in_words(now, resque_enqueue_timestamp) return time end def distance_of_time_in_words(from_time, to_time = 0, include_seconds = true, options = {}) from_time = from_time.to_time if from_time.respond_to?(:to_time) to_time = to_time.to_time if to_time.respond_to?(:to_time) distance_in_minutes = (((to_time - from_time).abs)/60).round distance_in_seconds = ((to_time - from_time).abs).round ago = from_time > to_time ? ' ago' : '' case distance_in_minutes when 0..1 return distance_in_minutes == 0 ? "less than 1 minute" + ago : "#{distance_in_minutes} minutes" + ago unless include_seconds case distance_in_seconds when 0..4 then "less than 5 seconds" + ago when 5..9 then "less than 10 seconds" + ago when 10..19 then "less than 20 seconds" + ago when 20..39 then "half a minute" + ago when 40..59 then "less than 1 minute" + ago else "1 minute" + ago end when 2..44 then "#{distance_in_minutes} minutes" + ago when 45..89 then "about 1 hour" + ago when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours" + ago when 1440..2529 then "about 1 day" + ago when 2530..43199 then "about #{(distance_in_minutes.to_f / 1440.0).round} days" + ago when 43200..86399 then "about 1 month" + ago when 86400..525599 then "about #{(distance_in_minutes.to_f / 43200.0).round} months" + ago else distance_in_years = distance_in_minutes / 525600 minute_offset_for_leap_year = (distance_in_years / 4) * 1440 remainder = ((distance_in_minutes - minute_offset_for_leap_year) % 525600) if remainder < 131400 "about #{distance_in_years} years" + ago elsif remainder < 394200 "over #{distance_in_years} years" + ago else "almost #{distance_in_years} years" + ago end end end # to make things easier on ourselves get "/?" do redirect url_path(:overview) end %w( overview workers ).each do |page| get "/#{page}.poll/?" do show_for_polling(page) end get "/#{page}/:id.poll/?" do show_for_polling(page) end end %w( overview queues working workers key ).each do |page| get "/#{page}/?" do show page end get "/#{page}/:id/?" do show page end end post "/queues/:id/remove" do Resque.remove_queue(params[:id]) redirect u('queues') end get "/failed/?" do if Resque::Failure.url redirect Resque::Failure.url else show :failed end end post "/failed/clear" do Resque::Failure.clear redirect u('failed') end post "/failed/requeue/all" do Resque::Failure.count.times do |num| Resque::Failure.requeue(num) end redirect u('failed') end get "/failed/requeue/:index/?" do Resque::Failure.requeue(params[:index]) if request.xhr? return Resque::Failure.all(params[:index])['retried_at'] else redirect u('failed') end end get "/failed/remove/:index/?" do Resque::Failure.remove(params[:index]) redirect u('failed') end get "/stats/?" do redirect url_path("/stats/resque") end get "/stats/:id/?" do show :stats end get "/stats/keys/:key/?" do show :stats end get "/stats.txt/?" do info = Resque.info stats = [] stats << "resque.pending=#{info[:pending]}" stats << "resque.processed+=#{info[:processed]}" stats << "resque.failed+=#{info[:failed]}" stats << "resque.workers=#{info[:workers]}" stats << "resque.working=#{info[:working]}" Resque.queues.each do |queue| stats << "queues.#{queue}=#{Resque.size(queue)}" end content_type 'text/plain' stats.join "\n" end def resque Resque end def self.tabs @tabs ||= ["Overview", "Working", "Failed", "Queues", "Workers", "Stats"] end end end