reqless 0.0.1

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