reqless 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +8 -0
  3. data/README.md +648 -0
  4. data/Rakefile +117 -0
  5. data/bin/docker-build-and-test +22 -0
  6. data/exe/reqless-web +11 -0
  7. data/lib/reqless/config.rb +31 -0
  8. data/lib/reqless/failure_formatter.rb +43 -0
  9. data/lib/reqless/job.rb +496 -0
  10. data/lib/reqless/job_reservers/ordered.rb +29 -0
  11. data/lib/reqless/job_reservers/round_robin.rb +46 -0
  12. data/lib/reqless/job_reservers/shuffled_round_robin.rb +21 -0
  13. data/lib/reqless/lua/reqless-lib.lua +2965 -0
  14. data/lib/reqless/lua/reqless.lua +2545 -0
  15. data/lib/reqless/lua_script.rb +90 -0
  16. data/lib/reqless/middleware/requeue_exceptions.rb +94 -0
  17. data/lib/reqless/middleware/retry_exceptions.rb +72 -0
  18. data/lib/reqless/middleware/sentry.rb +66 -0
  19. data/lib/reqless/middleware/timeout.rb +63 -0
  20. data/lib/reqless/queue.rb +189 -0
  21. data/lib/reqless/queue_priority_pattern.rb +16 -0
  22. data/lib/reqless/server/static/css/bootstrap-responsive.css +686 -0
  23. data/lib/reqless/server/static/css/bootstrap-responsive.min.css +12 -0
  24. data/lib/reqless/server/static/css/bootstrap.css +3991 -0
  25. data/lib/reqless/server/static/css/bootstrap.min.css +689 -0
  26. data/lib/reqless/server/static/css/codemirror.css +112 -0
  27. data/lib/reqless/server/static/css/docs.css +839 -0
  28. data/lib/reqless/server/static/css/jquery.noty.css +105 -0
  29. data/lib/reqless/server/static/css/noty_theme_twitter.css +137 -0
  30. data/lib/reqless/server/static/css/style.css +200 -0
  31. data/lib/reqless/server/static/favicon.ico +0 -0
  32. data/lib/reqless/server/static/img/glyphicons-halflings-white.png +0 -0
  33. data/lib/reqless/server/static/img/glyphicons-halflings.png +0 -0
  34. data/lib/reqless/server/static/js/bootstrap-alert.js +94 -0
  35. data/lib/reqless/server/static/js/bootstrap-scrollspy.js +125 -0
  36. data/lib/reqless/server/static/js/bootstrap-tab.js +130 -0
  37. data/lib/reqless/server/static/js/bootstrap-tooltip.js +270 -0
  38. data/lib/reqless/server/static/js/bootstrap-typeahead.js +285 -0
  39. data/lib/reqless/server/static/js/bootstrap.js +1726 -0
  40. data/lib/reqless/server/static/js/bootstrap.min.js +6 -0
  41. data/lib/reqless/server/static/js/codemirror.js +2972 -0
  42. data/lib/reqless/server/static/js/jquery.noty.js +220 -0
  43. data/lib/reqless/server/static/js/mode/javascript.js +360 -0
  44. data/lib/reqless/server/static/js/theme/cobalt.css +18 -0
  45. data/lib/reqless/server/static/js/theme/eclipse.css +25 -0
  46. data/lib/reqless/server/static/js/theme/elegant.css +10 -0
  47. data/lib/reqless/server/static/js/theme/lesser-dark.css +45 -0
  48. data/lib/reqless/server/static/js/theme/monokai.css +28 -0
  49. data/lib/reqless/server/static/js/theme/neat.css +9 -0
  50. data/lib/reqless/server/static/js/theme/night.css +21 -0
  51. data/lib/reqless/server/static/js/theme/rubyblue.css +21 -0
  52. data/lib/reqless/server/static/js/theme/xq-dark.css +46 -0
  53. data/lib/reqless/server/views/_job.erb +259 -0
  54. data/lib/reqless/server/views/_job_list.erb +8 -0
  55. data/lib/reqless/server/views/_pagination.erb +7 -0
  56. data/lib/reqless/server/views/about.erb +130 -0
  57. data/lib/reqless/server/views/completed.erb +11 -0
  58. data/lib/reqless/server/views/config.erb +14 -0
  59. data/lib/reqless/server/views/failed.erb +48 -0
  60. data/lib/reqless/server/views/failed_type.erb +18 -0
  61. data/lib/reqless/server/views/job.erb +17 -0
  62. data/lib/reqless/server/views/layout.erb +451 -0
  63. data/lib/reqless/server/views/overview.erb +137 -0
  64. data/lib/reqless/server/views/queue.erb +125 -0
  65. data/lib/reqless/server/views/queues.erb +45 -0
  66. data/lib/reqless/server/views/tag.erb +6 -0
  67. data/lib/reqless/server/views/throttles.erb +38 -0
  68. data/lib/reqless/server/views/track.erb +75 -0
  69. data/lib/reqless/server/views/worker.erb +34 -0
  70. data/lib/reqless/server/views/workers.erb +14 -0
  71. data/lib/reqless/server.rb +549 -0
  72. data/lib/reqless/subscriber.rb +74 -0
  73. data/lib/reqless/test_helpers/worker_helpers.rb +55 -0
  74. data/lib/reqless/throttle.rb +57 -0
  75. data/lib/reqless/version.rb +5 -0
  76. data/lib/reqless/worker/base.rb +237 -0
  77. data/lib/reqless/worker/forking.rb +215 -0
  78. data/lib/reqless/worker/serial.rb +41 -0
  79. data/lib/reqless/worker.rb +5 -0
  80. data/lib/reqless.rb +309 -0
  81. metadata +399 -0
@@ -0,0 +1,17 @@
1
+ <script>
2
+ var fade = function(jid, type) {
3
+ if (type == 'cancel') {
4
+ $('#job-' + jid).slideUp();
5
+ }
6
+ }
7
+ </script>
8
+
9
+ <% if job.nil? %>
10
+ <div class="row">
11
+ <div class="span12">
12
+ <h2><%= jid %> doesn't exist, was canceled, or expired</h2>
13
+ </div>
14
+ </div>
15
+ <% else %>
16
+ <%= erb :_job, :layout => false, :locals => { :job => job, :queues => queues } %>
17
+ <% end %>
@@ -0,0 +1,451 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title><%= application_name %><%= (title.length == 0) ? '' : (' | ' + title) %></title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta name="description" content="">
8
+ <meta name="author" content="Dan Lecocq">
9
+
10
+ <link href="<%= u '/css/bootstrap.css' %>" rel="stylesheet">
11
+ <link href="<%= u '/css/bootstrap-responsive.css' %>" rel="stylesheet">
12
+ <link href="<%= u '/css/docs.css' %>" rel="stylesheet">
13
+ <link href="<%= u '/css/jquery.noty.css' %>" rel="stylesheet">
14
+ <link href="<%= u '/css/noty_theme_twitter.css' %>" rel="stylesheet">
15
+ <script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
16
+
17
+ <style type="text/css">
18
+ body {
19
+ padding-top: 60px;
20
+ }
21
+
22
+ .btn-group span {
23
+ /* This is ugly. Anyone want to change it? */
24
+ border-color: #e6e6e6 #e6e6e6 #bfbfbf;
25
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
26
+ border: 1px solid #cccccc;
27
+ border-bottom-color: #b3b3b3;
28
+ background-color: #f5f5f5;
29
+ font-size: 13px;
30
+ line-height: 18px;
31
+ padding: 4px 10px 4px;
32
+ }
33
+
34
+ .btn-group input, .btn-group span {
35
+ position: relative;
36
+ float: left;
37
+ margin-left: -1px;
38
+ -webkit-border-radius: 0;
39
+ -moz-border-radius: 0;
40
+ border-radius: 0;
41
+ margin-bottom: 0px;
42
+ }
43
+
44
+ .btn-group input:first-child, .btn-group span:first-child {
45
+ margin-left: 0;
46
+ -webkit-border-top-left-radius: 4px;
47
+ -moz-border-radius-topleft: 4px;
48
+ border-top-left-radius: 4px;
49
+ -webkit-border-bottom-left-radius: 4px;
50
+ -moz-border-radius-bottomleft: 4px;
51
+ border-bottom-left-radius: 4px;
52
+ }
53
+
54
+ .large-text {
55
+ font-size:18px;
56
+ }
57
+
58
+ .queue-column {
59
+ min-width:300px;
60
+ }
61
+
62
+ </style>
63
+
64
+ <script type="text/javascript">
65
+ /* This is a helper method to display an alert at the top of the page,
66
+ * in a vein similar to that of Ruby's flash */
67
+ var flash = function(message, t, duration) {
68
+ var noty_id = noty({
69
+ text : '<strong>' + message + '</strong>',
70
+ layout : 'top',
71
+ type : t || 'error',
72
+ theme : 'noty_theme_twitter',
73
+ timeout: duration || 1500});
74
+ }
75
+
76
+ /* This just sets a few options that we use */
77
+ var _ajax = function(obj) {
78
+ $.ajax({
79
+ url: obj.url,
80
+ type: 'POST',
81
+ dataType: 'json',
82
+ data: JSON.stringify(obj.data),
83
+ processData: false,
84
+ success: obj.success || function() {},
85
+ error: obj.error || function() {}
86
+ });
87
+ }
88
+
89
+ /* This is a helper method to move a job into a queue, and then it will
90
+ * flash a message to that effect on the page */
91
+ var move = function(jid, queue, cb) {
92
+ _ajax({
93
+ url: '<%= u "/move" %>',
94
+ data: {id:jid, queue:queue},
95
+ success: function() { flash('Moved ' + jid + ' to ' + queue, 'success', 1500); cb(jid, queue); },
96
+ error: function() { flash('Failed to move ' + jid + ' to ' + queue); }
97
+ });
98
+ }
99
+
100
+ /* Helper function for retrying a job */
101
+ var retry = function(jid, cb) {
102
+ _ajax({
103
+ url: '<%= u "/retry" %>',
104
+ data: {id:jid},
105
+ success: function() { flash('Retrying ' + jid, 'success', 1500); if (cb) { cb(jid, 'retry'); } },
106
+ error: function() { flash('Failed to retry ' + jid); }
107
+ });
108
+ }
109
+
110
+ /* This is a helper method to cancel a job */
111
+ var cancel = function(jid, cb) {
112
+ _ajax({
113
+ url: '<%= u "/cancel" %>',
114
+ data: [jid],
115
+ success: function() { flash('Canceled ' + jid, null, 1500); if (cb) { cb(jid, 'cancel'); } },
116
+ error: function() { flash('Failed to cancel ' + jid); }
117
+ });
118
+ }
119
+
120
+ /* This is a helper method to untrack a job */
121
+ var untrack = function(jid, cb) {
122
+ _ajax({
123
+ url: '<%= u "/untrack" %>',
124
+ data: [jid],
125
+ success: function() { flash('Stopped tracking ' + jid, 'success', 1500); if (cb) { cb(jid, 'untrack'); } },
126
+ error: function() { flash('Failed to track ' + jid); }
127
+ });
128
+ }
129
+
130
+ /* This is a helper to start tracking a job, with tags */
131
+ var track = function(jid, tags, cb) {
132
+ _ajax({
133
+ url: '<%= u "/track" %>',
134
+ data: {id:jid, tags:(tags || [])},
135
+ success: function() { flash('Now tracking ' + jid, 'success', 1500); if (cb) { cb(jid, 'track'); } },
136
+ error: function() { flash('Failed to track ' + jid); }
137
+ });
138
+ }
139
+
140
+ /* This is a helper to retry all jobs of a certain failure type */
141
+ var retryall = function(type, cb) {
142
+ _ajax({
143
+ url: '<%= u "/retryall" %>',
144
+ data: {type: type},
145
+ success: function(response) {
146
+ flash('Retrying failures of type ' + type, 'success', 1500);
147
+ if (cb) {
148
+ for (var i in response) {
149
+ cb(response[i].id);
150
+ }
151
+ }
152
+ },
153
+ error: function() { flash('Failed to retry failures of type ' + type); }
154
+ });
155
+ }
156
+
157
+ /* This is a helper to cancel all jobs of a certain failure type */
158
+ var cancelall = function(type, cb) {
159
+ _ajax({
160
+ url: '<%= u "/cancelall" %>',
161
+ data: {type: type},
162
+ success: function(response) {
163
+ flash('Canceling failures of type ' + type, 'success', 1500);
164
+ if (cb) {
165
+ for (var i in response) {
166
+ cb(response[i].id);
167
+ }
168
+ }
169
+ },
170
+ error: function() { flash('Failed to cancel failures of type ' + type); }
171
+ });
172
+ }
173
+
174
+ /* This is a helper to remove job dependencies */
175
+ var undepend = function(jid, dependency, cb) {
176
+ _ajax({
177
+ url: '<%= u "/undepend" %>',
178
+ data: {id:jid, dependency:dependency},
179
+ success: function() { flash(jid + ' no longer depends on ' + dependency, 'success', 1500); if (cb) { cb(jid, 'undepend'); } },
180
+ error: function() { flash('Failed to remove ' + jid + '\'s dependency on ' + dependency); }
181
+ });
182
+ }
183
+
184
+ /* Helper function to fade out a particular element id */
185
+ var fade = function(jid, type) {
186
+ if (type != 'untrack' && type != 'track') {
187
+ $('#job-' + jid).slideUp();
188
+ }
189
+ }
190
+
191
+ /* Helper function to make a button ask for confirmation
192
+ * after being pressed once. Accepts the confirmation text,
193
+ * and the function to execute after it has been run */
194
+ var confirmation = function(button, html, action, delay) {
195
+ var obj = $(button);
196
+ var original = obj.html();
197
+ var timeout = setTimeout(function() {
198
+ obj.html(original).unbind('click').click(function() {
199
+ confirmation(button, html, action, delay);
200
+ });
201
+ }, delay || 3000);
202
+ obj.removeAttr('onclick').html(html).unbind('click').click(function() {
203
+ clearTimeout(timeout);
204
+ obj.html(original).unbind('click').click(function() {
205
+ confirmation(button, html, action, delay);
206
+ });
207
+ action();
208
+ });
209
+ }
210
+
211
+ /* Helper function for adding a tag to a job */
212
+ var tag = function(jid, tag) {
213
+ var data = {};
214
+ data[jid] = [tag];
215
+ // The button group of the 'add tag' bit
216
+ var group =
217
+ _ajax({
218
+ url: '<%= u "/tag" %>',
219
+ data: data,
220
+ success : function() {
221
+ var div = $('<div>').attr('class', 'btn-group').attr('style', 'float:left');
222
+ var span = $('<span>').attr('class', 'tag').text(tag);
223
+ var btn = $('<button>').attr('class', 'btn').click(function() {
224
+ untag(jid, tag);
225
+ });
226
+ btn.append($('<i>').attr('class', 'icon-remove'));
227
+ div.append(span).append(btn);
228
+ $('#job-' + jid).find('.add-tag').val(null).parent().before(div);
229
+ }, error: function() {
230
+ flash('Failed to tag ' + jid + ' with ' + tag);
231
+ }
232
+ });
233
+ }
234
+
235
+ /* Helper function for untagging a job */
236
+ var untag = function(jid, tag) {
237
+ var data = {};
238
+ data[jid] = [tag];
239
+ // The button group of the 'add tag' bit
240
+ var group =
241
+ _ajax({
242
+ url: '<%= u "/untag" %>',
243
+ data: data,
244
+ success : function() {
245
+ $("#job-" + jid).find('.tag').filter(function() {
246
+ return $(this).text() === tag;
247
+ }).parent().remove();
248
+ }, error: function() {
249
+ flash('Failed to untag ' + jid + ' with ' + tag);
250
+ }
251
+ });
252
+ }
253
+
254
+ /* Helper function for changing a job's priority */
255
+ var priority = function(jid, priority) {
256
+ var p = parseInt(priority);
257
+ var input = $('#job-' + jid).find('.priority');
258
+ if (p != null) {
259
+ input.attr('disabled', true);
260
+ var data = {};
261
+ data[jid] = priority;
262
+ _ajax({
263
+ url: '<%= u "/priority" %>',
264
+ data: data,
265
+ success : function(data) {
266
+ if (data[jid] != 'failed' && data[jid] != null) {
267
+ input.attr('disabled', false).attr('placeholder', 'Pri ' + priority).val(null).blur();
268
+ } else {
269
+ flash('Couldn\'t reprioritize ' + jid);
270
+ input.val(null).blur();
271
+ }
272
+ }, error: function() {
273
+ flash('Couldn\'t reprioritize ' + jid);
274
+ input.val(null).blur();
275
+ }
276
+ });
277
+ } else {
278
+ // Reset it to its original value, and print an error
279
+ flash('Cannot derive integer from "' + priority + '"');
280
+ input.val(null).blur();
281
+ }
282
+ }
283
+
284
+ var pause = function(queue) {
285
+ _ajax({
286
+ url: '<%= u "/pause" %>',
287
+ data: {
288
+ 'queue': queue
289
+ }, success: function(data) {
290
+ var button = $('#' + queue + '-pause');
291
+ button.attr('title', 'Unpause').attr(
292
+ 'data-original-title', 'Unpause');
293
+ button.addClass('btn-success').removeClass('btn-warning');
294
+ button.children().addClass('icon-play').removeClass('icon-pause');
295
+ button.attr('onclick', 'unpause("' + queue + '")');
296
+ }, error: function() {
297
+ flash('Couldn\'t pause queue ' + queue);
298
+ }
299
+ })
300
+ }
301
+
302
+ var unpause = function(queue) {
303
+ _ajax({
304
+ url: '<%= u "/unpause" %>',
305
+ data: {
306
+ 'queue': queue
307
+ }, success: function(data) {
308
+ var button = $('#' + queue + '-pause');
309
+ button.attr('title', 'Pause').attr(
310
+ 'data-original-title', 'Pause');
311
+ button.addClass('btn-warning').removeClass('btn-success');
312
+ button.children().addClass('icon-pause').removeClass('icon-play');
313
+ button.attr('onclick', 'pause("' + queue + '")');
314
+ }, error: function() {
315
+ flash('Couldn\'t unpause queue ' + queue);
316
+ }
317
+ })
318
+ }
319
+
320
+ var timeout = function(jid) {
321
+ _ajax({
322
+ url: '<%= u "/timeout" %>',
323
+ data: {
324
+ 'jid': jid
325
+ }, success: function(data) {
326
+ flash('Job timed out', 'success');
327
+ }, error: function(data) {
328
+ flash('Failed to time out job: ' + data);
329
+ console.log(data);
330
+ }
331
+ })
332
+ }
333
+
334
+ var expire_throttle = function(throttle_id, expiration) {
335
+ var data = { 'id': throttle_id, 'expiration': expiration };
336
+
337
+ $.ajax({
338
+ url: '<%= u "/throttle" %>',
339
+ type: 'PUT',
340
+ dataType: 'json',
341
+ processData: false,
342
+ data: JSON.stringify(data),
343
+ success: function(data) {
344
+ flash('Set expiration for throttle ' + throttle_id, 'success');
345
+ },
346
+ error: function(data) {
347
+ flash('Couldn\'t update expiration for throttle ' + throttle_id);
348
+ }
349
+ })
350
+ }
351
+
352
+ var delete_throttle = function(throttle_id) {
353
+ var data = { 'id': throttle_id };
354
+
355
+ $.ajax({
356
+ url: '<%= u "/throttle" %>',
357
+ type: 'DELETE',
358
+ dataType: 'json',
359
+ processData: false,
360
+ data: JSON.stringify(data),
361
+ success: function(data) {
362
+ flash('Deleted throttle for ' + throttle_id, 'success');
363
+ $('.' + throttle_id.replace(/:/g, '-') + '-maximum').val(data['maximum']);
364
+ },
365
+ error: function(data) {
366
+ flash('Couldn\'t delete thottle ' + throttle_id);
367
+ }
368
+ })
369
+ }
370
+
371
+ var update_throttle = function(throttle_id, maximum) {
372
+ _ajax({
373
+ url: '<%= u "/throttle" %>',
374
+ data: {
375
+ 'id': throttle_id,
376
+ 'maximum': maximum
377
+ }, success: function(data) {
378
+ flash('Updated throttle for ' + throttle_id, 'success');
379
+ }, error: function(data) {
380
+ flash('Couldn\'t update throttle ' + throttle_id);
381
+ }
382
+ })
383
+ }
384
+
385
+ $(document).ready(function() {
386
+ $('button').tooltip({delay:200});
387
+ });
388
+ </script>
389
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
390
+ <!--[if lt IE 9]>
391
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
392
+ <![endif]-->
393
+ </head>
394
+
395
+ <body>
396
+ <div class="navbar navbar-fixed-top">
397
+ <div class="navbar-inner">
398
+ <div class="container">
399
+ <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
400
+ <span class="icon-bar"></span>
401
+ <span class="icon-bar"></span>
402
+ <span class="icon-bar"></span>
403
+ </a>
404
+ <a class="brand" href="<%= u '/' %>"><%= application_name %></a>
405
+ <div class="nav-collapse">
406
+ <ul class="nav nav-bar">
407
+ <% tabs.each do |tab| %>
408
+ <li><a href='<%= u tab[:path] %>'><%= tab[:name] %></a></li>
409
+ <% end %>
410
+ </ul>
411
+ <ul class="nav nav-bar pull-right">
412
+ <li>
413
+ <form class="navbar-search" action="<%= u '/tag' %>">
414
+ <input id="tag-search" type="text" class="search-query" placeholder="Search by Tag" data-provide="typeahead" name="tag"/>
415
+ </form>
416
+ </li>
417
+ </ul>
418
+ </div>
419
+ </div>
420
+ </div>
421
+ </div>
422
+
423
+ <div class="container">
424
+ <%= yield %>
425
+ <!-- <footer class="footer">
426
+ <p>Powered by <a href="http://github.com/tdg5/reqless-rb">reqless</a> v<%=Reqless::VERSION%></p>
427
+ </footer> -->
428
+ </div> <!-- /container -->
429
+
430
+ <!-- Le javascript
431
+ ================================================== -->
432
+ <script src="<%= u '/js/bootstrap.min.js' %>"></script>
433
+ <script src="<%= u '/js/bootstrap-tab.js' %>"></script>
434
+ <script src="<%= u '/js/bootstrap-alert.js' %>"></script>
435
+ <script src="<%= u '/js/bootstrap-tooltip.js' %>"></script>
436
+ <script src="<%= u '/js/bootstrap-scrollspy.js' %>"></script>
437
+ <script src="<%= u '/js/bootstrap-typeahead.js' %>"></script>
438
+
439
+ <!-- Noty! This is such a wonderful-looking library, and /exactly/ what I wanted. Thank you so much! -->
440
+ <script src="<%= u '/js/jquery.noty.js' %>"></script>
441
+ <!--
442
+ <script src="../assets/js/bootstrap-transition.js"></script>
443
+ <script src="../assets/js/bootstrap-modal.js"></script>
444
+ <script src="../assets/js/bootstrap-dropdown.js"></script>
445
+ <script src="../assets/js/bootstrap-popover.js"></script>
446
+ <script src="../assets/js/bootstrap-button.js"></script>
447
+ <script src="../assets/js/bootstrap-collapse.js"></script>
448
+ <script src="../assets/js/bootstrap-carousel.js"></script>
449
+ -->
450
+ </body>
451
+ </html>
@@ -0,0 +1,137 @@
1
+ <% if queues.empty? %>
2
+ <div class="page-header">
3
+ <h1>No Queues <small>I wish I had some queues :-/</small></h1>
4
+ </div>
5
+ <% else %>
6
+ <div class="page-header">
7
+ <h1>Queues <small>And their job counts</small></h1>
8
+ </div>
9
+
10
+ <table class="table">
11
+ <thead>
12
+ <tr>
13
+ <th></th>
14
+ <th>running</th>
15
+ <th>waiting</th>
16
+ <th>throttled</th>
17
+ <th>scheduled</th>
18
+ <th>stalled</th>
19
+ <th>depends</th>
20
+ <th>recurring</th>
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ <% queues.each do |queue| %>
25
+ <tr class="queue-row">
26
+ <td class="queue-column large-text">
27
+ <% if queue['paused'] %>
28
+ <button
29
+ id="<%= queue['name'] %>-pause"
30
+ title="Unpause"
31
+ class="btn btn-success"
32
+ onclick="unpause('<%= queue['name'] %>')"><i class="icon-play"></i>
33
+ </button>
34
+ <% else %>
35
+ <button
36
+ id="<%= queue['name'] %>-pause"
37
+ title="Pause"
38
+ class="btn btn-warning"
39
+ onclick="pause('<%= queue['name'] %>')"><i class="icon-pause"></i>
40
+ </button>
41
+ <% end %>
42
+ <a href="<%= u "/queues/#{CGI::escape(queue['name'])}" %>"><%= queue['name'] %></a>
43
+ </td>
44
+ <td><%= queue['running'] %></td>
45
+ <td><%= queue['waiting'] %></td>
46
+ <td><%= queue['throttled'] %></td>
47
+ <td><%= queue['scheduled'] %></td>
48
+ <td><%= queue['stalled'] %></td>
49
+ <td><%= queue['depends'] %></td>
50
+ <td><%= queue['recurring'] %></td>
51
+ </tr>
52
+ <% end %>
53
+ </tbody>
54
+ </table>
55
+ <% end %>
56
+
57
+ <% if failed.empty? %>
58
+ <div class="page-header">
59
+ <h1>No Failed Jobs <small>Clean as a whistle</small></h1>
60
+ </div>
61
+ <% else %>
62
+ <div class="page-header">
63
+ <h1>Failed Jobs <small>D'oh!</small></h1>
64
+ </div>
65
+
66
+ <table class="table">
67
+ <thead>
68
+ <tr>
69
+ <th>failure</th>
70
+ <th>count</th>
71
+ </tr>
72
+ </thead>
73
+ <tbody>
74
+ <% failed.sort_by { |t, count| -count }.each do |t, count| %>
75
+ <tr class="failed-row">
76
+ <td class="large-text"><a href="<%= u "/failed/#{t}" %>"><%= t %></a></td>
77
+ <td><%= count %></td>
78
+ </tr>
79
+ <% end %>
80
+ </tbody>
81
+ </table>
82
+ <% end %>
83
+
84
+ <% if tracked['jobs'].empty? %>
85
+ <div class="page-header">
86
+ <h1>No Tracked Jobs <small>These aren't the droids you're looking for</small></h1>
87
+ </div>
88
+ <% else %>
89
+ <div class="page-header">
90
+ <h1>Tracked Jobs <small>These <i>are</i> the droids you're looking for</small></h1>
91
+ </div>
92
+ <% counts = Hash.new; tracked['jobs'].each { |job| counts[job.state] ||= 0; counts[job.state] += 1 } %>
93
+ <table class="table">
94
+ <thead>
95
+ <tr>
96
+ <th>state</th>
97
+ <th>count</th>
98
+ </tr>
99
+ </thead>
100
+ <tbody>
101
+ <% counts.sort_by { |state, count| - count }.each do |state, count| %>
102
+ <tr class="tracked-row">
103
+ <td class="large-text"><a href="<%= u "/track##{state}" %>"><%= state %></a></td>
104
+ <td><%= count %></td>
105
+ </tr>
106
+ <% end %>
107
+ </tbody>
108
+ </table>
109
+ <% end %>
110
+
111
+ <% if workers.empty? %>
112
+ <div class="page-header">
113
+ <h1>No Workers <small>Nobody's doin' nothin'!</small></h1>
114
+ </div>
115
+ <% else %>
116
+ <div class="page-header">
117
+ <h1>Current Workers <small>And their job counts</small></h1>
118
+ </div>
119
+ <table class="table">
120
+ <thead>
121
+ <tr>
122
+ <th></th>
123
+ <th>running</th>
124
+ <th>stalled</th>
125
+ </tr>
126
+ </thead>
127
+ <tbody>
128
+ <% workers.each do |worker| %>
129
+ <tr class='worker-row'>
130
+ <td class="large-text"><a href="<%= u "/workers/#{worker['name']}" %>"><%= worker['name'] %></a></td>
131
+ <td><%= worker['jobs'] %></td>
132
+ <td><%= worker['stalled'] %></td>
133
+ </tr>
134
+ <% end %>
135
+ </tbody>
136
+ </table>
137
+ <% end %>