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.
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%>