lib/qless/server.rb in qless-0.9.3 vs lib/qless/server.rb in qless-0.10.0

- old
+ new

@@ -1,11 +1,12 @@ +# Encoding: utf-8 + require 'sinatra/base' require 'qless' -# Much of this is shamelessly poached from the resque web client - module Qless + # The Qless web interface class Server < Sinatra::Base # Path-y-ness dir = File.dirname(File.expand_path(__FILE__)) set :views , "#{dir}/server/views" set :public_folder, "#{dir}/server/static" @@ -25,11 +26,11 @@ helpers do include Rack::Utils def url_path(*path_parts) - [ path_prefix, path_parts ].join("/").squeeze('/') + [path_prefix, path_parts].join('/').squeeze('/') end alias_method :u, :url_path def path_prefix request.env['SCRIPT_NAME'] @@ -47,15 +48,15 @@ query.merge('page' => current_page + offset) end end def next_page_url - page_url 1 + page_url(1) end def prev_page_url - page_url -1 + page_url(-1) end def current_page @current_page ||= begin Integer(params[:page]) @@ -73,97 +74,99 @@ def paginated(qless_object, method, *args) qless_object.send(method, *(args + pagination_values)) end def tabs - return [ - {:name => 'Queues' , :path => '/queues' }, - {:name => 'Workers' , :path => '/workers' }, - {:name => 'Track' , :path => '/track' }, - {:name => 'Failed' , :path => '/failed' }, - {:name => 'Config' , :path => '/config' }, - {:name => 'About' , :path => '/about' } + [ + { name: 'Queues' , path: '/queues' }, + { name: 'Workers' , path: '/workers' }, + { name: 'Track' , path: '/track' }, + { name: 'Failed' , path: '/failed' }, + { name: 'Completed', path: '/completed'}, + { name: 'Config' , path: '/config' }, + { name: 'About' , path: '/about' } ] end def application_name - return client.config['application'] + client.config['application'] end def queues - return client.queues.counts + client.queues.counts end def tracked - return client.jobs.tracked + client.jobs.tracked end def workers - return client.workers.counts + client.workers.counts end def failed - return client.jobs.failed + client.jobs.failed end # Return the supplied object back as JSON def json(obj) content_type :json obj.to_json end # Make the id acceptable as an id / att in HTML def sanitize_attr(attr) - return attr.gsub(/[^a-zA-Z\:\_]/, '-') + attr.gsub(/[^a-zA-Z\:\_]/, '-') end # What are the top tags? Since it might go on, say, every # page, then we should probably be caching it def top_tags @top_tags ||= { - :top => client.tags, - :fetched => Time.now + top: client.tags, + fetched: Time.now } - if (Time.now - @top_tags[:fetched]) > 60 then + if (Time.now - @top_tags[:fetched]) > 60 @top_tags = { - :top => client.tags, - :fetched => Time.now + top: client.tags, + fetched: Time.now } end @top_tags[:top] end def strftime(t) - # From http://stackoverflow.com/questions/195740/how-do-you-do-relative-time-in-rails + # From http://stackoverflow.com/questions/195740 diff_seconds = Time.now - t + formatted = t.strftime('%b %e, %Y %H:%M:%S') case diff_seconds - when 0 .. 59 - "#{diff_seconds.to_i} seconds ago" - when 60 ... 3600 - "#{(diff_seconds/60).to_i} minutes ago" - when 3600 ... 3600*24 - "#{(diff_seconds/3600).to_i} hours ago" - when (3600*24) ... (3600*24*30) - "#{(diff_seconds/(3600*24)).to_i} days ago" - else - t.strftime('%b %e, %Y %H:%M:%S %Z (%z)') + when 0 .. 59 + "#{formatted} (#{diff_seconds.to_i} seconds ago)" + when 60 ... 3600 + "#{formatted} (#{(diff_seconds / 60).to_i} minutes ago)" + when 3600 ... 3600 * 24 + "#{formatted} (#{(diff_seconds / 3600).to_i} hours ago)" + when (3600 * 24) ... (3600 * 24 * 30) + "#{formatted} (#{(diff_seconds / (3600 * 24)).to_i} days ago)" + else + formatted end end end get '/?' do - erb :overview, :layout => true, :locals => { :title => "Overview" } + erb :overview, layout: true, locals: { title: 'Overview' } end # Returns a JSON blob with the job counts for various queues get '/queues.json' do json(client.queues.counts) end get '/queues/?' do - erb :queues, :layout => true, :locals => { - :title => 'Queues' + erb :queues, layout: true, locals: { + title: 'Queues' } end # Return the job counts for a specific queue get '/queues/:name.json' do @@ -173,24 +176,23 @@ filtered_tabs = %w[ running scheduled stalled depends recurring ].to_set get '/queues/:name/?:tab?' do queue = client.queues[params[:name]] tab = params.fetch('tab', 'stats') - jobs = if tab == 'waiting' - queue.peek(20) + jobs = [] + if tab == 'waiting' + jobs = queue.peek(20) elsif filtered_tabs.include?(tab) - paginated(queue.jobs, tab).map { |jid| client.jobs[jid] } - else - [] + jobs = paginated(queue.jobs, tab).map { |jid| client.jobs[jid] } end - erb :queue, :layout => true, :locals => { - :title => "Queue #{params[:name]}", - :tab => tab, - :jobs => jobs, - :queue => client.queues[params[:name]].counts, - :stats => queue.stats + erb :queue, layout: true, locals: { + title: "Queue #{params[:name]}", + tab: tab, + jobs: jobs, + queue: client.queues[params[:name]].counts, + stats: queue.stats } end get '/failed.json' do json(client.jobs.failed) @@ -198,110 +200,121 @@ get '/failed/?' do # qless-core doesn't provide functionality this way, so we'll # do it ourselves. I'm not sure if this is how the core library # should behave or not. - erb :failed, :layout => true, :locals => { - :title => 'Failed', - :failed => client.jobs.failed.keys.map { |t| client.jobs.failed(t).tap { |f| f['type'] = t } } + erb :failed, layout: true, locals: { + title: 'Failed', + failed: client.jobs.failed.keys.map do |t| + client.jobs.failed(t).tap { |f| f['type'] = t } + end } end get '/failed/:type/?' do - erb :failed_type, :layout => true, :locals => { - :title => 'Failed | ' + params[:type], - :type => params[:type], - :failed => paginated(client.jobs, :failed, params[:type]) + erb :failed_type, layout: true, locals: { + title: 'Failed | ' + params[:type], + type: params[:type], + failed: paginated(client.jobs, :failed, params[:type]) } end + get '/completed/?' do + completed = paginated(client.jobs, :complete) + erb :completed, layout: true, locals: { + title: 'Completed', + jobs: completed.map { |jid| client.jobs[jid] } + } + end + get '/track/?' do - erb :track, :layout => true, :locals => { - :title => 'Track' + erb :track, layout: true, locals: { + title: 'Track' } end get '/jobs/:jid' do - erb :job, :layout => true, :locals => { - :title => "Job | #{params[:jid]}", - :jid => params[:jid], - :job => client.jobs[params[:jid]] + erb :job, layout: true, locals: { + title: "Job | #{params[:jid]}", + jid: params[:jid], + job: client.jobs[params[:jid]] } end get '/workers/?' do - erb :workers, :layout => true, :locals => { - :title => 'Workers' + erb :workers, layout: true, locals: { + title: 'Workers' } end get '/workers/:worker' do - erb :worker, :layout => true, :locals => { - :title => 'Worker | ' + params[:worker], - :worker => client.workers[params[:worker]].tap { |w| + erb :worker, layout: true, locals: { + title: 'Worker | ' + params[:worker], + worker: client.workers[params[:worker]].tap do |w| w['jobs'] = w['jobs'].map { |j| client.jobs[j] } w['stalled'] = w['stalled'].map { |j| client.jobs[j] } w['name'] = params[:worker] - } + end } end get '/tag/?' do jobs = paginated(client.jobs, :tagged, params[:tag]) - erb :tag, :layout => true, :locals => { - :title => "Tag | #{params[:tag]}", - :tag => params[:tag], - :jobs => jobs['jobs'].map { |jid| client.jobs[jid] }, - :total => jobs['total'] + erb :tag, layout: true, locals: { + title: "Tag | #{params[:tag]}", + tag: params[:tag], + jobs: jobs['jobs'].map { |jid| client.jobs[jid] }, + total: jobs['total'] } end get '/config/?' do - erb :config, :layout => true, :locals => { - :title => 'Config', - :options => client.config.all + erb :config, layout: true, locals: { + title: 'Config', + options: client.config.all } end get '/about/?' do - erb :about, :layout => true, :locals => { - :title => 'About' + erb :about, layout: true, locals: { + title: 'About' } end # These are the bits where we accept AJAX requests - post "/track/?" do + post '/track/?' do # Expects a JSON-encoded hash with a job id, and optionally some tags data = JSON.parse(request.body.read) - job = client.jobs[data["id"]] - if not job.nil? - data.fetch("tags", false) ? job.track(*data["tags"]) : job.track() + job = client.jobs[data['id']] + if !job.nil? + data.fetch('tags', false) ? job.track(*data['tags']) : job.track if request.xhr? - json({ :tracked => [job.jid] }) + json({ tracked: [job.jid] }) else redirect to('/track') end else if request.xhr? - json({ :tracked => [] }) + json({ tracked: [] }) else redirect to(request.referrer) end end end - post "/untrack/?" do + post '/untrack/?' do # Expects a JSON-encoded array of job ids to stop tracking - jobs = JSON.parse(request.body.read).map { |jid| client.jobs[jid] }.select { |j| not j.nil? } + jobs = JSON.parse(request.body.read).map { |jid| client.jobs[jid] } + jobs.compact! # Go ahead and cancel all the jobs! jobs.each do |job| - job.untrack() + job.untrack end - return json({ :untracked => jobs.map { |job| job.jid } }) + return json({ untracked: jobs.map { |job| job.jid } }) end - post "/priority/?" do + post '/priority/?' do # Expects a JSON-encoded dictionary of jid => priority response = Hash.new r = JSON.parse(request.body.read) r.each_pair do |jid, priority| begin @@ -312,11 +325,44 @@ end end return json(response) end - post "/tag/?" do + post '/pause/?' do + # Expects JSON blob: {'queue': <queue>} + r = JSON.parse(request.body.read) + if r['queue'] + @client.queues[r['queue']].pause + return json({ queue: 'paused' }) + else + raise 'No queue provided' + end + end + + post '/unpause/?' do + # Expects JSON blob: {'queue': <queue>} + r = JSON.parse(request.body.read) + if r['queue'] + @client.queues[r['queue']].unpause + return json({ queue: 'unpaused' }) + else + raise 'No queue provided' + end + end + + post '/timeout/?' do + # Expects JSON blob: {'jid': <jid>} + r = JSON.parse(request.body.read) + if r['jid'] + @client.jobs[r['jid']].timeout + return json({ jid: r['jid'] }) + else + raise 'No jid provided' + end + end + + post '/tag/?' do # Expects a JSON-encoded dictionary of jid => [tag, tag, tag] response = Hash.new JSON.parse(request.body.read).each_pair do |jid, tags| begin client.jobs[jid].tag(*tags) @@ -326,11 +372,11 @@ end end return json(response) end - post "/untag/?" do + post '/untag/?' do # Expects a JSON-encoded dictionary of jid => [tag, tag, tag] response = Hash.new JSON.parse(request.body.read).each_pair do |jid, tags| begin client.jobs[jid].untag(*tags) @@ -340,101 +386,102 @@ end end return json(response) end - post "/move/?" do + post '/move/?' do # Expects a JSON-encoded hash of id: jid, and queue: queue_name data = JSON.parse(request.body.read) - if data["id"].nil? or data["queue"].nil? - halt 400, "Need id and queue arguments" + if data['id'].nil? || data['queue'].nil? + halt 400, 'Need id and queue arguments' else - job = client.jobs[data["id"]] + job = client.jobs[data['id']] if job.nil? - halt 404, "Could not find job" + halt 404, 'Could not find job' else - job.move(data["queue"]) - return json({ :id => data["id"], :queue => data["queue"]}) + job.requeue(data['queue']) + return json({ id: data['id'], queue: data['queue'] }) end end end - post "/undepend/?" do + post '/undepend/?' do # Expects a JSON-encoded hash of id: jid, and queue: queue_name data = JSON.parse(request.body.read) - if data["id"].nil? - halt 400, "Need id" + if data['id'].nil? + halt 400, 'Need id' else - job = client.jobs[data["id"]] + job = client.jobs[data['id']] if job.nil? - halt 404, "Could not find job" + halt 404, 'Could not find job' else job.undepend(data['dependency']) - return json({:id => data["id"]}) + return json({ id: data['id'] }) end end end - post "/retry/?" do + post '/retry/?' do # Expects a JSON-encoded hash of id: jid, and queue: queue_name data = JSON.parse(request.body.read) - if data["id"].nil? - halt 400, "Need id" + if data['id'].nil? + halt 400, 'Need id' else - job = client.jobs[data["id"]] + job = client.jobs[data['id']] if job.nil? - halt 404, "Could not find job" + halt 404, 'Could not find job' else - queue = job.raw_queue_history[-1]["q"] - job.move(queue) - return json({ :id => data["id"], :queue => queue}) + job.requeue(job.queue_name) + return json({ id: data['id'], queue: job.queue_name }) end end end # Retry all the failures of a particular type - post "/retryall/?" do + post '/retryall/?' do # Expects a JSON-encoded hash of type: failure-type data = JSON.parse(request.body.read) - if data["type"].nil? - halt 400, "Neet type" + if data['type'].nil? + halt 400, 'Neet type' else - return json(client.jobs.failed(data["type"], 0, 500)['jobs'].map do |job| - queue = job.raw_queue_history[-1]["q"] - job.move(queue) - { :id => job.jid, :queue => queue} - end) + jobs = client.jobs.failed(data['type'], 0, 500)['jobs'] + results = jobs.map do |job| + job.requeue(job.queue_name) + { id: job.jid, queue: job.queue_name } + end + return json(results) end end - post "/cancel/?" do + post '/cancel/?' do # Expects a JSON-encoded array of job ids to cancel - jobs = JSON.parse(request.body.read).map { |jid| client.jobs[jid] }.select { |j| not j.nil? } + jobs = JSON.parse(request.body.read).map { |jid| client.jobs[jid] } + jobs.compact! # Go ahead and cancel all the jobs! jobs.each do |job| - job.cancel() + job.cancel end if request.xhr? - return json({ :canceled => jobs.map { |job| job.jid } }) + return json({ canceled: jobs.map { |job| job.jid } }) else redirect to(request.referrer) end end - post "/cancelall/?" do + post '/cancelall/?' do # Expects a JSON-encoded hash of type: failure-type data = JSON.parse(request.body.read) - if data["type"].nil? - halt 400, "Neet type" + if data['type'].nil? + halt 400, 'Neet type' else - return json(client.jobs.failed(data["type"])['jobs'].map do |job| - job.cancel() - { :id => job.jid } + return json(client.jobs.failed(data['type'])['jobs'].map do |job| + job.cancel + { id: job.jid } end) end end # start the server if ruby file executed directly - run! if app_file == $0 + run! if app_file == $PROGRAM_NAME end end