resque_ui 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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%>
|