├── LICENSE ├── README.md ├── public ├── favicon.ico ├── idle.png ├── jquery-1.3.2.min.js ├── jquery.relatize_date.js ├── poll.png ├── ranger.js ├── reset.css ├── style.css └── working.png ├── server.rb ├── test_helper.rb └── views ├── error.erb ├── failed.erb ├── key_sets.erb ├── key_string.erb ├── layout.erb ├── next_more.erb ├── overview.erb ├── queues.erb ├── stats.erb ├── workers.erb └── working.erb /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Chris Wanstrath 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sinatra-based web UI for Resque 2 | 3 | Currently being extracted from [defunkt/resque](https://github.com/defunkt/resque). 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defunkt/resque-web/776b2c4f5b23029a565d985269c06e0664fbe0b8/public/favicon.ico -------------------------------------------------------------------------------- /public/idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defunkt/resque-web/776b2c4f5b23029a565d985269c06e0664fbe0b8/public/idle.png -------------------------------------------------------------------------------- /public/jquery-1.3.2.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery JavaScript Library v1.3.2 3 | * http://jquery.com/ 4 | * 5 | * Copyright (c) 2009 John Resig 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://docs.jquery.com/License 8 | * 9 | * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) 10 | * Revision: 6246 11 | */ 12 | (function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
","
"]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); 13 | /* 14 | * Sizzle CSS Selector Engine - v0.9.3 15 | * Copyright 2009, The Dojo Foundation 16 | * Released under the MIT, BSD, and GPL Licenses. 17 | * More information: http://sizzlejs.com/ 18 | */ 19 | (function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); -------------------------------------------------------------------------------- /public/jquery.relatize_date.js: -------------------------------------------------------------------------------- 1 | // All credit goes to Rick Olson. 2 | (function($) { 3 | $.fn.relatizeDate = function() { 4 | return $(this).each(function() { 5 | if ($(this).hasClass( 'relatized' )) return 6 | $(this).text( $.relatizeDate(this) ).addClass( 'relatized' ) 7 | }) 8 | } 9 | 10 | $.relatizeDate = function(element) { 11 | return $.relatizeDate.timeAgoInWords( new Date($(element).text()) ) 12 | } 13 | 14 | // shortcut 15 | $r = $.relatizeDate 16 | 17 | $.extend($.relatizeDate, { 18 | shortDays: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], 19 | days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], 20 | shortMonths: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ], 21 | months: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ], 22 | 23 | /** 24 | * Given a formatted string, replace the necessary items and return. 25 | * Example: Time.now().strftime("%B %d, %Y") => February 11, 2008 26 | * @param {String} format The formatted string used to format the results 27 | */ 28 | strftime: function(date, format) { 29 | var day = date.getDay(), month = date.getMonth(); 30 | var hours = date.getHours(), minutes = date.getMinutes(); 31 | 32 | var pad = function(num) { 33 | var string = num.toString(10); 34 | return new Array((2 - string.length) + 1).join('0') + string 35 | }; 36 | 37 | return format.replace(/\%([aAbBcdHImMpSwyY])/g, function(part) { 38 | switch(part[1]) { 39 | case 'a': return $r.shortDays[day]; break; 40 | case 'A': return $r.days[day]; break; 41 | case 'b': return $r.shortMonths[month]; break; 42 | case 'B': return $r.months[month]; break; 43 | case 'c': return date.toString(); break; 44 | case 'd': return pad(date.getDate()); break; 45 | case 'H': return pad(hours); break; 46 | case 'I': return pad((hours + 12) % 12); break; 47 | case 'm': return pad(month + 1); break; 48 | case 'M': return pad(minutes); break; 49 | case 'p': return hours > 12 ? 'PM' : 'AM'; break; 50 | case 'S': return pad(date.getSeconds()); break; 51 | case 'w': return day; break; 52 | case 'y': return pad(date.getFullYear() % 100); break; 53 | case 'Y': return date.getFullYear().toString(); break; 54 | } 55 | }) 56 | }, 57 | 58 | timeAgoInWords: function(targetDate, includeTime) { 59 | return $r.distanceOfTimeInWords(targetDate, new Date(), includeTime); 60 | }, 61 | 62 | /** 63 | * Return the distance of time in words between two Date's 64 | * Example: '5 days ago', 'about an hour ago' 65 | * @param {Date} fromTime The start date to use in the calculation 66 | * @param {Date} toTime The end date to use in the calculation 67 | * @param {Boolean} Include the time in the output 68 | */ 69 | distanceOfTimeInWords: function(fromTime, toTime, includeTime) { 70 | var delta = parseInt((toTime.getTime() - fromTime.getTime()) / 1000); 71 | if (delta < 60) { 72 | return 'just now'; 73 | } else if (delta < 120) { 74 | return 'about a minute ago'; 75 | } else if (delta < (45*60)) { 76 | return (parseInt(delta / 60)).toString() + ' minutes ago'; 77 | } else if (delta < (120*60)) { 78 | return 'about an hour ago'; 79 | } else if (delta < (24*60*60)) { 80 | return 'about ' + (parseInt(delta / 3600)).toString() + ' hours ago'; 81 | } else if (delta < (48*60*60)) { 82 | return '1 day ago'; 83 | } else { 84 | var days = (parseInt(delta / 86400)).toString(); 85 | if (days > 5) { 86 | var fmt = '%B %d, %Y' 87 | if (includeTime) fmt += ' %I:%M %p' 88 | return $r.strftime(fromTime, fmt); 89 | } else { 90 | return days + " days ago" 91 | } 92 | } 93 | } 94 | }) 95 | })(jQuery); 96 | -------------------------------------------------------------------------------- /public/poll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defunkt/resque-web/776b2c4f5b23029a565d985269c06e0664fbe0b8/public/poll.png -------------------------------------------------------------------------------- /public/ranger.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | var poll_interval = 2 3 | 4 | var relatizer = function(){ 5 | var dt = $(this).text(), relatized = $.relatizeDate(this) 6 | if ($(this).parents("a").length > 0 || $(this).is("a")) { 7 | $(this).relatizeDate() 8 | if (!$(this).attr('title')) { 9 | $(this).attr('title', dt) 10 | } 11 | } else { 12 | $(this) 13 | .text('') 14 | .append( $('') 15 | .append('' + dt + 16 | '' + 17 | relatized + '') ) 18 | } 19 | }; 20 | 21 | $('.time').each(relatizer); 22 | 23 | $('.time a.toggle_format .date_time').hide() 24 | 25 | var format_toggler = function(){ 26 | $('.time a.toggle_format span').toggle() 27 | $(this).attr('title', $('span:hidden',this).text()) 28 | return false 29 | }; 30 | 31 | $('.time a.toggle_format').click(format_toggler); 32 | 33 | $('.backtrace').click(function() { 34 | $(this).next().toggle() 35 | return false 36 | }) 37 | 38 | $('a[rel=poll]').click(function() { 39 | var href = $(this).attr('href') 40 | $(this).parent().text('Starting...') 41 | $("#main").addClass('polling') 42 | 43 | setInterval(function() { 44 | $.ajax({dataType: 'text', type: 'get', url: href, success: function(data) { 45 | $('#main').html(data) 46 | $('#main .time').relatizeDate() 47 | }}) 48 | }, poll_interval * 1000) 49 | 50 | return false 51 | }) 52 | 53 | $('ul.failed li').hover(function() { 54 | $(this).addClass('hover'); 55 | }, function() { 56 | $(this).removeClass('hover'); 57 | }) 58 | 59 | $('ul.failed a[rel=retry]').click(function() { 60 | var href = $(this).attr('href'); 61 | $(this).text('Retrying...'); 62 | var parent = $(this).parent(); 63 | $.ajax({dataType: 'text', type: 'get', url: href, success: function(data) { 64 | parent.html('Retried ' + data + ''); 65 | relatizer.apply($('.time', parent)); 66 | $('.date_time', parent).hide(); 67 | $('a.toggle_format span', parent).click(format_toggler); 68 | }}); 69 | return false; 70 | }) 71 | 72 | 73 | }) -------------------------------------------------------------------------------- /public/reset.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, font, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | dl, dt, dd, ul, li, 7 | form, label, legend, 8 | table, caption, tbody, tfoot, thead, tr, th, td { 9 | margin: 0; 10 | padding: 0; 11 | border: 0; 12 | outline: 0; 13 | font-weight: inherit; 14 | font-style: normal; 15 | font-size: 100%; 16 | font-family: inherit; 17 | } 18 | 19 | body { 20 | line-height: 1; 21 | } 22 | 23 | ul { 24 | list-style: none; 25 | } 26 | 27 | table { 28 | border-collapse: collapse; 29 | border-spacing: 0; 30 | } 31 | 32 | caption, th, td { 33 | text-align: left; 34 | font-weight: normal; 35 | } 36 | 37 | blockquote:before, blockquote:after, 38 | q:before, q:after { 39 | content: ""; 40 | } 41 | 42 | blockquote, q { 43 | quotes: "" ""; 44 | } -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | html { background:#efefef; font-family:Arial, Verdana, sans-serif; font-size:13px; } 2 | body { padding:0; margin:0; } 3 | 4 | .header { background:#000; padding:8px 5% 0 5%; border-bottom:1px solid #444;border-bottom:5px solid #ce1212;} 5 | .header h1 { color:#333; font-size:90%; font-weight:bold; margin-bottom:6px;} 6 | .header ul li { display:inline;} 7 | .header ul li a { color:#fff; text-decoration:none; margin-right:10px; display:inline-block; padding:8px; -webkit-border-top-right-radius:6px; -webkit-border-top-left-radius:6px; -moz-border-radius-topleft:6px; -moz-border-radius-topright:6px; } 8 | .header ul li a:hover { background:#333;} 9 | .header ul li.current a { background:#ce1212; font-weight:bold; color:#fff;} 10 | 11 | .header .namespace { position: absolute; right: 75px; top: 10px; color: #7A7A7A; } 12 | 13 | .subnav { padding:2px 5% 7px 5%; background:#ce1212; font-size:90%;} 14 | .subnav li { display:inline;} 15 | .subnav li a { color:#fff; text-decoration:none; margin-right:10px; display:inline-block; background:#dd5b5b; padding:5px; -webkit-border-radius:3px; -moz-border-radius:3px;} 16 | .subnav li.current a { background:#fff; font-weight:bold; color:#ce1212;} 17 | .subnav li a:active { background:#b00909;} 18 | 19 | #main { padding:10px 5%; background:#fff; overflow:hidden; } 20 | #main .logo { float:right; margin:10px;} 21 | #main span.hl { background:#efefef; padding:2px;} 22 | #main h1 { margin:10px 0; font-size:190%; font-weight:bold; color:#ce1212;} 23 | #main h2 { margin:10px 0; font-size:130%;} 24 | #main table { width:100%; margin:10px 0;} 25 | #main table tr td, #main table tr th { border:1px solid #ccc; padding:6px;} 26 | #main table tr th { background:#efefef; color:#888; font-size:80%; font-weight:bold;} 27 | #main table tr td.no-data { text-align:center; padding:40px 0; color:#999; font-style:italic; font-size:130%;} 28 | #main a { color:#111;} 29 | #main p { margin:5px 0;} 30 | #main p.intro { margin-bottom:15px; font-size:85%; color:#999; margin-top:0; line-height:1.3;} 31 | #main h1.wi { margin-bottom:5px;} 32 | #main p.sub { font-size:95%; color:#999;} 33 | 34 | #main table.queues { width:40%;} 35 | #main table.queues td.queue { font-weight:bold; width:50%;} 36 | #main table.queues tr.failed td { border-top:2px solid; font-size:90%; } 37 | #main table.queues tr.failure td { background:#ffecec; border-top:2px solid #d37474; font-size:90%; color:#d37474;} 38 | #main table.queues tr.failure td a{ color:#d37474;} 39 | 40 | #main table.jobs td.class { font-family:Monaco, "Courier New", monospace; font-size:90%; width:50%;} 41 | #main table.jobs td.args{ width:50%;} 42 | 43 | #main table.workers td.icon {width:1%; background:#efefef;text-align:center;} 44 | #main table.workers td.icon img { height: 16px; width: 16px; } 45 | #main table.workers td.where { width:25%;} 46 | #main table.workers td.queues { width:35%;} 47 | #main .queue-tag { background:#b1d2e9; padding:2px; margin:0 3px; font-size:80%; text-decoration:none; text-transform:uppercase; font-weight:bold; color:#3274a2; -webkit-border-radius:4px; -moz-border-radius:4px;} 48 | #main table.workers td.queues.queue { width:10%;} 49 | #main table.workers td.process { width:35%;} 50 | #main table.workers td.process span.waiting { color:#999; font-size:90%;} 51 | #main table.workers td.process small { font-size:80%; margin-left:5px;} 52 | #main table.workers td.process code { font-family:Monaco, "Courier New", monospace; font-size:90%;} 53 | #main table.workers td.process small a { color:#999;} 54 | #main.polling table.workers tr.working td { background:#f4ffe4; color:#7ac312;} 55 | #main.polling table.workers tr.working td.where a { color:#7ac312;} 56 | #main.polling table.workers tr.working td.process code { font-weight:bold;} 57 | 58 | 59 | #main table.stats th { font-size:100%; width:40%; color:#000;} 60 | #main hr { border:0; border-top:5px solid #efefef; margin:15px 0;} 61 | 62 | #footer { padding:10px 5%; background:#efefef; color:#999; font-size:85%; line-height:1.5; border-top:5px solid #ccc; padding-top:10px;} 63 | #footer p a { color:#999;} 64 | 65 | #main p.poll { background:url(poll.png) no-repeat 0 2px; padding:3px 0; padding-left:23px; float:right; font-size:85%; } 66 | 67 | #main ul.failed {} 68 | #main ul.failed li {background:-webkit-gradient(linear, left top, left bottom, from(#efefef), to(#fff)) #efefef; margin-top:10px; padding:10px; overflow:hidden; -webkit-border-radius:5px; border:1px solid #ccc; } 69 | #main ul.failed li dl dt {font-size:80%; color:#999; width:60px; float:left; padding-top:1px; text-align:right;} 70 | #main ul.failed li dl dd {margin-bottom:10px; margin-left:70px;} 71 | #main ul.failed li dl dd .retried { float:right; text-align: right; } 72 | #main ul.failed li dl dd .retried .remove { display:none; margin-top: 8px; } 73 | #main ul.failed li.hover dl dd .retried .remove { display:block; } 74 | #main ul.failed li dl dd .controls { display:none; float:right; } 75 | #main ul.failed li.hover dl dd .controls { display:block; } 76 | #main ul.failed li dl dd code, #main ul.failed li dl dd pre { font-family:Monaco, "Courier New", monospace; font-size:90%; white-space: pre-wrap;} 77 | #main ul.failed li dl dd.error a {font-family:Monaco, "Courier New", monospace; font-size:90%; } 78 | #main ul.failed li dl dd.error pre { margin-top:3px; line-height:1.3;} 79 | 80 | #main p.pagination { background:#efefef; padding:10px; overflow:hidden;} 81 | #main p.pagination a.less { float:left;} 82 | #main p.pagination a.more { float:right;} 83 | 84 | #main form {float:right; margin-top:-10px;margin-left:10px;} 85 | 86 | #main .time a.toggle_format {text-decoration:none;} -------------------------------------------------------------------------------- /public/working.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defunkt/resque-web/776b2c4f5b23029a565d985269c06e0664fbe0b8/public/working.png -------------------------------------------------------------------------------- /server.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'erb' 3 | require 'resque' 4 | require 'resque/version' 5 | require 'time' 6 | 7 | module Resque 8 | class Server < Sinatra::Base 9 | dir = File.dirname(File.expand_path(__FILE__)) 10 | 11 | set :views, "#{dir}/server/views" 12 | set :public, "#{dir}/server/public" 13 | set :static, true 14 | 15 | helpers do 16 | include Rack::Utils 17 | alias_method :h, :escape_html 18 | 19 | def current_section 20 | url_path request.path_info.sub('/','').split('/')[0].downcase 21 | end 22 | 23 | def current_page 24 | url_path request.path_info.sub('/','') 25 | end 26 | 27 | def url_path(*path_parts) 28 | [ path_prefix, path_parts ].join("/").squeeze('/') 29 | end 30 | alias_method :u, :url_path 31 | 32 | def path_prefix 33 | request.env['SCRIPT_NAME'] 34 | end 35 | 36 | def class_if_current(path = '') 37 | 'class="current"' if current_page[0, path.size] == path 38 | end 39 | 40 | def tab(name) 41 | dname = name.to_s.downcase 42 | path = url_path(dname) 43 | "
  • #{name}
  • " 44 | end 45 | 46 | def tabs 47 | Resque::Server.tabs 48 | end 49 | 50 | def redis_get_size(key) 51 | case Resque.redis.type(key) 52 | when 'none' 53 | [] 54 | when 'list' 55 | Resque.redis.llen(key) 56 | when 'set' 57 | Resque.redis.scard(key) 58 | when 'string' 59 | Resque.redis.get(key).length 60 | when 'zset' 61 | Resque.redis.zcard(key) 62 | end 63 | end 64 | 65 | def redis_get_value_as_array(key, start=0) 66 | case Resque.redis.type(key) 67 | when 'none' 68 | [] 69 | when 'list' 70 | Resque.redis.lrange(key, start, start + 20) 71 | when 'set' 72 | Resque.redis.smembers(key)[start..(start + 20)] 73 | when 'string' 74 | [Resque.redis.get(key)] 75 | when 'zset' 76 | Resque.redis.zrange(key, start, start + 20) 77 | end 78 | end 79 | 80 | def show_args(args) 81 | Array(args).map { |a| a.inspect }.join("\n") 82 | end 83 | 84 | def worker_hosts 85 | @worker_hosts ||= worker_hosts! 86 | end 87 | 88 | def worker_hosts! 89 | hosts = Hash.new { [] } 90 | 91 | Resque.workers.each do |worker| 92 | host, _ = worker.to_s.split(':') 93 | hosts[host] += [worker.to_s] 94 | end 95 | 96 | hosts 97 | end 98 | 99 | def partial? 100 | @partial 101 | end 102 | 103 | def partial(template, local_vars = {}) 104 | @partial = true 105 | erb(template.to_sym, {:layout => false}, local_vars) 106 | ensure 107 | @partial = false 108 | end 109 | 110 | def poll 111 | if @polling 112 | text = "Last Updated: #{Time.now.strftime("%H:%M:%S")}" 113 | else 114 | text = "Live Poll" 115 | end 116 | "

    #{text}

    " 117 | end 118 | 119 | end 120 | 121 | def show(page, layout = true) 122 | response["Cache-Control"] = "max-age=0, private, must-revalidate" 123 | begin 124 | erb page.to_sym, {:layout => layout}, :resque => Resque 125 | rescue Errno::ECONNREFUSED 126 | erb :error, {:layout => false}, :error => "Can't connect to Redis! (#{Resque.redis_id})" 127 | end 128 | end 129 | 130 | def show_for_polling(page) 131 | content_type "text/html" 132 | @polling = true 133 | show(page.to_sym, false).gsub(/\s{1,}/, ' ') 134 | end 135 | 136 | # to make things easier on ourselves 137 | get "/?" do 138 | redirect url_path(:overview) 139 | end 140 | 141 | %w( overview workers ).each do |page| 142 | get "/#{page}.poll" do 143 | show_for_polling(page) 144 | end 145 | 146 | get "/#{page}/:id.poll" do 147 | show_for_polling(page) 148 | end 149 | end 150 | 151 | %w( overview queues working workers key ).each do |page| 152 | get "/#{page}" do 153 | show page 154 | end 155 | 156 | get "/#{page}/:id" do 157 | show page 158 | end 159 | end 160 | 161 | post "/queues/:id/remove" do 162 | Resque.remove_queue(params[:id]) 163 | redirect u('queues') 164 | end 165 | 166 | get "/failed" do 167 | if Resque::Failure.url 168 | redirect Resque::Failure.url 169 | else 170 | show :failed 171 | end 172 | end 173 | 174 | post "/failed/clear" do 175 | Resque::Failure.clear 176 | redirect u('failed') 177 | end 178 | 179 | post "/failed/requeue/all" do 180 | Resque::Failure.count.times do |num| 181 | Resque::Failure.requeue(num) 182 | end 183 | redirect u('failed') 184 | end 185 | 186 | get "/failed/requeue/:index" do 187 | Resque::Failure.requeue(params[:index]) 188 | if request.xhr? 189 | return Resque::Failure.all(params[:index])['retried_at'] 190 | else 191 | redirect u('failed') 192 | end 193 | end 194 | 195 | get "/failed/remove/:index" do 196 | Resque::Failure.remove(params[:index]) 197 | redirect u('failed') 198 | end 199 | 200 | get "/stats" do 201 | redirect url_path("/stats/resque") 202 | end 203 | 204 | get "/stats/:id" do 205 | show :stats 206 | end 207 | 208 | get "/stats/keys/:key" do 209 | show :stats 210 | end 211 | 212 | get "/stats.txt" do 213 | info = Resque.info 214 | 215 | stats = [] 216 | stats << "resque.pending=#{info[:pending]}" 217 | stats << "resque.processed+=#{info[:processed]}" 218 | stats << "resque.failed+=#{info[:failed]}" 219 | stats << "resque.workers=#{info[:workers]}" 220 | stats << "resque.working=#{info[:working]}" 221 | 222 | Resque.queues.each do |queue| 223 | stats << "queues.#{queue}=#{Resque.size(queue)}" 224 | end 225 | 226 | content_type 'text/html' 227 | stats.join "\n" 228 | end 229 | 230 | def resque 231 | Resque 232 | end 233 | 234 | def self.tabs 235 | @tabs ||= ["Overview", "Working", "Failed", "Queues", "Workers", "Stats"] 236 | end 237 | end 238 | end 239 | -------------------------------------------------------------------------------- /test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rack/test' 2 | require 'resque/server' 3 | 4 | module Resque 5 | module TestHelper 6 | class Test::Unit::TestCase 7 | include Rack::Test::Methods 8 | def app 9 | Resque::Server.new 10 | end 11 | 12 | def self.should_respond_with_success 13 | test "should respond with success" do 14 | assert last_response.ok?, last_response.errors 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /views/error.erb: -------------------------------------------------------------------------------- 1 |

    <%= error %>

    -------------------------------------------------------------------------------- /views/failed.erb: -------------------------------------------------------------------------------- 1 | <%start = params[:start].to_i %> 2 | <%failed = Resque::Failure.all(start, 20)%> 3 | <% index = 0 %> 4 | <% date_format = "%Y/%m/%d %T %z" %> 5 | 6 |

    Failed Jobs

    7 | <%unless failed.empty?%> 8 |
    9 | 10 |
    11 |
    12 | 13 |
    14 | <%end%> 15 | 16 |

    Showing <%=start%> to <%= start + 20 %> of <%= size = Resque::Failure.count %> jobs

    17 | 18 |
      19 | <%for job in failed%> 20 | <% index += 1 %> 21 |
    • 22 |
      23 | <% if job.nil? %> 24 |
      Error
      25 |
      Job <%= index%> could not be parsed; perhaps it contains invalid JSON?
      26 | <% else %> 27 |
      Worker
      28 |
      29 | <%= job['worker'].split(':')[0...2].join(':') %> on <%= job['queue'] %> at <%= Time.parse(job['failed_at']).strftime(date_format) %> 30 | <% if job['retried_at'] %> 31 |
      32 | Retried <%= Time.parse(job['retried_at']).strftime(date_format) %> 33 | " class="remove" rel="remove">Remove 34 |
      35 | <% else %> 36 | 41 | <% end %> 42 |
      43 |
      Class
      44 |
      <%= job['payload'] ? job['payload']['class'] : 'nil' %>
      45 |
      Arguments
      46 |
      <%=h job['payload'] ? show_args(job['payload']['args']) : 'nil' %>
      47 |
      Exception
      48 |
      <%= job['exception'] %>
      49 |
      Error
      50 |
      51 | <% if job['backtrace'] %> 52 | <%= h(job['error']) %> 53 |
      <%=h job['backtrace'].join("\n") %>
      54 | <% else %> 55 | <%=h job['error'] %> 56 | <% end %> 57 |
      58 | <% end %> 59 |
      60 |
      61 |
      62 |
    • 63 | <%end%> 64 |
    65 | 66 | <%= partial :next_more, :start => start, :size => size %> 67 | 68 | -------------------------------------------------------------------------------- /views/key_sets.erb: -------------------------------------------------------------------------------- 1 | <% if key = params[:key] %> 2 | 3 |

    4 | Showing <%= start = params[:start].to_i %> to <%= start + 20 %> of <%=size = redis_get_size(key) %> 5 |

    6 | 7 |

    Key "<%= key %>" is a <%= resque.redis.type key %>

    8 | 9 | <% for row in redis_get_value_as_array(key, start) %> 10 | 11 | 14 | 15 | <% end %> 16 |
    12 | <%= row %> 13 |
    17 | 18 | <%= partial :next_more, :start => start, :size => size %> 19 | <% end %> 20 | -------------------------------------------------------------------------------- /views/key_string.erb: -------------------------------------------------------------------------------- 1 | <% if key = params[:key] %> 2 |

    Key "<%= key %>" is a <%= resque.redis.type key %>

    3 |

    size: <%= redis_get_size(key) %>

    4 | 5 | 6 | 9 | 10 |
    7 | <%= redis_get_value_as_array(key) %> 8 |
    11 | <% end %> 12 | -------------------------------------------------------------------------------- /views/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Resque. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 | 19 | <% if Resque.redis.namespace != :resque %> 20 | 21 | <%= Resque.redis.namespace %> 22 | 23 | <% end %> 24 |
    25 | 26 | <% if @subtabs %> 27 | 32 | <% end %> 33 | 34 |
    35 | <%= yield %> 36 |
    37 | 38 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /views/next_more.erb: -------------------------------------------------------------------------------- 1 | <%if start - 20 >= 0 || start + 20 <= size%> 2 |

    3 | <% if start - 20 >= 0 %> 4 | « less 5 | <% end %> 6 | <% if start + 20 <= size %> 7 | more » 8 | <% end %> 9 |

    10 | <%end%> -------------------------------------------------------------------------------- /views/overview.erb: -------------------------------------------------------------------------------- 1 | <%= partial :queues %> 2 |
    3 | <%= partial :working %> 4 | <%= poll %> 5 | -------------------------------------------------------------------------------- /views/queues.erb: -------------------------------------------------------------------------------- 1 | <% @subtabs = resque.queues unless partial? || params[:id].nil? %> 2 | 3 | <% if queue = params[:id] %> 4 | 5 |

    Pending jobs on <%= queue %>

    6 |
    " class='remove-queue'> 7 | 8 |
    9 |

    Showing <%= start = params[:start].to_i %> to <%= start + 20 %> of <%=size = resque.size(queue)%> jobs

    10 | 11 | 12 | 13 | 14 | 15 | <% for job in (jobs = resque.peek(queue, start, 20)) %> 16 | 17 | 18 | 19 | 20 | <% end %> 21 | <% if jobs.empty? %> 22 | 23 | 24 | 25 | <% end %> 26 |
    ClassArgs
    <%= job['class'] %><%=h job['args'].inspect %>
    There are no pending jobs in this queue
    27 | <%= partial :next_more, :start => start, :size => size %> 28 | <% else %> 29 | 30 |

    Queues

    31 |

    The list below contains all the registered queues with the number of jobs currently in the queue. Select a queue from above to view all jobs currently pending on the queue.

    32 | 33 | 34 | 35 | 36 | 37 | <% for queue in resque.queues.sort_by { |q| q.to_s } %> 38 | 39 | 40 | 41 | 42 | <% end %> 43 | "> 44 | 45 | 46 | 47 |
    NameJobs
    "><%= queue %><%= resque.size queue %>
    failed<%= Resque::Failure.count %>
    48 | 49 | <% end %> 50 | -------------------------------------------------------------------------------- /views/stats.erb: -------------------------------------------------------------------------------- 1 | <% @subtabs = %w( resque redis keys ) %> 2 | 3 | <% if params[:key] %> 4 | 5 | <%= partial resque.redis.type(params[:key]).eql?("string") ? :key_string : :key_sets %> 6 | 7 | <% elsif params[:id] == "resque" %> 8 | 9 |

    <%= resque %>

    10 | 11 | <% for key, value in resque.info.to_a.sort_by { |i| i[0].to_s } %> 12 | 13 | 16 | 19 | 20 | <% end %> 21 |
    14 | <%= key %> 15 | 17 | <%= value %> 18 |
    22 | 23 | <% elsif params[:id] == 'redis' %> 24 | 25 |

    <%= resque.redis_id %>

    26 | 27 | <% for key, value in resque.redis.info.to_a.sort_by { |i| i[0].to_s } %> 28 | 29 | 32 | 35 | 36 | <% end %> 37 |
    30 | <%= key %> 31 | 33 | <%= value %> 34 |
    38 | 39 | <% elsif params[:id] == 'keys' %> 40 | 41 |

    Keys owned by <%= resque %>

    42 |

    (All keys are actually prefixed with "<%= Resque.redis.namespace %>:")

    43 | 44 | 45 | 46 | 47 | 48 | 49 | <% for key in resque.keys.sort %> 50 | 51 | 54 | 55 | 56 | 57 | <% end %> 58 |
    keytypesize
    52 | "><%= key %> 53 | <%= resque.redis.type key %><%= redis_get_size key %>
    59 | 60 | <% else %> 61 | 62 | <% end %> 63 | -------------------------------------------------------------------------------- /views/workers.erb: -------------------------------------------------------------------------------- 1 | <% @subtabs = worker_hosts.keys.sort unless worker_hosts.size == 1 %> 2 | 3 | <% if params[:id] && worker = Resque::Worker.find(params[:id]) %> 4 | 5 |

    Worker <%= worker %>

    6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | <% host, pid, queues = worker.to_s.split(':') %> 21 | 22 | 23 | 24 | 25 | 26 | 27 | 36 | 37 |
     HostPidStartedQueuesProcessedFailedProcessing
    <%= state %><%= host %><%= pid %><%= worker.started %><%= queues.split(',').map { |q| '' + q + ''}.join('') %><%= worker.processed %><%= worker.failed %> 28 | <% data = worker.processing || {} %> 29 | <% if data['queue'] %> 30 | <%= data['payload']['class'] %> 31 | "><%= data['run_at'] %> 32 | <% else %> 33 | Waiting for a job... 34 | <% end %> 35 |
    38 | 39 | <% elsif params[:id] && !worker_hosts.keys.include?(params[:id]) && params[:id] != 'all' %> 40 | 41 |

    Worker doesn't exist

    42 | 43 | <% elsif worker_hosts.size == 1 || params[:id] %> 44 | 45 | <% if worker_hosts.size == 1 || params[:id] == 'all' %> 46 | <% workers = Resque.workers %> 47 | <% else %> 48 | <% workers = worker_hosts[params[:id]].map { |id| Resque::Worker.find(id) } %> 49 | <% end %> 50 | 51 |

    <%= workers.size %> Workers

    52 |

    The workers listed below are all registered as active on your system.

    53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | <% for worker in (workers = workers.sort_by { |w| w.to_s }) %> 61 | 62 | 63 | 64 | <% host, pid, queues = worker.to_s.split(':') %> 65 | 66 | 67 | 68 | 77 | 78 | <% end %> 79 | <% if workers.empty? %> 80 | 81 | 82 | 83 | <% end %> 84 |
     WhereQueuesProcessing
    <%= state %>"><%= host %>:<%= pid %><%= queues.split(',').map { |q| '' + q + ''}.join('') %> 69 | <% data = worker.processing || {} %> 70 | <% if data['queue'] %> 71 | <%= data['payload']['class'] %> 72 | "><%= data['run_at'] %> 73 | <% else %> 74 | Waiting for a job... 75 | <% end %> 76 |
    There are no registered workers
    85 | <%=poll%> 86 | 87 | <% else %> 88 | <% @subtabs = [] %> 89 |

    Workers

    90 |

    The hostnames below all have registered workers. Select a hostname to view its workers, or "all" to see all workers.

    91 | 92 | 93 | 94 | 95 | 96 | <% for hostname, workers in worker_hosts.sort_by { |h,w| h } %> 97 | 98 | 99 | 100 | 101 | <% end %> 102 | 103 | 104 | 105 | 106 |
    HostnameWorkers
    "><%= hostname %><%= workers.size %>
    ">all workers<%= Resque.workers.size %>
    107 | 108 | 109 | <% end %> 110 | -------------------------------------------------------------------------------- /views/working.erb: -------------------------------------------------------------------------------- 1 | <% if params[:id] && (worker = Resque::Worker.find(params[:id])) && worker.job %> 2 |

    <%= worker %>'s job

    3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <% host, pid, _ = worker.to_s.split(':') %> 16 | 17 | <% data = worker.job %> 18 | <% queue = data['queue'] %> 19 | 20 | 21 | 24 | 25 | 26 |
     WhereQueueStartedClassArgs
    working"><%= host %>:<%= pid %>"><%= queue %><%= data['run_at'] %> 22 | <%= data['payload']['class'] %> 23 | <%=h data['payload']['args'].inspect %>
    27 | 28 | <% else %> 29 | 30 | <% 31 | workers = resque.working 32 | jobs = workers.collect {|w| w.job } 33 | worker_jobs = workers.zip(jobs) 34 | worker_jobs = worker_jobs.reject { |w, j| w.idle? } 35 | %> 36 | 37 |

    <%= worker_jobs.size %> of <%= resque.workers.size %> Workers Working

    38 |

    The list below contains all workers which are currently running a job.

    39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | <% if worker_jobs.empty? %> 47 | 48 | 49 | 50 | <% end %> 51 | 52 | <% worker_jobs.sort_by {|w, j| j['run_at'] ? j['run_at'] : '' }.each do |worker, job| %> 53 | 54 | 55 | <% host, pid, queues = worker.to_s.split(':') %> 56 | 57 | 60 | 68 | 69 | <% end %> 70 |
     WhereQueueProcessing
    Nothing is happening right now...
    <%= state %>"><%= host %>:<%= pid %> 58 | "><%= job['queue'] %> 59 | 61 | <% if job['queue'] %> 62 | <%= job['payload']['class'] %> 63 | "><%= job['run_at'] %> 64 | <% else %> 65 | Waiting for a job... 66 | <% end %> 67 |
    71 | 72 | <% end %> 73 | --------------------------------------------------------------------------------