resque_ui 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +6 -0
- data/Gemfile.lock +102 -0
- data/History.txt +5 -0
- data/MIT-LICENSE +21 -0
- data/README.markdown +279 -0
- data/Rakefile +49 -0
- data/VERSION.yml +5 -0
- data/app/assets/images/idle.png +0 -0
- data/app/assets/images/poll.png +0 -0
- data/app/assets/images/working.png +0 -0
- data/app/assets/javascripts/resque/jquery-1.3.2.min.js +19 -0
- data/app/assets/javascripts/resque/jquery.relatize_date.js +95 -0
- data/app/assets/javascripts/resque/ranger.js +24 -0
- data/app/assets/stylesheets/resque/resque.css +93 -0
- data/app/assets/stylesheets/resque/resque_reset.css +48 -0
- data/app/controllers/resque_controller.rb +236 -0
- data/app/helpers/resque_helper.rb +107 -0
- data/app/views/layouts/resque.html.erb +39 -0
- data/app/views/resque/_key.html.erb +17 -0
- data/app/views/resque/_next_more.html.erb +10 -0
- data/app/views/resque/_queues.html.erb +52 -0
- data/app/views/resque/_status_styles.erb +98 -0
- data/app/views/resque/_workers.html.erb +110 -0
- data/app/views/resque/_working.html.erb +69 -0
- data/app/views/resque/delayed.html.erb +35 -0
- data/app/views/resque/delayed_timestamp.html.erb +26 -0
- data/app/views/resque/error.erb +1 -0
- data/app/views/resque/failed.html.erb +54 -0
- data/app/views/resque/overview.html.erb +4 -0
- data/app/views/resque/schedule.html.erb +96 -0
- data/app/views/resque/stats.html.erb +62 -0
- data/app/views/resque/status.html.erb +57 -0
- data/app/views/resque/statuses.html.erb +72 -0
- data/app/views/resque/workers.html.erb +1 -0
- data/lib/resque_ui/cap.rb +6 -0
- data/lib/resque_ui/cap_recipes.rb +106 -0
- data/lib/resque_ui/overrides/resque/failure/failure.rb +22 -0
- data/lib/resque_ui/overrides/resque/job.rb +12 -0
- data/lib/resque_ui/overrides/resque/resque.rb +8 -0
- data/lib/resque_ui/overrides/resque/worker.rb +230 -0
- data/lib/resque_ui/overrides/resque_scheduler/resque_scheduler.rb +58 -0
- data/lib/resque_ui/overrides/resque_status/chained_job_with_status.rb +24 -0
- data/lib/resque_ui/overrides/resque_status/job_with_status.rb +59 -0
- data/lib/resque_ui/overrides/resque_status/status.rb +53 -0
- data/lib/resque_ui.rb +26 -0
- data/lib/tasks/failure.rake +8 -0
- data/lib/tasks/scheduler.rake +11 -0
- data/lib/tasks/worker.rake +80 -0
- data/rdoc/Resque/ChainedJobWithStatus.html +284 -0
- data/rdoc/Resque/Failure/Base.html +229 -0
- data/rdoc/Resque/Failure.html +202 -0
- data/rdoc/Resque/Job.html +202 -0
- data/rdoc/Resque/JobWithStatus.html +410 -0
- data/rdoc/Resque/Status.html +368 -0
- data/rdoc/Resque/Worker.html +1104 -0
- data/rdoc/Resque.html +232 -0
- data/rdoc/ResqueScheduler.html +434 -0
- data/rdoc/ResqueUi/Cap.html +150 -0
- data/rdoc/ResqueUi/Engine.html +150 -0
- data/rdoc/ResqueUi.html +157 -0
- data/rdoc/created.rid +13 -0
- data/rdoc/images/brick.png +0 -0
- data/rdoc/images/brick_link.png +0 -0
- data/rdoc/images/bug.png +0 -0
- data/rdoc/images/bullet_black.png +0 -0
- data/rdoc/images/bullet_toggle_minus.png +0 -0
- data/rdoc/images/bullet_toggle_plus.png +0 -0
- data/rdoc/images/date.png +0 -0
- data/rdoc/images/find.png +0 -0
- data/rdoc/images/loadingAnimation.gif +0 -0
- data/rdoc/images/macFFBgHack.png +0 -0
- data/rdoc/images/package.png +0 -0
- data/rdoc/images/page_green.png +0 -0
- data/rdoc/images/page_white_text.png +0 -0
- data/rdoc/images/page_white_width.png +0 -0
- data/rdoc/images/plugin.png +0 -0
- data/rdoc/images/ruby.png +0 -0
- data/rdoc/images/tag_green.png +0 -0
- data/rdoc/images/wrench.png +0 -0
- data/rdoc/images/wrench_orange.png +0 -0
- data/rdoc/images/zoom.png +0 -0
- data/rdoc/index.html +163 -0
- data/rdoc/js/darkfish.js +116 -0
- data/rdoc/js/jquery.js +32 -0
- data/rdoc/js/quicksearch.js +114 -0
- data/rdoc/js/thickbox-compressed.js +10 -0
- data/rdoc/lib/resque_overrides_rb.html +54 -0
- data/rdoc/lib/resque_scheduler_overrides_rb.html +52 -0
- data/rdoc/lib/resque_status_overrides_rb.html +52 -0
- data/rdoc/lib/resque_ui/cap_rb.html +52 -0
- data/rdoc/lib/resque_ui/cap_recipes_rb.html +58 -0
- data/rdoc/lib/resque_ui/engine_rb.html +52 -0
- data/rdoc/lib/resque_ui/overrides/resque/failure/failure_rb.html +54 -0
- data/rdoc/lib/resque_ui/overrides/resque/job_rb.html +52 -0
- data/rdoc/lib/resque_ui/overrides/resque/resque_rb.html +52 -0
- data/rdoc/lib/resque_ui/overrides/resque/worker_rb.html +54 -0
- data/rdoc/lib/resque_ui/overrides/resque_scheduler/resque_scheduler_rb.html +52 -0
- data/rdoc/lib/resque_ui/overrides/resque_status/chained_job_with_status_rb.html +52 -0
- data/rdoc/lib/resque_ui/overrides/resque_status/job_with_status_rb.html +52 -0
- data/rdoc/lib/resque_ui/overrides/resque_status/status_rb.html +52 -0
- data/rdoc/lib/resque_ui/resque_ui_rb.html +52 -0
- data/rdoc/lib/resque_ui/tasks_rb.html +64 -0
- data/rdoc/lib/resque_ui_rb.html +76 -0
- data/rdoc/rdoc.css +763 -0
- data/resque_ui.gemspec +153 -0
- data/test/resque_ui_test.rb +8 -0
- data/test/test_helper.rb +3 -0
- metadata +205 -0
@@ -0,0 +1,95 @@
|
|
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);
|
@@ -0,0 +1,24 @@
|
|
1
|
+
var poll_interval = 2;
|
2
|
+
|
3
|
+
$(function() {
|
4
|
+
|
5
|
+
$('.time').relatizeDate()
|
6
|
+
$('.backtrace').click(function() {
|
7
|
+
$(this).next().toggle()
|
8
|
+
return false
|
9
|
+
})
|
10
|
+
|
11
|
+
$('a[rel=poll]').click(function() {
|
12
|
+
var href = $(this).attr('href')
|
13
|
+
$(this).parent().text('Starting...')
|
14
|
+
$("#main").addClass('polling')
|
15
|
+
setInterval(function() {
|
16
|
+
$.ajax({dataType:'text', type:'get', url:href, success:function(data) {
|
17
|
+
$('#main').html(data)
|
18
|
+
$('#main .time').relatizeDate()
|
19
|
+
}})
|
20
|
+
}, poll_interval * 1000)
|
21
|
+
return false
|
22
|
+
})
|
23
|
+
|
24
|
+
})
|
@@ -0,0 +1,93 @@
|
|
1
|
+
html { background:#efefef; font-family:Arial, Verdana, sans-serif; font-size:13px; }
|
2
|
+
body { padding:0; margin:0; }
|
3
|
+
|
4
|
+
.flash .notice {
|
5
|
+
color: #AE5F68;
|
6
|
+
}
|
7
|
+
.flash .warning {
|
8
|
+
color: #ff5f68;
|
9
|
+
}
|
10
|
+
.flash .error {
|
11
|
+
color: #ff5f68;
|
12
|
+
}
|
13
|
+
.flash .message {
|
14
|
+
color: #ff5f68;
|
15
|
+
}
|
16
|
+
|
17
|
+
.header { background:#000; padding:8px 5% 0 5%; border-bottom:1px solid #444;border-bottom:5px solid #ce1212;}
|
18
|
+
.header h1 { color:#333; font-size:90%; font-weight:bold; margin-bottom:6px;}
|
19
|
+
.header ul li { display:inline;}
|
20
|
+
.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; }
|
21
|
+
.header ul li a:hover { background:#333;}
|
22
|
+
.header ul li.current a { background:#ce1212; font-weight:bold; color:#fff;}
|
23
|
+
|
24
|
+
.subnav { padding:2px 5% 7px 5%; background:#ce1212; font-size:90%;}
|
25
|
+
.subnav li { display:inline;}
|
26
|
+
.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;}
|
27
|
+
.subnav li.current a { background:#fff; font-weight:bold; color:#ce1212;}
|
28
|
+
.subnav li a:active { background:#b00909;}
|
29
|
+
|
30
|
+
#main { padding:10px 5%; background:#fff; overflow:hidden; }
|
31
|
+
#main .logo { float:right; margin:10px;}
|
32
|
+
#main span.hl { background:#efefef; padding:2px;}
|
33
|
+
#main h1 { margin:10px 0; font-size:190%; font-weight:bold; color:#ce1212;}
|
34
|
+
#main h2 { margin:10px 0; font-size:130%;}
|
35
|
+
#main table { width:100%; margin:10px 0;}
|
36
|
+
#main table tr td, #main table tr th { border:1px solid #ccc; padding:6px;}
|
37
|
+
#main table tr th { background:#efefef; color:#888; font-size:80%; font-weight:bold;}
|
38
|
+
#main table tr td.no-data { text-align:center; padding:40px 0; color:#999; font-style:italic; font-size:130%;}
|
39
|
+
#main a { color:#111;}
|
40
|
+
#main p { margin:5px 0;}
|
41
|
+
#main p.intro { margin-bottom:15px; font-size:85%; color:#999; margin-top:0; line-height:1.3;}
|
42
|
+
#main h1.wi { margin-bottom:5px;}
|
43
|
+
#main p.sub { font-size:95%; color:#999;}
|
44
|
+
|
45
|
+
#main table.queues { width:40%;}
|
46
|
+
#main table.queues td.queue { font-weight:bold; width:50%;}
|
47
|
+
#main table.queues tr.failed td { background:#ffecec; border-top:2px solid #d37474; font-size:90%; color:#d37474;}
|
48
|
+
#main table.queues tr.failed td a{ color:#d37474;}
|
49
|
+
|
50
|
+
#main table.jobs td.class { font-family:Monaco, "Courier New", monospace; font-size:90%; width:50%;}
|
51
|
+
#main table.jobs td.args{ width:50%;}
|
52
|
+
|
53
|
+
#main table.workers td.icon {width:1%; background:#efefef;text-align:center;}
|
54
|
+
#main table.workers td.where { width:25%;}
|
55
|
+
#main table.workers td.queues { width:35%;}
|
56
|
+
#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;}
|
57
|
+
#main table.workers td.queues.queue { width:10%;}
|
58
|
+
#main table.workers td.process { width:35%;}
|
59
|
+
#main table.workers td.process span.waiting { color:#999; font-size:90%;}
|
60
|
+
#main table.workers td.process small { font-size:80%; margin-left:5px;}
|
61
|
+
#main table.workers td.process code { font-family:Monaco, "Courier New", monospace; font-size:90%;}
|
62
|
+
#main table.workers td.process small a { color:#999;}
|
63
|
+
#main.polling table.workers tr.working td { background:#f4ffe4; color:#7ac312;}
|
64
|
+
#main.polling table.workers tr.working td.where a { color:#7ac312;}
|
65
|
+
#main.polling table.workers tr.working td.process code { font-weight:bold;}
|
66
|
+
|
67
|
+
|
68
|
+
#main table.stats th { font-size:100%; width:40%; color:#000;}
|
69
|
+
#main hr { border:0; border-top:5px solid #efefef; margin:15px 0;}
|
70
|
+
|
71
|
+
#footer { padding:10px 5%; background:#efefef; color:#999; font-size:85%; line-height:1.5; border-top:5px solid #ccc; padding-top:10px;}
|
72
|
+
#footer p a { color:#999;}
|
73
|
+
|
74
|
+
#main p.poll { background:url(../../images/resque/poll.png) no-repeat 0 2px; padding:3px 0; padding-left:23px; float:right; font-size:85%; }
|
75
|
+
|
76
|
+
#main ul.failed {}
|
77
|
+
#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; }
|
78
|
+
#main ul.failed li dl dt {font-size:80%; color:#999; width:60px; float:left; padding-top:1px; text-align:right;}
|
79
|
+
#main ul.failed li dl dd {margin-bottom:10px; margin-left:70px;}
|
80
|
+
#main ul.failed li dl dd code, #main ul.failed li dl dd pre { font-family:Monaco, "Courier New", monospace; font-size:90%;}
|
81
|
+
#main ul.failed li dl dd.error a {font-family:Monaco, "Courier New", monospace; font-size:90%; }
|
82
|
+
#main ul.failed li dl dd.error pre { margin-top:3px; line-height:1.3;}
|
83
|
+
|
84
|
+
#main ul.new_worker {}
|
85
|
+
#main ul.new_worker 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; }
|
86
|
+
#main ul.new_worker li dl dt {font-size:80%; width:60px; float:left; padding-top:1px; text-align:right;}
|
87
|
+
#main ul.new_worker li dl dd {margin-bottom:10px; margin-left:70px; font-size:80%; color:#999;}
|
88
|
+
|
89
|
+
#main p.pagination { background:#efefef; padding:10px; overflow:hidden;}
|
90
|
+
#main p.pagination a.less { float:left;}
|
91
|
+
#main p.pagination a.more { float:right;}
|
92
|
+
|
93
|
+
#main form.clear-failed {float:right; margin-top:-10px;}
|
@@ -0,0 +1,48 @@
|
|
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
|
+
:focus {
|
20
|
+
outline: 0;
|
21
|
+
}
|
22
|
+
|
23
|
+
body {
|
24
|
+
line-height: 1;
|
25
|
+
}
|
26
|
+
|
27
|
+
ul {
|
28
|
+
list-style: none;
|
29
|
+
}
|
30
|
+
|
31
|
+
table {
|
32
|
+
border-collapse: collapse;
|
33
|
+
border-spacing: 0;
|
34
|
+
}
|
35
|
+
|
36
|
+
caption, th, td {
|
37
|
+
text-align: left;
|
38
|
+
font-weight: normal;
|
39
|
+
}
|
40
|
+
|
41
|
+
blockquote:before, blockquote:after,
|
42
|
+
q:before, q:after {
|
43
|
+
content: "";
|
44
|
+
}
|
45
|
+
|
46
|
+
blockquote, q {
|
47
|
+
quotes: "" "";
|
48
|
+
}
|
@@ -0,0 +1,236 @@
|
|
1
|
+
require 'resque'
|
2
|
+
require 'resque/version'
|
3
|
+
|
4
|
+
class ResqueController < ApplicationController
|
5
|
+
unloadable(self) #needed to prevent errors with authenticated system in dev env.
|
6
|
+
|
7
|
+
layout 'resque'
|
8
|
+
|
9
|
+
before_filter :check_connection
|
10
|
+
|
11
|
+
verify :method => :post, :only => [:clear_failures, :clear_failure, :requeue_failure, :stop_worker, :restart_worker,
|
12
|
+
:start_worker, :schedule_requeue, :remove_from_schedule, :add_scheduled_job,
|
13
|
+
:start_scheduler, :stop_scheduler, :requeue_failures_in_class,
|
14
|
+
:kill, :clear_statuses],
|
15
|
+
:render => {:text => "<p>Please use the POST http method to post data to this API.</p>".html_safe}
|
16
|
+
|
17
|
+
|
18
|
+
def index
|
19
|
+
redirect_to(:action => 'overview')
|
20
|
+
end
|
21
|
+
|
22
|
+
def working
|
23
|
+
render('_working')
|
24
|
+
end
|
25
|
+
|
26
|
+
def queues
|
27
|
+
render('_queues', :locals => {:partial => nil})
|
28
|
+
end
|
29
|
+
|
30
|
+
def poll
|
31
|
+
@polling = true
|
32
|
+
render(:text => (render_to_string(:action => "#{params[:page]}.html", :layout => false, :resque => Resque)).gsub(/\s{1,}/, ' '))
|
33
|
+
end
|
34
|
+
|
35
|
+
def status_poll
|
36
|
+
@polling = true
|
37
|
+
|
38
|
+
@start = params[:start].to_i
|
39
|
+
@end = @start + (params[:per_page] || 20)
|
40
|
+
@statuses = Resque::Status.statuses(@start, @end)
|
41
|
+
@size = Resque::Status.status_ids.size
|
42
|
+
|
43
|
+
render(:text => (render_to_string(:action => 'statuses.html', :layout => false)))
|
44
|
+
end
|
45
|
+
|
46
|
+
def failed
|
47
|
+
if Resque::Failure.url
|
48
|
+
redirect_to Resque::Failure.url
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def clear_failures
|
53
|
+
Resque::Failure.clear
|
54
|
+
redirect_to(:action => 'failed')
|
55
|
+
end
|
56
|
+
|
57
|
+
def clear_failure
|
58
|
+
remove_failure_from_list(Resque.decode(params[:payload]))
|
59
|
+
redirect_to(:action => 'failed')
|
60
|
+
end
|
61
|
+
|
62
|
+
def requeue_failure
|
63
|
+
#first clear the job we're restarting from the failure list.
|
64
|
+
payload = Resque.decode(params["payload"])
|
65
|
+
remove_failure_from_list(payload)
|
66
|
+
args = payload["args"]
|
67
|
+
Resque.enqueue(eval(payload["class"]), *args)
|
68
|
+
redirect_to(:action => 'failed')
|
69
|
+
end
|
70
|
+
|
71
|
+
def requeue_failures_in_class
|
72
|
+
Resque::Failure.requeue_class(params['class'])
|
73
|
+
redirect_to(:action => 'failed')
|
74
|
+
end
|
75
|
+
|
76
|
+
def remove_job
|
77
|
+
Resque.dequeue(params['class'].constantize, *Resque.decode(params['args']))
|
78
|
+
redirect_to request.referrer
|
79
|
+
end
|
80
|
+
|
81
|
+
def stop_worker
|
82
|
+
worker = find_worker(params[:worker])
|
83
|
+
worker.quit if worker
|
84
|
+
redirect_to(:action => "workers")
|
85
|
+
end
|
86
|
+
|
87
|
+
def restart_worker
|
88
|
+
worker = find_worker(params[:worker])
|
89
|
+
worker.restart if worker
|
90
|
+
redirect_to(:action => "workers")
|
91
|
+
end
|
92
|
+
|
93
|
+
def start_worker
|
94
|
+
Resque::Worker.start(params[:hosts], params[:queues])
|
95
|
+
redirect_to(:action => "workers")
|
96
|
+
end
|
97
|
+
|
98
|
+
def stats
|
99
|
+
unless params[:id]
|
100
|
+
redirect_to(:action => 'stats', :id => 'resque')
|
101
|
+
end
|
102
|
+
|
103
|
+
if params[:id] == 'txt'
|
104
|
+
info = Resque.info
|
105
|
+
|
106
|
+
stats = []
|
107
|
+
stats << "resque.pending=#{info[:pending]}"
|
108
|
+
stats << "resque.processed+=#{info[:processed]}"
|
109
|
+
stats << "resque.failed+=#{info[:failed]}"
|
110
|
+
stats << "resque.workers=#{info[:workers]}"
|
111
|
+
stats << "resque.working=#{info[:working]}"
|
112
|
+
|
113
|
+
Resque.queues.each do |queue|
|
114
|
+
stats << "queues.#{queue}=#{Resque.size(queue)}"
|
115
|
+
end
|
116
|
+
|
117
|
+
render(:text => stats.join("</br>").html_safe)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def schedule
|
122
|
+
@farm_status = ResqueScheduler.farm_status
|
123
|
+
end
|
124
|
+
|
125
|
+
def schedule_requeue
|
126
|
+
config = Resque.schedule[params['job_name']]
|
127
|
+
Resque::Scheduler.enqueue_from_config(config)
|
128
|
+
redirect_to(:action => 'overview')
|
129
|
+
end
|
130
|
+
|
131
|
+
def add_scheduled_job
|
132
|
+
errors = []
|
133
|
+
if Resque.schedule.keys.include?(params[:name])
|
134
|
+
errors << 'Name already exists'
|
135
|
+
end
|
136
|
+
if params[:ip].blank?
|
137
|
+
errors << 'You must enter an ip address for the server you want this job to run on.'
|
138
|
+
end
|
139
|
+
if params[:cron].blank?
|
140
|
+
errors << 'You must enter the cron schedule.'
|
141
|
+
end
|
142
|
+
if errors.blank?
|
143
|
+
config = {params['name'] => {'class' => params['class'],
|
144
|
+
'ip' => params['ip'],
|
145
|
+
'cron' => params['cron'],
|
146
|
+
'args' => Resque.decode(params['args'].blank? ? nil : params['args']),
|
147
|
+
'description' => params['description']}
|
148
|
+
}
|
149
|
+
Resque.redis.rpush(:scheduled, Resque.encode(config))
|
150
|
+
ResqueScheduler.restart('ip')
|
151
|
+
else
|
152
|
+
flash[:error] = errors.join('<br>')
|
153
|
+
end
|
154
|
+
redirect_to(:action => 'schedule')
|
155
|
+
end
|
156
|
+
|
157
|
+
def remove_from_schedule
|
158
|
+
Resque.list_range(:scheduled, 0, -0).each do |s|
|
159
|
+
|
160
|
+
if s[params['job_name']]
|
161
|
+
Resque.redis.lrem(:scheduled, 0, s.to_json)
|
162
|
+
# Restart the scheduler on the server that has changed it's schedule
|
163
|
+
ResqueScheduler.restart(params['ip'])
|
164
|
+
end
|
165
|
+
end
|
166
|
+
redirect_to(:action => 'schedule')
|
167
|
+
end
|
168
|
+
|
169
|
+
def start_scheduler
|
170
|
+
ResqueScheduler.start(params[:ip])
|
171
|
+
redirect_to(:action => 'schedule')
|
172
|
+
end
|
173
|
+
|
174
|
+
def stop_scheduler
|
175
|
+
ResqueScheduler.quit(params[:ip])
|
176
|
+
redirect_to(:action => 'schedule')
|
177
|
+
end
|
178
|
+
|
179
|
+
def statuses
|
180
|
+
@start = params[:start].to_i
|
181
|
+
@end = @start + (params[:per_page] || 20)
|
182
|
+
@statuses = Resque::Status.statuses(@start, @end)
|
183
|
+
@size = Resque::Status.status_ids.size
|
184
|
+
if params[:format] == 'js'
|
185
|
+
render :text => @statuses.to_json
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def clear_statuses
|
190
|
+
Resque::Status.clear
|
191
|
+
redirect_to(:action => 'statuses')
|
192
|
+
end
|
193
|
+
|
194
|
+
def status
|
195
|
+
@status = Resque::Status.get(params[:id])
|
196
|
+
if params[:format] == 'js'
|
197
|
+
render :text => @status.to_json
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def kill
|
202
|
+
Resque::Status.kill(params[:id])
|
203
|
+
s = Resque::Status.get(params[:id])
|
204
|
+
s.status = 'killed'
|
205
|
+
Resque::Status.set(params[:id], s)
|
206
|
+
redirect_to(:action => 'statuses')
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
def check_connection
|
212
|
+
Resque.keys
|
213
|
+
rescue Errno::ECONNREFUSED
|
214
|
+
render(:template => 'resque/error', :layout => false, :locals => {:error => "Can't connect to Redis! (#{Resque.redis.server})"})
|
215
|
+
false
|
216
|
+
end
|
217
|
+
|
218
|
+
def remove_failure_from_list(payload)
|
219
|
+
count = Resque::Failure.count - 1
|
220
|
+
loop do
|
221
|
+
f = Resque::Failure.all(count, 1)
|
222
|
+
if f && f['payload'] == payload
|
223
|
+
Resque.redis.lrem(:failed, 0, f.to_json)
|
224
|
+
end
|
225
|
+
count = count - 1
|
226
|
+
break if count < 0
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def find_worker(worker)
|
231
|
+
first_part, *rest = worker.split(':')
|
232
|
+
first_part.gsub!(/_/, '.')
|
233
|
+
Resque::Worker.find("#{first_part}:#{rest.join(':')}")
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module ResqueHelper
|
2
|
+
#include Rack::Utils
|
3
|
+
#alias_method :h, :escape_html
|
4
|
+
|
5
|
+
|
6
|
+
def classes_in_failure
|
7
|
+
Resque.list_range(:failed,0,1000).collect{|job| job['payload']['class']}.uniq
|
8
|
+
end
|
9
|
+
|
10
|
+
def flash_helper
|
11
|
+
[:notice, :warning, :message, :error].collect do |key|
|
12
|
+
content_tag(:div, flash[key], :class => "flash #{key}") unless flash[key].blank?
|
13
|
+
end.join
|
14
|
+
end
|
15
|
+
|
16
|
+
def format_time(t)
|
17
|
+
t.strftime("%Y/%m/%d %H:%M:%S %Z")
|
18
|
+
end
|
19
|
+
|
20
|
+
def current_section
|
21
|
+
request.path_info.sub('/','').split('/')[1].downcase
|
22
|
+
end
|
23
|
+
|
24
|
+
def current_page
|
25
|
+
url request.path_info.sub('/','').downcase
|
26
|
+
end
|
27
|
+
|
28
|
+
def url(*path_parts)
|
29
|
+
[ path_prefix, path_parts ].join("/").squeeze('/')
|
30
|
+
end
|
31
|
+
alias_method :u, :url
|
32
|
+
|
33
|
+
def path_prefix
|
34
|
+
request.env['SCRIPT_NAME']
|
35
|
+
end
|
36
|
+
|
37
|
+
def class_if_current(page = '')
|
38
|
+
'class="current"' if current_page.include? page.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
def tab(name)
|
42
|
+
dname = "resque/#{name.to_s.downcase}"
|
43
|
+
"<li #{class_if_current(dname)}>#{link_to(name, url(dname))}</li>".html_safe
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_worker(worker)
|
47
|
+
first_part, *rest = worker.split(':')
|
48
|
+
first_part.gsub!(/_/,'.')
|
49
|
+
Resque::Worker.find("#{first_part}:#{rest.join(':')}")
|
50
|
+
end
|
51
|
+
|
52
|
+
def redis_get_size(key)
|
53
|
+
case Resque.redis.type(key)
|
54
|
+
when 'none'
|
55
|
+
[]
|
56
|
+
when 'list'
|
57
|
+
Resque.redis.llen(key)
|
58
|
+
when 'set'
|
59
|
+
Resque.redis.scard(key)
|
60
|
+
when 'string'
|
61
|
+
Resque.redis.get(key).length
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def redis_get_value_as_array(key)
|
66
|
+
case Resque.redis.type(key)
|
67
|
+
when 'none'
|
68
|
+
[]
|
69
|
+
when 'list'
|
70
|
+
Resque.list_range(key, 0, 20)
|
71
|
+
when 'set'
|
72
|
+
Resque.redis.smembers(key)
|
73
|
+
when 'string'
|
74
|
+
[Resque.redis.get(key)]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def show_args(args)
|
79
|
+
Array(args).map { |a| a.inspect }.join("\n")
|
80
|
+
end
|
81
|
+
|
82
|
+
def partial?
|
83
|
+
@partial
|
84
|
+
end
|
85
|
+
|
86
|
+
def poll
|
87
|
+
if @polling
|
88
|
+
text = "Last Updated: #{Time.now.strftime("%H:%M:%S")}"
|
89
|
+
else
|
90
|
+
text = link_to('Live Poll', {:action => 'poll', :page => current_section}, :rel => 'poll')
|
91
|
+
end
|
92
|
+
"<p class='poll'>#{text}</p>".html_safe
|
93
|
+
end
|
94
|
+
|
95
|
+
def status_poll(start)
|
96
|
+
if @polling
|
97
|
+
text = "Last Updated: #{Time.now.strftime("%H:%M:%S")}"
|
98
|
+
else
|
99
|
+
text = link_to('Live Poll', {:action => 'status_poll', :start => start}, :rel => 'poll')
|
100
|
+
end
|
101
|
+
"<p class='poll'>#{text}</p>".html_safe
|
102
|
+
end
|
103
|
+
|
104
|
+
def resque
|
105
|
+
Resque
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Resque.</title>
|
5
|
+
<%= javascript_include_tag "resque/jquery-1.3.2.min.js", "resque/jquery.relatize_date.js", "resque/ranger.js" %>
|
6
|
+
<%= yield :javascript %>
|
7
|
+
<%= stylesheet_link_tag "resque/resque_reset", :media=>"screen" %>
|
8
|
+
<%= stylesheet_link_tag "resque/resque", :media=>"screen" %>
|
9
|
+
<%= yield :stylesheets %>
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
<div class="header">
|
13
|
+
<ul class='nav'>
|
14
|
+
<li><%=link_to("Home",url_for('/'))%></li>
|
15
|
+
<% Resque::Server.tabs.each do |name| -%>
|
16
|
+
<%= tab name -%>
|
17
|
+
<%end-%>
|
18
|
+
</ul>
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<% if @subtabs %>
|
22
|
+
<ul class='subnav'>
|
23
|
+
<% for subtab in @subtabs %>
|
24
|
+
<li <%= class_if_current "#{current_section}/#{subtab}" %>><%= link_to(subtab, :action => current_section, :id => subtab) %> </li>
|
25
|
+
<% end %>
|
26
|
+
</ul>
|
27
|
+
<% end %>
|
28
|
+
|
29
|
+
<div id="main">
|
30
|
+
<%= yield %>
|
31
|
+
</div>
|
32
|
+
|
33
|
+
<div id="footer">
|
34
|
+
<p>Powered by <a href="http://github.com/defunkt/resque">Resque</a> v<%=Resque::Version%></p>
|
35
|
+
<p>Connected to Redis namespace <%= Resque.redis.namespace %> on <%=Resque.redis_id%></p>
|
36
|
+
</div>
|
37
|
+
|
38
|
+
</body>
|
39
|
+
</html>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<% if key = params[:key] %>
|
2
|
+
|
3
|
+
<h1>Key "<%= key %>" is a <%= resque.redis.type key %></h1>
|
4
|
+
<h2>size: <%= redis_get_size(key) %></h2>
|
5
|
+
<table>
|
6
|
+
<% for row in redis_get_value_as_array(key) %>
|
7
|
+
<tr>
|
8
|
+
<td>
|
9
|
+
<%= row %>
|
10
|
+
</td>
|
11
|
+
</tr>
|
12
|
+
<% end %>
|
13
|
+
</table>
|
14
|
+
|
15
|
+
<% else %>
|
16
|
+
|
17
|
+
<% end %>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<%if start - 20 >= 0 || start + 20 <= size%>
|
2
|
+
<p class='pagination'>
|
3
|
+
<% if start - 20 >= 0 %>
|
4
|
+
<%= link_to('« less',:start => start - 20, :class => 'less') %>
|
5
|
+
<% end %>
|
6
|
+
<% if start + 20 < size %>
|
7
|
+
<%= link_to('more »',:start => start + 20, :class => 'more') %>
|
8
|
+
<% end %>
|
9
|
+
</p>
|
10
|
+
<%end%>
|