resque_ui 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. data/Gemfile +6 -0
  2. data/Gemfile.lock +102 -0
  3. data/History.txt +5 -0
  4. data/MIT-LICENSE +21 -0
  5. data/README.markdown +279 -0
  6. data/Rakefile +49 -0
  7. data/VERSION.yml +5 -0
  8. data/app/assets/images/idle.png +0 -0
  9. data/app/assets/images/poll.png +0 -0
  10. data/app/assets/images/working.png +0 -0
  11. data/app/assets/javascripts/resque/jquery-1.3.2.min.js +19 -0
  12. data/app/assets/javascripts/resque/jquery.relatize_date.js +95 -0
  13. data/app/assets/javascripts/resque/ranger.js +24 -0
  14. data/app/assets/stylesheets/resque/resque.css +93 -0
  15. data/app/assets/stylesheets/resque/resque_reset.css +48 -0
  16. data/app/controllers/resque_controller.rb +236 -0
  17. data/app/helpers/resque_helper.rb +107 -0
  18. data/app/views/layouts/resque.html.erb +39 -0
  19. data/app/views/resque/_key.html.erb +17 -0
  20. data/app/views/resque/_next_more.html.erb +10 -0
  21. data/app/views/resque/_queues.html.erb +52 -0
  22. data/app/views/resque/_status_styles.erb +98 -0
  23. data/app/views/resque/_workers.html.erb +110 -0
  24. data/app/views/resque/_working.html.erb +69 -0
  25. data/app/views/resque/delayed.html.erb +35 -0
  26. data/app/views/resque/delayed_timestamp.html.erb +26 -0
  27. data/app/views/resque/error.erb +1 -0
  28. data/app/views/resque/failed.html.erb +54 -0
  29. data/app/views/resque/overview.html.erb +4 -0
  30. data/app/views/resque/schedule.html.erb +96 -0
  31. data/app/views/resque/stats.html.erb +62 -0
  32. data/app/views/resque/status.html.erb +57 -0
  33. data/app/views/resque/statuses.html.erb +72 -0
  34. data/app/views/resque/workers.html.erb +1 -0
  35. data/lib/resque_ui/cap.rb +6 -0
  36. data/lib/resque_ui/cap_recipes.rb +106 -0
  37. data/lib/resque_ui/overrides/resque/failure/failure.rb +22 -0
  38. data/lib/resque_ui/overrides/resque/job.rb +12 -0
  39. data/lib/resque_ui/overrides/resque/resque.rb +8 -0
  40. data/lib/resque_ui/overrides/resque/worker.rb +230 -0
  41. data/lib/resque_ui/overrides/resque_scheduler/resque_scheduler.rb +58 -0
  42. data/lib/resque_ui/overrides/resque_status/chained_job_with_status.rb +24 -0
  43. data/lib/resque_ui/overrides/resque_status/job_with_status.rb +59 -0
  44. data/lib/resque_ui/overrides/resque_status/status.rb +53 -0
  45. data/lib/resque_ui.rb +26 -0
  46. data/lib/tasks/failure.rake +8 -0
  47. data/lib/tasks/scheduler.rake +11 -0
  48. data/lib/tasks/worker.rake +80 -0
  49. data/rdoc/Resque/ChainedJobWithStatus.html +284 -0
  50. data/rdoc/Resque/Failure/Base.html +229 -0
  51. data/rdoc/Resque/Failure.html +202 -0
  52. data/rdoc/Resque/Job.html +202 -0
  53. data/rdoc/Resque/JobWithStatus.html +410 -0
  54. data/rdoc/Resque/Status.html +368 -0
  55. data/rdoc/Resque/Worker.html +1104 -0
  56. data/rdoc/Resque.html +232 -0
  57. data/rdoc/ResqueScheduler.html +434 -0
  58. data/rdoc/ResqueUi/Cap.html +150 -0
  59. data/rdoc/ResqueUi/Engine.html +150 -0
  60. data/rdoc/ResqueUi.html +157 -0
  61. data/rdoc/created.rid +13 -0
  62. data/rdoc/images/brick.png +0 -0
  63. data/rdoc/images/brick_link.png +0 -0
  64. data/rdoc/images/bug.png +0 -0
  65. data/rdoc/images/bullet_black.png +0 -0
  66. data/rdoc/images/bullet_toggle_minus.png +0 -0
  67. data/rdoc/images/bullet_toggle_plus.png +0 -0
  68. data/rdoc/images/date.png +0 -0
  69. data/rdoc/images/find.png +0 -0
  70. data/rdoc/images/loadingAnimation.gif +0 -0
  71. data/rdoc/images/macFFBgHack.png +0 -0
  72. data/rdoc/images/package.png +0 -0
  73. data/rdoc/images/page_green.png +0 -0
  74. data/rdoc/images/page_white_text.png +0 -0
  75. data/rdoc/images/page_white_width.png +0 -0
  76. data/rdoc/images/plugin.png +0 -0
  77. data/rdoc/images/ruby.png +0 -0
  78. data/rdoc/images/tag_green.png +0 -0
  79. data/rdoc/images/wrench.png +0 -0
  80. data/rdoc/images/wrench_orange.png +0 -0
  81. data/rdoc/images/zoom.png +0 -0
  82. data/rdoc/index.html +163 -0
  83. data/rdoc/js/darkfish.js +116 -0
  84. data/rdoc/js/jquery.js +32 -0
  85. data/rdoc/js/quicksearch.js +114 -0
  86. data/rdoc/js/thickbox-compressed.js +10 -0
  87. data/rdoc/lib/resque_overrides_rb.html +54 -0
  88. data/rdoc/lib/resque_scheduler_overrides_rb.html +52 -0
  89. data/rdoc/lib/resque_status_overrides_rb.html +52 -0
  90. data/rdoc/lib/resque_ui/cap_rb.html +52 -0
  91. data/rdoc/lib/resque_ui/cap_recipes_rb.html +58 -0
  92. data/rdoc/lib/resque_ui/engine_rb.html +52 -0
  93. data/rdoc/lib/resque_ui/overrides/resque/failure/failure_rb.html +54 -0
  94. data/rdoc/lib/resque_ui/overrides/resque/job_rb.html +52 -0
  95. data/rdoc/lib/resque_ui/overrides/resque/resque_rb.html +52 -0
  96. data/rdoc/lib/resque_ui/overrides/resque/worker_rb.html +54 -0
  97. data/rdoc/lib/resque_ui/overrides/resque_scheduler/resque_scheduler_rb.html +52 -0
  98. data/rdoc/lib/resque_ui/overrides/resque_status/chained_job_with_status_rb.html +52 -0
  99. data/rdoc/lib/resque_ui/overrides/resque_status/job_with_status_rb.html +52 -0
  100. data/rdoc/lib/resque_ui/overrides/resque_status/status_rb.html +52 -0
  101. data/rdoc/lib/resque_ui/resque_ui_rb.html +52 -0
  102. data/rdoc/lib/resque_ui/tasks_rb.html +64 -0
  103. data/rdoc/lib/resque_ui_rb.html +76 -0
  104. data/rdoc/rdoc.css +763 -0
  105. data/resque_ui.gemspec +153 -0
  106. data/test/resque_ui_test.rb +8 -0
  107. data/test/test_helper.rb +3 -0
  108. 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%>